Develop new Player Widgets (and Providers)

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 provides 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 develop and integrate custom plugin (and player) widgets using the API.

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

Top

Implement the PlayerProviderFactory

The PlayerProviderFactory interface defines the methods required by player providers. The implementation is consulted by the API to learn about the player widgets it handles including version of required media plugin installed, media mime-types supported by each player; as well as when a new instance of the wrapped player is required.

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("com.example.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;
        }
    }

    @Override
    public Set<String> getPermittedMimeTypes(String playerName, PluginVersion version) {
        // return a set of mime-types supported by the players.
        // This is important for the dynamic player selection algorithm
        HashSet<String> types = new HashSet<String>();
        if(playerName.equals('MyXPlayer')) {
            types.add("video/mp4");
            types.add("audio/mp4");
            if (version.compareTo(10, 5, 0) >= 0) {
                types.add("audio/3gpp");
                types.add("video/3gpp");
            }
        }
        return types;
    }

    @Override
    public Set<String> getPermittedMediaProtocols(String playerName, PluginVersion version) {
        // return a set of protocols supported by the players
        // This is important for the dynamic player selection algorithm
        HashSet<String> types = new HashSet<String>();
        if(playerName.equals('MyXPlayer') && version.compareTo(10, 5, 0) >= 0) {
            types.add("rtp");
            types.add("rtsp");
        }
        return types;
    }
}
Top

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, only alphanumeric characters should be used as the value of this annotation. Generally, it is recommended that Java's package naming convention be followed in choosing a name.

However, the value "core" is reserved for the Core Player Provider 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
  • providerFactory: the PlayerProviderFactory implementation class
  • minPluginVersion: the minimum version of the media plugin required by the player in the format <major>.<minor>.<revision>
Top

Associate mime-types with the players

The ability of selecting player widgets dynamically depends on the plugin/mime-types association. The mime-types are specified in the default-mime-types properties file.

The mime-type association is specified with the getPermittedMimeTypes(playerName, pluginVersion) method while the streaming protocol association is specified with the getPermittedMediaProtocols(playerName, pluginVersion) method. This is illustrated in the PlayerProviderFactory implementation class above. Only player widgets that return a populated mime-type set are considered in the dynamic player selection algorithm.

The PluginVersion should be considered only when a set of mime-types/streaming protocols are supported by specific versions of the player plugin.

Top

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("com.example.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="com.example.MyProvider:MyXPlayer" autoplay='true' height='250px' 
    width='100%' mediaURL='http://www.example.com/my-video.mp4' />

...    

<player:Player name="com.example.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("com.example.MyProvider:MyXPlayer",
            "http://www.example.com/my-sound.mp3", false, "100%", "50px", null);
        player.inject('my-player');
    }
</script>
Top