Behind the script: Building a Roku SDK
mParticle's Sam Dozor walks us through building our open source Roku SDK and the many eccentricities of the platform that made the experience so unique.
We’re excited to announce that mParticle now supports Roku!
mParticle empowers you to collect data once, and rescue it from a siloed existence by sending it to over 100 partners across the analytics, attribution, marketing, and data-services ecosystem. As with mParticle’s mobile and server-to-server SDKs, the Roku SDK is open source and makes it simple to collect install, session, custom events, and other data within your Roku channels.
Read on to learn more about the SDK and grab the code on Github.
Why Roku?
Businesses need visibility into all of their customer activity – across all platforms. According to comScore, Roku is the most popular OTT streaming device, beating out Chromecast and Apple TV, and has an estimated 49% share of the living room as of 2016. But, for all of its popularity and proliferation, the Roku platform and its 3500+ channels have nearly no official libraries to choose from to support even basic analytics, much less more advanced ecosystem tools.
mParticle’s Roku support lets you send data into the mParticle platform, alongside all of your web and mobile data, and then downstream to other services that otherwise have no integration or API for Roku developers.
Key Features
- Install and Session Tracking – Automatically collect channel install and upgrade events, along with user-session count and duration statistics.
- Security – mParticle’s Roku SDK includes support for SSL pinning to keep your data safe while in transit.
- Device and Application Metrics – Improve your channel’s user-experience by learning which Roku devices are most popular for your user-base, channel upgrade rate, and other crucial metrics such as TV resolution.
- Device ID and User Attribute Association – Collect the Roku Advertising ID, and associate known user information with your Roku data via mParticle’s user identity and attribute APIs, just as with any of our SDKs.
- Custom Event Tracking – Study and act on explicit user behavior in your channels with custom events and mParticle’s granular eCommerce API.
Challenges
Roku has some eccentricities compared to the relatively mature iOS and Android development environments, to put it mildly.
BrightScript
First off, there’s BrightScript, the one and only language you can use in a Roku channel. You’d be forgiven if you’ve never heard of it, as Roku is likewise the one and only platform that uses it. It’s a dynamic, interpreted language that looks vaguely like Javascript, and with some fun features like first-class functions. Some example gotchas are that you cannot define classes, making many common object-oriented patterns useless, and all functions you define are global to the entire channel – so you have to be careful not to pollute the global namespace.
Writing idiomatic BrightScript while leveraging well-known design patterns from other languages was uniquely challenging. The ultimate goal was to provide an intuitive interface for channel developers, and a maintainable code base for the mParticle engineering team. It’s crucial for all SDKs or libraries to highlight what developers are meant to use — the public API — and what they shouldn’t be messing with. Similar to Javascript, there’s not much in the way of privacy, or “information hiding,” built into BrightScript. The “module” pattern in Javascript is a common workaround, using closures to define functions privately and expose a different, public interface.
Unfortunately, this doesn’t work in BrightScript:
module = function() as object
privateApi = function()
print "In a private function!"
end function
return {
publicApi: function()
'Nope! Can't do this - not in scope.
privateApi()
end function
}
end function
Ultimately, except for truly temporary values, everything in the mParticle SDK is technically public but with a clearly marked private API – an associative array named “_internal.” Some libraries in the Roku ecosystem and elsewhere choose to obfuscate their naming as a way of information hiding, but that can be counterproductive for SDK maintenance, and also developer productivity – we want developers to dive into the source and see how the SDK works, and not have to jump through hoops.
Scene Graph vs. legacy SDK
The Roku platform is in the middle of a transition from the so-called “Legacy” SDK, to the “Scene Graph” SDK. With the older SDK still in wide use, we chose from the onset of development to support both types of channels, which meant some added complexity. Whereas with the legacy SDK there is no multithreading, and limited support for asynchronous operations, Scene Graph provides a solid multithreading mechanism by way of “Tasks.”
Asynchronous Networking
Since a core part of the mParticle SDK’s functionality is the ability to securely and reliably upload data, we needed to nail the nuances of asynchronous networking in both the legacy and Scene Graph contexts. Whereas historically, it’s been a bit of a free-for-all in Roku SDK channels, the latest Roku OS and the channel approval process for Scene Graph impose new restrictions on channels, preventing network, file system, and other slow operations from being performed on the “main” and “render” threads.
Our Roku SDK is designed with popular Roku channel and API patterns in mind. The legacy Roku SDK exposes asynchronous APIs for long-running operations such as network access, despite its lack of multithreading. Channel developers can invoke these APIs and specify a “MessagePort” to receive the results. We wanted all of our APIs to return nearly instantly, so rather than performing a blocking upload whenever developers invoke a public mParticle API, the SDK fires off an asynchronous upload, using a developer-specified MessagePort.
This design turned out really simple to adapt to Scene Graph. With Scene Graph, developers add the predefined mParticle “Task” to each of their channels’ scenes. The mParticle Task has its own run loop, MessagePort, and its own background thread. It’s responsible for initializing the SDK and specifying its own MessagePort – and all of the same message-processing code as in the Legacy SDK is reused.
Cross-thread communication
Having developed a shiny Scene Graph Task, there was a new problem: receiving data via mParticle API calls from other threads. BrightScript does not have any of the typical concurrency support of other languages – there are no mutexes or synchronization mechanisms. You can access a global “Node” within your scene and store information there, but there’s no guarantee of cross-thread consistency. Roku also imposes a strict limitation on the types of objects you can share across threads – notably, function-type objects will be silently stripped as their parent object is cloned from one thread to another.
Luckily, Scene Graph exposes a mechanism by which Tasks can specify a child Node, which can be set to the value of an object from another thread, and the “SGNodeEvent,” allowing a Task to listen for changes to each child Node’s value. This allowed us to create a contract whereby the mParticle Task invokes APIs based on the changing value of a Node. We defined a simple structure – whereby the Node value is expected to contain the method name of a public mParticle API, along with the arguments to pass that API. To abstract this all from channel developers, we created the “mParticleSGBridge” object, which mirrors the public API of the core mParticle SDK object, and manipulates the mParticle Task’s child Node whenever an API is invoked.
Ultimately, these are all just eccentricities of the platform rather than shortcomings, and there’s a refreshing simplicity to the Roku development environment once you overcome the initial learning curve.
Wrapping up
The mParticle Roku SDK is available right now on Github – thanks for reading!