JReceiver: SDK Programmer's Guide for Java

This document is intended to assist those looking to implement JRec support for a client player or device driver using Java.

A Quick Example

Here's a short example showing how to use JRec from a client:
    import java.util.*;
    import java.net.*;
    import jreceiver.client.common.ClientContentCache;
    import jreceiver.common.rec.*;
    import jreceiver.common.rec.driver.*;
    import jreceiver.common.rec.security.*;
    import jreceiver.common.rec.source.*;
    import jreceiver.common.rpc.*;

    // ------------------------------------------------------------
    // initialization time

    // initialize security
    User user = new User("my_user_id", "my_password");
    RpcFactory.setDefaultCredentials(user);

    // init for driver-to-server requests
    URL remote_host = new URL("http://localhost:8080/jreceiver/servlet/RPC2");
    RpcFactory.setDefaultHost( remote_host );

    Playlists pl_rpc   = RpcFactory.newPlaylists();
    Tunes     tune_rpc = RpcFactory.newTunes();
    Drivers   drv_rpc  = RpcFactory.newDrivers();

    // provide the server with a list of natively-supported mime-types
    Vector native_mime_types = new Vector();
    native_mime_types.add("audio/x-mpeg");
    native_mime_types.add("audio/x-ogg" );

    // we respond to DIRECT control only
    Vector slave_medium_types = new Vector();
    slave_medium_types.add( "DIRECT" );

    // specify how we will receive the direct control
    URL callback_url         = new URL("http://localhost:8080/mydriver/RPC2");
    String callback_user_id  = "control_user";
    String callback_password = RandomString();

    Driver driver = drv_rpc.register(callback_url,
                                     callback_user_id,
                                     callback_password,
                                     native_mime_types,
                                     null,             // we're not a controller
                                     slave_medium_types,
                                     false);           // settings restricted to driver registrar

    Vector cmds = new Vector();
    cmds.add( new CommandRec(0, Command.STD_PLAY, Command.TYPE_DIRECT) );
    cmds.add( new CommandRec(0, Command.STD_STOP, Command.TYPE_DIRECT) );
    cmds.add( new CommandRec(0, Command.STD_NEXT, Command.TYPE_DIRECT) );
    drv_rpc.registerCommandSet(driver.getId(), cmds);

    ClientContentCache content_cache = ClientContentCache.getInstance();

    // ------------------------------------------------------------
    // Assign a unique identifier for your player, which you'll
    // want to provide to maintain session state between calls.
    // Here we use the player's IP address.
    String client_ip_addr = "192.168.100.114";

    // ------------------------------------------------------------
    // after initialization, and whenever a fresh menu is needed
    System.out.println("Playlists: ");
    Vector playlist_ids = pl_rpc.getKeys(null, //use default sort order
                                         0, Playlist.NO_LIMIT);

    // ensure that the dynamic playlist filter is included where avail
    Hashtable pl_args = new Hashtable();
    pl_args.put( Playlist.POPULATE_FILTERABLE, new Boolean(true) );

    Iterator it = playlists.getRecs(playlist_ids,
                                    null,          //use default sort order
                                    pl_args).iterator();
    while (it.hasNext()) {
        Playlist pl = (Playlist)it.next();
        System.out.println("Playlist src_id="+pl.getSrcId()
                           +" title="+pl.getTitle()
                           +" duration=" + pl.getDuration() );
    }

    // ------------------------------------------------------------
    // the user chooses one of the playlists, and thus a playlist src_id
    // is selected
    int playlist_src_id = 500;

    // ------------------------------------------------------------
    // obtain a list of all tune src_ids associated with the playlist.
    // Note that providing a driver_id will filter out unsupported mime-types.
    byte[] buf = new byte[32768];   // buffer to be reused between calls
    Iterator it_tune = tune_rpc.getKeysForPlaylist(playlist_src_id,
                                                   driver.getId(),
                                                   null,       //default sort order
                                                   0, Tune.NO_LIMIT).iterator();
    while (it_tune.hasNext()) {
        int tune_src_id = ((Integer)it_tune.next()).intValue();

        Hashtable args = new Hashtable();
        args.put( Tune.POPULATE_ARTIST      , new Boolean(true) );
        args.put( Tune.POPULATE_GENRELIST   , new Boolean(true) );
        args.put( Source.POPULATE_MFILE     , new Boolean(true) );
        args.put( Source.POPULATE_CONTENT_URL, Source.DEFAULT_URL );

        // get the details on the tune (expensive!)
        Tune tune = tunes.getRec(tune_src_id, args);

        // create content delivery object
        ClientContent content = content_cache.getContent(driver.getId(),
                                                         client_ip_addr,    // who's asking?
                                                         tune_src_id,
                                                         tune.getURL());

        // show some tag info for the tune on the display
        System.out.println( "title = " + tune.getTitle() );
        System.out.println( "content_url = " + tune.getContentURL() );
        if (tune instanceof Mfile)
            System.out.println( "filesize = " + ((Mfile)tune).getFileSize() );

        // you'll typically want to start at the suggested offset
        int tune_offset = tune.getDataOffset();

        while (true) {
            int count = content.getBytes(tune_offset, buf, 0, buf.length);
            if (count == 0)
                break;

            yourMpegPlayer(buf, count);   // this would be a call to your own player

            tune_offset += count;
        }
    }

Of course, this is over-simplified. Some things to consider:

Tips

Tip: How to build Menus? NEW!

You can use the new Menus interface...
Menus mnu_rpc = RpcFactory.newMenus();

TuneQuery tq = new TuneQuery();
tq.setArtistName("Pink Floyd");

Vector albums = mnu_rpc.getAlbumMenuRecs(tq, driver_id, 0, Menu.NO_LIMIT);
Iterator it = albums.iterator();
while (it.hasNext()) {
   Menu menu = (Menu)it.next();
   System.out.println("album_name=" + menu.getMenuText()
                      + " tune_count=" + menu.getTuneCount());
}
You can even combine several criteria. For example, query the titles for a specific album that start with "An":
TuneQuery tq = new TuneQuery();
tq.setArtistName("Pink Floyd");
tq.setAlbumName("The Wall");
tq.setTitleName("^An", true);   //true==IS_REG_EXP
might return:

Another Brick In The Wall (Part 1)
Another Brick In The Wall (Part 2)
Another Brick In The Wall (Part 3)
Anybody Out There?

Note that you can specify whether match will be exact (default) or a regular expression. The regular expression queries are a powerful feature that allow such things as selecting over a large list. The Rio driver does this to support queries from the remote:

     ^[2abc][5jkl][4ghi].*'

...to find all titles where "2", "a", "b" or "c" is the first letter, etc.

Note that the regular-expression capabilities may be limited by the database. See the MySQL documentation of REGEXP.

Once you have the artists, albums, genres or titles you want to play, consult the Tunes.getKeysForQuery() method which is quite similar to the menu interface, but returns tune src_ids.

Tip: How do you get tunes from Menus? NEW!

Use the following methods in the Tunes interface:
    public int getKeyCountForQuery(TuneQuery tune_query, int driver_id);

    public Vector getKeysForQuery(TuneQuery tune_query,
                                  int driver_id,
                                  String order_by,
                                  int rec_offset, int rec_count);
Note that they take the same TuneQuery argument you used in building menus.

Tip: How do I store settings with webapp support? NEW!

It's vastly improved with the 0.2.2 release. For now I'll point you to the code

Tip: Daemon or Servlet?

The Rio driver is implemented as a servlet because the Rio itself talks http. Unless your player does the same, you will probably want to consider another implementation.

For example, if you are developing a driver for a player that will talk to your driver via a custom protocol over a Winsock connection, you'll probably want to implement your driver as a daemon.

As another example, if you are developing a desktop player, you can simply call the API directly.

Tip: Use Paging

Because a playlist can have literally thousands of entries, you will want to avoid fetching the entire playlist in one batch, especially when presenting a menu to the user in a timely manner. Use the getRec(..., offset, count) versions to retrieve ranged results and consider caching for reuse.

To support really large lists, consider using a sparse array to cache ranges of ids instead of getting them all at once.

Tip: Preserve Server Bandwidth

Get mpeg data at the rate that your player is consuming it. 32k or 64k at a time should be sufficient in most cases.

Tip: Read through interfaces, not implementations

You shouldn't assume that the Tunes.getRecs() will always be returning FtuneRec objects. All you can be guaranteed is that each object in the resulting vector exposes the Tune interface. Other properties, like filesize, would be available through the Mfile interface, which you'd want to test for:
    long filesize = 0L;
    if (obj instanceof Mfile)
         filesize = ((Mfile)obj).getFileSize();
Ditto for external resources (which you could play directly, bypassing JRec's content server):
    URL remote_tune_url = null
    if (obj instanceof Mexternal)
         remote_tune_url = ((Mexternal)obj).getRemoteURL();

So don't cast to the implementations. Use the interfaces and test for their presence.

Ditto for getting playlists. When calling Playlists.getRecs(), you can only be guaranteed that each object of the returned vector will expose the Playlist interface. It may or may not expose Mfile or Mexternal or Filterable or SourceList or SourceFolder.


Copyright © 2001-2002, Reed Esau & the JReceiver Project (http://jreceiver.sourceforge.net), All Rights Reserved