Scaling 9Now Development on Connected TVs with React Native

Article by Craig Bruce and Nick Carroll.

No alt text provided for this image


The Video Technology team at Nine have been working hard on a new 9Now app designed to provide users with a modern UI and consistent experience across Smart TVs, streaming devices and gaming consoles. We have made some bold technology choices to help with cross-platform development and to improve the ad experience. We will explore these technology choices in this article and reflect on our learnings as there have been many.

9Now has recently passed its 4th anniversary. In that time, we have developed and released 9Now apps on Web, iOS, Android, Chromecast, Apple TV, HbbTV, Telstra TV, Fetch, Samsung, LG, Sony, PS4 and Android TV. In the early years, we developed Web, iOS and Android internally and outsourced what we referred to as Connected TV (CTV) apps. The desire to reach as many platforms as quickly as possible, coupled with our lack of experience in this area, was the primary motivation for outsourcing. This strategy proved successful as we were able to roll out 9Now apps across 13 platforms in 3 years.

As with anything you aim to do as quickly as possible, some compromises were made to meet aggressive timelines. The compromise we made was to make use of template apps that were available on several platforms. This reduced development effort which allowed us to launch apps quickly on to new platforms. However, this resulted in different user experiences when using 9Now across different platforms. It also made further product changes more challenging to implement across multiple platforms. The same feature would need to be implemented differently within each template app, so over time product enhancements became more expensive and release cycles slowed down.

Time to Market chart

In 2019 we decided to insource the development of some of our CTV apps. We wanted to build a new app with a user experience that would be consistent across the CTV platforms. We also wanted to move away from Client-Side Ad Insertion (CSAI) and transition to Server-Side Ad Insertion (SSAI) to provide a better viewing experience. The benefits of SSAI are frame-accurate ad insertion and removing the spinning wheel effect when buffering for ad requests with CSAI. We attempted moving to SSAI twice before and both times failed. The failure came down to performing an R&D activity with multiple parties involved and having a small finite budget that limited how deep you can go with an issue.

Insourcing CTV development helped us to build knowledge within a dedicated engineering team and made us more effective in solving challenging problems on CTV platforms. For example, we have been able to go deeper with R&D activities like trialling a variety of players to see which player would help us to play SSAI streams for both VOD and Live. We discovered that no player could support the range of streaming and DRM formats across CTV platforms. We concluded that we needed a tech partner that was willing to go on this journey with us. Through close collaboration with the selected tech partner, we were able to deliver SSAI for both VOD and Live streams on a Smart TV platform. After achieving this milestone, we were confident that the same approach could apply to other CTV platforms.

The other big decision we made was to use React Native to build a "mono-repo" project – a monolithic code repository containing many projects – to create distributions of the 9Now app for several CTV platforms while streamlining dependency versions across platforms. Through this approach, we believe we can produce distributions for Apple TV, Android TV, Samsung, LG, Sony, PS4 and eventually other Smart TV and gaming consoles.

We successfully launched the application on the Android TV platform earlier this year and aim to deploy to many other platforms in future. To achieve this, we decided to use:

So why did we land on React Native as our technology stack? Our team is small and made up of predominantly web developers who are very experienced with React. Also, due to some tight deadlines, the team was under pressure to get the same codebase running on as many platforms as possible.

There was a certain amount of risk involved in this decision, not least the fact that at the time React Native was getting some negative press, but also the available version at the time (0.57.x) was _just_ starting to support Android TV.

We were going to live on the bleeding edge and needless to say, there were challenges ahead!

Challenges

No alt text provided for this image

Living on the Bleeding Edge

As mentioned, Android TV support was scant from the get-go, so we needed to keep up with the very latest version to get the features we need.

Early on, we had very little support for Touchable events on TV Devices, so we needed to wait for a particular contributor to finish adding this functionality.

In a later React Native release, this functionality was inadvertently removed, so we needed to wait for a fix, which was frustrating. These kinds of issues are symptomatic of React Native being geared more toward mobile devices, so any other devices such as TVs are not tested by the core React Native team. A case in point is when Touchables were "double-eventing" on TVs - something that at the time of going to press is still not fixed.

We also encountered several issues with bundling and building, where we needed to provide workarounds to be able to compile and release the application.

Thankfully, these issues are beginning to stabilise as React Native matures and Android TV support improves.

The biggest challenge we faced was navigation. React Native is inherently a mobile-first framework, with support for gestures like swiping and pinching. With a TV application users tend to navigate using a remote control with a directional pad (DPAD).

No alt text provided for this image

Android

All events provided by React Native's Gesture Responder System are bypassed, and we are left with `blur` and `focus`. Moreover, we have very little information we need from these events, for example, a reference to the underlying Android View.

Out of the box, Android provides a proximity-based navigation system, which means after pressing a directional button, navigation moves to the closest component. This situation is not ideal for us as we have relatively complex navigation rules.

We also had issues implementing focus and navigation logic in JavaScript. By the time the native focus event fired, it was too late for the JavaScript code to act after we responded to the event over React Native's asynchronous bridge.

As a consequence, we needed to bake our navigation logic into a native rules engine. We pass down component identifiers and the DPAD direction from JavaScript and let the native layer handle things from there.

Web

Thankfully, navigation was a little more straightforward in React Native Web as we are dealing with the familiar Document Object Model (DOM). However, reconciling DOM element focus with our navigation logic got messy very quickly using vanilla DOM events; the code was difficult to reason with and debug.

To mitigate this, we enlisted the help of RxJS to implement smart observables, hierarchical on-the-fly subscriptions and event filtering.

Video Player

No alt text provided for this image


The intention is to keep as much business logic in the JavaScript layer as possible; however, native code is inevitable in some cases. Of course, each platform has a specific player technology. For Android, we are using the Exoplayer, and for web-based platforms, as previously mentioned, we needed to engage a tech partner to help us launch quickly.

Every player emits unique streaming events in a particular order, for example, ad break start, pause, play, end etc. We needed to write an event normalisation layer to have a standard interface to hook our business logic into relevant player events.

Another major challenge with implementing a web-based video player across multiple CTV platforms was dealing with many different versions of browsers. For example, many TV manufacturers use early versions of Chromium depending on the year the model was released. These versions of Chromium do not update so we need to work around their limitations.

Another significant constraint we encountered was different DRM implementations and support across different browsers. Our chosen tech partner helped us overcome this hurdle, and there will undoubtedly be more challenges ahead as we bring our offering to more devices.

Wins

Android TV was the first cab off the ranks. Once that was launched and stabilised, it was time to port the app to other platforms. Instead of the more obvious choice of Apple's tvOS (which is on our roadmap), we decided to press ahead with porting to web-based TV platforms.

The port was more time consuming than we expected, mainly due to CSS differences and the need to split out some components into `.web.tsx` and `.native.tsx` files. We also needed to work out how to achieve version parity across all platforms (using some Git gymnastics) and structure the code into a mono-repo so we can build and test across platforms quickly.

The upshot of this work was a very high code reuse rate across Web and native - ~80-90%. This exercise was very gratifying for the team and validated the decision to use React Native. We are highly confident that adding new features or porting to new native or web platforms should be a straightforward exercise moving forward.

Conclusion

No alt text provided for this image


The team could have taken several paths launching a new application across multiple platforms and devices. Building separate native apps or developing a single, core native (e.g. C++) library would have been expensive or difficult to resource.

We chose React Native so we could leverage existing web skills within the team and attract new talent while keeping the team small and focused. This decision proved to be the right one for us at Nine.

I hope to be able to drill into some of the more technical solutions mentioned in this article in future posts.

[1] For Xbox, we are potentially aiming to build a PWA using our React Native Web solution and UWP