Queueing

Overview

Queueing allows partner 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, solving our Receiver v2 message size limit issue.
  • 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.
  • Better integration with the Cast eco-system such as Google Home and Google Assistant through new queueing data.
  • An easy-to-use QueueManager API that allows insertion, removal, and update of queue items.

Creating a queue

Application developers can create a 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.

Tip: Also see Loading media using contentId, contentUrl and entity.

// 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);
   }
   const queueData =
       loadRequestData.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 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.

Tip: Also see Loading media using contentId, contentUrl and entity.

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) {
   // Put the first set of items into the queue
   const items = this.nextItems();
   const queueData =
       loadRequestData.queueData || new cast.framework.messages.QueueData();
   queueData.name = 'Your Playlist';
   queueData.description = 'Your Playlist Description';
   queueData.items = items;
   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

As part of CAF, we now expose a new QueueManager class that 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. Methods also provide operations such as insertion, removal, and update of queueing items. To access an instance of QueueManager:

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

Here is an example of a queueing 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.

Tip: Also see Loading media using contentId, contentUrl and entity.

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 Receiver will (fetch through prevItems() if necessary) and start playing the previous item.
FETCH_ITEMS FetchItemsRequestData QUEUE_CHANGE A cast.receiver.media.QueueChange or 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

ITEMS_INFO cast.receiver.media.ItemsInfo with queue item information.
GET_QUEUE_IDS No parameter needed. QUEUE_IDS cast.receiver.media.QueueId.

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

For FETCH_ITEM, the corresponding function fetchItems in the QueueBase implementation will be called for cloud queues and will retrieve the relevant data to be returned to the receiver to store.

Whenever more items are fetched, a new message type QUEUE_CHANGE will be 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 Receiver returns media information already known to the list of ids.

Note: In general, the **Incoming **and Outgoing queueing messages will be handled by the respective CAF sender SDK (Web, iOS, Android) and ensure the proper UI is shown on the sender side. Application developers are free to intercept or listen to these messages as desired.