Queueing

Overview

The Web Receiver SDK supports queueing with the default queue provided by the SDK using QueueData and QueueManager or using a custom queue by implementing cast.framework.QueueBase and using QueueManager for updates.

The Queueing API allows applications to better integrate with Cast by providing the following features:

  • Support of Google's and partner's cloud queue implementation so externally stored and created queue can be directly loaded into Cast devices.
  • Mechanisms that allows pagination of items in the queue rather than loading everything at once.
  • Support for new messaging such as going to the next item, the previous item, fetching a window of items, as well as getting media information related to a set of queue items.
  • The QueueManager to manage insertion, removal, and update of queue items.

Default queue

The Web Receiver SDK provides limited queue support out of the box in the form of a default queue.

To use the default queue, provide the queueData in the LoadRequestData of your sender-side loads or send a local load request using PlayerManager#load. Also see Loading media.

On the receiver-side, the queue can be modified using the QueueManager once the initial media has been loaded.

Custom queue

If the default queue does not provide the queueing functionality required for your app, the ability to create a custom queue is available, allowing for more capabilities and flexibility.

Application developers can create a Web Receiver side queue by implementing cast.framework.QueueBase.

Here is a basic example of a simple queue where the initialize call is overridden and then a list of queue items along with queue descriptions are provided to the Cast device.

Also see Loading media.

// Creates a simple queue with a combination of contents.
const DemoQueue = class extends cast.framework.QueueBase {
 constructor() {
   super();

   /**
    * List of media urls.
    * @private @const {!Array<string>}
    */
   this.myMediaUrls_ = [...];
 }
 /**
  * Provide a list of items.
  * @param {!cast.framework.messages.LoadRequestData} loadRequestData
  * @return {!cast.framework.messages.QueueData}
  */
 initialize(loadRequestData) {
   const items = [];
   for (const mediaUrl of this.myMediaUrls_) {
     const item = new cast.framework.messages.QueueItem();
     item.media = new cast.framework.messages.MediaInformation();
     item.media.contentId = mediaUrl;
     items.push(item);
   }
   let queueData = loadRequestData.queueData;
   // Create a new queue with media from the load request if one doesn't exist.
   if (!queueData) {
     queueData = new cast.framework.messages.QueueData();
     queueData.name = 'Your Queue Name';
     queueData.description = 'Your Queue Description';
     queueData.items = items;
     // Start with the first item in the playlist.
     queueData.startIndex = 0;
     // Start from 10 seconds into the first item.
     queueData.currentTime = 10;
   }
   return queueData;
 }
};

In this example, the list of items in the initialize call is provided in the provider's QueueBase constructor call. However, for a cloud queue implementation, the custom Web Receiver logic can fetch the items externally and then return them as part of the initialize call.

To demonstrate a more comprehensive use of the queueing API, here is a Demo queue that implements most of the QueueBase class.

const DemoQueue = class extends cast.framework.QueueBase {
 constructor() {
   /** @private {} */
   super();
   YourServer.onSomeEvent = this.updateEntireQueue_;
 }

 /**
  * Initializes the queue.
  * @param {!cast.framework.messages.LoadRequestData} loadRequestData
  * @return {!cast.framework.messages.QueueData}
  */
 initialize(loadRequestData) {
   let queueData = loadRequestData.queueData;
   // Create a new queue with media from the load request if one doesn't exist.
   if (!queueData) {
     queueData = new cast.framework.messages.QueueData();
     queueData.name = 'Your Queue Name';
     queueData.description = 'Your Queue Description';
     // Put the first set of items into the queue
     const items = this.nextItems();
     queueData.items = items;
     // Start with the first item in the playlist.
     queueData.startIndex = 0;
     // Start from 10 seconds into the first item.
     queueData.currentTime = 10;
   }
   return queueData;
 }

 /**
  * Picks a set of items from remote server after the reference item id and
  * return as the next items to be inserted into the queue. When
  * referenceItemId is omitted, items are simply appended to the end of the
  * queue.
  * @param {number} referenceItemId
  * @return {!Array<cast.framework.QueueItem>}
  */
 nextItems(referenceItemId) {
   // Assume your media has a itemId and the media url
   return this.constructQueueList_(YourServer.getNextMedias(referenceItemId));
 }

 /**
  * Picks a set of items from remote server before the reference item id and
  * return as the items to be inserted into the queue. When
  * referenceItemId is omitted, items are simply appended to beginning of the
  * queue.
  * @param {number} referenceItemId
  * @return {!Array<cast.framework.QueueItem>}
  */
 prevItems(referenceItemId) {
   return this.constructQueueList_(YourServer.getPrevMedias(referenceItemId));
 }

 /**
  * Constructs a list of QueueItems based on the media information containing
  * the item id and the media url.
  * @param {number} referenceItemId
  * @return {!Array<cast.framework.QueueItem>}
  */
 constructQueueList_(medias) {
   const items = [];
   for (media of medias) {
     const item = new cast.framework.messages.QueueItem(media.itemId);
     item.media = new cast.framework.messages.MediaInformation();
     item.media.contentId = media.url;
     items.push(item);
   }
   return items;
 }

 /**
  * Logs the currently playing item.
  * @param {number} itemId The unique id for the item.
  * @export
  */
 onCurrentItemIdChanged(itemId) {
   console.log('We are now playing video ' + itemId);
   YourServer.trackUsage(itemId);
 }
};

In the example above, YourServer is your cloud queue server and has logic about how to fetch certain media items.

To use QueueBase -implemented queueing, one would set the queue option in the CastReceiverContext:

const context = cast.framework.CastReceiverContext.getInstance();
context.start({queue: new DemoQueue()});

Managing a queue

The QueueManager gives developers flexibility in developing their queueing solutions by providing methods to access the currently stored list of queue items as well as the current playing item. It also provides operations such as insertion, removal, and update of queueing items. The following snippet shows how to access an instance of QueueManager:

const context = cast.framework.CastReceiverContext.getInstance();
const queueManager = context.getPlayerManager().getQueueManager();

Default queue management

Once the initial queue has been loaded, the QueueManager can be used to perform actions such as retrieving the current item, retrieving all items in the queue, and updating the items in the queue using insertItems, removeItems, and updateItems.

Custom queue management

Here is an example of a custom queue implementation that uses the insertion and removal methods based on some event. The example also demonstrates a usage of updateItems where the developers can modify the queue items in the existing queue, such as removing ad breaks.

const DemoQueue = class extends cast.framework.QueueBase {
  constructor() {
    super();

    /** @private @const {!cast.framework.QueueManager} */
    this.queueManager_ = context.getPlayerManager().getQueueManager();
  }

  /**
   * Provide a list of items.
   * @param {!cast.framework.messages.LoadRequestData} loadRequestData
   * @return {!cast.framework.messages.QueueData}
   */
  initialize(loadRequestData) {
    // Your normal initialization; see examples above.
    return queueData;
  }

  /** Inserts items to the queue. */
  onSomeEventTriggeringInsertionToQueue() {
    const twoMoreUrls = ['http://url1', 'http://url2'];
    const items = [];
    for (const mediaUrl of twoMoreUrls) {
      const item = new cast.framework.QueueItem();
      item.media = new cast.framework.messages.MediaInformation();
      item.media.contentId = mediaUrl;
      items.push(item);
    }
    // Insert two more items after the current playing item.
    const allItems = this.queueManager_.getItems();
    const currentItemIndex = this.queueManager_.getCurrentItemIndex();
    const nextItemIndex = currentItemIndex + 1;
    let insertBefore = undefined;
    if (currentItemIndex >= 0 &&
        currentItemIndex < allItems.length - 1) {
      insertBefore = allItems[nextItemIndex].itemId;
    }
    this.queueManager_.insertItems(items, insertBefore);
  }

  /** Removes a particular item from the queue. */
  onSomeEventTriggeringRemovalFromQueue() {
    this.queueManager_.removeItems([2]);
  }

  /** Removes all the ads from all the items across the entire queue. */
  onUserBoughtAdFreeVersion() {
    const items = this.queueManager_.getItems();
    this.queueManager_.updateItems(items.map(item => {
      item.media.breaks = undefined;
      return item;
    }));
  }
};

Incoming and outgoing messages

To fully support receiver-side queue fetching as the source of truth, the following additional queueing messages are introduced and handled by the CAF Receiver SDK:

Incoming Message Parameters Outgoing Response Message Return
NEXT No parameter needed. MEDIA_STATUS Receiver will (fetch through nextItems() if necessary) and start playing the next item.
PREVIOUS No parameter needed. MEDIA_STATUS The Web Receiver will (fetch through prevItems() if necessary) and start playing the previous item.
FETCH_ITEMS FetchItemsRequestData QUEUE_CHANGE A cast.framework.messages.QueueChange. As an example, for an insert case, the items field in the JSON will contain the list of new items fetched.
GET_ITEMS_INFO GetItemsInfoRequestData containing itemIds: Array<number> ITEMS_INFO cast.framework.messages.ItemsInfo with queue item information.
GET_QUEUE_IDS No parameter needed. QUEUE_IDS cast.framework.messages.QueueIds.

For NEXT/PREVIOUS, if the existing queue representation on the Web Receiver does not have more items, the QueueBase.nextItems() or QueueBase.prevItems() is automatically invoked to receive more items.

For FETCH_ITEM, the corresponding function fetchItems in the QueueBase implementation is called for cloud queues, which retrieves the relevant data to be returned to the Web Receiver to store.

Whenever more items are fetched, a new message type QUEUE_CHANGE is triggered and sent back to the sender. See the various types of queue changes.

For GET_ITEMS_INFO, QueueBase's implementation is not triggered and the Web Receiver returns media information already known to the list of ids.

Shuffling a queue

To set the items in your queue to be shuffled, set the shuffle flag of QueueData to true when loading your items into the queue.

If you are using an implementation of QueueBase, use the shuffle method to return a shuffled list of items.

To shuffle an existing queue, use the shuffle flag of the QUEUE_UPDATE MessageType, rather than the QUEUE_SHUFFLE command. See QueueUpdateRequestData for more information.

Repeat mode

To set the items in your queue to be repeated, set the repeatMode property of QueueData to the desired RepeatMode when loading your items into the queue.

To change the RepeatMode of an existing queue, use the repeatMode property of the QueueUpdateRequestData, which uses the QUEUE_UPDATE MessageType.