The Java Media Components API promises to finally deliver an easy to use and easy to install platform for media playback in the Java Virtual Machine, and so far, they seem to have succeeded!
The other API that offers media playback on the JVM is the Java Media Framework: a library that requires the user to configure a registry containing information on available playback (and capture) devices and forces the developer to craft many lines of code just to play some video. Especially the configuration part for the user’s system makes the library in my opinion useless for inclusion in consumer applications.
The Java Media Components API is much easier and obtaining the library is the hardest part at this moment. There seems to be no public project, and the only access seems to be to follow these steps:
If you fail to include the last step, the system will complain about not being able to find a codec for all media you’re trying to play. Currently this dependency on an external library means that the media components library is only usable on Windows and Mac. Hopefully the library will have evolved and ported when the final JavaFX release arrives.
The library is built around several usage patterns, that either offer an easier interface or give the developer more control. Let’s have a look.
The first option is the JMediaPlayer. This is a Swing component providing a the UI for both the video playback, as well as the controls for the user. THe following illustrates the use. Note that the actual component requires just one statement!
Code:
import com.sun.media.jmc.JMediaPlayer; | |
import java.net.URI; | |
import javax.swing.JComponent; | |
import javax.swing.JFrame; | |
| |
public class BasicPlayer { | |
public static void main(String[] args) { | |
JFrame frame = new JFrame(); | |
frame.setTitle("Basic Player"); | |
frame.getContentPane().add(createPlayer()); | |
frame.pack(); | |
frame.setVisible(true); | |
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
} | |
| |
private static JComponent createPlayer() { | |
JMediaPlayer player = new JMediaPlayer( | |
URI.create("file:///c:/demo/mad2a.wmv")); | |
return player; | |
} | |
| |
} |
The second pattern gives you more freedom, and doesn’t provide any visible controls: you’ll have to implement the interactions yourself. Since this would again require basically one line plus some code for a call to play, I spiced the application with a little bit of the SceneGraph API, inspired by this post of Kirill Grouchnikov.
Code:
import com.sun.media.jmc.JMediaPane; | |
import com.sun.scenario.effect.Reflection; | |
import com.sun.scenario.scenegraph.JSGPanel; | |
import com.sun.scenario.scenegraph.SGComponent; | |
import com.sun.scenario.scenegraph.SGEffect; | |
import com.sun.scenario.scenegraph.SGNode; | |
import com.sun.scenario.scenegraph.event.SGMouseAdapter; | |
import java.awt.BorderLayout; | |
import java.awt.Dimension; | |
import java.awt.event.MouseEvent; | |
import java.net.URI; | |
import java.util.Timer; | |
import java.util.TimerTask; | |
import javax.swing.JComponent; | |
import javax.swing.JFrame; | |
| |
public class ExtendedPlayer { | |
private JMediaPane mediaPanel; | |
| |
public static void main(String[] args) { | |
new ExtendedPlayer(); | |
} | |
| |
public ExtendedPlayer() { | |
JFrame frame = new JFrame(); | |
frame.setTitle("Extended Player"); | |
frame.getContentPane().add(createScreen(), BorderLayout.CENTER); | |
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
frame.setSize(new Dimension(640, 640)); | |
frame.setVisible(true); | |
} | |
| |
private JComponent createScreen() { | |
JSGPanel panel = new JSGPanel(); | |
| |
SGComponent playerComponent = new SGComponent(); | |
playerComponent.setComponent(createPlayer()); | |
playerComponent.addMouseListener(new SGMouseAdapter(){ | |
public void mouseClicked(MouseEvent arg0, SGNode arg1) { | |
mediaPanel.play(); | |
} | |
}); | |
SGEffect effect = new SGEffect(); | |
effect.setEffect(new Reflection()); | |
effect.setChild(playerComponent); | |
| |
panel.setScene(effect); | |
| |
return panel; | |
} | |
| |
private JComponent createPlayer() { | |
mediaPanel = new JMediaPane(URI.create("file:///c:/demo/mad2a.wmv")); | |
new Timer().schedule(new TimerTask(){ | |
public void run() { | |
final double time = mediaPanel.getMediaTime(); | |
System.out.printf("%.1f\n", time); | |
} | |
| |
}, 1000, 100); | |
return mediaPanel; | |
} | |
} |
The final way to use the library is doing without a Swing component. The MediaProvider class provides access to several so-called controls. These give you access to information on and control over audio, subtitles, tracks, video and rendering. This allows one to apply an effect to the video-frames before they are rendered or to render them into a 3D scene. The best way to learn about the controls is by looking at the methods provided by the different classes from within you favourite IDE.
Although currently the native codecs are the only ones available, thus limiting you in choice and complicating the delivery of media to all platforms, Sun is working on providing a cross-platform codec with JMC, which will be licensed from On2. Hopefully this will be ready when JavaFX is released in a few weeks. The JavaOne talk also mentioned that people are working on adding video capture to the API, allowing for video-conferencing. If they succeed we finally have a really nice API to work with media on the JVM!
References:
JavaOne session TS-6509 - the session on JMC at JavaOne 2008
The Date and Time API, also known as JSR 310, aims to provide the Java platform with a proper API for working with times. It is based on Joda-Time (Stephen Colebourne is spec lead for JSR 310 and is lead-developer for the Joda-Time project).
The API intends to fix the issues related to the original Date and Calendar classes, such as:
The following are some examples:
Code:
static void createZonedDateTimeFromCurrent() { | |
Clock clk = Clock.system(); | |
ZonedDateTime zdt = clk.currentZonedDateTime(); | |
print(zdt); | |
print(zdt.withZoneSameInstant(TimeZone.UTC)); | |
print(zdt.withZoneSameLocal(TimeZone.UTC)); | |
} |
This creates a ZonedDateTime from a clock that returns the current time. A ZonedDateTime is a real instant in time: it has a date, a time and a zone. The clock object is useful, especially for unit-tests: you could supply your own clock to unittests, for instance to simulate the switch to year 2000. The method names to create derivatives from the zdt object are a bit cryptic at first, but the first creates a new ZoneDateTime object for the same instant in time but in a different timezone, whereas the second uses the same wall-clock time (hours/minutes) but in a different timezone.
Code:
static void simpleManipulations() { | |
Clock clk = Clock.system(); | |
ZonedDateTime zdt = clk.currentZonedDateTime(); | |
print(zdt.plusDays(1)); | |
print(zdt.plusHours(-1)); | |
print(zdt.plus(Period.hours(-1))); | |
print(zdt.withTime(0, 0)); | |
print(zdt.toLocalDate().minusWeeks(1)); | |
} |
These are some simple manipulations. They read easily, except perhaps for the plusHours(-1): minusHours(1) would have made more sense, but given the large number of methods already in the ZonedDateTime class, I assume this is the sensible way. The last line extracts a LocalDateObject from the ZonedDateTime, which is used to represent just a date (and it does have minusXXX methods, plus a notion of weeks).
Code:
static void hourDetermination() { | |
ZonedDateTime from, to; | |
LocalDateTime okt26 = LocalDateTime.dateMidnight(2008, 10, 26); | |
from = ZonedDateTime.dateTime(okt26, | |
TimeZone.timeZone("Europe/Amsterdam")); | |
to = from.plusDays(1); | |
Duration d = Duration.durationBetween(from, to); | |
System.out.println(d.getSeconds() / 60 / 60); | |
} |
the above code calculates the number of hours on last October 26th, when the switch from summer to winter time occurred in the Netherlands. It creates two instants (or ZonedDateTimes) from a LocalDate object. A Duration is an object holding an exact time span: this contrasts with a Period object, which holds a time difference in human time-units: for the above date, the Period would be one day, but the duration is 25 x 60 x 60 seconds.
Code:
static boolean isMeeting() { | |
LocalDate date = Clock.system().today(); | |
DateMatcher matcher = DateMatchers.dayOfWeekInMonth(3, | |
DayOfWeek.THURSDAY); | |
return matcher.matchesDate(date); | |
} |
This shows a simple date matcher: many matchers are provided in the DateMatchers class. This naming convention is used throughout the API: if there are implementations for some interface, there will probably be a class with the name of the interface postfixed with an ’s’, containing static methods that supply instances of the class.
The API provides more functionality, like date adjusters, date resolvers, input and output (thread-safe this time!). Some integration with the original date and calendar classes is also provided by having those implement the new interfaces.
There is much more to be learnt on this new API, but I’d like to refer you to the reference for that. I think this API is a valuable replacement that reduces the amount of problems when working with times.
References:
https://jsr-310.dev.java.net/ - the project page.
http://today.java.net/pub/a/today/2008/09/18/jsr-310-new-java-date-time-api.html - a well written introduction to the API.
JavaOne session TS-6578 - the session on JSR 310 at JavaOne 2008
The SceneGraph API has been created to fill a need in the JavaFX implementation. A scenegraph allows the developer to concentrate on creating a hierarchical description of the graphics to be shown, instead of telling the system how to actually draw the graphics. This approach has several advantages:
The following is code shown during my talk:
Code:
import com.sun.scenario.animation.Clip; | |
import com.sun.scenario.effect.DropShadow; | |
import com.sun.scenario.scenegraph.JSGPanel; | |
import com.sun.scenario.scenegraph.SGAbstractShape; | |
import com.sun.scenario.scenegraph.SGComponent; | |
import com.sun.scenario.scenegraph.SGEffect; | |
import com.sun.scenario.scenegraph.SGGroup; | |
import com.sun.scenario.scenegraph.SGNode; | |
import com.sun.scenario.scenegraph.SGShape; | |
import com.sun.scenario.scenegraph.SGText; | |
import com.sun.scenario.scenegraph.SGTransform; | |
import com.sun.scenario.scenegraph.event.SGMouseAdapter; | |
import java.awt.BorderLayout; | |
import java.awt.Color; | |
import java.awt.Dimension; | |
import java.awt.Font; | |
import java.awt.Point; | |
import java.awt.Rectangle; | |
import java.awt.RenderingHints; | |
import java.awt.event.MouseEvent; | |
import java.awt.geom.Ellipse2D; | |
import java.awt.geom.Line2D; | |
import javax.swing.JFrame; | |
import javax.swing.JTextField; | |
| |
public class Main { | |
| |
private SGTransform.Translate carPosition; | |
| |
public static void main(String[] args) { | |
new Main(); | |
} | |
| |
public Main() { | |
JFrame demoFrame = new JFrame("Scenegraph demo"); | |
| |
JSGPanel panel = new JSGPanel(); | |
panel.setBackground(new Color(230, 230, 255)); | |
panel.setScene(createRotatableWorld()); | |
panel.setPreferredSize(new Dimension(640, 480)); | |
| |
demoFrame.getContentPane().setLayout(new BorderLayout()); | |
demoFrame.getContentPane().add(panel, BorderLayout.CENTER); | |
demoFrame.pack(); | |
demoFrame.setVisible(true); | |
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
| |
createAnimation(); | |
} | |
| |
private SGNode createWorld() { | |
SGGroup worldGroup = new SGGroup(); | |
| |
SGShape ground = new SGShape(); | |
ground.setShape(new Line2D.Double(50, 300, 600, 300)); | |
ground.setDrawPaint(Color.GREEN); | |
ground.setMode(SGAbstractShape.Mode.STROKE); //Very important | |
worldGroup.add(ground); | |
| |
SGShape sun = new SGShape(); | |
sun.setShape(new Ellipse2D.Double(50, 50, 75, 75)); | |
sun.setFillPaint(Color.YELLOW); | |
worldGroup.add(sun); | |
| |
SGText text = new SGText(); | |
text.setText(":-)"); | |
text.setLocation(new Point(80, 90)); | |
text.setFont(text.getFont().deriveFont(Font.BOLD)); | |
worldGroup.add(text); | |
| |
SGComponent component = new SGComponent(); | |
component.setComponent(new JTextField(30)); | |
SGTransform componentPosition = SGTransform.createTranslation( | |
100, 305, component); | |
worldGroup.add(componentPosition); | |
| |
SGNode car = createCar(); | |
carPosition = SGTransform.createTranslation( | |
100, | |
ground.getBounds().getY() - car.getBounds().getHeight(), | |
car); | |
worldGroup.add(carPosition); | |
| |
| |
return worldGroup; | |
} | |
| |
private SGNode createCar() { | |
SGGroup carGroup = new SGGroup(); | |
| |
SGShape body = new SGShape(); | |
body.setShape(new Rectangle(0, 50, 200, 50)); | |
body.setFillPaint(Color.RED); | |
carGroup.add(body); | |
| |
SGShape top = new SGShape(); | |
top.setShape(new Rectangle(50, 0, 100, 50)); | |
top.setFillPaint(Color.RED); | |
carGroup.add(top); | |
| |
SGShape wheel = new SGShape(); | |
wheel.setShape(new Ellipse2D.Double(20, 80, 40, 40)); | |
wheel.setFillPaint(Color.BLACK); | |
carGroup.add(0, wheel); | |
| |
SGShape wheel2 = new SGShape(); | |
wheel2.setShape(new Ellipse2D.Double(140, 80, 40, 40)); | |
wheel2.setFillPaint(Color.BLACK); | |
carGroup.add(0, wheel2); | |
| |
return carGroup; | |
} | |
| |
private SGNode createShadedWorld() { | |
SGEffect effect = new SGEffect(); | |
effect.setChild(createWorld()); | |
effect.setEffect(new DropShadow()); | |
return effect; | |
} | |
| |
private SGNode createRotatableWorld() { | |
SGGroup group = new SGGroup(); | |
| |
final SGNode world = createWorld(); | |
group.add(world); | |
final SGShape handle = new SGShape(); | |
handle.setShape( | |
new Rectangle( | |
(int)world.getBounds().getMaxX() - 30, | |
300, 20, 20)); | |
handle.setFillPaint(Color.BLUE); | |
handle.setDrawPaint(Color.BLACK); | |
group.add(handle); | |
| |
SGTransform move1 = | |
SGTransform.createTranslation(-50, -300, group); | |
final SGTransform.Rotate rotation = | |
SGTransform.createRotation(-.1, move1); | |
SGTransform move2 = | |
SGTransform.createTranslation(50, 300, rotation); | |
| |
handle.addMouseListener(new SGMouseAdapter(){ | |
public void mouseDragged(MouseEvent arg0, SGNode arg1) { | |
double diffy = arg0.getY() - 300; | |
double diffx = arg0.getX() - 50; | |
double angle = Math.atan2(diffy, diffx); | |
rotation.setRotation(angle); | |
} | |
}); | |
return move2; | |
} | |
| |
private void createAnimation() { | |
Clip c = Clip.create( | |
5000, | |
Clip.INDEFINITE, | |
carPosition, | |
"translateX", | |
100.0, | |
300.0); | |
c.start(); | |
} | |
} |
This code demonstrates the basics of the SceneGraph API. It actually consists of a few demonstrations. By changing the line ‘panel.setScene(createRotatableWorld());’ to call createWorld(), createShadedWorld() and createRotatableWorld(), you’ll get a very simple world, a world with a drop shadow and finally a world that can be rotated. To run the example, you’ll need to download the SceneGraph API from https://scenegraph.dev.java.net/.
References:
https://scenegraph.dev.java.net/ - the project page of the SceneGraph project
JavaOne session TS-6610 - the session on SceneGraph at JavaOne 2008
JFall came and was over before I knew it. It was a great day, with some nice sessions, like the talk on the JMonkey engine by Erik Hooijmeijer and the talk by Wilfred Springer on Preon (a very interesting API to parse binary file formats).
My own talk was attended by something like 150-200 people judging from the total number of seats and most of them being filled. From the reactions I received afterwards, it was received well, great to hear!
Time flew, and starting five or ten minutes early also helped to finish in time.
The sheets can be downloaded from here (Dutch). Over the next weeks I’ll post some abstracts and code for the APIs that were discussed.
Personal blog on my interests.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| << < | Current | > >> | ||||
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | ||||||