as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
AWS
Documentation
Support
Contact Us
My Cases
Get Started
Design and Develop
Publish
Reference
Support

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.

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:

  1. Create the player server as a service component in your app.
  2. Change the player code to interact with the service vs. directly controlling a player.
  3. 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:

  1. Add components and capabilities to your app manifest.
  2. Create a service.js file.
  3. Create the headless service file.
  4. Update your player code.
    1. Initialize the client.
    2. Handle video loading.
    3. Handle playback controls.
  5. 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.

Copied to clipboard.

[[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"

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.

Copied to clipboard.

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,
);

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

  1. Enable KMC. Add the necessary manifest entries to your manifest.toml file as described in Get started with Vega Media Controls Overview.
  2. Update your package.json file to take a dependency on @amazon-devices/kepler-media-controls.
  3. Get the component instance of the interactive component:

    Copied to clipboard.

     import { useComponentInstance , IComponentInstance } from '@amazon-devices/react-native-kepler';
    
     const componentInstance: IComponentInstance = useComponentInstance();
    
  4. Before calling the IPlayerClient.load API, set the media control focus on the player instance.

    Copied to clipboard.

     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.

  1. Extend PlayerClientMediaControlHandler provided by @amazon-devices/kepler-player-client and override the function that the app wants to override.

    Copied to clipboard.

     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);
             }
         }
     };
    
  2. Pass the app's media control handler as the second parameter to the setMediaControlFocus method.

    Copied to clipboard.

     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.

Copied to clipboard.

"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": "*"
  },

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.


Last updated: Sep 30, 2025