Implement Headless JavaScript Playback
This guide explains how to implement headless playback in your app. Headless playback runs media playback on a separate JavaScript thread from the UI, providing up to 30% improvement in Time to First Video Frame (TTFVF) for apps with complex UIs.
Watch this video for an introduction to Headless Tasks and Services for Vega.
About headless integration
Resource contention issues
Media playback and UI rendering can block each other when running on the same JavaScript thread in two main scenarios: when media playback blocks UI responsiveness, or when UI operations block smooth media playback.
Common media player implementations for streaming standards, such as MPEG DASH or HLS, can require significant system resources from the CPU and network. Specifically, the playback and live streaming processes demand CPU-intesive manifest parsing. When the player runs on the same thread as the UI, these components compete for resources, resulting in delayed playback start, choppy video playback, unresponsive UI elements, and poor overall user experience.
The headless implementation solves these issues by running the player in a separate JS thread from the UI, creating a service component for the player. This enables the UI controls to interact with the service component to control the media playback. In this scenario, the player UI in the interactive component is a client and the player running the service component is the server.
Benefits of headless integration include improved playback start time, enhanced UI responsiveness, and better overall playback performance.
To implement headless playback in your app, you need to perform three tasks:
- Create the player server as a service component in your app.
- Change the player code to interact with the service vs. directly controlling a player.
- Start the service from the app.
The following image outlines the steps and provides a diagram of how headless playback works in your app.

For detailed implementation steps, see Implement the headless service below.
Implement the headless service
To implement the headless service, perform the following steps:
- Add components and capabilities to your app manifest.
- Create a service.js file.
- Create the headless service file.
- Update your player code.
- Initialize the client.
- Handle video loading.
- Handle playback controls.
- Install additional dependencies.
The following sections walk you through each of these steps.
Add components to your app manifest
A Vega app package must contain a configuration file called manifest.toml located at the root of the package. This file describes essential information about the package.
To implement headless playback in your app, you need to update your app manifest in the components, processes, wants, and offers sections. The following example shows you the specific updates for each section. Replace <your-app-package-name> with your app's package name.
[[components.interactive]]
id = "<your-app-package-name>.main"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
launch-type = "singleton"
categories = ["com.amazon.category.main"]
[[components.service]]
id = "<your-app-package-name>.service"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
launch-type = "singleton"
.
.
.
[processes]
# Ensure that the player UI and headless JS player components are in the same process group
[[processes.group]]
component-ids = ["<your-app-package-name>.main", "<your-app-package-name>.service"]
.
.
.
[wants]
[[wants.service]]
id = "<your-app-package-name>.service"
.
.
.
[offers]
[[offers.service]]
id = "<your-app-package-name>.service"
[[components.interactive]]
id = "<your-app-package-name>.main"
runtime-module = "/com.amazon.kepler.runtime.react_native_kepler_4@IReactNativeKepler_0"
launch-type = "singleton"
categories = ["com.amazon.category.main"]
[[components.service]]
id = "<your-app-package-name>.service"
runtime-module = "/com.amazon.kepler.runtime.react_native_kepler_4@IReactNativeKepler_0"
launch-type = "singleton"
.
.
.
[processes]
# Ensure that the player UI and headless JS player components are in the same process group
[[processes.group]]
component-ids = ["<your-app-package-name>.main", "<your-app-package-name>.service"]
.
.
.
[wants]
[[wants.service]]
id = "<your-app-package-name>.service"
.
.
.
[offers]
[[offers.service]]
id = "<your-app-package-name>.service"
For more information about the manifest, see Vega App Manifest.
Create a service.js file
Create a service.js file in your project root. The service.js file allows you to register the entry points (onStartService and onStopService) for the headless JS service. Replace <your-app-package-name> with your app's package name used in the manifest.toml file above to register the entry and exit points.
import {HeadlessEntryPointRegistry} from '@amazon-devices/headless-task-manager';
import {onStartService, onStopService} from './src/PlayerService';
HeadlessEntryPointRegistry.registerHeadlessEntryPoint(
'<your-app-package-name>.service::onStartService',
() => onStartService,
);
HeadlessEntryPointRegistry.registerHeadlessEntryPoint(
'<your-app-package-name>.service::onStopService',
() => onStopService,
);
react-native build-vega command. You must use the react-native build-vega command to build your app.Create the headless service file
Create a helper file to handle player initialization and control within the ./src/PlayerService.ts file.
Update your player code
Initialize the client
In your App.tsx file, add the following code to initialize the client.
Handle video loading
Next, in your App.tsx file, add the following code to handle video loading.
Handle playback controls
Next, in your App.tsx file, add the following code to handle playback controls.
Integrate Vega Media Controls
Vega Media Controls (KMC) provides functionality that allows media app developers on Vega to streamline the integration of various input modalities for media control. While media apps typically feature in-app user interface controls, such as play, pause, and stop, customers often want additional interaction methods, such as remote control or voice commands through services such as Alexa. Vega Media Controls functionality handles the integration of these diverse input methods, allowing you to focus on the core business logic of smooth media playback.
Apps that use the headless JS playback API to play their content can now get the KMC integration for free for basic playback controls such as pause, play, and seek. The headless JS playback APIs internally creates the KMC server, publishes the server states, and handles basic commands of play, pause, and seek.
Headless JS playback must only update the KMC server state for the data pertaining to basic playback. If the app handles KMCs itself, it need not use headless JS playback integration. It must integrate directly into KMC to handle the commands. This integration is only applicable for use cases where the player is integrated with an interactive component. Apps shouldn't call player.setMediaControlFocus(componentInstance) or implement override functionality.
To opt-in to KMC
- Enable KMC. Add the necessary manifest entries to your manifest.toml file as described in Get started with Vega Media Controls Overview.
- Update your package.json file to take a dependency on
@amazon-devices/kepler-media-controls. - Get the component instance of the interactive component:
import { useComponentInstance , IComponentInstance } from '@amazon-devices/react-native-kepler'; const componentInstance: IComponentInstance = useComponentInstance(); -
Before calling the
IPlayerClient.loadAPI, set the media control focus on the player instance.playerClient.current.setMediaControlFocus(componentInstance);
Now, your playerClient instance is enabled with KMC and supports basic functionality of pause/play and seek remote control and Alexa voice controls.
Override certain control commands
In some cases, the app might want to override the handling of certain control commands that is handled by headless JS playback APIs by default. For example, you can disable seek during live content playback. Headless JS playback APIs also provides a way to achieve this experience.
- Extend
PlayerClientMediaControlHandlerprovided by@amazon-devices/kepler-player-clientand override the function that the app wants to override.import { PlayerClientMediaControlHandler } from "@amazon-devices/kepler-player-client"; import { IMediaSessionId } from '@amazon-devices/kepler-media-controls'; class AppOverrideMediaControlHandler extends PlayerClientMediaControlHandler { async handlePlay(mediaSessionId?: IMediaSessionId) { if(shouldOverride) { // do custom handling as per app's requirement } else { // call default handler super.handlePlay(mediaSessionId); } } }; - Pass the app's media control handler as the second parameter to the
setMediaControlFocusmethod.playerClient.current.setMediaControlFocus(componentInstance, new AppOverrideMediaControlHandler());
Install additional dependencies
You need to install the following packages to use the headless service:
@amazon-devices/kepler-player-server@amazon-devices/kepler-player-client@amazon-devices/react-native-w3cmedia
Add these dependencies in your app's package.json, shown in the following example.
"dependencies": {
"@amazon-devices/headless-task-manager": "~1.0.0",
"@amazon-devices/kepler-player-server": "~2.0.4",
"@amazon-devices/kepler-player-client": "~2.0.4",
"@amazon-devices/keplerscript-turbomodule-api": "~1.2.1",
"@amazon-devices/react-native-kepler": "~2.0.0",
"@amazon-devices/react-native-w3cmedia": "~2.1.66",
"base-64": "*",
"react": "18.2.0",
"react-native": "0.72.0",
"xmldom": "*"
},
"dependencies": {
"@amazon-devices/headless-task-manager": "~1.0.0",
"@amazon-devices/kepler-player-server": "~4.0.0",
"@amazon-devices/kepler-player-client": "~4.0.0",
"@amazon-devices/react-native-kepler": "~4.0.0",
"@amazon-devices/react-native-w3cmedia": "~4.0.0",
"base-64": "*",
"react": "19.2.0",
"react-native": "0.83.0",
"xmldom": "*"
},
Best practices
Background mode
When the app goes to background, it's recommended that the UI calls IPlayerClient::unloadSync to unload the media player. Similarly, headless JS service should unload the media player and do the necessary cleanup in its onStopService callback.
Related topics
- Vega Headless Tasks and Services
- Vega Player Client API Reference
- Vega Player Server API Reference
- Vega App Manifest
- App Performance Best Practices
- Vega Media Controls API reference
Last updated: Dec 03, 2025

