Developers: Add Live Channels to Your App with ChannelSurfer

One of Android TV’s killer features is Live Channels. As I’ve written before, channel surfing is a very enticing opportunity for content creators to show what they’ve made, but it’s something that has always been just for cable companies and TV networks. The Live Channels app gives developers the opportunity to build their own streams of content side by side with cable networks. Some developers have already built channels into their apps.

However, the guide to do so can be somewhat difficult to understand and perhaps intimidating to many developers. I’m hoping that this guide, and the library that I have created, will spur more developers to add live content into their apps and help contribute to a large list of streaming channels.

Vineyard

Using the ChannelSurfer Library

Add my library to your app. It is also available on GitHub. As the library continues to develop, you should refer to the README there for more information.

compile 'com.github.fleker.channelsurfer:0.1.+'

You will need to specify in your manifest the service that you want to run for TV input, just as before. This service will be an extension of the TvInputProvider class.

The library includes a lot of the boilerplate files, like @xml/channel_surfer_tv_input, so you don’t need to worry about creating and maintaining these files yourself.

<service
     android:name=".livechannels.VineInputProvider"
     android:enabled="true"
     android:exported="true"
     android:permission="android.permission.BIND_TV_INPUT">
     <intent-filter>
         <action android:name="android.media.tv.TvInputService" />
     </intent-filter>
     <meta-data
         android:name="android.media.tv.input"
         android:resource="@xml/channel_surfer_tv_input" />
 </service>
 <meta-data
     android:name="TvInputService"
     android:value="com.hitherejoe.vineyard.livechannels.VineInputProvider" />
 <activity
     android:name=".SampleTvSetup"
     android:exported="true"
     android:enabled="true"
     android:label="@string/title_activity_sample_tv_setup">
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
     </intent-filter>
 </activity> 

Then you have to place your activity’s name as a string.

<string name="tv_input_activity">com.felkertech.sample.channelsurfer.SampleTvSetup</string>

Once these things are set, you can start adding in your channels and player implementation.

Create a Setup Activity

The first thing you need to do is create an activity. This will be opened when the user sees the “New Channels Available” prompt in Live Channels. There is an activity class, SimpleTvSetup, which can do all of the work of calling and managing the SyncAdapter. You can just extend it and you’re done. The GitHub page contains more information about how to customize parts of it.

public class SampleTvSetup extends SimpleTvSetup {
}

Add Channels and Programs

Now go to your TvInputProvider service. In this tutorial, we are adding this feature to the Vine client Vineyard. Videos are taken from a web URL, so we can play them with a MediaPlayer object. Instead of extending TvInputProvider, we are extending MediaPlayerInputProvider to remove some boilerplate.

There are a few methods that we must override. Two of them are getAllChannels and getProgramsForChannel. These methods return lists that are inserted into Live Channels. The README can provide a full list of the Channel and Program objects, as only a handful of those properties are being used in this implementation.

@Override
    public List getAllChannels() {
        List topics = new ArrayList<>();
            topics.add(new Channel()
                .setName("Popular Vines")
                .setNumber("101"));
        return topics;
     }

With this implementation, a single channel, Popular Vines, will be available.

To add programs, you can use a Channel parameter to target programs for each one. They will have to be inserted beginning at the current start time until the end time. This is not necessary, but having a list of programs makes your channels more visibility and improve your branding. There’s a lot of attributes for a program, such as including a season and episode number, genres, and ratings.

Vineyard Live Channels

In Vineyard, this provides a small dilemma. Vines are far too short to supply two or three days worth of programs without many web requests. Instead, I used the getGenericProgram method. This creates a nice program block which isn’t very specific. It’ll say “Popular Vines Live” and a description of “Currently Streaming”. Now this has to be returned in a list for the entire period. It also needs to extend until the end of our syncing period.

@Override
 public List<Program> getProgramsForChannel(Uri channelUri, Channel channelInfo, long startTimeMs, long endTimeMs) {
     int programs = (int) ((endTimeMs-startTimeMs)/1000/60/60); //Hour long segments
     int SEGMENT = 1000*60*60; //Hour long segments
     List<Program> programList = new ArrayList<>();
     for(int i=0;i<programs;i++) {
         programList.add(new Program.Builder(getGenericProgram(channelInfo))
             .setStartTimeUtcMillis((getNearestHour() + SEGMENT * i))
             .setEndTimeUtcMillis((getNearestHour() + SEGMENT * (i + 1)))
             .build()
         );
     }
     return programList;
 }

We are using another method, getNearestHour, so that our time blocks are neatly rounded off to the hour regardless of when the actual sync begins.

What if each Vine was in the guide?
What if each Vine was in the guide?

The channel list is created and our electronic programming guide has been populated. Now we have to do playback.

Playback

The MediaPlayer class is used to handle playback. We just need code to handle tuning to each channel.

There is not a need to display any view over our playback surface, so this method should just return null.

@Override
    public View onCreateOverlayView() {
        return null;
    }

Now we can program our tuning method.

 @Override
    public boolean onTune(Channel channel) {
        notifyVideoUnavailable(REASON_BUFFERING);
    ...

The first thing we do is alert the Live Channels app that we are currently buffering. This means it will not show our overlay view or our media until we say it’s okay.

I make sure our MediaPlayer exists.

if(mediaPlayer == null)
    mediaPlayer = new MediaPlayer();

Once all these things are set, I use Vine’s API to grab the most popular videos, making sure that I am in fact in that channel.

VineyardService vineyardService = VineyardService.Creator.newVineyardService();

mChannel = channel;
if(channel.getNumber().equals("101")) {
    vineyardService.getPopularPosts().enqueue(new Callback() {
        @Override
        public void onResponse(Response response, Retrofit retrofit) {
            tuneResponse(response);
        }
        @Override
        public void onFailure(Throwable t) {
            t.printStackTrace();
        }
    });
}

The tuneResponse method takes the result of this web request and begins playing the first video.

public void tuneResponse(Response response) {
        VineyardService.PostResponse postResponse = response.body();
        try {
            beginPlayingVine(postResponse, 0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
public void beginPlayingVine(final VineyardService.PostResponse response, final int index) throws IOException {
        String url = response.data.records.get(index).videoUrl;
        notifyVideoUnavailable(REASON_BUFFERING);
        Log.d(TAG, "Switch to " + index + ", " + url);
        try {
            mediaPlayer.setDataSource(url);
        } catch(IllegalStateException exception) {
            try {
                mediaPlayer.stop();
                mediaPlayer.reset();
                beginPlayingVine(response, index);
                return;
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        mediaPlayer.setSurface(mSurface);
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        try {
                            mp.stop();
                            mp.reset();
                            if (index + 1 < response.data.records.size())
                                beginPlayingVine(response, index + 1);
                            else
                                onTune(mChannel);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                notifyVideoAvailable();
                mp.start();
            }
        });
        mediaPlayer.prepare();
    }

From there, the MediaPlayer is prepared with a particular video url. Before the video starts playing, the Live Channels app is notified that the video is available.

After the video is completed, the next video in the list is played until the end. Then the channel is tuned to again to get another batch of videos.

Websites as Live Channels

Having the ability to lean back and watch any sort of video is enticing, but what if we could go further? Today, video is not the only sort of passive content that we can consume. The web is full of amazing visual experiences accessible just through a web browser. That is why the ChannelSurfer library also allows users to use any website as a valid url. It imitates a Windows version of Chrome with JavaScript enabled.

"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.35 Safari/537.36"

To use websites as channels, extend the WebViewInputProvider class. It takes care of a lot of boilerplate of setting up a WebView. Call loadUrl(String) to load the site.

What can you do with this? The possibilities are up to you! Having the web as a Live Channel allows developers to embed videos, such as from YouTube or Vimeo, without having to obtain the video files directly.

Conclusion

With only a couple of changes in your manifest, and a hundred lines in a single class, your app can have its own Live Channel. The goal is this library is remove all of the boilerplate out of Live Channels. This platform makes it easy for anyone to create a high quality leanback experience with any sort of content. There are already a handful of services that use Live Channels, and I hope that developers will use this library to make more great experiences.

I welcome contributions on its GitHub page. This library will continue to be actively developed as long as people are reporting bugs or suggestions.

Nick Felker

Nick Felker

Nick Felker is a student Electrical & Computer Engineering student at Rowan University (C/O 2017) and the student IEEE webmaster. When he's not studying, he is a software developer for the web and Android (Felker Tech). He has several open source projects on GitHub (http://github.com/fleker)Devices: Moto G-2013 Moto G-2015, Moto 360, Google ADT-1, Nexus 7-2013 (x2), Lenovo Laptop, Custom Desktop.Although he was an intern at Google, the content of this blog is entirely independent and his own thoughts.

More Posts - Website

Follow Me:
TwitterLinkedInGoogle PlusReddit

  • Karl Hintz

    You’re doing the Lord’s work, Nick. Lets hope this helps spur adoption by developers.

  • Donald Heath

    I have a goal of making a “Vulkano Flow”‘s output stream available as a channel on my Nexus Player (intend to merge with OTA channels from my HDHomerun device). I was originally just going to use the app (purchased), but it does not appear on the leanback launcher. What do I need to learn/ create?

    • You’ll have to spend most of your time figuring out how to pull data from that device using the Android SDK. Once you do, integrating with Live Channels will be easy.

    • Tom G Deitte

      Is there a way to get the EPG Guide working on the Vulkano? This is a great product, but they have not updated the EPG Guide. I have sent many emails to Moonsoon Media, but they give no date to work again. I would even consider paying for this subscription.