SC playlist

Note

The single-controller (SC) playlist API is experimental.

The single-controller (SC) playlist is built for smooth, continuous back-to-back playback of multiple items. ScPlaylist inherits from Player, so every method defined there — Player.on, Player.off, Player.getPosition, Player.seek, and more — also works on the playlist instance.

Create the playlist

Instantiate the playlist the same way you’d instantiate a Player:

<video id="my_video"></video>
const playlist = new clpp.playlist.ScPlaylist("my_video", {
  license: "<your_license_key>",
  viewerId: "<user_id>",
});

Load content

Load a playlist of two items with ScPlaylist.openPlaylist. Playback starts from the first item. Each item must have a unique id:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
  },
  {
    id: "ASSET2",
    source: { url: "..." },
  },
]);

Playback-related options such as autoplay, startTime, and streaming must be passed either through the constructor or as properties of the first item passed to openPlaylist():

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
    autoplay: true,
    playlist: {
      startTime: 120,
    },
  },
  {
    id: "ASSET2",
    source: { url: "..." },
  },
]);

Encrypted content is supported, but only when every item is encrypted with the same method and key. Pass the drm configuration as a property of the first item:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
    drm: { /* ... */ },
  },
  {
    id: "ASSET2",
    source: { url: "..." },
  },
]);

Use playlist.startTime to set the position playback starts from. The position must fall inside the first item’s timeline. It defaults to 0 and doesn’t affect the timeline’s duration:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
    playlist: {
      startTime: 120, // start 120 seconds in
    },
  },
]);

Use playlist.endTime to set the position playback stops at. The value is relative to the start of the first item and can span any number of items. It doesn’t affect the timeline’s duration:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
    playlist: {
      endTime: 600, // end at 600 seconds
    },
  },
]);

Append more items with ScPlaylist.addItem:

await playlist.addItem({
  id: "ASSET3",
  source: { url: "..." },
});

Seeking

Call Player.seek to seek within the currently playing item:

// Seek to position 2 minutes inside the current item
await playlist.seek(120);

Call ScPlaylist.seekWith to seek relative to the current position — the seek can cross into a following item, or a preceding one if the value is negative:

// Seek 2 minutes forward, possibly into one of the next items
await playlist.seekWith(120);

// Seek 2 minutes backward, possibly into one of the preceding items
await playlist.seekWith(-120);

Timeline windows

Call ScPlaylist.getTimeline to get a representation of the playlist timeline. Each playlist item is represented by one window, and the currently playing item is the current window:

const timeline = playlist.getTimeline();
// Get the index of the currently playing/selected item
const activeIndex = timeline.getCurrentWindowIndex();
const windows = timeline.getWindows();

windows.forEach((win, index) => {
  win.getStart();
  win.getDuration();
  // window ID is equal to configuration.id of the corresponding playlist item
  win.getId();
});

Timeline modes

The playlist.timeline configuration option switches to flat mode. In flat mode, the timelines of individual playlist items join into a single continuous timeline:

const playlist = new clpp.playlist.ScPlaylist("video", {
  license: "<your_license_key>",
  viewerId: "<user_id>",
  playlist: {
    timeline: "flat", // switch to flat timeline mode
  },
});

Switching to flat mode changes the behavior of timeline-related methods such as Player.getPosition, Player.getDuration, Player.getSeekRange, and Player.seek.

Timeline cropping

Use playlist.cropTimeline to crop the timeline according to the values of playlist.startTime and playlist.endTime. Cropping affects the duration of the timeline.

Crop off the first 120 seconds of the first item and start playback from the new position 0:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." },
    playlist: {
      startTime: 120,
      cropTimeline: true,
    },
  },
]);

Crop off the last 200 seconds of the last item — that is, crop anything past a cumulative 600 seconds from the start of the first item:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1", // duration 400
    source: { url: "..." },
    playlist: {
      endTime: 600,
      cropTimeline: true,
    },
  },
  {
    id: "ASSET2", // duration 400
    source: { url: "..." },
  },
]);

Crop off the first 200 seconds of the first item and the last 200 seconds of the second (last) item:

await playlist.openPlaylist(0, [
  {
    id: "ASSET1", // duration 400
    source: { url: "..." },
    playlist: {
      startTime: 200,
      endTime: 600,
      cropTimeline: true,
    },
  },
  {
    id: "ASSET2", // duration 400
    source: { url: "..." },
  },
]);

Timeline cues

Listen for timeline cues defined by DASH EventStream events or HLS EXT-X-DATERANGE tags:

playlist.on(clpp.events.TIMELINE_CUE_ADDED, (e) => {
  // The cue appeared in the manifest
  const cue = e.detail;
});
playlist.on(clpp.events.TIMELINE_CUE_ENTER, (e) => {
  // Playback position entered the cue
  const cue = e.detail;
});
playlist.on(clpp.events.TIMELINE_CUE_EXIT, (e) => {
  // Playback position exited the cue
  const cue = e.detail;
});

Each item’s cues are parsed as soon as the item is added to the playlist, so you get an overview of the cues of every item, not just the currently playing one. Call Player.getTimelineCues for a list of all cues across all items:

const cues = playlist.getTimelineCues();

Each cue can be matched to the item it belongs to via the configuration ID:

cue.configurationId === configuration.id;

In basic timeline mode, cues are positioned relative to the start of the item they belong to:

// [ASSET1, ASSET2]
cue.configurationID; // ASSET2
cue.startTime === 10; // starts 10 seconds into ASSET2
cue.endTime === 20; // ends 20 seconds into ASSET2

In flat timeline mode, cues are positioned relative to the start of the first item of the playlist:

// [ASSET1, ASSET2]
// ASSET1 has a duration of 400 seconds
cue.configurationID; // ASSET2
cue.startTime === 410; // starts 10 seconds into ASSET2
cue.endTime === 420; // ends 20 seconds into ASSET2

Stopping playback

Call ScPlaylist.release to stop playback and reset the playlist to its initial state:

await playlist.release();

Events

The playlist emits events for the currently playing item and the timeline. Events specific to the single-controller playlist are prefixed SC_PLAYLIST.

SC_PLAYLIST_TIMELINE_CHANGED

Triggered whenever a new window is added to the timeline, or when a timeline window changes duration or turns static for a live stream. Depending on your UI, this may be a good time to redraw the timeline:

function handler() {
  const duration = playlist.getDuration();
  const position = playlist.getPosition();
  // redraw the timeline
}
playlist.on(clpp.events.SC_PLAYLIST_TIMELINE_CHANGED, handler);
// playlist.off(clpp.events.SC_PLAYLIST_TIMELINE_CHANGED, handler);

SC_PLAYLIST_ITEM_CHANGED

Triggered whenever playback transitions from one item to another. The event’s id property corresponds to the id property of the item’s configuration — another good point to redraw your UI timeline:

function handler(event) {
  // event.id contains the `id` property of the current item (e.g. "ASSET1")
  console.log("Item changed", event.id);

  const duration = playlist.getDuration();
  const position = playlist.getPosition();
  // redraw the timeline
}
playlist.on(clpp.events.SC_PLAYLIST_ITEM_CHANGED, handler);
// playlist.off(clpp.events.SC_PLAYLIST_ITEM_CHANGED, handler);

SC_PLAYLIST_ENDED

Triggered when playback reaches the end of the playlist. The end of the playlist is either the endTime configuration or, by default, the end of the last item:

function handler() {
  // Reached the end of the playlist
}
playlist.on(clpp.events.SC_PLAYLIST_ENDED, handler);
// playlist.off(clpp.events.SC_PLAYLIST_ENDED, handler);

Content modes

The playlist supports two modes of operation based on the type of content it receives. The default is VOD mode, where each item is expected to be a static video stream.

The second mode is DVR.

DVR content mode

In DVR mode, every item except the last is expected to be VOD, and the last item may be a DVR stream (a growing live stream).

While the DVR stream is live, you can’t add more items to the playlist. The DVR stream may turn static over time — at that point, you can append another DVR stream item to the end of the playlist.

Enable DVR content mode with the playlist.content option. The playlist.endTime option must also be specified — it determines the last item and the end time of the playlist:

const playlist = new clpp.playlist.ScPlaylist("video", {
  license: "<your_license_key>",
  viewerId: "<user_id>",
  playlist: {
    content: "DVR",
    endTime: 950, // end after 950 seconds
  },
});

await playlist.openPlaylist(0, [
  {
    id: "ASSET1",
    source: { url: "..." }, // VOD
  },
  {
    id: "ASSET2",
    source: { url: "..." }, // DVR
  },
]);

DVR mode adds two events on top of the basic set.

SC_PLAYLIST_LIVE_TURNED_STATIC

Triggered when a DVR item becomes static. After this, you can append another DVR item to the playlist:

async function handler() {
  await playlist.addItem({
    id: "ASSET3",
    source: { url: "..." }, // DVR
  });
}
playlist.on(clpp.events.SC_PLAYLIST_LIVE_TURNED_STATIC, handler);
// playlist.off(clpp.events.SC_PLAYLIST_LIVE_TURNED_STATIC, handler);

LIVE_TURNED_STATIC

Triggered shortly before playback reaches the end of the playlist as defined by playlist.endTime. At this point, the playlist stops loading manifest updates and the timeline turns static:

function handler() {
  // The stream became static
  playlist.isLive(); // false
}
playlist.on(clpp.events.LIVE_TURNED_STATIC, handler);
// playlist.off(clpp.events.LIVE_TURNED_STATIC, handler);
Next topic: Chromecast
Previous topic: MC playlist