@kromatv/react-native-tizen
v0.1.0-alpha.6
Published
React Native and Expo for Samsung Tizen TVs: Hermes and Fabric run natively, with no web runtime in between.
Maintainers
Readme
Why
Samsung's TV store takes two kinds of apps: web apps and .NET apps. A React Native app reaches a Samsung TV today as a web app: react-native-web renders it to the DOM, in the TV's browser engine. That is a second renderer, with its own layout, input and performance, to build and test for.
react-native-tizen runs the app the way Android and iOS run it. A small .NET
app, which the store accepts, loads libreact-native-tizen.so. That library
holds Hermes, React Native's C++ core (Fabric, TurboModules, Yoga) and a Tizen
host that draws with EFL, the TV's own graphics toolkit. Your JavaScript ships
as Hermes bytecode. There is no browser and no WebView in the process.
For an Expo app, Tizen is one more platform. The same project and the same
app.json build for it, and Platform.OS is 'tizen'.
Features
- Expo integration.
npx expo installadds the config plugin, andnpx expo startopens the app on the TV witht, and lists Run on TV, Run in simulator, Find TVs and Create certificate under shift+m. - Development builds. Fast Refresh,
rto reload,console.login the Expo terminal, LogBox on the TV, and React Native DevTools on the TV's Hermes withj.rn-tizen screenshotsaves what a development build draws, on a retail TV too. It captures the app's window only: video plays on a plane under it, so a video's area comes out transparent. - The remote, as on Apple TV.
useTVEventHandlergets one event per press, a key held for 300 ms sendslongSelectorlongUp, and Back reachesBackHandler. - Video.
expo-videoplays on the TV's hardware player, with audio and subtitle track selection. - Samsung TV features. Voice commands and voice search, Smart Hub preview
tiles, background audio, and a 3840x2160 UI plane on TVs that grant one. Each
is one prop in
app.json. - Speech recognition.
expo-speech-recognitionhears the Smart Remote's microphone, with the same code as on Android and iOS, andMicButtonhands the app the remote's mic button so a hold-to-talk search needs no assistant. - Expo modules.
expo-image,expo-video,expo-file-system,expo-font,expo-blur,expo-device,expo-constants,expo-keep-awake,expo-speech-recognitionandexpo/fetchhave Tizen implementations. - Native libraries. A library with a
codegenConfigcompiles into the runtime. react-native-svg ships with a Tizen implementation. Intl. ICU 78.3 is compiled in, with its data trimmed to the locales you list: 2.49 MB for English.- No Tizen Studio tools at run time.
rn-tizenspeaks sdb to the TV, signs.tpkand.wgtpackages in TypeScript and finds TVs on the network in about five seconds. The same code is a Node API. - A simulator. The same runtime, built for aarch64 Linux, runs in a VM on an Apple silicon Mac, with screenshots, remote keys and native backtraces. It has no media player, so it plays no video.
Quick start
1. Install the tools
macOS on Apple silicon is the only build host verified so far.
Node.js 22.13 or newer, which runs the CLI and Expo.
Tizen Studio with, from its Package Manager,
IOT-Headed-6.0-NativeAppDevelopment-CLI(the Tizen 6.0 rootstrap the native build links against),NativeToolchain-Gcc-14.2(the GCC runtime it links) and the Samsung Certificate Extension. A package built against 6.0 runs on every later Tizen. Another floor takes its own rootstrap package, whichnpx rn-tizen initandnpx rn-tizen doctorname.rn-tizennever runs Tizen Studio'ssdbortizentools: it reaches the TV and signs the package itself.LLVM, CMake, Ninja and .NET:
brew install llvm lld cmake ninja dotnet ccache
2. Put the TV in Developer mode
On the TV, open Apps, type 12345 on the remote, turn Developer mode on with
your computer's IP address, then restart the TV.
3. Add the package
In an Expo SDK 58 app, point react-native at
react-native-tvos, the
TV fork of React Native this package is built and tested against. It brings
useTVEventHandler and the other TV APIs. In package.json:
{
"dependencies": {
"react-native": "npm:[email protected]"
},
"overrides": {
"react-native": "$react-native"
}
}The override keeps a single React Native in the app. react-native-tvos's own
list package asks for react-native 0.88.0, so without it npm installs stock
React Native beside the fork, and the app stops on the TV with "main" has not
been registered. With Yarn or pnpm, put npm:[email protected] under
resolutions or pnpm.overrides instead.
Then install, add the package and set the app up:
npm install
npx expo install @kromatv/react-native-tizen
npx rn-tizen initThe commands here use npm's npx. With Bun, pnpm or Yarn, run rn-tizen with
bunx, pnpm exec or yarn instead, as init prints them. In a Bun
workspace npx rn-tizen answers 404: npm finds no bin at the workspace root,
where Bun does not link it, and asks the npm registry for a package of that
name.
rn-tizen init gives the config plugin Expo listed in app.json your app's
Tizen package id. It asks for one, suggesting your Android package or one made
from the slug. It also writes metro.config.mjs, which teaches Metro the
tizen platform:
import { getDefaultConfig } from 'expo/metro-config';
import { withTV } from '@kromatv/react-native-tizen/metro';
export default withTV(getDefaultConfig(import.meta.dirname));withTV teaches Metro every TV platform the app depends on, so an app that
also runs on LG TVs with
@kromatv/react-native-webos
keeps this one call, whichever of the two packages it imports withTV from.
An app that already has a Metro config keeps it: init wraps its export with
withTV, or prints the two lines to add when it cannot change the file
safely.
Last, init points react-native at
react-native-tvos,
which brings useTVEventHandler and the other TV APIs, adds the override that
keeps every other package on that copy, and runs your package manager's
install. Running init again changes nothing.
4. Get a Samsung certificate for your TV, once
npx rn-tizen deviceslists the Samsung TVs on your network with each one's DUID. In Tizen Studio,
Tools, Certificate Manager, create a Samsung profile with that DUID and make it
the active profile. Samsung issues both its certificates, and a retail TV takes
no other. rn-tizen reads the profile from there.
5. Run it from expo start
npx expo startPress t, as you press a for Android. The app opens on the TV you used
last, or on the only TV in Developer mode on your network: a development build
that loads its JavaScript from the dev server you are running, with Fast
Refresh, r to reload, j for React Native DevTools, and its logs in this
terminal. Press shift+t to pick another TV or
the simulator from a list. It needs no setup and works
the same with npm, pnpm, Yarn and Bun (bunx expo start).
The first run builds the native runtime, which takes a few minutes; later runs
only rebuild what changed. Opening checks first that the app loads one
react-native and that its Metro config knows tizen, and says to run
npx rn-tizen init when not. npx rn-tizen doctor names the exact fix for
anything a build needs.
shift+m ("more tools") lists @kromatv/react-native-tizen with more:
Run on TV with an IP address, Run in simulator, Find TVs, which
lists the Samsung TVs on your network with their Developer mode, sdb and DUID,
and Create certificate, which makes an author certificate. Expo asks
"(y/N)" before running one; answer y. Each prints the address of a page
inside the dev server where you can follow its progress and logs.
Expo has no expo run:tizen, and expo prebuild and expo export --platform
tizen do not know the platform. To package the app with its JavaScript inside
instead, add a script beside Expo's own:
{
"scripts": {
"tizen": "rn-tizen run"
}
}npm run tizen installs that build on the only TV used before, launches it and
prints its logs until Ctrl+C.
Store packages need a release build, which package makes; --dev makes a
development one that streams its logs:
npx rn-tizen package
npx rn-tizen package --devPublishing to Samsung's store needs a Seller Office partner group, because the package carries a native library.
Examples
Each one compiles against the package's API and react-native-tvos's types. The example app has a screen for each.
Remote keys
import { useEffect, useState } from 'react';
import { BackHandler, Text, useTVEventHandler } from 'react-native';
export function LastKey() {
const [key, setKey] = useState('none');
useTVEventHandler((event) => setKey(event.eventType));
useEffect(() => {
const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
setKey('back');
return true;
});
return () => subscription.remove();
}, []);
return <Text>{`Last key: ${key}`}</Text>;
}eventType is up, down, left, right, select, playPause,
fastForward, rewind, a digit, a color key, or long<Key> for a hold. A Back
handler that returns false lets the app exit.
Focus
Tizen has no native focus engine yet: focusable, hasTVPreferredFocus and
TVFocusGuideView do nothing. Keep the focused item in state and move it on
remote events:
import { useState } from 'react';
import { StyleSheet, Text, View, useTVEventHandler } from 'react-native';
const ITEMS = ['Home', 'Movies', 'Shows', 'Settings'];
export function Menu({ onOpen }: { onOpen: (item: string) => void }) {
const [focused, setFocused] = useState(0);
useTVEventHandler((event) => {
if (event.eventType === 'right') setFocused((index) => Math.min(ITEMS.length - 1, index + 1));
if (event.eventType === 'left') setFocused((index) => Math.max(0, index - 1));
if (event.eventType === 'select') onOpen(ITEMS[focused]!);
});
return (
<View style={styles.row}>
{ITEMS.map((item, index) => (
<View key={item} style={[styles.item, index === focused && styles.focused]}>
<Text style={styles.label}>{item}</Text>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', gap: 24 },
item: { paddingHorizontal: 32, paddingVertical: 16, borderRadius: 12 },
focused: { backgroundColor: '#3d5afe' },
label: { color: '#fff', fontSize: 32 },
});Video
import { useVideoPlayer, VideoView } from 'expo-video';
import { useTVEventHandler } from 'react-native';
export function Player({ uri }: { uri: string }) {
const player = useVideoPlayer(uri, (created) => created.play());
useTVEventHandler((event) => {
if (event.eventType === 'playPause') {
if (player.playing) player.pause();
else player.play();
}
if (event.eventType === 'fastForward') player.seekBy(30);
if (event.eventType === 'rewind') player.seekBy(-10);
});
return <VideoView player={player} contentFit="contain" style={{ flex: 1 }} />;
}The TV draws video on its own plane, under the UI, so rn-tizen screenshot
shows the video's area transparent. What expo-video does on Tizen, call by
call, is in
the media reference.
Voice
With "voice": { "searchUrl": "myapp://search?q={query}" } in the plugin's
props, "pause", "skip forward 30 seconds" and "select Dune" work with no code.
To handle a search yourself:
import { Voice } from '@kromatv/react-native-tizen/voice';
import { useEffect } from 'react';
export function useVoiceSearch(openSearch: (query: string) => void) {
useEffect(() => {
Voice.setScreen('home');
const search = Voice.addListener('search', (query) => openSearch(query.title ?? query.utterance));
return () => search.remove();
}, [openSearch]);
}Speech recognition
Install expo-speech-recognition and list its plugin in app.json, as on
Android and iOS; rn-tizen adds the recorder privilege it needs. Recognition
hears the Smart Remote's microphone while its button is held, so take the
button with MicButton: while a listener is registered, Samsung's assistant
stays closed and each press reaches the app as it happens.
import { ExpoSpeechRecognitionModule, useSpeechRecognitionEvent } from 'expo-speech-recognition';
import { MicButton } from '@kromatv/react-native-tizen/mic-button';
import { useEffect, useState } from 'react';
export function useHoldToTalk() {
const [transcript, setTranscript] = useState('');
useEffect(() => {
const pressIn = MicButton.addListener('pressIn', () =>
ExpoSpeechRecognitionModule.start({ interimResults: true }),
);
const pressOut = MicButton.addListener('pressOut', () => ExpoSpeechRecognitionModule.stop());
return () => {
pressIn.remove();
pressOut.remove();
};
}, []);
useSpeechRecognitionEvent('result', (event) => setTranscript(event.results[0]?.transcript ?? ''));
return transcript;
}lang defaults to the TV's own language; tv-features.md
lists the events, the errors and what each option does on a TV.
Smart Hub preview
With "preview": { "refreshMinutes": 30 } in the plugin's props, the app
publishes the tiles Samsung's home bar shows above its icon. Pressing a tile
opens the app with its url through Linking.
import { SmartHubPreview } from '@kromatv/react-native-tizen/preview';
export async function publishContinueWatching() {
const result = await SmartHubPreview.set([
{
title: 'Continue watching',
tiles: [
{
title: 'Blade Runner',
subtitle: '1 h 12 left',
image: 'https://example.com/posters/blade-runner-640x360.jpg',
ratio: '16:9',
url: 'myapp://item/42',
playable: true,
},
],
},
]);
if (!result.published) console.warn(result.reason);
}Off a Samsung TV, Voice and SmartHubPreview answer "not available", so the
same code runs on Android, iOS and the web. Samsung documents voice and the
personal preview for web apps only; ask your Seller Office content manager
before you ship either to the store. See TV features.
Benchmarks
Measured on a Samsung GQ75LS03DAUXZG (The Frame, 2024, Tizen 9.0, armv7), with the example app's benchmark screen: a 1,000-row FlatList and 12 views on the native animated driver. Each figure is a median.
| What | Result | Runs |
|---|---|---|
| Cold start, launch command to first frame (release) | 1,196 ms | 17 launches |
| The same screen as a Tizen web app (react-native-web), same launches | 2,075 ms | 17 launches |
| FlatList, focus moving one row every 200 ms | 54.1 frames/s, 22 dropped frames in 114 s | 3 runs |
| Native-driver animation | 49.9 frames/s at the TV's 50 Hz, 0 dropped | 3 runs |
| Resident memory (release), list still / scrolling | 63.7 MB / 94.4 MB | 3 runs |
| .tpk size (release) | 8.0 MB, against 0.16 MB for the web app | 1 build |
Frame counts come from a development build, the only kind rn-tizen perf
reads. At one row every 50 ms the JavaScript thread saturates and frames drop.
The web app was compared on cold start and size only: a web page cannot read
the canvas's frame counter or its own process's memory. The method, every run
and the commands to reproduce them are in docs/benchmarks.md.
Status
This is an alpha. rn-tizen builds for Tizen 6.0 to 10.0, Samsung's TVs from
2021 on: a package reaches its floor, 6.0 by default, and every later version
(Tizen versions). One TV is verified on hardware so
far: a Samsung GQ75LS03DAUXZG (The Frame, 2024) on Tizen 9.0, armv7.
Known gaps:
- Only Tizen 9.0 has run on a TV. A 6.0-floor package runs there, and the runtime's imports resolve in every rootstrap from 6.0 to 10.0, but no TV on another version has run it.
- TVs from 2020 and earlier (Tizen 5.5 and below) cannot run it: Samsung blocks the native libraries an app carries below 6.0 (reference).
- No native focus engine, as above.
- react-native-svg draws no text, images, masks or clip paths yet (details).
- A native library cannot declare Tizen privileges, and an app cannot leave a library out of the runtime (limits).
expo prebuild,expo export --platform tizenandexpo run:tizendo not exist: Expo's CLI has no way to add a platform.rn-tizen packageandrn-tizen runstand in.
Documentation
Getting started
- Quick start, above
- The simulator: a virtual TV on an Apple silicon Mac
- Tizen versions: which Samsung TVs a package reaches, and why the floor is 6.0
- Benchmarks: the method and the raw results
- Releases: what
each version changed, also in
CHANGELOG.md
CLI
- The
rn-tizenCLI: every command and option, the config plugin's props, and where builds land - The
tizenNode API: find TVs, sign and install packages from your own tools - Runtime config: what the runtime reads at startup
Expo
- Inside
npx expo start: the shift+m commands - Develop with
expo start: Fast Refresh, reload, logs, LogBox, DevTools - Native libraries and Expo modules: what compiles into the runtime, and how a library adds Tizen support
- Expo CLI integration and the Expo modules contract
TV features
- Voice, Smart Hub preview, background audio and 4K
- Remote keys and text input,
<TextInput> - Video
- App integration and the Smart Hub preview
Platform reference
- Design decisions: what the package is built on, and why
- The reference index, with sources for every fact:
the TV platform,
EFL rendering,
React Native's platform contract,
dev tooling,
Intl, networking, the sdb protocol, package signing, TV discovery
Contributing
- Contributing: set up, the loop, the checks
- Security policy
- Third-party notices
CI runs the TypeScript tests on Linux and macOS and installs the packed package into a fresh Expo app under Node. When the runtime changes, it builds it for aarch64 Linux and runs its C++ tests (doctest). SonarCloud's quality gate checks every pull request, and mutation testing runs weekly.
Licence
MIT. What the runtime and the packages it builds carry from other projects,
and under which licences, is in THIRD_PARTY_NOTICES.md.
Not affiliated with or endorsed by Samsung, the Linux Foundation, Meta or Expo. Samsung, Tizen, Smart Hub, React Native and Expo are trademarks of their owners.
