- Introduction to the MMAPI
- Simple Audio Playback
- Advanced Media Playback
- Media Capture
- Summary
Advanced Media Playback
In the previous section, we showed how to play MIDI files on Series 40 devices. Although most other media formats in the MMAPI are not yet supported on current Series 40 devices, Nokia plans to gradually support them in the future as the Series 40 hardware grows more powerful. On the other hand, today's Series 60 devices already support audio and video media formats beyond simple MIDI and tone files. For developers who are interested in writing scalable applications across Series 40 and Series 60 devices, it is important to understand those advanced MMAPI features. So, in this section and the next, we use Series 60–based examples to explain upcoming and advanced features of the MMAPI to Series 40 developers.
The MediaPlayer MIDlet is tested on Nokia 6600 and 3650 devices. Once it is started, you can select from a list of playback actions (Figure 9-5). Besides MIDI playback, MediaPlayer shows how to play wav format audio files. However, what's more interesting is that the MediaPlayer MIDlet demonstrates how to play back video files (3gpp format is supported on current devices).
Figure 9-5 The MediaPlayer MIDlet.
The menu object in the MediaPlayer MIDlet is the List object that represents the main menu (Figure 9-5). The MIDlet class itself acts as menu object's CommandListener. Hence, all user-initiated media playback actions are processed in the MediaPlayer.commandAction() method. The worker thread technique is widely used in the MediaPlayer.commandAction() method. So, let's briefly review why we need to use background threads for media playback.
Initializing Players in a Thread
As we discussed in Chapter 7, we ought to avoid executing time-consuming blocking methods in the CommandListener, since they would temporarily freeze the user interface and result in a bad user experience. In media playback, especially when playing a remote media file, we need to perform several lengthy tasks before the player can be started. They include the following:
- When we create the player from a remote URL, the Manager.createPlayer() method makes the server connection and checks the MIME type. It might take a couple of seconds to make an HTTP connection on wireless networks.
- The Player.realize() method can take a long time to complete, since it must download the entire remote media file.
- The Player.prefetch() method gains control over exclusive resources and fills the buffer. On Nokia devices, it normally returns quickly but could possibly take a long time.
In the MediaPlayer MIDlet example, we use a background worker thread to do the above tasks for both remote and local media file playbacks.
Play Back wav Audio Files
When the user selects "Play local Wav" or "Play remote Wav" from the main menu, the following code snippet in the MediaPlayer.commandAction() method is triggered. Depending on whether the file is local or remote, the program first constructs a URL pointing to the file. Then, it constructs an AudioPlayer object with three arguments: the first argument is a boolean value indicating whether the URL is for a local file; the second argument is the URL string itself; the third argument is the MIME type of the media file pointed to by this URL. The AudioPlayer object is a Thread object that can be started in a separate thread. After the thread is started, the commandAction() method returns and frees up the main UI thread. At this point, the wait screen is displayed to the user.
public class MediaPlayer extends MIDlet implements CommandListener { public void commandAction(Command c, Displayable s) { // ... ... } else if (menu.getSelectedIndex() == 3) { display.setCurrent (wait); String url = localPrefix + mediaWav; String mime = "audio/x-wav"; Thread t = new AudioPlayer(true, url, mime); t.start (); } else if (menu.getSelectedIndex() == 4) { display.setCurrent (wait); String url = remotePrefix + mediaWav; String mime = "audio/x-wav"; Thread t = new AudioPlayer(false, url, mime); t.start (); // ... ... } // ... ... }
The worker thread started by the above t.start() statement executes the AudioPlayer object's run() method. It creates a new Player instance based on the URL and whether or not the media file is local. After the worker thread initializes the Player object, it switches the display to the AudioForm screen and starts playing the audio.
class AudioPlayer extends Thread { private boolean local; private String url; private String mime; public AudioPlayer (boolean l, String u, String m) { local = l; url = u; mime = m; } public void run () { try { if (local) { InputStream is = getClass().getResourceAsStream (url); MediaPlayer.player = Manager.createPlayer(is, mime); } else { MediaPlayer.player = Manager.createPlayer(url); } MediaPlayer.player.addPlayerListener( new StopListener ()); MediaPlayer.player.realize(); MediaPlayer.player.prefetch (); AudioForm form = new AudioForm (); MediaPlayer.display.setCurrent (form); MediaPlayer.player.start (); } catch (Exception e) { e.printStackTrace (); } } }
The AudioForm screen provides a Done command to stop the playback. The Done command listener invokes the static method stopPlayer() in the MediaPlayer class.
public class MediaPlayer extends MIDlet implements CommandListener { // ... ... public static void stopPlayer () { try { player.stop (); } catch (Exception e) { e.printStackTrace (); } } // ... ... }
Play Back Video Files
Code for video playback in the MediaPlayer MIDlet is very similar to that for audio playback. The following snippet shows the relevant parts in the MediaPlayer.commandAction() method. It starts the VideoPlayer thread with the required file URL and MIME type information.
public class MediaPlayer extends MIDlet implements CommandListener { public void commandAction(Command c, Displayable s) { // ... ... } else if (menu.getSelectedIndex() == 5) { display.setCurrent (wait); String url = localPrefix + media3gpp; String mime = "video/3gpp"; Thread t = new VideoPlayer(true, url, mime); t.start (); } else if (menu.getSelectedIndex() == 6) { display.setCurrent (wait); String url = remotePrefix + media3gpp; String mime = "video/3gpp"; Thread t = new VideoPlayer(false, url, mime); t.start (); } // ... ... } // ... ... }
The VideoPlayer class is similar to the AudioPlayer class with one crucial difference. Since the video player needs access to the device's display, we need to set up the communication between the video playback screen and the player before starting the player. Hence, the VideoPlayer.run() method does not directly start the Player object. Instead, the VideoPlayer object delegates the player.start() call to the video playback screen's startPlayer() method, which handles the communication setup logic.
class VideoPlayer extends Thread { private boolean local; private String url; private String mime; public VideoPlayer (boolean l, String u, String m) { local = l; url = u; mime = m; } public void run () { try { if (local) { InputStream is = getClass().getResourceAsStream (url); MediaPlayer.player = Manager.createPlayer(is, mime); } else { MediaPlayer.player = Manager.createPlayer(url); } MediaPlayer.player.realize(); MediaPlayer.player.prefetch (); VideoForm form = new VideoForm (); // Or alternatively: // VideoForm form = new VideoCanvas (); MediaPlayer.display.setCurrent (form); form.startPlayer (MediaPlayer.player); } catch (Exception e) { e.printStackTrace (); } } }
In the MediaPlayer MIDlet, we provide two alternative implementations of the video playback screen: VideoForm for the Form UI and VideoCanvas for the Canvas UI. Now, let's check out how the startPlayer() method displays the video to the Form and Canvas screens.
Play Back Video on a Form
The VideoControl of a video player can generate an Item object, which shows the video window. On creation, the dimensions of the Item match the dimensions of the video content.
VideoControl video = (VideoControl) player.getControl("VideoControl"); Item item = (Item) video.initDisplayMode( GUIControl.USE_GUI_PRIMITIVE, null);
The Item object can be appended to any Form as a component widget. For example, we can center the video window on a form using layout attributes or add captions by appending StringItem objects before or after the video Item. The VideoForm.startPlayer() method demonstrates how to set up the player's video control on a Form (see Figure 9-6).
public class VideoForm extends Form implements CommandListener { private Player player; private Command done; public VideoForm () { super ("Play video"); done = new Command("Done", Command.OK, 1); addCommand (done); setCommandListener (this); } public void startPlayer (Player p) { player = p; try { // Add the video playback window (item) VideoControl video = (VideoControl) player.getControl("VideoControl"); Item item = (Item) video.initDisplayMode( GUIControl.USE_GUI_PRIMITIVE, null); item.setLayout(Item.LAYOUT_CENTER | Item.LAYOUT_NEWLINE_AFTER); append(item); // Add a caption StringItem s = new StringItem ("", "Video"); s.setLayout(Item.LAYOUT_CENTER); append (s); player.start(); } catch (Exception e) { e.printStackTrace(); append ("An error has occurred"); } } public void commandAction(Command c, Displayable s) { if (c == done) { MediaPlayer.stopPlayer (); MediaPlayer.showMenu (); } } }
Figure 9-6 Play back video in a Form.
Play Back Video on a Canvas
The Canvas class gives us direct control over the LCD display and the keypad. It is sometimes desirable to play back video content directly on a Canvas. For example, we could add custom borders to the video window or respond to game key events. The initDisplayMode() method in the VideoControl class initializes the Canvas to display the video. It is called in the Canvas constructor. The video window takes up the entire drawable area of the Canvas. The VideoCanvas class shows how to play back a video file on a Canvas.
public class VideoCanvas extends Canvas implements PlayerHost, CommandListener { private Player player; private int width, height; private Command done; public VideoCanvas () { width = getWidth(); height = getHeight(); done = new Command("Done", Command.OK, 1); addCommand (done); setCommandListener (this); } public void startPlayer (Player p) { player = p; try { VideoControl video = (VideoControl) player.getControl("VideoControl"); video.initDisplayMode( VideoControl.USE_DIRECT_VIDEO, this); video.setDisplayLocation(2, 2); video.setDisplaySize(width - 4, height - 4); player.start (); } catch (Exception e) { e.printStackTrace(); } } public void paint(Graphics g) { // Draw a green border around the VideoControl. g.setColor(0x00ff00); g.drawRect(0, 0, width - 1, height - 1); g.drawRect(1, 1, width - 3, height - 3); } public void commandAction(Command c, Displayable s) { if (c == done) { MediaPlayer.stopPlayer (); MediaPlayer.showMenu (); } } }