Media Controls



13.3 Media Controls

Control objects are used for controlling content-type-specific media processing operations. The set of operations are usually functionally related. Thus a Control object provides a logical grouping of media-processing functions.

All Control classes implement the interface Controllable, and the Player interface is a subinterface of Controllable. Therefore, a Player implementation can use the Control interface to extend its media-processing functions. For example, a Player can expose a VolumeControl to allow the volume level to be set.

The javax.microedition.media.control package specifies a set of predefined Controls classes. In MIDP 2.0, two controls are specified and required: ToneControl and VolumeControl.

The Controllable interface provides a mechanism for obtaining the Controls from an object such as a Player. It provides methods to get all the supported Controls and to obtain a particular Control based on its class name.

1 ToneControl

ToneControl is an interface that enables the playback of a user-defined monotonic tone sequence. A tone sequence is specified as a list of tone duration pairs and user-defined sequence blocks. The list is packaged as an array of bytes. The setSequence method is used for defining the sequence for the ToneControl.

Here is the syntax of a tone sequence, described using an Augmented BNF notation. (See http://www.ietf.org/rfc/rfc2234 for details of the Augmented BNF notation.)

sequence              = version *1tempo_definition
                        *1resolution_definition
                        *block_definition 1*sequence_event

version               = VERSION version_number
VERSION               = byte-value
version_number        = 1      ; version # 1

tempo_definition      = TEMPO tempo_modifier
TEMPO                 = byte-value
tempo_modifier        = byte-value
        ; multiply by 4 to get the tempo (in bpm) used
        ; in the sequence.

resolution_definition = RESOLUTION resolution_unit
RESOLUTION            = byte-value
resolution_unit       = byte-value

block_definition      = BLOCK_START block_number
                          1*sequence_event
                        BLOCK_END block_number
BLOCK_START           = byte-value
BLOCK_END             = byte-value
block_number          = byte-value
        ; block_number specified in BLOCK_END has to be
        ; the same as the one in BLOCK_START

sequence_event        = tone_event / block_event /
                          volume_event / repeat_event

tone_event            = note duration
note                  = byte-value ; note to be played
duration              = byte-value ; duration of the note

block_event           = PLAY_BLOCK block_number
PLAY_BLOCK            = byte-value
block_number          = byte-value
        ; block_number must be previously defined
        ; by a full block_definition

volume_event          = SET_VOLUME volume
SET_VOLUME            = byte-value
volume                = byte-value ; new volume

repeat_event          = REPEAT multiplier tone_event
REPEAT                = byte-value
multiplier            = byte-value
        ; number of times to repeat a tone

byte-value            = -128 - 127
        ; the value of each constant and additional
        ; constraints on each parameter are specified below

Note that VERSION, TEMPO, RESOLUTION, BLOCK_START, BLOCK_END, PLAY_BLOCK, and SET_VOLUME REPEAT are predefined constants.

Example: Tone sequencing

The following example illustrates the use of tone sequencing by playing the familiar song "Mary Had a Little Lamb."

/**
 * "Mary Had A Little Lamb" has "ABAC" structure.
 * Use block to repeat "A" section.
 */
byte tempo = 30; // set tempo to 120 bpm
byte d = 8;      // eighth-note

byte C4 = ToneControl.C4;;
byte D4 = (byte)(C4 + 2); // a whole step
byte E4 = (byte)(C4 + 4); // a major third
byte G4 = (byte)(C4 + 7); // a fifth
byte rest = ToneControl.SILENCE; // rest

byte[] mySequence = {
    ToneControl.VERSION, 1,       // version 1
    ToneControl.TEMPO, tempo,     // set tempo
    ToneControl.BLOCK_START, 0,   // start define "A" section
      E4,d, D4,d, C4,d, E4,d,     // content of "A" section
      E4,d, E4,d, E4,d, rest,d,
    ToneControl.BLOCK_END, 0,     // end define "A" section
    ToneControl.PLAY_BLOCK, 0,    // play "A" section
    D4,d, D4,d, D4,d, rest,d,     // play "B" section
    E4,d, G4,d, G4,d, rest,d,
    ToneControl.PLAY_BLOCK, 0,    // repeat "A" section
    D4,d, D4,d, E4,d, D4,d, C4,d  // play "C" section
};

try {
    Player p = Manager.createPlayer(
               Manager.TONE_DEVICE_LOCATOR);
    p.realize();
    ToneControl c = (ToneControl)p.getControl("ToneControl");
    c.setSequence(mySequence);
    p.start();
} catch (IOException ioe) {
} catch (MediaException me) {
}

2 VolumeControl

VolumeControl is an interface for manipulating the audio volume of a Player object. The volume settings interface allows the output volume to be specified using an integer value that varies between 0 and 100. The level scale specifies volume in a linear scale. It ranges from 0 to 100, where 0 represents silence and 100 represents the highest volume. The mapping for producing a linear multiplicative value is implementation-dependent. Setting mute on or off does not change the volume level returned by method getLevel. If mute is true, no audio signal is produced by the Player; if mute is false, an audio signal is produced and the volume is restored. When the state of the VolumeControl changes, a VOLUME_CHANGED event is delivered to the Player through its PlayerListener.

3 Custom Controls

MIDP Specification version 2.0 defines only two custom controls: ToneControl and VolumeControl. Additional controls are available in the Mobile Media API. These features are discussed in more detail in Section 13.4, "Enhanced Media Support Using the Mobile Media API."

4 Sample Code

The sample application below defines a simple media player for sampled audio with an attached volume control. The Player is created from an InputStream. A Gauge is used for visualizing and controlling the volume of the player. Simple player functions including start, stop, and rewind are supported. Furthermore, PlayerListener events are defined for printing the player states during the execution of the program.

Note that in MIDP 2.0, the wav format is mandatory only when the device supports sampled audio.

PlayerMidlet.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

/**
 * Simple music player application using the MIDP 2.0 Sound API
 */
public class PlayerMidlet extends MIDlet
        implements CommandListener, ItemStateListener {
    Display display;
    Command exitCommand = new Command("Exit", Command.EXIT, 99);
    Command stopCommand = new Command("Stop", Command.ITEM, 1);
    Command startCommand = new Command("Start", Command.ITEM, 1);
    Command rewindCommand = new Command("Rewind", Command.ITEM, 2);
    Form form;
    Source source;
    Gauge gauge;
    int itemNum;

    public PlayerMidlet() {
        display = Display.getDisplay(this);

        // Create the form
        form = new Form("MIDP 2.0 sound demo");
        form.addCommand(startCommand);
        form.addCommand(exitCommand);
        form.setCommandListener(this);

        // Set up interactive gauge for the volume control
        gauge = new Gauge("Player Volume", true, 100, 50);
        form.append(gauge);
        gauge.setLayout(0x800);
        itemNum = form.append("Value: " + gauge.getValue());
        form.setItemStateListener(this);

        // Instantiate player
        source = new Source("/sound.wav");
    }

    protected void startApp() throws MIDletStateChangeException {
        display.setCurrent(form);
    }

    protected void pauseApp() {
        source.stop();
    }

    protected void destroyApp(boolean unconditional)
            throws MIDletStateChangeException {
        source.destroy();
    }

    /**
     * Define the gauge updates
     */
    public void itemStateChanged(Item item) {
        if (item == gauge) {
            // Set the current gauge value to the player volume
            source.setLevel(gauge.getValue());
            form.delete(itemNum);
            form.append("Value: " + gauge.getValue());
        }
    }

    /**
     * Respond to commands issued on the form
     */
    public void commandAction(Command c, Displayable s) {
        if (c == stopCommand) {
            source.stop();
            form.removeCommand(stopCommand);
            form.addCommand(startCommand);
            form.addCommand(rewindCommand);
        } else if (c == startCommand) {
            source.start();
            form.removeCommand(startCommand);
            form.removeCommand(rewindCommand);
            form.addCommand(stopCommand);
        } else if (c == rewindCommand) {
            source.rewind();
            form.removeCommand(rewindCommand);
        } else if (c == exitCommand) {
            try {
                destroyApp(false);
                notifyDestroyed();
            } catch (MIDletStateChangeException e) {
            }
        }
    }
}
Source.java
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.*;

public class Source implements PlayerListener {

    Player p;
    VolumeControl volC;

    Source(String fileName) {

        // Define input stream to read from the sound file
        InputStream is = getClass().getResourceAsStream(fileName);

        if (is == null) {
            System.out.println("Error creating InputStream "
                               + fileName);
        }

        try {
            // Create player: give input stream and media type.
            // In this demo the sound file is in WAV format.
            p = Manager.createPlayer(is, "audio/X-wav");

            if (p == null) {
                System.out.println("Error creating Player "
                                   + fileName);
            } else {
                p.addPlayerListener(this);
                p.realize();        // Realize the player
                p.prefetch();       // Prefetch the player
                p.setLoopCount(-1); // Play indefinitely
                System.out.println("Realized Player: "
                                   + fileName);
            }
        } catch (IOException e) {
            System.out.println(e);
        } catch (MediaException e) {
            System.out.println(e);
        }
        // Obtain volume control for the player
        volC = (VolumeControl)p.getControl("VolumeControl");
    }

    public void destroy() {
        if (p != null) {
            // Close the player
            p.close();
        }
    }

    public void start() {
        if (p != null) {
            try {
                // Start the player from current media time
                p.start();
            } catch (MediaException e) {
                System.out.println(e);
            }
        }
    }

    public void stop() {
        if (p != null && p.getState() == Player.STARTED) {
            try {
                // Stop the player. Note that player
                // does not rewind to beginning when
                // stop is called
                p.stop();
            } catch (MediaException e) {
                System.out.println(e);
            }
        }
    }

    public void rewind() {
        if (p != null && p.getState() == Player.PREFETCHED) {
            try {
                // Rewind the player to beginning by setting
                // media time to a negative value
                p.setMediaTime(-1);
            } catch (MediaException e) {
                System.out.println(e);
            }
        }
    }

    public int setLevel(int level) {
        // Update the player volume
        return volC.setLevel(level);
    }

    /**
     * Define the player updates
     */
    public void playerUpdate(Player p, String event,
                             Object eventData) {

        if (event == STARTED)
            System.out.println("Player started");
        else if (event == STOPPED)
            System.out.println("Player stopped");
        else if (event == END_OF_MEDIA)
            System.out.println("Player at end of file");
        else if (event == VOLUME_CHANGED)
            System.out.println("Player volume changed");
        else if (event == CLOSED)
            System.out.println("Player closed");
    }
}