Economy Size Geek - Remix the Internet and Your Television with the Roku DVP

by Dirk Elmendorf

This month, the challenge is to write about Linux and entertainment. MythTV is cool, but it's been covered. Boxee looks great, but someone beat me to that punch. I was getting worried I wouldn't find something appropriate to cover until my wife reminded me that the device on which we were watching Farscape runs Linux, and she suggested I see if there was something worth discussing beyond the great selection of documentaries. As luck would have it, there is quite a bit worth writing about.

The device is the Roku Digital Video Player, which launched in 2008. This tiny box uses Linux to stream Netflix video to your television. Until recently, that was the only reason to get one, but now, Roku has started announcing other channels of content (Major League Baseball, Facebook, Pandora and more). It is interesting to see the tiny little box expand its capability. Even more interesting is that Roku has opened a developer program that allows anyone to create custom channels for it.

Before I dive in, here's a little background. My wife is from Seattle, Washington, and is a huge fan and supporter of the KEXP radio station. I have done several projects in the past to bring KEXP to our home in San Antonio, Texas, but looking at the Roku's capabilities, it seems like the perfect platform for bringing KEXP on-demand to our living room.

The first step in becoming a developer is to go to the Roku.com Web site and create an account in its portal. After that, click the link for Developer. There is a small sign-up form where you agree to abide by the rules of the developer program. Once completed, you can download the SDK and get started.

Roku tries to honor its obligation to the GPL code involved in allowing the Roku to work. If you visit www.roku.com/support/gpl_rdvp.aspx, you can download tarballs of software. One thing that struck me in reading the developer agreement is the section that says “Subject to the Grace Period, Your Channel Application must at all times....iii. not contain any open-source code or other restricted code that could require Roku to publicly post or display any third-party notices or any modifications to such code.” I understand what the Roku folks are trying to protect themselves from. Apparently, parts of the Roku platform are licensed from third parties (like the support for WMA), and Roku wants to keep parts proprietary. I assume the MIT license is fine, but it sounds like this environment might not be very friendly to the GPL. I posted a question to the developer forum but did not get any response by the time I had submitted this article for publication.

Assuming you still are interested in the platform, you have three different options for deploying a channel: push it to a local Roku, create it as a private channel or get it approved as a public channel.

The public channel requires Roku's approval. Once it's approved, Roku offers it in the channel store on the Web site. This is the path to take if you are building a channel for public consumption. The private channel allows you to roll out your channel to people. The upside is you don't need Roku's approval to roll it out. The downside is that more steps are involved in getting the channel onto a Roku. The local push is designed for the development phase of your channel. Once you enable development on a Roku (use a remote to enter Home 3x, Up 2x, Right, Left, Right, Left, Right), you can just upload your channel. The SDK includes a handy Makefile to make this incredibly easy. This is how I have done all of my development, as I'm developing channels for myself (and other developers). The main limitation of this last form of development is that you can have only one “development” channel installed at a time. That did not pose much of a problem for me, but if you're sharing code, it could make things more complicated.

Roku development is all done in BrightScript, which appears to be a custom language that combines ideas from Visual Basic and JavaScript. The main goal is to allow you to write code in a dynamic language that can easily compile down to efficient code for the Roku's embedded environment. This is accomplished by adding in BrightScript Components, such as default screens and media tools. You use BrightScript to customize the screens and build up your channel. There is even a PowerPoint presentation template to allow you to mock up your application screens easily.

Getting Down to Business

All the code for this article is being shared at github.com/economysizegeek/linux_journal_roku. This will make it a lot easier to see the different channels. Simply git clone that repository, and you will have the latest version of all the code. I assume you have turned on developer mode for your Roku. You also need to set an environment variable in your shell so that the Makefile will know where to push the code. In my case, I added export ROKU_DEV_TARGET=192.168.210.244 to my .bashrc. Make sure you put in the IP address of your Roku.

Let's start by doing a simple Hello World! example. This will get you familiar with the tools and also confirm that you have everything working. In the git repository is a directory called hello_world. Inside that directory, you should be able to type make install, and it will push a new channel onto your Roku automatically. If that doesn't work, confirm that you have make, curl and zip installed (the Makefile depends on them). Also make sure your Roku is in Dev Mode (enter the key combo from this article). Additionally, ensure that you didn't set the environment variable (echo $ROKU_DEV_TARGET should print an IP), and check that you didn't set it to the wrong IP (the Roku's IP can be confirmed in Settings→Player Info).

Once you have sorted that out, you should be able to go to the Roku and see a new channel called “Hello World”. Clicking on it with your remote will start the channel. It doesn't actually do anything other than say “Hello World!” The point is to confirm your environment and give you a quick-and-dirty tour of what you need to start making your own channel.

In the hello_world directory, you will see two directories and a few files. The images directory has a variety of images needed for this small application. You will notice that some have HD and some have SD in their names. This represents High Definition and Standard Definition. Because the Roku is connected to a television, you have to make sure any art you display is sized correctly for the TV screen. The Makefile is there to make it easy to compile, install and remove the channel. The manifest file is required by the Roku to build the package on the other side. That just leaves the source directory and the HelloWorld.brs file (Listing 1). The brs file is the BrightScript that actually sets up the channel.

Listing 1. HelloWorld.brs

'Copyright 2010 Dirk Elmendorf

Function Main() as Integer
  print "Hello World! to the console"
  print ' you will not see this '
  p = CreateObject("roMessagePort")
  screen = CreateObject("roSpringboardScreen")
  screen.SetMessagePort(p)

  o = CreateObject("roAssociativeArray")
  o.ContentType = "episode"
  o.Title = "Sample App"
  o.Description = "Hello World!"
  o.SDPosterURL = "pkg:/images/episode_icon_sd.png"
  o.HDPosterURL = "pkg:/images/episode_icon_sd.png"
  screen.SetContent(o)
  screen.AddButton(1, "Exit")
  screen.Show()
  while true
    msg = wait(0, p)
    if msg.isButtonPressed()
      print "Goodbye World!"
      return 0
    endif
  end while
end Function

Let's do a walk-through of the code, but if the version you get from the git repository is different, it means I fixed a bug after this went to press.

The first code listing is a crash course in BrightScript. All it does is create a Main that builds a single screen and exits on any button push. The print statements are spit out to the Roku console. The console also allows you to enter a debugger and see any syntax errors. You can access the Roku console by entering telnet $ROKU_DEV_TARGET 8085. I had to use it a couple times to find out why the channel wasn't showing up (which often means there is a syntax error).

As you can see, I created a function called Main. This is the entry point for all BrightScript applications. I included two different print statements. The first one works, but the second one only prints out a blank line, because a single quote is considered a comment in BrightScript. Then, I created two objects. The roMessagePort is where messages (events) are sent. The roSpringboardScreen is a BrightScript component. This is basically a screen you can reconfigure by providing information. Roku ships with a number of these screens for your use. I chose this one because it was the easiest to work with. I connect the screen and the port. This tells the system to send all events from the screen to the message port I created. There probably are cases where you need to have different message ports for different events, but so far, I have used only a single message port shared with every screen.

Then, I create a roAssociativeArray. This is very much like a hash (or a plain JavaScript object). I use it to set up up a number of predefined fields. This gets passed into the screen. Next, I create a single button on the screen and tell the system to draw the screen. The last step is to set up an infinite loop. The wait allows the system to sit until an event is triggered. Then, I can have the result of that event processed. In this case, it means the program exits.

Rocking Out

The next step was to add in another BrightScript component—namely roAudioPlayer. This component handles playing MP3 streams. I wrote a new script from scratch to do this. The channel to handle streaming is in the kexp directory. The main file (Listing 2) is in the source/KEXP.brs (at github.com/economysizegeek/linux_journal_roku).

Listing 2. KEXP.brs

'Copyright 2010 Dirk Elmendorf

Function Init() as Object
  obj = {
    port: CreateObject("roMessagePort")
    screen: CreateObject("roSpringboardScreen")
    player: CreateObject("roAudioPlayer")
    screen_options: CreateObject("roAssociativeArray")
    song: CreateObject("roAssociativeArray")
    status: ""
    drawScreen: function(description)
        m.screen_options.Description = description
        m.screen.SetContent(m.screen_options)
        m.screen.Show()
    end function
    playingNow: function()
        m.screen.ClearButtons()
        m.screen.AddButton(1, "Pause Stream")
        m.screen.AddButton(3, "Exit")
        m.DrawScreen("Live MP3 Stream from KEXP.org")
    end function
    play: function()
        if m.status = "" then
          m.player.AddContent(m.song)
          m.player.SetLoop(true)
          m.player.play()
          m.status = "playing"
        else if m.status = "paused"
          m.player.resume()
          m.status = "playing"
        endif

        m.screen.ClearButtons()
        m.screen.AddButton(3, "Exit")

        m.drawScreen("Buffering....")
    end function
    pause: function()
        if m.status = "playing" then
          m.player.pause()
          m.status = "paused"
        endif

        m.screen.ClearButtons()
        m.screen.AddButton(2, "Resume Stream")
        m.screen.AddButton(3, "Exit")

        m.drawScreen("Stream Paused")
    end function
    exit: function()
        print "Goodbye World!"
        m.player.stop()
        return 0
    end function
  }
  obj.screen.SetMessagePort(obj.port)
  obj.player.SetMessagePort(obj.port)


  obj.screen_options.ContentType = "episode"
  obj.screen_options.Title = "KEXP"
  obj.screen_options.SDPosterURL = "pkg:/images/episode_icon_sd.png"
  obj.screen_options.HDPosterURL = "pkg:/images/episode_icon_sd.png"
  obj.screen.SetStaticRatingEnabled(false)

  obj.song.Url = "http://kexp-mp3-2.cac.washington.edu:8000/"
  obj.song.StreamFormat = "mp3"
  obj.status = ""
  return(obj)
end Function

Function Main() as Integer

  app = Init()
  app.play()

  while true
    msg = wait(0, app.port)
    if type(msg) = "roAudioPlayerEvent"
      if msg.isStatusMessage() then
        print "Audio Player Event:"; msg.getmessage()
        if msg.GetMessage() = "start of play" then
          app.playingNow()
        endif
      endif
    else if msg.isButtonPressed()  then
        if msg.GetIndex() = 1 then
          app.pause()
        else if msg.GetIndex() = 2 then
          app.play()
        else if msg.GetIndex() = 3 then
          return app.exit()
        endif
    else if msg.isScreenClosed() then
      return app.exit()
    endif
  end while
end Function

Listing 2 is for this channel. It is different in two main ways. First, this time, the event loop actually looks at the button being pushed. The loop is now able to trigger a different behavior depending on the index value of the button that was pushed. The other change is that I created an application object. I wanted to demonstrate that Roku has provided an incredibly dynamic language, so I built it up much the same way you would in JavaScript. The one odd thing to notice is the use of “m.” inside the methods for the app object. The “m.” is a pointer to the object. You can read it as “this.”, because that is how Roku is resolving it when it gets compiled.

The event code is also tracking the state of the stream itself. The roAudioPlayer considers it an error if you try to pause a stream that is not playing or try to resume a stream that is not paused. The code handles those cases. It also handles shutting down the stream when you exit.

With a working streaming application in hand, I started trying to get the “on-demand” portion of the KEXP Web site integrated into it. The first step was to navigate to a show and find a URL for a single show. This took some work, because the on-demand programs are served using a different protocol and URL from the live feed. Eventually, I was able to get a URL by downloading and opening files that the KEXP server provides.

Unfortunately, I ran into a Roku platform limitation. It does not support the codec that KEXP is using. Currently, there is no way for a third-party developer to add additional codec support. Because the live stream is available in MP3, the Roku handles it fine, which has been the case for a number of the radio feeds I looked at. So, if the stream you want to use streams via Flash or some other codec, you may be out of luck as well. Roku also supports WMA, but that seems to be more for files than streams.

For this article, I wrote everything from scratch. When you are working on your own projects, you won't need to do that. Roku includes a number of different sample applications in the SDK. The code gives a great tour of the components and how they can be used, including audio, video, XML parsing and handling a registration process. Those examples also are a lot more polished than the code I have provided here (they include examples of how to create your own theme for the channel).

The next thing to play with is video streaming. I chose to focus on audio for this article, because it's very straightforward. Streaming video means obtaining a video source, as well as encoding it properly, and then you have to generate an XML document to describe it. You will need a Web server that can serve both the video and the XML. Once you have everything working, you should be able to run your own little Netflix-style video-streaming service. Now that you have your feet wet with Roku development, head over to Brian Lane's blog. He has documented some hacking he's done to get all of this working (blog.brianlane.com/2009/12/20/streaming-local-video-with-your-roku).

My goal with this article was to get streaming up for my wife (which I mostly achieved—I'm still working on getting the Shake the Shack show on demand). I was incredibly surprised at how easy it was to get up and hacking. Originally, I was turned off by having to deal with a new language, but BrightScript is close enough to JavaScript that it wasn't the hurdle I thought it would be. For me, the best part is that the Roku already had earned its place in my AV rack thanks to Netflix. Adding the ability to hack on it and the opportunity to try channels other people have built makes it that much more awesome.

Dirk Elmendorf is cofounder of Rackspace, some-time home-brewer, longtime Linux advocate and even longer-time programmer.

Load Disqus comments