Automatically broadcast a Live Channels event stream

Configure a web page and player to detect and play a Live Channels event stream.


Through Live Channels, you can broadcast live events to your audience:

  • Supporting event streams that are up to 12 hours
  • Streaming live events to your web players and mobile apps
  • Providing a video-on-demand (VOD) asset after a live stream event completes

Using a combination of Live Channels and JW Player player methods, you can load a player with pre-event content and replace the pre-event content with a live event stream when a Live Channel event stream is live.



Requirements

ItemNotes
Cloud-hosted or
self-hosted player library
See: Add a web player to a site
Configured encoder settingsSee: Configure your encoder settings
Live Channel ID1. From your dashboard, click Live Channels.
2. Click the name of a Live Channel.
3. Copy the CHANNEL ID.

If you do not already have a Live Channel set up, see Create a Live Channel.
Pre-event contentContent that plays when no event stream is live

The pre-event content can be a single video (including a recording of a previous event) or a playlist of videos.

VIDEO
1. From your dashboard, click Videos.
2. Copy the ID from the MEDIA ID column.
     If this column does not appear, click the gear icon > Media ID.

PLAYLIST
1. From your dashboard, click Playlists.
2. Click the name of a playlist.
3. On the DEVELOPER or DEVELOPER RESOURCES tab, copy the JSON URL.


Embed the live event stream experience

  1. Add the full code example to your page.
  2. Replace {channel_id} with the CHANNEL ID from your JW Player dashboard.
  3. Define VOD_CONFIG with a JSON object for a video or playlist. Use the following examples to help you define the VOD_CONFIG object.
{
    "playlist": `https://cdn.jwplayer.com/v2/media/${mediaId}`,
    // Repeat the VOD indefinitely while we wait for the livestream to become available.
    "repeat": true, 
    "autostart": true,
};
{
    "playlist": "https://cdn.jwplayer.com/v2/playlists/{PLAYLIST_ID}"
};

Full code example

/* 
Please Note: This implementation is a Proof of Concept only provided 
to show the possibilities of the JW Player and should not be taken as an 
offer to create, edit or maintain custom integration or development. 
*/

<!-- The DIV for the Player -->
<div id="player"></div>

<script type="text/javascript">

/**
 * Put the Channel ID here. This is shown on the Channel Detail page in the JW Dashboard.
 */
let channelId = '{channel_id}';

/**
 * The provided snippet will play a looping VOD asset if the channel is not active.
 * Specify the Media ID of the VOD asset here.
 */
let mediaId = '{media_id}';

if (!channelId.match(/[a-zA-Z0-9]{8}/)) {
    alert("The provided channel id is not a valid Live Channel channel ID.");
    throw new Error('Please modify the channel ID');
}


/** The player config to use in order to initialize the player */
const VOD_CONFIG = {
    "playlist": `https://cdn.jwplayer.com/v2/media/${mediaId}`,
    // Repeat the VOD indefinitely while we wait for the livestream to become available.
    "repeat": true, 
    "autostart": true,
};

/**
 * How often we should be checking for an update to the live status.
 * 10 seconds is generally a good frequency, it is not useful to check more often.
 */
const UPDATE_FREQUENCY = 10 * 1e3;

/**
 * The code of the error which the player may raise if the livestream goes offline or finishes.
 */
const LIVESTREAM_COMPLETE_ERROR = 230001;

/**
 * The code of the error emitted by JW Player's HLS implementation if buffering stalls.
 * This may happen when a livestream (suddenly) finishes and no more segments are available to buffer.
 * In this case we'll switch back to VOD playback.
 */
const HLS_BUFFER_STALL_WARNING = 334001;

/**
 * The maximum number of times we'll try before giving up configuring a player.
 * @type {number}
 */
const MAX_RETRIES = 3;

/** The player on the page which we'll use for playback */
const playerInstance = jwplayer('player').setup(VOD_CONFIG);

/** The identifier of the current/last played event. */
let currentEventId;

/** An id used by the setInterval()/clearInterval() functions in order to manage the update loop. */
let intervalId;

// Start the update loop.
checkChannelStatus();

// Register an event listener that triggers when the JW Player has finished playing all
// elements in its playlist. In this demo, this event is triggered when livestream playback
// has finished.
playerInstance.on('playlistComplete', handleLivestreamFinished);

// Register an event listener that triggers when the player emits an error.
playerInstance.on('error', (error) => {
  // Check if the error may have been because the livestream stopped updating, in this case
  // we'll switch back to playing the VOD.
  if (playerInstance.getPlaylistItem().mediaid !== currentEventId) {
    // Ignore errors during VOD playback.
    return;
  }
  if (error.code === LIVESTREAM_COMPLETE_ERROR) {
    handleLivestreamFinished();
  }
});

// Register an event listener which listens for buffer warnings from the player.
// We can use the warnings generated by the player to realize a very fast switchover
// between the livestream and the VOD asset.
playerInstance.on('warning', (warn) => {
  if (playerInstance.getPlaylistItem().mediaid !== currentEventId) {
    // Ignore warnings during VOD playback.
    return;
  }
  if (warn.code === HLS_BUFFER_STALL_WARNING) {
    // The player failed to buffer more media.
    // This *may* be an indicator that the livestream has finished - in this demo we'll switch back to attempting to play
    // the VOD asset if this is the case.
    handleLivestreamFinished();
  }
});

function handleLivestreamFinished() {
  if (intervalId) {
    // We are already checking for a livestream.
    // In this state there should not be a reason to re-initialize the player -- it should already be in the correct
    // state.
    return;
  }
  console.log('Detected livestream completion. Switching to VOD playback.');

  // Enable looping of media.
  playerInstance.setConfig({repeat: true});
  // Reload the VOD playlist.
  playerInstance.load(VOD_CONFIG.playlist);
  if (channelId) {
    // Start checking for a new event.
    checkChannelStatus();
  }
  playerInstance.play();
}

/**
 * Periodically checks whether the specified livestream channel is available, and if it is, configures the player
 * to start playing it.
 */
function checkChannelStatus() {
  if (!intervalId) {
    console.log(`Waiting for Live Channel ${channelId} to become active.`);
    // Make sure to execute this method every UPDATE_FREQUENCY milliseconds.
    intervalId = setInterval(checkChannelStatus, UPDATE_FREQUENCY);
  }
  getChannelStatus(channelId).then((channelStatus) => {
    console.log(`Received channel status: %O`, channelStatus);
    if (channelStatus['status'] === 'active') {
      // Determine the id of the active event based on the returned status.
      const eventId = channelStatus['current_event'];

      // Check if we have seen this eventId before.
      if (currentEventId === eventId) {
        // The eventId returned by the API was not a *new* event id.
        // Ignore it and continue polling until we see a new id.
        return;
      }
      currentEventId = eventId;

      // Stop polling the channel status.
      intervalId = clearInterval(intervalId);

      // Attempt to configure the player in order to start livestream playback.
      configurePlayer(eventId).catch((error) => {
        console.log(`Failed to start live event stream playback: ${error}`);
      });
    }
  }, (error) => {
    console.log(`Unable to fetch the channel status for ${channelId}: ${error}`);
    // If we fail to retrieve the channel status, then give up.
    intervalId = clearInterval(intervalId);
  });
}

/**
 * (Re-)configures the active playerInstance to play the livestream identified by eventId.
 */
async function configurePlayer(eventId) {
  // There may be a slight delay between the livestream becoming available, and its playlist to become available.
  // Therefore, we first attempt to fetch the playlist for the new live event, as soon as we have successfully fetched
  // a playlist, we will load it on the player and start playback of the livestream.
  let playlist;
  let attempts = 0;
  console.log(`Fetching playlist for ${eventId}.`);
  while (!playlist) {
    try {
      playlist = await getPlaylist(eventId);
    } catch (e) {
      ++attempts;
      console.error(e);
      if (attempts >= MAX_RETRIES) {
        // Manually set up the player if we were not able to retrieve the playlist after 3 retries
        console.log('Configuring Player with m3u8');
        playlist = {
          'playlist': [{
            'mediaid': eventId,
            'file': `https://cdn.jwplayer.com/live/events/${eventId}.m3u8`
          }]
        };
        break;
      }
      // Retry with exponential backoff, i.e. first retry after 5, 10, 20, 40, 80 seconds
      // after which we ultimately give up.
      await sleep(2 ** (attempts - 1) * 5 * 1000);
    }
  }

  // Once a playlist is available, use it to configure the player.
  playerInstance.setConfig({
    repeat: false,
  });
  playerInstance.load(playlist.playlist);
  // Start playback
  playerInstance.play();
  console.log(`Playing live event stream with id '${eventId}'.`);
}

/**
 * Utility function to fetch a JSON document.
 * @param url
 */
async function fetchJSON(url, init) {
  return await fetch(url, init)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`Unable to fetch ${url}: ${response.statusText}`);
      }
      return response.json();
    });
}

/**
 * Fetches the current status of a Live Channel.
 * Returns a promise that will yield the status for a particular channel.
 *
 * @param channelId The channel to fetch the status for.
 */
function getChannelStatus(channelId) {
  return fetchJSON(`https://cdn.jwplayer.com/live/channels/${channelId}.json`);
}

/**
 * Fetches a JW Platform feed for a particular media item.
 *
 * @param mediaId The media id to fetch a single item playlist for.
 */
function getPlaylist(mediaId) {
  return fetchJSON(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { cache: "no-cache" });
}

/**
 * A simple utility method which can be used to wait for some time between retries.
 *
 * @param ms The amount of milliseconds to wait between retries.
 */
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

</script>


Start the Live Channels event stream

  1. Verify that you have configured your encoder settings.
  2. From your encoder, start the stream.

The player on your page will switch to the live event stream shortly after you have started the broadcast from your encoder. If you do not see the live event stream, check your browser's console for errors and check for any errors in the implementation on your page.