paint-brush
How to Build Your Own "LoFi Radio" Station and Stream it Live With Amazon IVSby@amazonivs
1,016 reads
1,016 reads

How to Build Your Own "LoFi Radio" Station and Stream it Live With Amazon IVS

tldt arrow

Too Long; Didn't Read

This post shows you how to use live streaming with Amazon Interactive Video Service (Amazon IVS) Live streams are popular among people looking for a non-distracting soundtrack to relax, study, or chat and make new friends. We'll walk through the process of creating your own lofi live stream from the beginning.
featured image - How to Build Your Own "LoFi Radio" Station and Stream it Live With Amazon IVS
Amazon Interactive Video Service (IVS)  HackerNoon profile picture

This post might not fall into the "normal" ways that you'd use live streaming with Amazon Interactive Video Service (Amazon IVS), but it's a fun project to play around and learn with and it illustrates some unique features of the Web Broadcast SDK (notably the ability to stream from a source other than a traditional camera and microphone).


You may be familiar with the concept of "lofi radio" channels, but if not, they usually consist of some sort of animated character in a mostly static scene with a looped audio track containing lofi music. These live streams are popular among people looking for a non-distracting soundtrack to relax, study, or chat and make new friends. It doesn't fit the "traditional" user generated content (UGC) model of a broadcaster streaming a game or their webcam, but there is no arguing that these streams are hugely popular. For example, as of the time of publishing this article, the Lofi Girl channel on YouTube currently has 11.7 million subscribers and boasts nearly 1.5 billion total views.


Maybe it's time that someone built an entire live streaming UGC platform that gives users the ability to create their own custom lofi channels and broadcast them to their friends? That's a free idea - run with it!


I'm going to assume that you're new to Amazon IVS, so we'll walk through the process of creating your own lofi live stream from the beginning. If you're already familiar with Amazon IVS, feel free to jump past the parts that you're already comfortable with.

Sign Up for a Free AWS Account

Step one is to sign up for a free AWS account. New accounts will be eligible for 5 hours of basic input, 100 hours of SD output, and a generous amount of monthly chat messages for the first 12 months. This should be plenty of time to play around with Amazon IVS and learn all about how it works.

Creating an Amazon IVS Channel

I've blogged about how to get started creating an Amazon IVS channel, but the quickest way to create a one-off channel is via the AWS CLI (install docs). The command to create a channel is:

$ aws ivs create-channel --name lofi-demo --latency-mode LOW --type BASIC


This will return some important information about your stream:

{
  "channel": {
    "arn": "arn:aws:ivs:us-east-1:[redacted]:channel/[redacted]",
    "authorized": false,
    "ingestEndpoint": "f99084460c35.global-contribute.live-video.net",
    "latencyMode": "LOW",
    "name": "lofi-demo",
    "playbackUrl": "https://f99084460c35.us-east-1.playback.live-video.net/api/video/v1/us-east-1.[redacted].channel.[redacted].m3u8",
    "recordingConfigurationArn": "",
    "tags": {},
    "type": "BASIC"
  },
  "streamKey": {
    "arn": "arn:aws:ivs:us-east-1:[redacted]:stream-key/[redacted]",
    "channelArn": "arn:aws:ivs:us-east-1:[redacted]:channel/[redacted]",
    "tags": {},
    "value": "sk_us-east-1[redacted]"
  }
}


Keep this info handy, as we'll need the ingestEndpoint, playbackUrl, and streamKey in just a bit.

Broadcasting to the Lofi Channel with the Web Broadcast SDK

Now that we have a channel created, we can immediately start broadcasting to it. If you've done any live streaming, you're probably familiar with desktop streaming software (like OBS, Streamlabs Desktop, etc). Instead of using third-party software, we're going to write some code to broadcast our lofi stream directly from a browser.


Before We Code: You should have your own animation handy and an MP3 track. Copyright is a thing - please only utilize assets that you have obtained the proper licensing to use. If your animation is a GIF, you'll want to convert it to an MP4 file (there are plenty of tools available online for this).


The first step is to create an HTML page that includes the Web Broadcast SDK script, and contains a <canvas> element for the broadcast preview, a <video> element for the source animation, and a button to start the broadcast. The src of the <video> tag should point to your animation source and should have a width and height of 1px (we're going to preview the video on the <canvas> in just a bit, so no need for the source video to be visible).


<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Lofi Radio</title>
  <script src="https://web-broadcast.live-video.net/1.2.0/amazon-ivs-web-broadcast.js"></script>
</head>
<body>

  <canvas id="broadcast-preview"></canvas>

  <video id="src-video" 
    src="/video/lofi.mp4" 
    style="width: 1px; height: 1px;"
    muted loop></video>

  <button id="broadcast-btn">Broadcast</button>

</body>
</html>


Next, create an external JS file called lofi.js (don't forget to include it in your HTML above). Start off this file with a DOMContentLoaded listener and define an init() function to get things set up. In the init() function, we'll create an instance of the IVSBroadcastClient from the Web Broadcast SDK. We'll need to pass it the ingestEndpoint from our channel. We'll also create a click handler for the broadcast button that will call a toggleBroadcast() function that we'll define in just a bit.


const init = () => {
  window.broadcastClient = IVSBroadcastClient.create({
    streamConfig: IVSBroadcastClient.BASIC_FULL_HD_LANDSCAPE,
    ingestEndpoint: [Your ingestEndpoint],
  });
  document.getElementById('broadcast-btn').addEventListener('click', toggleBroadcast);
};
document.addEventListener('DOMContentLoaded', init);


Note: To keep things simple, we're setting the broadcastClient into the window scope. In reality, you'll probably be using a framework so you'll usually avoid using the global window scope like this.


Next, we need to create our video stream and attach it to the broadcastClient. Modify the init() function as follows:


const init = () => {
  window.broadcastClient = IVSBroadcastClient.create({
    streamConfig: IVSBroadcastClient.BASIC_FULL_HD_LANDSCAPE,
    ingestEndpoint: [Your ingestEndpoint],
  });

  window.video = document.getElementById('src-video');
  window.broadcastClient.addImageSource(window.video, 'video-track', { index: 0 });
  const preview = document.getElementById('broadcast-preview');
  window.broadcastClient.attachPreview(preview);

  document.getElementById('broadcast-btn').addEventListener('click', toggleBroadcast);
};


At this point, we've got our animation source created and added to the client. Next, let's add a createAudioStream() function that will attach the MP3 audio source to the stream.


const createAudioStream = async () => {
  /* Music from Uppbeat (free for Creators!): https://uppbeat.io/t/vens-adams/alone-in-kyoto */
  const audioContext = new AudioContext();
  const mp3 = await fetch('/audio/alone-in-kyoto.mp3');
  const mp3Buffer = await mp3.arrayBuffer();
  const audioBuffer = await audioContext.decodeAudioData(mp3Buffer);
  const streamDestination = audioContext.createMediaStreamDestination();
  const bufferSource = audioContext.createBufferSource();
  bufferSource.buffer = audioBuffer;
  bufferSource.start(0);
  bufferSource.connect(streamDestination);
  bufferSource.loop = true; 
  window.broadcastClient.addAudioInputDevice(streamDestination.stream, 'audio-track');
};


And modify the init() function to add async and call await createAudioStream().


const init = async () => {
  window.broadcastClient = IVSBroadcastClient.create({
    streamConfig: IVSBroadcastClient.BASIC_FULL_HD_LANDSCAPE,
    ingestEndpoint: [Your ingestEndpoint],
  });

  window.video = document.getElementById('src-video');
  window.broadcastClient.addImageSource(window.video, 'video-track', { index: 0 });
  const preview = document.getElementById('broadcast-preview');
  window.broadcastClient.attachPreview(preview);

  await createAudioStream();

  document.getElementById('broadcast-btn').addEventListener('click', toggleBroadcast);
};


Finally, we can define the toggleBroadcast() function to start/stop the stream when the button is clicked.


Note: you'll need to plug in your streamKey at this point. Treat this like any other sensitive credential and protect it from being committed to source control or included in a file that can be accessed by others.


const toggleBroadcast = () => {
  if (!window.isBroadcasting) {
    window.video.play();
    window.broadcastClient
      .startBroadcast([your streamKey])
      .then(() => {
        window.isBroadcasting = true;
      })
      .catch((error) => {
        window.isBroadcasting = false;
        console.error(error);
      });
  }
  else {
    window.broadcastClient.stopBroadcast();
    window.video.pause();
    window.isBroadcasting = false;
  }
};


Now we can save and run the application. Here's how my page looks (with a bit of CSS applied for styling and layout).


When I click the Broadcast button, the animation begins to play and our live stream is broadcast to our channel.


Note: You will not be able to hear the channel audio from the broadcast tool. We'll look at playback in just a bit.

Playing Back the Lofi Channel

I've blogged about live stream playback with Amazon IVS before, but we'll create a quick and simple player here to complete the demo. Create an HTML file, include the Amazon IVS player SDK, add a <video> element, create an instance of the player (plug in your playbackUrl), and initialize playback.


<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Lofi Radio Playback</title>
  <script src="https://player.live-video.net/1.14.0/amazon-ivs-player.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const ivsPlayer = IVSPlayer.create();
      ivsPlayer.attachHTMLVideoElement(document.getElementById('video-player'));
      ivsPlayer.load([your playbackUrl]);
      ivsPlayer.play();
    })
  </script>
</head>

<body>
  <video id="video-player" controls autoplay playsinline></video>
</body>

</html>


And now we can playback our lofi radio stream in it's own page!


The nice thing about our code is that both the audio and animation will continue on a loop until your channel stops broadcasting.

Bonus: Broadcasting Without a Browser

Another option for broadcasting your own lofi stream is to avoid having any user interface and broadcast in headless mode with FFMPEG (more about that here). Here's an example command that would work for this channel.


$ ffmpeg \
    -re \
    -stream_loop -1 \
    -i ./public/video/lofi.mp4 \
    -stream_loop -1 \
    -i ./public/audio/alone-in-kyoto.mp3 \
    -map 0:v:0 \
    -map 1:a:0 \
    -c:v libx264 \
    -b:v 6000K \
    -maxrate 6000K \
    -pix_fmt yuv420p \
    -s 1920x1080 \
    -profile:v main \
    -preset veryfast \
    -force_key_frames "expr:gte(t,n_forced*2)" \
    -x264opts "nal-hrd=cbr:no-scenecut" \
    -acodec aac \
    -ab 160k \
    -ar 44100 \
    -f flv \
    $DEMO_STREAM_INGEST_ENDPOINT/$DEMO_STREAM_KEY

Summary

In this post, we learned how to create our own lofi radio live stream with Amazon IVS. To make your own lofi stream even more interactive, add live chat with both automated and manual chat moderation. If you have any questions about this demo, please leave a comment below.


Also published here.