Developing for TVs with React-TV

Preface

I’ve always worked with Web. Then last year I joined a team with a mission: Make a Component Platform for products, which have to work on different ecosystems: Web, Native and TVs.

We ended up choosing React, for several reasons:

  • My team already had a solid ReactJS background (working in React projects since 2014).
  • Existence of React Native.
  • Rich Ecosystem

As soon as we released stable versions for the Web, we started working with Native. Then my perception was that the team felt comfortable, but one thing caught my attention: React ceased to be a library and became a universal language for both developers and designers.

I began to studying smart TVs development on my free time to be able to design some kind of architecture. I was frustrated. The concept of a universal language seems to be a utopia on TV scenario.

There is almost no good documentation, few cross-platform solutions. To make it even worse, I remembered Netflix's article about using React on TVs. Which reminded me the biggest problem: TVs have limited hardware and React doesn't help it.

I would love for React-Gibbon (Netflix's internal solution for React ecosystem) to exists (as does John Titor) and go on with it. So I wouldn’t be to be writing this article. But it wasn't an option.

I really didn't want to give up thinking about React Components for cross-platforms. So I started studying React-DOM’s source code and development for LG WebOS.

After a month, I had a solution for a unified build and I came to the conclusion that optimizing the React renderer for TVs was the way to go.

Then React-TV was born.

Why and what's React-TV?

As Netflix said:

"Crafting a high-performance TV user interface using React is a real challenge"

TVs usually have limited graphics acceleration, single core CPUs and high memory usage for a common TV App. These restrictions make super responsive 60fps experiences especially tricky.

React-TV is an ecosystem for React Applications on TVs. Includes a Renderer and a CLI tool for building applications. Focused on be a better tool for building and developing fast for TVs.

React-TV optimizations includes removing cross-browser support, being friendly to TVs’ events, preventing DOM or Fiber caching to reduce memory sweep and adding support to canvas-based components (not ready yet).

Disclamer!

React-TV is still in experimental stage, many of these instructions may change in the upcoming versions. One of the discussions is about using canvas at the main base of the renderer (maintaining DOM support).

Another critical topic is the navigation interface, it must leave the renderer level and enter as HOCs from the platform itself. Other topic is about to move to declarative focus handling.

UPDATED 01/21: on react-tv@0.3.3 focus navigation was removed from renderer level and become a separated package, see more: https://github.com/react-tv/react-tv#navigation

Let’s create a simple app for LG WebOS.

This guide will teach you to build for WebOS using react-tv-cli, however if you already know how to build for WebOS TVs. I really recommend to you only use React-TV Renderer.

Before starting make sure you have installed:

  • Nodejs >=8
  • Yarn >=1.3.2

Install globally react-tv-cli

$ yarn global add react-tv-cli@0.3.0

Download the WebOS Installer

Install the Package for your current OS:

OSX

Linux

Windows

Setup WebOS Environment

Unzip and execute your Installer:

If you’re in Linux or Mac the Installer will ask for be executed with sudo

Will pop the SDK Installer, select “Agree” about LG Agreement and choose the destination folder for this installation to continue.

For OSX: Note if you doesn’t have /opt folder, you must to create manually.

Select only “Emulator 3.0” and IDE, then move forward.

You’ll install all packages.

After installation step, restart your machine.

SDK Installed!

Let's create a simple app, based on App Template from CLI. I'll name it as react-video-talks.

$ react-tv-cli init react-video-talks

Open react-video-talks folder:

$ cd react-video-talks

Look the package.json:

{
"name": "react-video-talks",
"version": "0.1.0",
"react-tv": {
"files": ["index.html", "bundle.js", "style.css"]
},
"scripts": {
"build": "webpack",
"start": "yarn build && react-tv run-webos",
"start-dev": "webpack-dev-server --progress --colors"
},
...

About entries:

  • "files": List of files which be package for TV, similar to Electron configuration.
  • "start": Build app and run on WebOS Emulator (virtualbox based).
  • "start-dev": Build app and run Webpack Dev Server.

Ok, move forward.

Let's rewrite src/App.js to:

import React from 'react'
import ReactTV from 'react-tv'
const talks = [];
function HorizontalList({ title, data }) {
return (
<div className="contain">
<h1>{title}</h1>
{/* do nothing with data*/}
</div>
)
}
ReactTV.render(
<div className='my-app'>
<HorizontalList title='Talks' data={talks} />
</div>,
document.querySelector('#root')
)

We created a HorizontalList which only renders title prop. Very similar to React-DOM, no big deal right now. Let's install dependencies and start dev mode:

$ yarn && yarn start-dev

I made a cake recipe for styles, change current styles from styles.css to:

body,
html {
margin: 0;
background: #0e0f11;
color: #ecf0f1;
font-family: 'Open Sans', sans-serif;
min-height: 80vh;
width: 100%;
box-sizing: border-box;
}
.contain {
width: 100%;
}
.contain h1 {
font-size: 70px;
}
.row {
overflow: scroll;
width: 100%;
margin-top: -3em;
}
.row__inner {
font-size: 0;
white-space: nowrap;
margin: 70px 0;
padding-bottom: 10px;
}

Back to src/App.js, let's update talks with some data:

const talks = [
{
title: 'The Melting Pot of JavaScript - Dan Abramov',
thumbUrl: 'https://pbs.twimg.com/media/DKaSdA5V4AIsboQ.jpg'
},
{
title: 'Back to React - Andrew Clark',
thumbUrl: 'https://i.ytimg.com/vi/ZVYVtUFDf28/maxresdefault.jpg'
},
{
title: 'A Cartoon Guide to the Wilds of Data Handling - Lin Clark',
thumbUrl: 'https://i.ytimg.com/vi/WIqbzHdEPVM/maxresdefault.jpg'
},
{
title: 'Lightning Talks - Nicolas Gallagher',
thumbUrl: 'https://i.ytimg.com/vi/RBg2_uQE4KM/maxresdefault.jpg'
},
{
title: 'Scratching React Fiber - Raphael Amorim',
thumbUrl: 'https://i.imgur.com/xc6C3Lr.jpg'
},
{
title: 'Lightning Talks - Devon Lindsey',
thumbUrl: 'https://i.ytimg.com/vi/EUjQB3I53iI/maxresdefault.jpg',
},
{
title: 'Lightning Talks - Adam Wolff',
thumbUrl: 'https://i.ytimg.com/vi/n4Hn1kdgd5g/maxresdefault.jpg'
},
];

And then create a List component which understand this type of structure.

function List(data) {
let list = []
data.forEach((item, index) => {
const tile = (
<div className="tile" key={index}>
<div className="tile__media">
<img className="tile__img" src={item.thumbUrl}/>
</div>
<div className="tile__details"/>
<div className="tile__title">
{item.title}
</div>
</div>
)
list.push(tile)
})
return list
}

Notice the focusable prop, React-TV implements spatial navigation on the render layer, then you have props likefocusable for navigable elements and focused for an element which starts focused.

Update HorizontalList component to initialize List with data prop:

function HorizontalList({ title, data }) {
return (
<div className="contain">
<h1>{title}</h1>
<div class="row">
<div class="row__inner">
{List(data)}
</div>
</div>
</div>
)
}

Seems to be fine, but missing style.

Let's add some styles for List on styles.css

.tile {
position: relative;
display: inline-block;
width: 35%;
height: 500px;
margin-right: 10px;
font-size: 33px;
cursor: pointer;
transition: 450ms all;
}
.tile__img {
width: 100%;
height: 495px;
-o-object-fit: cover;
object-fit: cover;
}
.tile__details {
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
font-size: 10px;
background: -webkit-linear-gradient(bottom, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0) 100%);
background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0) 100%);
-webkit-transition: 450ms opacity;
transition: 450ms opacity;
}
.tile__details:after,
.tile__details:before {
content: '';
position: absolute;
top: 50%;
left: 50%;
display: #000;
}
.tile__details:after {
margin-top: -45px;
margin-left: -25px;
width: 50px;
height: 50px;
border: 3px solid #ecf0f1;
line-height: 50px;
text-align: center;
border-radius: 100%;
background: rgba(0,0,0,0.5);
z-index: 1;
}
.tile__details:before {
content: '▶';
left: 0;
width: 100%;
font-size: 30px;
margin-left: 7px;
margin-top: -38px;
text-align: center;
z-index: 2;
}
.tile .tile__details {
opacity: 1;
overflow: hidden;
}
.tile__title {
position: absolute;
bottom: 0;
padding: 10px;
max-width: 85%;
overflow: hidden;
}
.tile:focus {
outline: 5px solid #831010;
}

Ta-dã:

Build it for WebOS Simulator:

You just need to run:

$ yarn start

This command will start WebOS TV Simulator:

$ react-tv run-webos
Running on Emulator

Build it for WebOS TV

For build for WebOS are exactly 4 steps:

  • Create a Login Account
  • Install Developer Mode App
  • Turn Developer Mode On
  • Registry your TV on react-tv

Create a Login Account

You need to be a member of LG developer portal site. If you already have an LG Developer site account, skip this section.

Go to http://developer.lge.com and then click Sign In at the top right corner of the screen.

Select the CREATE ACCOUNT tab. The Select Country pop-up will be shown.

Select your country from the drop-down menu and click CONFIRM. You will be taken to the Accept Terms & Conditions screen.

Read through and accept our Terms & Conditions and Privacy Policy. Click AGREE to create your new LG Account.

Enter your email address as User ID, password, and date of birth. Select whether to subscribe to our mailing list, then click CONFIRM. We will send you a verification email to your address.

Click CONFIRM in the verification email to complete verification.

Once the Account Created screen appears, go to LG Developer Site (http://developer.lge.com) again. Click Sign In at the top right corner of the screen and sign in to LG Developer using your new LG Account credentials.

From the My Account screen, enter additional information required and click Save.

Install Developer Mode App

  1. Turn on your webOS TV and check if your webOS TV is connected to a network.
  2. Sign in with your LG Developer site account.
  3. Go to the LG STORE.
  4. Search with “Developer Mode”.
  5. Select the Developer Mode app and click the Install button.

Turn Developer Mode On

Once the Developer Mode app is installed, you can enable the Developer Mode on your TV. Run the Developer Mode app from the webOS TV Launcher.

Click the Dev Mode Status button to enable the Developer Mode. TV will reboot.

Run the Developer Mode app again and click the Key Server button to enable the Developer Mode.

Registry your TV on react-tv

Note: Your TV must be on the same network connection.

Run setup-webos:

$ react-tv-cli setup-webos

Select add. Input the information of the target TV.

Note: You do not need to enter the password. When using Developer Mode app, password is not required.

Get the key file from the webOS TV with the following command:

Copy the passphrase from Developer Mode App.

$ react-tv-cli get-key-webos <device>

Just build for production & run on your TV:

$ yarn build-prod && react-tv-cli run-webos <device>

Ta-dã:


Packager works only for LG WebOS?

Currently, yes. But it'll change :)

On Roadmap: Samsung Tizen, Samsung Orsay, Amazon Fire TV and others.

Prologue

Have a idea, question or issue? I would love to hear :)

If you want to, follow me on twitter: I'm frequently talking about react-tv.

Github: https://github.com/raphamorim/react-tv

Twitter: https://twitter.com/raphamorims