The Java Media Framework (JMF) is an application programming interface (API) developed by Sun Microsystems, Inc. that allows you to play back media in your Java Applet or Application. The RealNetworks JMF for RealSystem G2 adds to Javasoft's JMF implementation the ability to play RealSystem G2 datatypes such as RealAudio, RealVideo, and RealFlash using true network streaming technology via the industry standard RTSP (Real-Time Streaming Protocol).
Like RealPlayer G2, JMF for RealSystem G2 can also play SMIL (Synchronized Multimedia Integration Language) presentations. SMIL allows the combination and synchronization of a variety of media types, including RealAudio, RealVideo, RealFlash, RealPix and RealText. SMIL is fully functional using JMF for RealSystem G2. The JMF for RealSystem G2 is a wrapper around the RealSystem G2 architecture that allows you to use G2 functionality exposed by the JMF API.
![]() |
Additional Information |
---|
For more information about JMF from JavaSoft, see http://java.sun.com/products/java-media/jmf/1.0, on the Javasoft Web site. |
The Java Media Framework consists of a few dozen classes used within your application to play back media. The basic JMF application begins with a URL that points to a media file, either locally, on your Intranet, or on the Internet. JMF's starting point is the JMF Manager class. A JMF player" object represents the media URL you wish to present and is used to control playback.
A JMF application must implement the ControllerListener
interface to trap events fired by the JMF player object. Once your JMF player object has been created, it must pass through several intermediate states before playback can begin. Fortunately, JMF specifies that calling player.start()
will automatically transition these states without further coding.
However, it may be useful in some situations to transit the player through the different states manually, one at a time. In this case, you can call player.realize()
and wait for that to complete, then call player.prefetch()
followed by player.start()
once prefetch is complete. At each state transition, JMF ControllerEvents are sent to the ControllerListener class via the controllerUpdate()
method.
![]() |
Additional Information |
---|
Please read the JMF documentation at the Javasoft web site (http://java.sun.com/products/java-media/jmf/1.0) for more information on how JMF architecture works. |
To use RealPlayer with JMF, install RealPlayer for your platform from http://www.real.com/products/. Then choose Check for Upgrade from the Help menu. Select "RealSystem G2 for JMF" from the list. AutoUpdate then installs the necessary RealPlayer JMF components for you.
The RealSystem G2 JMF tutorials are located in the RealSystem G2 SDK's rmasdk/jmf/samples/tutorial
directory. RealNetworks suggests that you begin with the Tutorial1.java class and examine and test each tutorial in sequence. Each stage of the tutorial, culminating in Tutorial5.java, adds incremental feature demonstrations. The following sections describe each tutorial.
Tutorial | Function |
---|---|
Tutorial 1 | Basic JMF, audio only. This presents the simplest JMF app possible. Like all the tutorial demonstrations, it expects a media URL on the command line when invoking the class. You can use the tutorial 1 file, rmasdk/testdata/thanks2.ra. |
Tutorial 2 | JMF with video. This tutorial shows how to play back media including video. Try the tutorial 2 file rmasdk/testdata/welcome.rm to demonstrate this sample. |
Tutorial 3 | This tutorial demonstrates how to add a control panel to a video window (Frame). The control panel allows you to stop, pause, restart. and seek along the video's timeline. It also demonstrates how to use RMPlayer.setStickySize() to control visual component resizing. |
Tutorial 4 | Tutorial 4 adds a demonstration of how to display the caching control's progress bar component while prefetching (buffering) occurs. This demonstration is useful only for playback of network-originated content. In RealPlayer, open the URL pnm://video.real.com/welcome.rm . |
Tutorial 5 | This tutorial provides an example of a custom control panel, showing how to access the title/author/copyright (TAC) information and use the built-in TAC dialog. It also demonstrates a technique for capturing and displaying buffering progress that is different from the technique used in the preceding tutorial. |
![]() |
Note |
---|
In addition to these tutorials, there are several sample applications and
applets included in the rmasdk/jmf/samples directory.
|
To begin, a Java application uses the Manager
class's static method createPlayer()
to instantiate a player object. But before you can ask the manager for a player, you must first create a media locator. Think of the media locator as a fancy URLit supports any random protocol, unlike URLs, which require specific protocol handlers depending on the protocol:
// first create MediaLocator
MediaLocator mrl = new MediaLocator(urlString);
Next, use the JMF Manager
class to create the player object:
try
{
player = Manager.createPlayer(mrl);
}
catch (Exception e)
{
System.err.println("Tutorial1: Unable to create player for URL = " + urlString);
System.err.println(e);
return;
}
Once you have a player object, you can simply call start()
to begin playback! However, you may want to determine what JMF is doing. In this case, you'll need to know when playback has completed so you can close down the player. Without doing that, the application would never exit because the player creates several threads that are not killed until the player is closed.
To do this, implement the ControllerListener
interface and the updateController()
method as shown below. This simple example checks for the EndOfMediaEvent
event, but you also need to account for playback that is interrupted or fails before receiving an EndOfMediaEvent
event. Specifically, you also need to trap the ControllerErrorEvent
and the StopEvent
events:
public void controllerUpdate(ControllerEvent e)
{
if (e instanceof EndOfMediaEvent)
{
player.close()
;
}
}
Building on tutorial 1, tutorial 2 adds support for displaying video. The JMF architecture specifies that the application/applet must ask the JMF player object for a component in which the video is displayed. The Java application cannot simply create its own component and ask the player to render into it.
The "visual component" displays the visual portion of the presentation. It is retrieved from the player by calling getVisualComponent()
. However, this call cannot be made successfully until the "realized" state has been reached. By convention, this is done by waiting for RealizeCompleteEvent
in the controllerUpdate()
method as shown below, but you may do this any time after the realized state has been reached:
// realizing is done - we can get the VisualComponent now!
if (e instanceof RealizeCompleteEvent)
{
visualComponent = player.getVisualComponent()
;
if (visualComponent != null)
{
frame = new Frame(urlString);
frame.setLayout(new BorderLayout()
);
frame.addNotify()
;
Dimension vsize = visualComponent.getSize()
;
int xInsets = frame.getInsets()
.left + frame.getInsets()
.right;
int yInsets = frame.getInsets()
.top + frame.getInsets()
.bottom;
frame.setSize(vsize.width + xInsets, vsize.height + yInsets + 1);
frame.add(visualComponent, "Center");
frame.setVisible(true);
frame.addWindowListener(this);
}
}
The code above provides the visual component with a container (Frame) in which to live. Specifically, once you have a visual component, create a frame, set its layout, and call addNotify()
on it to force it to create its peer. Next, set the frame's size to fit the video and add the visual component to the frame. Adding the window listener allows you to trap the window closing event and close the player at that time.
![]() |
Note |
---|
Because an audio-only presentation will normally return null from
calling Player.getVisualComponent() , always check for null after calling this
method.
|
The third tutorial shows how to add a control panel to your app. It places the control panel at the bottom of the frame, with the visual component above it. To accommodate presentations that may contain just audio, as well as presentations that contain audio and video, first determine the frame's dimensions based on just the control panel component. Do this by using getSize()
combining with the frame's insets. The variables height
and width
then become the desired dimensions:
// create frame first
frame = new Frame(urlString);
frame.setLayout(new BorderLayout()
);
frame.addNotify()
;
// get frame insets
int xInsets = frame.getInsets()
.left + frame.getInsets()
.right;
int yInsets = frame.getInsets()
.top + frame.getInsets()
.bottom;
// get the control panel
controlPanel = player.getControlPanelComponent()
;
Dimension csize = controlPanel.getSize()
;
frame.add(controlPanel, "South");
int height = csize.height + yInsets;
int width = csize.width + xInsets;
Next, check for a visual component. If there is one, simply add its height to the currently calculated height. Thus the frame's height is equal to the control component's height plus the visual component's height. For width, simply substitute the visual component's width for the control panel component's width:
// handle the visual component
Dimension vsize = null;
visualComponent = player.getVisualComponent()
;
if (visualComponent != null)
{
vsize = visualComponent.getSize()
;
frame.add(visualComponent, "Center");
width = vsize.width + xInsets;
height += vsize.height;
}
Finally, set the frame's size, make it visible, and add the window listener introduced in Tutorial 2.
frame.setSize(width, height);
frame.setVisible(true);
frame.addWindowListener(this);
Note that the video frame is still sizeable. It relies on the layout manager to keep the two components sized to fit the frame if the user resizes the frame. However, you need to decide how to handle requests within the presentation to modify the visual component. RealSystem G2 is capable of playing SMIL and Ram files, each of which can instruct the player to load multiple videos in sequence, each with its own preferred native size. The default behavior is "sticky size" on.
Sticky means that the first visual segment in the presentation determines the size of the visual component regardless of the preferred sizes of subsequent videos. It also means that when you tell the component what size you want it to be, it doesn't change when the next clip in a presentation starts.
The alternative is to call RMPlayer.setStickySize(boolean b)
to disable "sticky size." In this case, when the next clip in the Ram or SMIL file begins, the visual component will be resized automatically. This tutorial step includes code that responds to the visual component's resizing during playback by adjusting the frame size to fit it.
The call to setStickySize()
is placed anywhere after RealizeCompleatEvent
. To demonstrate this, play the sample file threevids.ram in the tutorial directory after first enabling "sticky size," then after disabling it.
Tutorial 4 demonstrates several key features. First, it shows how to use the caching control's progress bar component to monitor and/or display buffering progress during the prefetching stage. The caching control is accessed through the getControl()
or getControls()
method. Both of these are explained in detail below. Finally the tutorial explains why and when to use the RealNetworks extension RMPlayer.setStickySize()
.
The biggest code change is the use of a new panel-derived class to manage the display of the control panel component and the progress bar component. The TutorialPanel
(private) class is derived from java.awt.Pane.
It has a null layout and both components are positioned to fully occupy the panel. Only one is shown while the other is hidden. Two methods in the TutorialPanel
class are used to switch the display (showControlPanel()
and showProgBar()
).
The panel is created while handling RealizeCompleteEvent
and is added to the frame in place of the control panel itself as was demonstrated in Tutorial 3.
You can use the prebuilt progress bar component or create your own using the javax.media.CachingControl
interface. The following example retrieves the caching control from the player using getControl()
, then asks it for its GUI progress bar component:
//get progress bar component
cacheControl = (javax.media.CachingControl) player.getControl("com.real.media.RMCachingControl");
progBar = cacheControl.getProgressBarComponent()
;
Next the lower panel is created using the TutorialPanel
class.
// create lower panel using controlPanel & progBar then add to frame
lowerPanel = new TutorialPanel(controlPanel, progBar);
frame.add(lowerPanel, "South");
To show and hide the progress bar, you track CachingControlEvents
. In the controllerUpdate()
method, code has been added to watch for these events. When CachingControlEvent
is received with a value greater than zero and less than 100, the progress bar is shown. Receiving an event with a value of 100 hides the progress bar and shows the control panel.
In an application, you need to be prepared for an error during prefetching. If an error occurs, check for ControllerErrorEvent
and/or StopEvents
, and hide the progress bar. This support is not implemented in this example:
else if (e instanceof CachingControlEvent)
{
// for file based playback, 100% buffering occurs almost
// instantly - consequently, the RealizeComplete event
// has not yet been sent so no lowerPanel has been
// created yet.
CachingControlEvent cce = (CachingControlEvent) e;
long percentComplete = cce.getContentProgress()
;
if (lowerPanel != null)
{
if (percentComplete > 0 && percentComplete < 100 &&
lowerPanel.isCPShowing()
)
{
lowerPanel.showProgBar()
;
}
else if (percentComplete == 100 && lowerPanel.isPBShowing()
)
{
lowerPanel.showControlPanel()
;
}
}
}
The final tutorial demonstrates how to create a simple, custom control panel and use methods specific to RealPlayer G2 for better pause performance. By casting the javax.media.Player
object to its actual type (com.real.media.RMPlayer
), you will be able to call RMPlayer.pausePlay
and RMPlayer.beginPlay
. To accommodate this example, the Tutorial 3 (not Tutorial 4) example has been modified to include a new MyControlPanel
(private) class, which is implemented as a simple panel containing three AWT Buttons and a r/o TextArea to display buffering status during the prefetching state.
The only change this requires, other than writing the MyControlPanel
class, is to instantiate the class during the RealizeCompleteEvent
handler instead of asking the player for its control panel. Otherwise this section of code remains the same:
// get the control panel
controlPanel = newMyControlPanel
(player, currentPercentDone);
Dimension csize = controlPanel.getSize()
;
frame.add(controlPanel, "South");
Next, the MyControlPanel
class includes not only Play, Pause and Stop buttons, but a text field to display the prefetching progress (buffering percent). This example changes the CachingControlEvent
handler to pass the percent complete to MyControlPanel
.SetBuffPercent()
a method in the new MyControlPanel
class:
else if (e instanceof CachingControlEvent)
{
CachingControlEvent cce = (CachingControlEvent) e;
currentPercentDone = cce.getContentProgress()
;
// for file based playback, 100% buffering occurs almost
// instantly - consequently, the RealizeComplete event
// has not yet been sent so no controlPanel has been
// created yet. So we must check for a null controlPanel.
if (controlPanel != null)
{
controlPanel.setBuffPercent(currentPercentDone);
}
}
Finally, MyControlPanel
is a fairly standard class, derived from java.awt.Panel,
that creates several java.awt.Button
controls and a single java/awt/text field component. It uses a FlowLayout
layout manager and monitors controllerEvents
so it can enable/disable the buttons at the appropriate times. The key methods of this class are shown below:
public void playButtonClicked(ActionEvent event)
{
if (isPaused)
{
player.beginPlay()
;
}
else
{
player.start()
;
// disable play button and enable stop only
playButton.setEnabled(false);
pauseButton.setEnabled(false);
stopButton.setEnabled(true);
}
isPaused = false;
}
public void pauseButtonClicked(ActionEvent event)
{
player.pausePlay()
;
}
public void stopButtonClicked(ActionEvent event)
{
player.stop()
;
}
This method compensates for the JMF specification's omission of a pause method. Traditionally, JMF apps write code calling Player.stop()
and Player.start
to "pause" playback. Unfortunately, those semantics don't map well onto RealSystem G2's true streaming protocols. To provide both a true stopin an environment that does not cache downloaded databoth "stop" and "pause" methods are required.
Calling RMPlayer.pausePlay()
stops playback and the timeline, but it keeps existing data in the buffer and remains in the prefetched state, ready to resume playback immediately. Unlike calling Player.stop()
, this transitions the player into the realized state, meaning that prefetching is required to resume playback.
![]() |
Tip |
---|
RealNetworks recommends using pausePlay() and beginPlay() when
implementing pause capability in your apps. See Tutorial 5 for an
example of a control panel component that uses these methods.
|
Calling RMPlayer.beginPlay()
resumes playback and the timeline. It is an error to call beginPlay()
if you are not currently pausedthat is, if you have not called pausePlay()
first.
RealPlayer G2 JMF is capable of playing multistream presentations either synchronously or asynchronously (in sequence). Using a Ram file you may specify a list of URLs to play back sequentially. RealPlayer G2 attempts to buffer upcoming clips before they start so as to minimize noticeable buffering between clips. This can also be achieved with SMIL, but in more complex forms and with more flexibility.
To deal with sequential playback of video clips, where one or more clips within a presentation differ in dimension, the RealPlayer G2 JMF has implemented a method that allows you to choose between one behavior or another.
By default, RealNetworks JMF assumes that each subsequent video clip played in a presentation should adopt the current size of the visual component. For example, if a Ram or SMIL file specifies sequential playback of two video clips, the first encoded at 160x120 pixels and the second at 320x180 pixels, the second clip will stretch to fit the visual component window, which is initially sized at 160x120. Furthermore, if you place the visual component in a resizable container, and the user resizes the container and thus the visual component, the component remains that size when subsequent clips play back regardless of their native, preferred size.
Alternately, you may disable the "sticky size" behavior and allow the visual component to adapt to sequential video clips in a presentation by taking on the preferred size of the new clip. Consequently, this means that if the user resizes the visual component (assuming you allow it to be resized at run time) that size will remain in effect only until the next sequential video clip in a presentation begins and imposes its size on the visual component.
![]() |
Tip |
---|
See Tutorial 3 for a good example of how setStickySize() works.
|
Because the RealSystem G2 core is at the heart of JMF for RealSystem G2, some additional events were required to accommodate certain functionality that is not defined in the JMF specification. The following is a list of event classes derived from ControllerEvent
, with descriptions of their use and delivery.
This class is used to inform the listener of the current playback position.
public RMOnPosChangedEvent(Controller player, long position);
public long getPositionInNanos()
;
This class is sent to a listener to notify it that the size of the visual component is changing. You can use this information to reposition the visual component, or, for example, to size the component's container to fit. This notification may be received multiple times with the same size in the situation where RMPlayer.setStickySize()
has been set.
public RMSizeChangingEvent(Controller player, int width, int height);
public Rectangle getBounds();
![]() |
Additional Information |
---|
See public void setStickySize(boolean b) .
|
This class derives from GainChangeEvent
. It sends notice that a volume change has occurred. This event is used because:
GainControl
,
GainControlEvent
,
GainControlEvent
does not allow distinction between different settings changing through GainChangeControl
.
public RMVolumeChangeEvent(GainControl from, boolean mute, float dB, float level);
This class derives from GainChangeEvent
. It sends notice that a mute status change has occurred. This event is used because:
GainControl
,
GainControlEvent
,
GainControlEvent
does not allow distinction between different settings changing through GainChangeControl
.
public RMMuteChangedEvent (GainControl from, boolean mute, float dB, float level);
This class, derived from StopEvent
, is used to notify the listener that playback has been paused. At this point, the player will be in the prefetched state.
public RMPauseEvent(Controller from, int previous, int current, int target, Time mediaTime);
This class is used to notify the listener that new statistics data is available. From this event, you can retrieve the RMStatsControl
object and call its methods to obtain the new statistics:
public RMStatsControl getStatsControl()
;
public RMStatsChangedEvent(Controller player, RMStatsControl stats);
This event is used to notify the listener when the title/author/copyright information has changed. From this event object you can either get the TACControl
object or ask the event object for the TAC (and now abstract) information.
public String getTitle()
;
public String getAuthor()
;
public String getCopyright()
;
public String getAbstract()
;
public RMTACChangedEvent(Controller player,
String titleStr,
String authorStr,
String copyrightStr,
String abstractStr);
The Player.getControl()
and Player.getControls()
methods are the access points for a variety of vendor-specific accessories and components. This section shows how to use them.
With JMF, a player can support random "controls." The JMF player supports several controlsthe caching control (com.real.media.RMCachingControl
), which all JMF players must support, the title/author/copyright (TAC) control (com.real.media.RMTACControl
), and the statistics control (com.real.media.RMStatsControl
).
Access the controls with the Player.getControls()
or Player.getControl()
method. The Player.getControls()
method returns an array of javax.media.Control
objects. The Player.getControl()
method allows you to ask for a specific control. For example, to get the TAC control you write:
Control tac = player.getControl("com.real.media.RMTACControl");
Of course, this won't let you do much because the control interface only includes one method (getControlComponent()
). To use one of these Control objects, you must cast it to its actual class name. For example:
com.real.media.RMTACControl tac = (com.real.media.RMTACControl) player.getControl("com.real.media.RMTACControl")
In addition to the controls accessible through getControl()
or getControls()
, the gain control is also available through its own specific access method, getGainControl()
. The class com.real.media.RMGainControl
implements the javax.media.GainControl
and supports all of the methods of that class.
This class implements the javax.media.CachingControl
interface and implements all required methods, including the optional progress bar component. However, due to differences in architecture, the method getContentLength()
always returns 100 while getContentProgress()
always returns a number between 0 and 100 representing the percentage of buffering completed.
This control class provides access to Title, Author and Copyright (TAC) information and optional dialog in RealSystem G2 content. This class includes the following methods:
// Title/Author/Copyright
public void setTitle(String str);
public String getTitle()
;
public void setAuthor(String str);
public String getAuthor()
;
public void setCopyright(String str);
public String getCopyright()
;
TAC information is not expected to be present until after prefetching has completed. Also note that because TAC information can change during a presentation, a message called com.real.media.RMTACChangedEvent
is used. This event is sent to controller listeners whenever new TAC data is available.
![]() |
Additional Information |
---|
See the full description of RMTACChangedEvent .
|
This class allows you to access statistics for the current stream's performance and identification. Because statistics information will change during a presentation, a controller event called com.real.media.RMStatsChangedEvent
is sent to controller listeners whenever new statistical data is available. The following methods are available once the control has been retrieved:
public long getPacketsReceived()
;
public String getPacketsReceivedStr()
;
public long getPacketsRecovered()
;
public String getPacketsRecoveredStr()
;
public long getTotalPacketsReceived()
;
public String getTotalPacketsReceivedStr()
;
public long getLostPackets()
;
public String getPacketsLostStr()
;
public long getLatePackets()
;
public String getPacketsLateStr()
;
public long getTotalPacketsLostOrLate()
;
public String getTotalPacketsLostOrLateStr()
;
public long getClipBandwidth()
;
public String getClipBandwidthStr()
;
public long getAverageBandwidth()
;
public String getAvgBandwidthStr()
;
public long getCurrentBandwidth()
;
public String getCurrBandwidthStr()
;
public float getMinBandwidth()
;
public String getMinBandwidthStr()
;
public float getMaxBandwidth()
;
public String getMaxBandwidthStr()
;
![]() |
Additional Information |
---|
See the full description of RMStatChangedEvent . See "Tutorials" for more
examples of how to use JMF Controls.
|
This section discusses miscellaneous classes and utilities that come with the RealSystem G2 JMF.
This class is used to register the "com.real
" package prefix with the JMF package manager. The class is designed to be either executed from the command line using the Java interpreter or called programmatically from within an application. Traditionally the JMF package prefix list (jmf.properties
) is updated once only during installation.
To execute this class, type the following at the command prompt:
java com.real.util.RJMFConfig
To use this class programatically, simply instantiate an object of this type and call the method registerPackage()
method. For example:
com.real.util.RJMFConfig config = new com.real.util.RJMFConfig()
;
config.registerPackage("com.real");
The DebugOut
class is used within the JMF sources to send output to the Java Console for testing and debugging. This class by default does not print these messages. However, in some cases, it may be useful to dump these messages to the console at runtime. For example, when reporting a bug to RealNetworks, including the console output after enabling DebugOut
could be extremely helpful.
The DebugOut
class only has four methods, all static. The first, setDebug(boolean)
, is used to enable/disable debug messages at runtime. The second is used to conditionally send output to the Java Console (System.out
) depending on the state of setDebug()
. The final method, startLog(String)
is used to divert messages to a file instead of the Java Console. Call startLog()
and pass a fully qualified path to a file that will contain the messages. This method will not work in a Web-based applet due to security restrictions. It can, however, be used with the Appletviewer.
static public void setDebug(boolean b);
static public void println(String str);
static public void startLog(String logFile);
For several reasons, the ability to synchronize one RealPlayer G2 controller with another to achieve synchronized playback is not supported in this version of JMF for RealSystem G2. Controller methods like addController()
and removeController()
are not supported.