DEV Community

Cover image for Amazon IVS Live Stream Playback with Chat Replay using the Sync Time API
Todd Sharp for AWS

Posted on

Amazon IVS Live Stream Playback with Chat Replay using the Sync Time API


In a previous post, we looked at an undocumented approach to assist with chat replay by using an event listener for the IVSPlayer.MetadataEventType.ID3 event type and using the transcode time from the stream's metadata to help with chat replay on VOD playback. In this post, we'll update the approach used in that post to utilize a new documented and reliable method which is available in the Amazon IVS Player SDK version 1.26.0 and beyond.

In the last post, we continued on a short series of posts where we looked at auto-recording Amazon Interactive Video Service (Amazon IVS) live streams to Amazon S3, and logging messages sent to an Amazon IVS chat room. Once you've got a stream in S3 and a log of chat messages, the next step is to combine them for VOD playback. Since chat messages are logged to the logging destination include a GMT based timestamp representing the wall clock time that the message was posted, we can use the getTimeSync method (and associated SYNC_TIME_UPDATE event) to know what messages should be visible at any point in time.

💡Tip: The getSyncTime API is not just for chat replay! Any application that needs the exact wall clock time for a live stream at any point in time can utilize this API. For example: trivia apps, live sports scores, live polls, gaming, etc!

The getSyncTime API

Per the docs, the getSyncTime method will provide:

The synchronized time is a UTC time that represents a specific time during playback, at a granularity of 1 second. It can be used to sync external events and state to a specific moment during playback.

Listening for Sync Time Events

Let's set up an Amazon IVS player to playback a recorded stream using the Player SDK. First, we'll include the latest Amazon IVS player SDK via a <script> tag.

New to Amazon IVS? Check out the blog series Getting Started with Amazon Interactive Video Service. If you have questions on getting started, post a comment on any post in that series (or below)!

<script src="https://player.live-video.net/1.26.0/amazon-ivs-player.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

As usual, we'll need to include a <video> element in our HTML markup that will be used for playback.

<video id="video-player" muted controls autoplay playsinline></video>
Enter fullscreen mode Exit fullscreen mode

Now we can create an instance of the IVS player. I'm hardcoding the URL below, but you can obtain this URL via the method described in this post.

const streamUrl = 'https://[redacted].cloudfront.net/ivs/v1/[redacted]/[redacted]/2022/11/17/18/6/[redacted]/media/hls/master.m3u8';
const videoEl = document.getElementById('video-player');
const ivsPlayer = IVSPlayer.create();
ivsPlayer.attachHTMLVideoElement(videoEl);
ivsPlayer.load(streamUrl);
ivsPlayer.play();
Enter fullscreen mode Exit fullscreen mode

Next, we can set up a listener for the IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE event and log out the timestamp:

ivsPlayer.addEventListener(IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE, (ts) => {
  console.log(`IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE: ${ts * 1000}`);
});
Enter fullscreen mode Exit fullscreen mode

SYNC_TIME_UPDATE logs

Retrieving the Chat Logs

When my page loads, I can utilize the method outlined in the previous post in this series to retrieve the entire chat log for the stream and render it in the chat container <div>. Since no messages should be visible at the very start of the stream, I'll make sure that they call contain a class that hides them from the user and store a data attribute with the proper timestamp so that I can know which messages should be visible given any timestamp in the stream.

window.chatLog = await getChatLogs(logGroupName, chatArn, startTime, endTime);
renderChat();
Enter fullscreen mode Exit fullscreen mode

My renderChat() function handles posting each message to the chat container.

const renderChat = () => {
  const chatContainer = document.getElementById('chat');
  window.chatLog.forEach(msg => {
    const msgTemplate = document.getElementById('chatMsgTemplate');
    const msgEl = msgTemplate.content.cloneNode(true);
    const ts = new Date(msg.event_timestamp).getTime() * 1000;
    msgEl.querySelector('.msg-container').setAttribute('data-timestamp', ts);
    msgEl.querySelector('.chat-username').innerHTML = msg.payload.Attributes.username;
    msgEl.querySelector('.msg').innerHTML = msg.payload.Content;
    chatContainer.appendChild(msgEl);
  });
};
Enter fullscreen mode Exit fullscreen mode

Now I can modify the IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE listener to call a replayChat() function and pass it the current timestamp.

ivsPlayer.addEventListener(IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE, (evt) => {
  console.log(`IVSPlayer.PlayerEventType.SYNC_TIME_UPDATE: ${evt * 1000}`);
  replayChat(evt * 1000);
});
Enter fullscreen mode Exit fullscreen mode

In replayChat(), I can find all of the chat nodes that contain a timestamp less than or equal to the current timestamp from the recorded stream and show/hide any chat message based on that timestamp.

const replayChat = (currentTimestamp) => {
  Array.from(document.querySelectorAll('[data-timestamp]')).forEach(node => {
    const chatMsgTs = Number(node.getAttribute('data-timestamp'));
    const isVisible = chatMsgTs <= currentTimestamp;
    if (isVisible) {
      node.classList.remove('d-none');
    }
    else {
      node.classList.add('d-none');
    }
  });
  const chatContainer = document.getElementById('chat');
  chatContainer.scrollTop = chatContainer.scrollHeight;
}
Enter fullscreen mode Exit fullscreen mode

At this point, we have achieved the goal of playing back a recorded Amazon IVS live stream with full chat replay.

Video playback with chat replay

Summary

In this post, we looked at how to combine recorded Amazon IVS live streams with logged chat messages to create an on-demand replay of a stream with properly timed chat messages.

Top comments (0)