» » Online radio for Android: step by step guide

Online radio for Android: step by step guide

In this tutorial, I'll walk you through building your own Android radio app.

I'll start right away with the essence, so let's define the functionality:

- Playing streaming audio with ExoPlayer .
- Parsing HTML page.
- Integration of the Last.fm API .
- Connecting a service to control playback from the "curtain".
- Working with custom libraries.

We figured out the requirements, now the most interesting part - implementation.

You can find all the code on GitHub , but here I will focus only on the main points.

I think that you have the SDK installed and you know how to create new projects, so let's create an empty (blank) project and add the libraries to build.gradle:

// Animations library
compile 'com.wang.avi:library:2.1.3'
// Connectivity library
compile 'org.jsoup:jsoup:1.8.3'
// Library for load and show images
compile 'com.squareup.picasso:picasso:2.5.2'
// Library for playing audio
compile 'com.google.android.exoplayer:exoplayer:r1.5.3'


Now let's take a quick look at the classes:

- Player is a class to initialize and manage our ExoPlayer.
- NotificationService - a class for playing audio in the background and displaying a notification in a curtain.
- Const is a class for describing links to audio and other things.
- CircularSeekBar a class that I borrowed from GitHub, adds a curved SeekBar to us.
- GetTrackInfo - Here we are accessing Last.fm and also parsing the HTML page.
- MainActivity is the main class of the application that performs the functions of drawing the screen and initializing methods.

We will also add a couple of layout files for the curtain and the main screen, drawables can be found here :

activity_main.xml
status_bar.xml

And add some permissions and services to our Manifest:

<!--Permisions for internet connection and vibrate function-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />

<!--Service for player and notification-->
<service android:name=".NotificationService" />


Now let's get the Last.fm API key, we need it in order to find his photo by the name of the artist and show it on the main screen. To do this, you need to go to the account creation page and log in or register, after which you will need to create an application. This operation will take 30 seconds, and we will finally get to the API KEY, you need to enter it in the LAST_FM_KEY field of the Const.java class:

public static String LAST_FM_KEY = "YOUR_API_KEY";


Next, I propose to move on to the method of obtaining a link to a live broadcast, I take it from here . To do this, we need to start the radio and, by right-clicking in Chrome, select the “view code” item, then select the Network tab and find the link with the longest timeline. This will be our stream, it has already been added to the Const class - in a similar way, I received a link to an HTML page with the name of the artist and the name of the track. There are a lot of crutches in this code, since parsing HTML is a strange thing in itself, but I will try to explain it anyway:

Here you can see the desired fragment of the HTML page I get :

<div class="boxed"
     <p><strong>On Air: Rockabilly Radio (136 connections)</strong></p>
     <p>
        <strong>Artist:</strong> Mack Stevens<br/>
        <strong>Track:</strong> In The Groove (143)<br/>
     </p>
</div>


And this is his parsing, I hope that the comments will be informative:

// With JSOUP we get all the content of the page above
Document doc = Jsoup.connect(Const.TRACK_INFO_URL).get();
// Since counting starts at 0, we end up with a second <p> element:
// as you can see, this is the name of the artist
after which we cut off the first 29 characters, leaving the beginning immediately with his name.
String first_letter =
doc.getElementsByClass("boxed").select("p").get(1).toString().substring(29,
doc.getElementsByClass("boxed").select("p").get(1).toString().length());

/*
After that, using the split function, we get an array of 2 elements
with text before entered
as a string function argument and text after, the first element
array is the "pure" artist name,
and the second still needs to be "cleared" of unnecessary "br" and "p" at the end
*/
String[] parts = first_letter.split("<br> <strong>Track:</strong> ");
String first = parts[0];
MainActivity.artist = first;
String second = parts[1];
// Here we remove extra characters by simply trimming the string
MainActivity.track = second.substring(0, second.length() - 9);

// Then we send a request to Last.fm for information
// about the artist and display it on the screen
readJsonFromUrl("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=
"+ MainActivity.artist.replace(" ", "%20") + "&api_key=
"+Const.LAST_FM_KEY+"&format=json".replace(" ", "%20")); 


This code example may not be the best practice, but the entire application was written overnight in a few hours. Parsing HTML is a tricky business, and if there is an opportunity not to use it, then you need to use this opportunity. I hope my experience of processing an HTML page will be useful to someone.

Next, parsing JSON and getting the user's photo.

Here is an example of a JSON response sent by Last.fm for singer Adele:

{
"artist" : -{
"name" : Adele,
"mbid" : cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493,
"url" : https://www.last.fm/music/Adele,
"image" : -[
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/34s/0f38adb5ecc1be3f.png,
"size" : small
},
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/64s/0f38adb5ec8911be3f.png,
"size" : medium
},
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/174s/0f38adb911be3f.png,
"size" : large
},
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/300x300/0f38d818911be3f.png,
"size" : extralarge
},
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/0f38adb5ec1be3f.png,
"size" : mega
},
-{
"#text" :
https://lastfm-img2.akamaized.net/i/u/arQ/0f38a818911be3f.png,
"size" :
}


All that interests us here is a “mega” image, we will parse it in this way:

// Create a JSONObject and pass our response from Last.fm to it
    JSONObject dataJsonObj = new JSONObject(sb.toString());
// Then check if an error has come
    if (!isError(dataJsonObj)) {
// If everything is fine, parse the 5th element of the array,
// namely photo size "mega"
       if (dataJsonObj.optJSONObject("artist").optJSONArray(
"image").optJSONObject(4).optString("#text").toString().length() > 10) {
          MainActivity.album = dataJsonObj.optJSONObject("
artist").optJSONArray("image").optJSONObject(4).optString("#text");
       } else MainActivity.album = "";
    } 


Now a little about ExoPlayer, and why I didn't use the standard MediaPlayer. MediaPlayer is absolutely not optimized for such loads and often stopped broadcasting. Also on older (older than 5) versions of Android, the rewind that I was going to add later works as if the player is loading the entire audio track between the current position and the user selected. After a short search, the choice fell on ExoPlayer, now I will briefly describe its capabilities, the code below can be used as a separate element in your project:

static ExoPlayer exoPlayer;
static TrackRenderer audioRenderer;

    public static void start(String URL, Context context)
    {
// Declare a URI with a link to our stream,
// or any audio file on the network
      Uri URI = Uri.parse(URL);
      FrameworkSampleSource sampleSource =
new FrameworkSampleSource(context, URI, null);
      audioRenderer=
new MediaCodecAudioTrackRenderer(sampleSource, null, true);
// Initialize the player
      exoPlayer = ExoPlayer.Factory.newInstance(1);
      exoPlayer.prepare(audioRenderer);
// Tell it to start playing audio,
// as soon as buffering is finished
      exoPlayer.setPlayWhenReady(true);
// Add a listener to know when it started playing
      exoPlayer.addListener(new ExoPlayer.Listener() {
        @Override
        public void onPlayerStateChanged(boolean playWhenReady,
int playbackState) {
// playbackState 4 means ready
// and start playing, here you can remove the download dialog
         if(playbackState == 4){
           MainActivity.playing_animation.setVisibility(View.VISIBLE);
           MainActivity.loading_animation.setVisibility(View.GONE);
           MainActivity.control_button.setVisibility(View.VISIBLE);
           MainActivity.control_button.setImageResource(R.drawable.pause);
          }
         }

            @Override
            public void onPlayWhenReadyCommitted() {
            }

            @Override
            public void onPlayerError(ExoPlaybackException error) {

            }
        });
    }
// Function to stop playback,
// after which you will need to call start()
    public static void stop()
    {
        if(exoPlayer!=null) {
            exoPlayer.stop();
        }
    }

// Setting the volume is also present
// and takes values ​​from 0.0 to 1.0
    public static void setVolume(float volume)
    {
        if(exoPlayer!= null) {
            exoPlayer.sendMessage(audioRenderer,
MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume);
        }
    } 


The final stage of the lesson is the notification in the blind, the service and how it works.

From the main Activity, when the audio starts playing, we start the service, and it takes over all the work, freeing up the work in the Activity. It looks like this:

// When the play button is clicked, this function is called,
// and we start the service described earlier in the Manifest
public void startPlayerService() {
     Intent serviceIntent = new Intent(MainActivity.this, NotificationService.class);
     serviceIntent.setAction(Const.ACTION.STARTFOREGROUND_ACTION);
     startService(serviceIntent);
} 


The service is managed with an Intent. When the user clicks on the "play / pause" button in the shade, the service sends an Intent to itself and processes it, this is how we send the Intent when clicked:

views.setOnClickPendingIntent(R.id.buttonID, pplayIntent);
pplayIntent = PendingIntent.getService(this, 0,
         playIntent, 0);
Intent playIntent = new Intent(this, NotificationService.class);
playIntent.setAction(Const.ACTION.PLAY_ACTION);
And we process it in onStartCommand:
if (intent.getAction().equals(Const.ACTION.PLAY_ACTION)) {
    // Here you can change the image on the button,
    // play sound, stop service and more
} 


Congratulations! We have developed a client-server application using additional libraries, audio playback, and working with services. The end result will be something similar to what you see in the screenshots:

Online radio for Android: step by step guide

Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.