Wrap Custom Plugin

There are far more media players used on the web than those currently supported by the API. In situations where we need to use such players with GWT, BST Player 1.3 introduced a simple means of integration into the API framework. The integration provides support for dynamic selection, UiBinder and Javascript exportation out-of-the-box.

The next sections describe the steps to integrate custom plugin (and player) wrappers

Implement the player wrapper

The AbstractMediaPlayer class provides the basic definition of media players in the API. Based on Java's OOP model, simply extend the class and provide custom implementation as required to support the plugin. Your implementation may have to interact with the plugin using JSNI and/or Javascript Overlays.

The PlaylistSupport interface could also be implemented to provide playlist support. The code below is an illustrution wrapping a Flash player

public class MyXPlayer extends AbstractMediaPlayer {

    private MyXPlayerImpl impl;
    private String playerId;
    private Logger logger;
    private PlayerWidget pw;

    public MyXPlayer(String mediaURL, boolean autoplay, String height, String width)
            throws LoadException, PluginNotFoundException {
        PluginVersion req = ... <the plugin version required for the player>
        PluginVersion v = PlayerUtil.getFlashPlayerVersion();
        if (v.compareTo(req) < 0) {
            throw new PluginVersionException(req.toString(), v.toString());
        }

        playerId = DOM.createUniqueId();

        // create the players DOM object.
        pw = new PlayerWidget("MyProvider", "MyXPlayer", playerId, mediaURL, autoplay);
        pw.addParam("flashVars", "autoplay=" + autoplay);
        pw.addParam("allowScriptAccess", "always");

        // add the widget to the GWT panel ...
        FlowPanel panel = new FlowPanel();
        panel.add(playerWidget);
        initWidget(panel);

        if ((width == null) || (height == null)) {
            _height = "0px";    // null height & width is used when
            _width = "0px";     // wrapping with skins
            isEmbedded = true;
        } else {
            _height = height;
            _width = width;

            logger = new Logger();  // log events
            logger.setVisible(false);
            panel.add(logger);

            addDebugHandler(new DebugHandler() {

                @Override
                public void onDebug(DebugEvent event) {
                    // show debug events in logger ...
                    logger.log(event.getMessage(), false);
                }
            });
        }
    }

    @Override
    protected void onLoad() {
        fireDebug("MyXPlayer");
        playerWidget.setSize("100%", _height);
        setWidth(_width);

        impl = ... get JSNI implementation
        firePlayerStateEvent(PlayerStateEvent.State.Ready);
    }

    @Override
    public void loadMedia(String mediaURL) throws LoadException {
        impl.load(mediaURL);
    }

    @Override
    public void playMedia() throws PlayException {
        impl.play();
    }
    ...    
}

A custom control (skin) implementation can also be used. Developing skins this way automatically make them available through UiBinder and Javascript

Implement the PlayerProviderFactory

The com.bramosystems.oss.player.core.client.spi.PlayerProviderFactory interface defines the methods required by player providers. The implementation is consulted by the API when a new instance of the wrapped player is required and also to determine the version of the required media plugin installed on the player.

The implementation is called via deffered binding, so browser specific implementations can also be used with GWT type-replacement

The implementation should also be annotated with the @PlayerProvider annotation. Multiple player widgets may be handled by one PlayerProviderFactory implementation, so the annotation binds the players to the same namespace.

The code below shows a sample implementation for MyXPlayer and MyPlayer player widgets

@PlayerProvider("MyProvider")
public class MyPlayerProvider implements PlayerProviderFactory {

    @Override
    public AbstractMediaPlayer getPlayer(String playerName, String mediaURL, 
            boolean autoplay, String height, String width)
            throws PluginNotFoundException, PluginVersionException {
        if(playerName.equals("MyPlayer")) {
            return new MyPlayer(mediaURL, height, width);
        } else if(playerName.equals("MyXPlayer")) {
            return new MyXPlayer(mediaURL, autoplay, height, width);
        } else {
            throw new IllegalArgumentException("Unknown player - '" + playerName + "'");
        }
    }

    @Override
    public AbstractMediaPlayer getPlayer(String playerName, String mediaURL,
            boolean autoplay) throws PluginNotFoundException, PluginVersionException {
        return getPlayer(playerName, mediaURL, autoplay, "50px", "100%");
    }

    @Override
    public PluginVersion getDetectedPluginVersion(String playerName) 
            throws PluginNotFoundException {
        if(playerName.equals("MyXPlayer")) {
            // return the version of the required plugin installed on the browser.
            // Existing plugin detection methods can also be used if applicable.
            return PlayerUtil.getFlashPlayerVersion();
        }
    }

    @Override
    public PlayerElement getPlayerElement(String playerName, String playerId, 
            String mediaURL, boolean autoplay, HashMap<String, String> params) {
        // this is only required if using the PlayerWidget helper class
        if(playerName.equals('MyXPlayer')) {
            PlayerElement e = new PlayerElement(PlayerElement.Type.EmbedElement,
            playerId, "application/x-shockwave-flash");
            e.addParam("src", "my-flash-player.swf");
            e.addParam("name", playerId);

            Iterator<String> keys = params.keySet().iterator();
            while (keys.hasNext()) {
                String name = keys.next();
                e.addParam(name, params.get(name));
            }
            return e;
        } else {
            return null;
        }
    }
}

Naming a provider

The value of the @PlayerProvider annotation specifies the name of the provider. The name is intended for use in various files including Java source files, Javascript and UiBinder XML files. For predictable behaviour, it is encouraged that only alphanumeric characters be used as the value of this annotation

However, the value "core" is reserved for the provider of the core player widgets bundled with the API.

Annotate the custom plugin/player wrapper

Place the @Player annotation on the player widget. The annotation is used during GWT compilation to link the player with the dynamic player selection mechanism of the API.

<-- MyXPlayer.java -->
@Player(name="MyXPlayer", providerFactory=MyPlayerProvider.class, minPluginVersion="10.0.0")
public class MyXPlayer extends AbstractMediaPlayer {

<-- MyPlayer.java -->
@Player(name="MyPlayer", providerFactory=MyPlayerProvider.class, minPluginVersion="1.0.0")
public class MyPlayer extends CustomAudioPlayer {

The Player annotation has the following required attributes:

  • name: the canonical name of the player. It is also used to associate mime-types with the player in the mime-types properties file
  • providerFactory: the PlayerProviderFactory implementation class
  • minPluginVersion: the minimum version of the media plugin required by the player in the format <major>.<minor>.<revision>

Associate mime-types with the player

The ability of selecting player widgets dynamically depends on the plugin/mime-types association. The association is specified in the mime-types properties file. Only player widgets listed in the file are considered in the dynamic player selection algorithm.

A plugin entry has the following format:

plugin.<ProviderName>.<PlayerName>    <comma separated list of supported mime-types>  
or
plugin.<ProviderName>.<PlayerName>.<majorVersionNumber>_<minorVersionNumber>_<revisionNumber>

Version numbers are required only when a set of mime-types are supported by a specific version of the plugin. The version numbers specify the lowest plugin version that supports the associated mime-types.

The MyXPlayer mime-types could be associated as:

plugin.MyProvider.MyXPlayer      audio/mpeg,video/flv,video/x-flv,video/mp4

Similarly, supported streaming protocols are defined in the following format:

protocols.<ProviderName>.<PlayerName>    <comma separated list of supported mime-types>  
or
protocols.<ProviderName>.<PlayerName>.<majorVersionNumber>_<minorVersionNumber>_<revisionNumber>

Version numbers are required only when a set of protocols are supported by a specific version of the plugin. The version numbers specify the lowest plugin version that supports the associated protocols.

The MyXPlayer streaming protocols could be associated as:

protocols.MyProvider.MyXPlayer      mms

For backwards compatibility, the core provider is assumed for mime-types/protocols association without the ProviderName i.e. :

plugin.PlayerName       =>      plugin.core.<PlayerName>
protocols.PlayerName    =>      protocols.core.<PlayerName>

Using the player widgets

With the above steps, using the widgets is pretty straight forward. You can either create directly:

AbstractMediaPlayer player = new MyPlayer("http://www.example.com/my-sound.mp3",
"50px", "100%");

or through the PlayerUtil factory:

PlayerInfo pi = PlayerUtil.getPlayerInfo("MyProvider", "MyXPlayer");
AbstractMediaPlayer player = PlayerUtil.getPlayer(pi, "http://www.example.com/my-sound.mp3",
false, "50px", "100%");

or in UiBinder XML:

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:player='urn:import:com.bramosystems.oss.player.uibinder.client'>
...

<player:Player name="MyProvider:MyXPlayer" autoplay='true' height='250px' width='100%'
mediaURL='http://www.example.com/my-video.mp4' />

...    

<player:Player name="MyProvider:MyPlayer" autoplay='true' height='50px' width='100%'
mediaURL='http://www.example.com/my-sound.mp3' />

...    
</ui:UiBinder>

And if using with Javascript:

<script type="text/javascript">
    var onBSTPlayerReady = function() {
        player = new bstplayer.Player("MyProvider:MyXPlayer",
            "http://www.example.com/my-sound.mp3", false, "100%", "50px", null);
        player.inject('my-player');
    }
</script>