Christian Semmler

How to set up a 24/7 live stream radio on YouTube (for free)

Nov 21, 2020
/assets/images/blog/blog-post-3.jpg
3D versions of "Space Invaders" aliens as seen in the music video of Röyksopp - Happy Up Here

Have you ever come across a YouTube video that endlessly loops a playlist of music? This post outlines a way to launch such a live radio using ffmpeg, the swiss army knife when it comes to any kind of media streaming or transformation, and a simple Docker image. You can even host the entire setup for free on services like Heroku.

tl;dr: Solution on GitHub

Motivation

One of my favorite childhood memories is playing a video game called LEGO Island, which featured an in-game radio broadcasting random DJ sequences and fun tunes. Unfortunately, the sound quality has been lackluster (it was 1997 after all) - until recently. Thanks to Lorin Nelson, the composer of a lot of LEGO Island’s music, an archive of cassette tapes has been found, and many of the songs have been recovered. This sparked the idea to replicate the radio on YouTube.

There are many ways and tools to stream media to YouTube Live (which is powered by RTMP), but my goal was to create a service that would play a collection of MP3s at random dynamically, 24/7, using a third-party host. ffmpeg came to mind, which is a crazily versatile command-line interface to various common media libraries available on most computing platforms.

Solution using ffmpeg

Eventually, I came up with a single command that performed exactly as desired:

ffmpeg -loglevel info -y -re \
  -f image2 -loop 1 -i bg.png \
  -f concat -safe 0 -i <((for f in ./mp3/*.mp3; do path="$PWD/$f"; echo "file ${path@Q}"; done) | shuf) \
  -c:v libx264 -preset veryfast -b:v 3000k -maxrate 3000k -bufsize 6000k \
  -framerate 25 -video_size 1280x720 -vf "format=yuv420p" -g 50 -shortest -strict experimental \
  -c:a aac -b:a 128k -ar 44100 \
  -f flv rtmp://a.rtmp.youtube.com/live2/$YOUTUBE_KEY

Here’s a breakdown of the ffmpeg options including short explanations:

-y -re

Overwrite output without asking and read input at the native frame rate. This should and is commonly used for all live streaming / real-time output setups.

-f image2 -loop 1 -i bg.png

Since YouTube requires video input, I’ve chosen a static, still image. With some adjustments, it would also be possible to use a collection of image or video files as input instead.

-f concat -safe 0 -i

This is probably the most interesting part. Using this option, we are leveraging the concatenation script demuxer of ffmpeg. Since all input files (MP3s) are of the same type, we can provide a list of input files to ffmpeg, which is subsequently going to perform simple concatenation without any kind of re-encoding, which is exactly what we need. -safe 0 allows us to use absolute paths.

<((for f in ./mp3/*.mp3; do path="$PWD/$f"; echo "file ${path@Q}"; done) | shuf)

Instead of providing a (redundant) file that contains an explicit list of inputs, we are taking advantage of bash’s process substitution. This essentially allows us to create an ad-hoc list file which will be used as the input for the concat option. The list needs to contain one line for each MP3 that should be played. Eventually, we are using shuf to shuffle all lines.

[...] -c:a aac -b:a 128k -ar 44100

After specifying some audio and video encoding options required for YouTube Live, we need to make sure that the video quality is sufficient to avoid a blurry still image.

-f flv rtmp://a.rtmp.youtube.com/live2/$YOUTUBE_KEY

Finally, we are writing flash video output to the RTMP endpoint provided by YouTube.

Live example

I’ve provided the entire working and dockerized solution including setup instructions as a sample app on GitHub. Feedback and pull requests are welcome! Using this solution, I’ve set up a LEGO Radio broadcast which is live at the time of writing (and hopefully will be for a long time to come). Thanks for reading!