This tutorial is out of date and no longer maintained.
Hello and welcome to this article! Today I would like to talk about creating an online streaming radio with the Ruby on Rails framework. This task is not that simple but it appears that by selecting the proper tools it can be solved without any big difficulties. By applying the concepts described in this tutorial you will be able to create your own radio station, share it with the world, and stream any music you like.
Our main technologies for today are going to be Rails (what a surprise!), Icecast streaming server, Sidekiq (to run background jobs), ruby-shout (to manage Icecast) and Shrine (to perform file uploading). In this article we will discuss the following topics:
Sounds promising, eh?
So, let’s get started, shall we?
We, as total dev geeks, are listening to a geek music, right (whatever it means)? So, the radio that we are going to create will, of course, be for the geeks only. Therefore create a new Rails application:
I am going to use Rails 5.1 for this demo. The described concepts can be applied to earlier versions as well, but some commands may differ (for example, you should write rake generate
, not rails generate
in Rails 4).
First of all, we will require a model called Song
. The corresponding songs
table is going to store all the music tracks played on our radio. The table will have the following fields:
title
(string
)singer
(string
)track_data
(text
) — will be utilized by the Shrine gem which is going to take care of the file uploading functionality. Note that this field must have a _data
postfix as instructed by the docs.Run the following commands to create and apply a migration as well as the corresponding model:
Now let’s create a controller to manage our songs:
This is a very trivial controller, but there are two things to note here:
admin
layout. This is because the main page of the website (with the actual radio player) will need to contain a different set of scripts and styles.track_data
, we are permitting the :track
attribute inside the song_params
private method. This is perfectly okay with Shrine — track_data
will only be used internally by the gem.Now let’s create an admin
layout that is going to include a separate admin.js
script and have no styling (though, of course, you are free to style it anything you like):
The app/assets/javascripts/admin.js
file is going to have the following contents:
So, we are adding the built-in Unobtrusive JavaScript adapter for Rails and Turbolinks to make our pages load much faster.
Also, note that the admin.js
should be manually added to the precompile
array. Otherwise, when you deploy to production this file and all the required libraries won’t be properly prepared and you will end up with an error:
Now it is time to add some views and partials. Start with the index.html.erb
:
In order for the render @songs
construct to work properly we need to create a _song.html.erb
partial that will be used to display each song from the collection:
Now the new
view:
And the _form
partial:
Note that here I am also using the :track
attribute, not :track_data
, just like in the SongsController
.
Lastly, add the necessary routes:
The next step involves integrating the Shrine gem that will allow to easily provide support for file uploads. After all, the site’s administrator should have a way to add some songs to be played on the radio. Surely, there are a bunch of other file uploading solutions, but I have chosen Shrine because of the following reasons:
But still, if for some reason you prefer another file uploading solution — no problem, the core functionality of the application will be nearly the same.
So, drop two new gems into the Gemfile
:
Note that we are also adding the aws-sdk-s3 gem because I’d like to store our files on Amazon S3 right away. You may ask why haven’t I included aws-sdk gem instead? Well, recently this library has undergone some major changes and various modules are now split into different gems. This is actually a good thing because you can choose only the components that will be really used.
Install the gems:
Now create an initializer with the following contents:
Here I am using ENV
to store my keys and other S3 settings because I don’t want them to be publically exposed. I will explain in a moment how to populate ENV
with all the necessary values.
Shrine has two storage locations: one for caching (file system, in our case) and one for permanent storage (S3). Apart from keys, region, and bucket, we are specifying two more settings for S3 storage:
upload_options
set ACL (access control list) settings for the uploaded files. In this case, we are providing the public-read
permission which means that all the files can be read by everyone. After all, that’s a public radio, isn’t it?prefix
means that all the uploaded files will end up in a store
folder. So, for example, if your bucket is named example
, the path to the file will be something like https://example.s3.amazonaws.com/store/your_file.mp3
Lastly, Shrine.plugin :activerecord
enables support for ActiveRecord. Shrine also has a :sequel
plugin by the way.
Now, what about the environment variables that will contain S3 settings? You may load them with the help of dotenv-rails gem really quickly. Add a new gem:
Install it:
Then create an .env
file in the root of your project:
This file should be ignored by Git because you do not want to push it accidentally to GitHub or Bitbucket. Therefore let’s exclude it from version control by adding the following line to .gitignore
:
.env
Okay, we’ve provided some global configuration for Shrine and now it is time to create a special uploader class:
Inside the uploader, you may require additional plugins and customize everything as needed.
Lastly, include the uploader in the model and also add some basic validations:
Note that Shrine has a bunch of special validation helpers that you may utilize as needed. I won’t do it here because, after all, this is not an article about Shrine.
Also, for our own convenience, let’s tweak the _song.html.erb
partial a bit to display a public link to the file:
Our job in this section is done. You may now boot the server by running:
And try uploading some of your favorite tracks.
We are now ready to proceed to the next, a bit more complex part of this tutorial, where I’ll show you how to install and configure Icecast.
As mentioned above, Icecast is a streaming media server that can work with both audio and video, supporting Ogg, Opus, WebM, and MP3 streams. It works on all major operating systems and provides all the necessary tools for us to quite easily enable streaming radio functionality, so we are going to use it in this article.
First, navigate to the Downloads section and pick the version that works for you (at the time of writing this article the newest version was 2.4.3). Installation instructions for Linux users can be found on this page, whereas Windows users can simply use the installation wizard. Note, however, that Icecast has a bunch of prerequisites, so make sure you have all the necessary components on your PC.
Before booting the server, however, we need to tweak some configuration options. All Icecast global configuration is provided in the icecast.xml
file in the root of the installation directory. There are lots of settings that you can modify but I will list only the ones that we really require:
So, we need to specify the following options:
/stream
.35689
). It means that in order to access our radio we will need to use the http://localhost:35689/stream
URL.Access-Control-Allow-Origin
header to easily embed the stream on other websites.After you are done with the settings, Icecast can be started by running the icecast
file from the command line interface (for Windows there is an icecast.bat
file).
You may also visit the http://localhost:35689
to see a pretty minimalistic web interface.
Note that in order to visit the Administration section, you will need to provide an admin’s password.
Great, now our Icecast server is up and running but we need to manipulate it somehow and perform the actual streaming. Let’s proceed to the next section and take care of that!
Icecast provides bindings for a handful of popular languages, including Python, Java, and Ruby. The gem for Ruby is called ruby-shout and we are going to utilize it in this article. Ruby-shout allows us to easily connect to Icecast, change information about the server (like its name or genre of the music), and, of course, perform the actual streaming.
Ruby-shout relies on the libshout base library that can also be downloaded from the official website. The problem is that this library is not really designed to work with Windows (there might be a way to compile it but I have not found it). So, if you are on Windows, you’ll need to stick with Cygwin. Install libshout from there and perform all the commands listed below from the Cygwin CLI.
Drop ruby-shout
into the Gemfile
:
We also need decide what tool are we going to use to perform streaming in the background. There are multiple possible ways to solve this task but I propose to stick with a popular gem called Sidekiq that makes working with background jobs a breeze:
Now install everything:
One thing to note is that Sidekiq relies on Redis, so don’t forget to install and run it as well.
So, the idea is quite simple:
current
attribute set to true
.Before creating our worker, let’s generate a new migration to add a current
field to the songs
table:
Tweak the migration a bit to make the current
attribute default to false
:
Apply the migration:
Now create a new Sidekiq worker:
shout
is our ruby-shout gem, whereas open-uri
will be used to open the tracks uploaded to Amazon.
Inside the perform
action we firstly need to connect to Icecast and provide some settings:
The username and the password are taken from the ENV
so add two more line to the .env
file:
The username is always source
whereas the password should be the same as the one you specified in the icecast.xml
inside the source-password
tag.
Now I’d like to keep track of the previously played song and iterate over all the songs in an endless loop:
Before iterating over the songs, we are making sure the current
attribute is set to false
for all the songs (just to be on the safe side, because the worker might crash). Then we take one song after another and mark it as current
.
Now let’s open the track file, add information about the currently played song and perform the actual streaming:
Here we are opening the track file using the open-uri library and set some metadata about the currently played song. original_filename
is the method provided by Shrine (it stored the original filename internally), whereas title
and singer
are just the model’s attributes. Then we are reading the file (16384
is the block size) and send portions of it to Icecast.
Here is the final version of the worker:
In order to run this worker upon server boot, create a new initializer:
To see everything it in action, make sure Icecast is started, then boot Sidekiq:
and start the server:
In the Sidekiq’s console you should see a similar output:
Examine:
2017-11-01T17:30:03.727Z 7752 TID-5xikpsg RadioWorker JID-57462d72129bbd0655d2e853 INFO: start
This line means that our background job is running which means that the radio is now live! Try visiting http://localhost:35689/stream
— you should hear your music playing.
Nice, the streaming functionality is done! Our next task is to display the actual player and connect to the stream, so proceed to the next part.
I would like to display the radio player on the root page of our website. Let’s create a new controller to manage semi-static website pages:
Add a new root route:
Now create a new view with an audio
element:
Of course, audio
will render a very minimalistic player with a very limited set of customization options. There are a handful of third-party libraries allowing you to replace the built-in player with something more customizable, but I think for the purposes of this article the generic solution will work just fine.
Now it is time to write some JavaScript code. I would actually like to stick with jQuery to simplify manipulating the elements and performing AJAX calls later, but the same task can be solved with vanilla JS. Add a new gem to the Gemfile
(newer versions of Rails do no have jquery-rails anymore):
Install it:
Tweak the app/assets/javascripts/application.js
file to include jQuery and our custom player.coffee
which will be created in a moment:
Note that we do not need rails-ujs or Turbolinks here because our main section of the site is very simple and consists of only one page.
Now create the app/assets/javascripts/player.coffee
file (be very careful about indents as CoffeeScript relies heavily on them):
I’ve decided to be more dynamic here but you may add the source
tag right inside your view. Note that in order to manipulate the player, you need to turn jQuery wrapped set to a JavaScript node by saying player.get(0)
.
Visit the http://localhost:3000
and make sure the player is there and Geek radio is actually live!
The radio is now working, but the problem is the users do not see what track is currently playing. Some people may not even care about this, but when I hear some good composition, I really want to know who sings it. So, let’s introduce this functionality now.
What’s interesting, there is no obvious way to see the name of the currently played song even though this meta information is added by us inside the RadioWorker
. It appears that the easiest solution would be to stick with good old XSL templates (well, maybe they are not that good actually). Go to the directory where Icecast is installed, open the web
folder, and create a new .xsl
file there, for example info.xsl
. Now it’s time for some ugly-looking code:
You might ask: “What’s going on here?”. Well, this file generates a JSONP callback parseMusic
for us that displays information about all the streams on the Icecast server. Each stream has the following data:
server_name
listeners
— Icecast updates listeners count internallydescription
title
— displays both title and the singer’s name (if it is available)genre
Now reboot your Icecast and navigate to http://localhost:35689/info.xsl
. You should see an output similar to this:
As you see, the mount point’s URL (/stream
in this case) is used as the key. The value is an object with all the necessary info. It means that the JSONP callback is available for us!
Tweak the index
view by adding a new section inside the #js-player-wrapper
block:
Next, define two new variables to easily change the contents of the corresponding elements later:
Create a function to send an AJAX request to our newly added URL:
We are setting jsonpCallback
to parseMusic
— this function should be generated for us by the XSL template. Inside the success
callback we are then updating the text for the two blocks as needed.
Of course, this data should be updated often, so let’s set an interval:
Now every 5 seconds the script will send an asynchronous GET request in order to update the information. Test it out by reloading the root page of the website.
The browser’s console should have an output similar to this one:
Here we can see that the GET requests with the proper callback are regularly sent to Icecast server and that the response contains all the data. Great!
“That’s nice but I want even more info about the played song!”, you might say. Well, let me show you a way to extract additional meta-information about the track and display it later to the listener. For example, let’s fetch the track’s bitrate.
In order to do this we’ll need two things:
Firstly, add the gem:
Note that this gem requires ffmpeg tool to be present on your PC, so firstly download and install it. Next, install the gem itself:
Now enable a new Shrine plugin:
Tweak the uploader to fetch the song’s bitrate:
The hash returned by add_metadata
will be properly added to the track_data
column. This operation will be performed before the song is actually saved, so you do not need to do anything else.
Next, tweak the worker a bit to add provide bitrate information:
Don’t forget to modify the XSL template:
Add a new span
tag to the view:
Lastly, define a new variable and update bitrate information inside the success
callback:
Upload a new track and its bitrate should now be displayed for you. This is it! You may use the described approach to provide any other information you like, for example, the track’s duration or the album’s name. Don’t be afraid to experiment!
In this article, we have covered lots of different topics and created our own online streaming radio powered by Rails and Icecast. You have seen how to:
Some of these concepts may seem complex for beginners, so don’t be surprised if something does not work for you right away (usually, all the installations cause the biggest pain). Don’t hesitate to post your questions if you are stuck — together we will surely find a workaround.
I really hope this article was entertaining and useful for you. If you manage to create a public radio station following this guide, do share it with me. I thank you for reading this tutorial and happy coding!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!