Get started with Vega Media Controls
This guide provides guidance on a example implementation of an app that implements the Vega Media Controls.
Vega Media Controls API reference
For reference documentation about this API, see amzn/kepler-media-controls.
Vega Media Controls Prerequisites
The following prerequisites are only applicable during the development of media provider apps and not when using client methods. Before you begin using these methods, you must update your app manifest to explicitly declare your intention to use the Vega Media Controls API. When modifying the manifest entries, as shown in the example below, remember to replace com.amazondeveloper.media.sample
with your app's actual package ID. This step makes sure that your app is properly configured to interact with the Vega Media Controls API.
[package]
title = "<Your app title>"
id = "com.amazondeveloper.media.sample"
[components]
[[components.interactive]]
id = "com.amazondeveloper.media.sample.main"
launch-type = "singleton"
# The category "com.amazon.category.kepler.media" is only necessary for the primary component, which is identified in the extras
# section of the manifest using the "interface.provider" key.
categories = ["com.amazon.category.kepler.media"]
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
[[extras]]
key = "interface.provider"
component-id = "com.amazondeveloper.media.sample.main"
[extras.value.application]
[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IMediaPlaybackServer"
command_options = [
"StartOver",
"Previous",
"Next",
"SkipForward",
"SkipBackward",
<insert other commands supported>
]
attribute_options = ["AudioAdvanceMuted", <insert any other attribute options supported>]
features = ["AdvancedSeek", "VariableSpeed", "AudioTracks", "TextTracks", <insert other features supported>]
Install and setup Vega Media Controls
To use the API, add the following dependency to your package.json
.
"@amazon-devices/kepler-media-controls": "^1.0.0",
"@amazon-devices/kepler-media-types": "^1.0.0"
Vega Media Controls common use cases
For Media Content Provider apps
To manage media session states in the provider app, implement a MediaPlayerState
class. This class encapsulates all of the necessary properties for a media session, including playback state, track information, duration, current position, playback rate, and available media actions. The MediaPlayerState
class offers methods to update these properties as the media session state changes. Additionally, include a method like getServerState()
to construct and return a valid MediaSessionState
object based on the current property values. By consistently updating the MediaPlayerState
instance when the media session changes, you make sure that the MediaSessionState
object accurately reflects the media player's current state, thereby centralizing state management and simplifying synchronization. For media providers supporting simultaneous playback of multiple videos, maintain a separate MediaPlayerState
object for each session, making sure that the mSessionId
is properly updated for each instance. This approach allows for efficient management of multiple media sessions within the app.
import {
Action,
ICapabilities,
IPlaybackState,
PlaybackStatus,
IPlaylistState,
RepeatMode,
IControl,
MediaSessionState,
ITimeValue,
IPlaybackPosition,
IMediaSessionId,
IMediaInfo,
ILocale,
} from '@amazon-devices/kepler-media-controls';
import {MediaId} from '@amazon-devices/kepler-media-types';
export class MediaPlayerState {
playbackStatus: PlaybackStatus = PlaybackStatus.NOT_PLAYING;
playbackSpeed: number = 1.0;
playbackState: IPlaybackState | undefined;
updatedAtTime: ITimeValue = {seconds: 1702965600, nanoseconds: 0};
currentPosition: ITimeValue = {seconds: 10, nanoseconds: 999999999};
mediaInfo: IMediaInfo | undefined;
playbackPosition: IPlaybackPosition = {
updatedAtTime: {seconds: 1702965600, nanoseconds: 0},
position: {seconds: 10, nanoseconds: 999999999},
};
forwardSkipSteps: ITimeValue[] = [
{seconds: 1, nanoseconds: 0},
{seconds: 2, nanoseconds: 0},
{seconds: 3, nanoseconds: 0},
{seconds: 4, nanoseconds: 0},
];
backwardSkipSteps: ITimeValue[] = [
{seconds: 1, nanoseconds: 0},
{seconds: 2, nanoseconds: 0},
{seconds: 3, nanoseconds: 0},
{seconds: 4, nanoseconds: 0},
];
mId: MediaId = {contentId: 'com.foo.bar', catalogName: 'sample-catalog-v1'};
mSessionId: IMediaSessionId = {id: 0};
currentSupportedActions: Action[] = [
Action.PLAY,
Action.PAUSE,
Action.STOP,
new Action('ENABLE_XRAY'),
new Action('DISABLE_XRAY'),
];
currentSupportedSpeeds: number[] = [
-2.0, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.0,
];
currentSupportedRatings: number[] = [0.0, 1.0];
capabilities: ICapabilities | undefined;
locale: ILocale | undefined;
repeatMode: RepeatMode = RepeatMode.REPEAT_TRACK;
enableShuffle: boolean = true;
playlistState: IPlaylistState | undefined;
xRayControlName: string = 'X-ray';
xRayControlStateOff: string = 'off';
xRayControlStateOn: string = 'on';
controls: IControl[] | undefined;
serverState: MediaSessionState | undefined;
getServerState() {
this.playbackState = {
playbackStatus: this.playbackStatus,
playbackSpeed: this.playbackSpeed,
position: this.playbackPosition,
};
this.capabilities = {
actions: this.currentSupportedActions,
speeds: this.currentSupportedSpeeds,
ratings: this.currentSupportedRatings,
forwardSkipSteps: this.forwardSkipSteps,
backwardSkipSteps: this.backwardSkipSteps,
};
this.playlistState = {
repeatMode: this.repeatMode,
shuffle: this.enableShuffle,
};
this.controls = [
{name: this.xRayControlName, state: this.xRayControlStateOff},
];
this.locale = {
identifier: 'en-US',
};
this.mediaInfo = {
id: this.mId,
hasVideo: true,
availableAudioTracks: [
{id: '0', displayName: 'English', language: this.locale},
],
availableTextTracks: [
{id: '0', displayName: 'English', language: this.locale},
],
};
this.serverState = {
id: this.mSessionId,
playbackState: this.playbackState,
capabilities: this.capabilities,
playlistState: this.playlistState,
controls: this.controls,
};
return this.serverState;
}
}
To implement media control functionality, create a class MediaControlHandlerAsync
that implements the IMediaControlHandlerAsync
interface. This class includes callback functions such as handlePlay
, handlePause
, and others to manage media playback controls. These functions use TypeScript Promise-based methods to make sure non-blocking execution. The class also implements a callback to asynchronously return the media session state using the previously created MediaPlayerState
class.
The provided code example only implements a subset of the methods. A complete implementation includes all methods from the IMediaControlHandlerAsync
interface. Most callback functions receive an instance of a IMediaSessionId
interface object, indicating the session identifier for which the requested action is performed. This is particularly relevant when the media provider app supports multiple sessions. If the value of sessionId
is empty or undefined, the provider app defaults to taking action on the default session. This approach makes sure proper handling of media control requests across single or multiple media sessions within the app.
The MediaControlHandlerAsync
class must implement the handleGetMetadataInfo
method, which is one of the methods defined in the IMediaControlHandlerAsync
interface. This method is responsible for returning the metadata of the media currently being played in a given session. Media metadata information is represented using the IMediaMetadata
data object, which includes various properties such as mediaId, composers, artists, playback source, and many others. In the provided code sample, the implementation of handleGetMetadataInfo
demonstrates how to create and return an IMediaMetadata
object. For simplicity, the example uses an inline object to represent the metadata. However, in a production environment, a more appropriate approach is to create a separate class that implements the IMediaMetadata
data object. This separate class serves as a dedicated data object for handling media metadata, promoting better code organization and maintainability while allowing for a more comprehensive representation of the media's attributes.
The MediaControlHandlerAsync
class constructor plays a crucial role in setting up the media control functionality. It creates a single instance of MediaControlServerComponentAsync
, which serves as the core component for handling media control operations asynchronously. A key step in this constructor is the registration of an IComponentInstance
object. This object is passed as a parameter to the constructor and is then registered with the MediaControlServerComponentAsync
instance. The registration process is essential because it establishes the link between the media control handler and the specific component in the app that receives media control callbacks. The importance of this registration becomes particularly evident in apps with multiple components. By passing the correct IComponentInstance
to the MediaControlHandlerAsync
constructor, you make sure that media control callbacks are directed to the appropriate component. This targeted approach prevents confusion and makes sure that media controls affect only the intended part of the app.
import {
Action,
IMediaControlHandlerAsync,
IMediaSessionId,
PlaybackStatus,
RepeatMode,
ITimeValue,
ITrack,
IMediaMetadata,
MediaSessionState,
} from '@amazon-devices/kepler-media-controls';
import {MediaId} from '@amazon-devices/kepler-media-types';
import {IComponentInstance} from '@amazon-devices/react-native-kepler';
import {
IMediaControlServerAsync,
MediaControlServerComponentAsync,
} from '@amazon-devices/kepler-media-controls';
import {MediaPlayerState} from './MediaPlayerState';
export class MediaControlHandlerAsync implements IMediaControlHandlerAsync {
mediaControlServer: IMediaControlServerAsync;
mediaPlayerState: MediaPlayerState;
constructor(componentInstance: IComponentInstance) {
MediaControlServerComponentAsync.getOrMakeServer();
this.mediaPlayerState = new MediaPlayerState();
this.mediaControlServer.setHandlerForComponent(this, componentInstance);
this.setMediaSessionState();
}
setMediaSessionState() {
const mSessionState = this.mediaPlayerState.getServerState();
this.mediaControlServer.updateMediaSessionStates([mSessionState]);
}
async handlePlay(_sessionId?: IMediaSessionId): Promise<void> {
this.mediaPlayerState.playbackStatus = PlaybackStatus.PLAYING;
this.mediaPlayerState.playbackSpeed = 1.0;
const mSessionState: MediaSessionState = {
...this.mediaPlayerState.getServerState(),
};
if (mSessionState === undefined) {
throw new Error('MediaSessionState is undefined');
}
this.mediaControlServer.updateMediaSessionStates([mSessionState]);
return Promise.resolve();
}
async handlePause(_sessionId?: IMediaSessionId): Promise<void> {
this.mediaPlayerState.playbackStatus = PlaybackStatus.PAUSED;
this.setMediaSessionState();
return Promise.resolve();
}
async handleGetSessionState(
_sessionId?: IMediaSessionId,
): Promise<MediaSessionState[]> {
const mSessionState = this.mediaPlayerState.getServerState();
if (mSessionState === undefined) {
throw new Error('Unable to get session state');
}
return Promise.resolve([mSessionState]);
}
async handleGetMetadataInfo(id: MediaId): Promise<IMediaMetadata> {
return Promise.resolve({
mediaId: id.contentId,
title: 'Sample Title',
date: '2024-01-01',
artwork: [
{
url: 'https://example.com/artwork1.jpg',
sizeTag: 'medium',
id: 'sample artwork id1',
tag: 'fantastic',
},
{
url: 'https://example.com/artwork2.jpg',
sizeTag: 'large',
id: 'sample artwork id2',
tag: 'wonderful',
},
],
});
}
}
In an interactive application, you can obtain an instance of IComponentInstance
using the useComponentInstance
method and then create an instance of MediaControlHandlerAsync
within React Native's useEffect
hook. Here's how to implement this approach:
- Use the
useComponentInstance
method to get an instance ofIComponentInstance
. - Then, in your React component, use the
useEffect
hook to create theMediaControlHandlerAsync
instance and pass in the component instance as a constructor parameter. This setup makes sure that theMediaControlHandlerAsync
object is initialized with the correctIComponentInstance
object as soon as the component mounts and thecomponentInstance
is available.
This approach allows your provider app to be ready to handle media control playback commands as soon as the app is initialized and its graphical user interface is set up, tying the media control functionality closely to the component's lifecycle and the availability of the necessary IComponentInstance
object.
import { useComponentInstance , IComponentInstance } from '@amazon-devices/react-native-kepler';
export const App = () => {
const componentInstance: IComponentInstance = useComponentInstance();
useEffect(() => {
const mediaControlHandler: IMediaControlHandlerAsync =
new MediaControlHandlerAsync(componentInstance);
}, []);
// Create user interface for your provider app here
};
For Media Control client apps
The client methods of the Vega Media Control API serves a crucial role in the media ecosystem, primarily facilitating the transmission of various commands such as play, pause, and other supported actions to media provider apps. They provide you with the flexibility to create diverse apps, including centralized media hub solutions that can control multiple media sources from a single interface. Additionally, you can use them for testing purposes, allowing you to verify if media provider apps correctly receive and process commands like handlePlay
and handlePause
, along with any associated arguments. This testing capability makes sure that media provider apps respond appropriately to user inputs, maintaining a seamless and responsive media playback experience. By leveraging these client methods, you can enhance the interoperability and reliability of media apps within the Vega ecosystem, ultimately leading to a more integrated and user-friendly media consumption experience.
To use the Vega Media Control Client API, create a helper class called MediaAppLocator
as a singleton class. Within this class, obtain an instance of the media app locator by calling MediaControlComponentAsync.makeMediaControlEndpointLocator()
, which returns an object implementing the IMediaControlEndpointLocatorAsync
interface. Use this instance to fetch media provider apps that use the Vega Media Controls API and are installed on the device. The endpoint locator interface plays a vital role in retrieving a ranked list of media control provider apps, with the ranking determined by the app's focus. The first app in the list is typically the one currently visible and having active focus. Importantly, the endpoints are fetched asynchronously using TypeScript Promise-based methods, ensuring that the main thread is not blocked during the retrieval process, thus maintaining the app's responsiveness.
The client methods of the Vega Media Control API offer an additional feature that enhances their functionality and responsiveness. By calling the addChangeListener
method, client apps can register a listener to monitor changes in the available media endpoints on the device. This listening capability is particularly useful for maintaining an up-to-date list of media apps in near real-time. When new media apps are installed or existing ones are uninstalled, the device's media endpoint landscape can change. By implementing this listener, client apps can automatically detect these changes without the need for manual polling or refreshing. This makes sure that the client app always has the most current information about available media apps. The listener can be set up to trigger appropriate actions when changes occur, such as updating the user interface, refreshing the list of available media controls, or adjusting the app's behavior based on the new set of available media endpoints. This real-time responsiveness significantly improves the user experience and keeps the client app synchronized with the device's media ecosystem.
import {
VegaMediaControlsError as KMCError,
IMediaControlClientAsync,
IMediaControlEndpointLocatorAsync,
MediaControlComponentAsync,
MediaSessionState,
IMediaMetadata,
} from '@amazon-devices/kepler-media-controls';
import {MediaId} from '@amazon-devices/kepler-media-types';
export class MediaAppLocator {
private static instance: MediaAppLocator = new MediaAppLocator();
private mediaAppLocator: IMediaControlEndpointLocatorAsync;
private mediaAppList: IMediaControlClientAsync[];
private constructor() {
this.mediaAppLocator = MediaControlComponentAsync.makeMediaControlEndpointLocator();
this.mediaAppList = [];
}
public static getInstance() {
return this.instance;
}
private setMediaAppList = (controllers: IMediaControlClientAsync[]) => {
// We have to manually release these resources
this.mediaAppList.forEach((client) => client.destroy());
this.mediaAppList = controllers;
}
public addRegistrarListener(onServerListChanged: IMediaControlEndpointLocatorListener) : Promise<ISubscription> {
return this.mediaAppLocator.addChangeListener(onServerListChanged);
}
public getMediaApp(index: number): IMediaControlClientAsync | undefined {
if (index >= 0 && index < this.mediaAppList.length) {
return this.mediaAppList[index];;
} else {
console.error('getMediaApp invoked with invalid index: ' + index);
return undefined;
}
}
public fetchMediaApps(): Promise<IMediaControlClientAsync[]> {
return this.mediaAppLocator
.getMediaControlEndpoints()
.then((controllers) => {
this.setMediaAppList(controllers);
return controllers;
})
.catch((error: KMCError) => {
console.error('Error fetching media apps: ' + error.message);
return [];
});
}
public fetchMediaAppMetadata(index: number, mediaId: MediaId): Promise<IMediaMetadata> {
const mediaApp = this.getMediaApp(index);
if (mediaApp) {
return mediaApp.getMetadata(mediaId);
} else {
return Promise.reject();
}
}
}
The Vega Media Controls client methods return the endpoints as a ranked list of IMediaControlClientAsync
objects. This ranking is based on app focus, with the first endpoint in the list representing the app that currently has active focus. Each IMediaControlClientAsync
object allows you to interact with specific media apps. These objects allow client apps to send control commands such as 'play', 'pause' and many others directly to a particular media app.
The Vega Media Controls client methods support multiple media sessions. This functionality is designed to accommodate advanced features like multiple video playback or picture-in-picture modes. To use this capability, all client methods accept an optional session ID parameter. When using these methods, if you omit the session ID, the provider app is expected to perform actions on the default session. However, if you specify a session ID, the action is targeted at that specific session. This flexibility allows for precise control over multiple concurrent media sessions within a single app.
let appLocator = MediaAppLocator.getInstance();
// Send play command to the default session of the currently focused app
let focusedMediaApp = appLocator.getMediaApp(0);
focusedMediaApp?.play().then(() => {
console.info("play action succeeded");
}).catch((error: Error) => {
console.error("play action failed. error: " + error.message);
});
// Send pause command to the seesion with identifier 2 of the currently focused app
let sessionId: IMediaSessionId = { id: 2};
focusedMediaApp?.pause(sessionId).then(() => {
console.info("pause action succeeded");
}).catch((error: Error) => {
console.error("pause action failed. error: " + error.message);
});
The Vega Media Controls API allows you to monitor changes in the available media provider apps. This functionality is implemented through the IGetMediaControlEndpointsResponseListener
interface. To use this feature, you need to create a class or struct that implements the IMediaControlEndpointLocatorListener
interface. This listener is automatically invoked whenever there's a change in the media provider app list, such as when a new provider app is installed or an existing one is uninstalled. When the callback is triggered, it doesn't just provide information about the specific changes that occurred. Instead, it sends the entire updated list of provider apps. This approach makes sure that the client app always has the most current and comprehensive view of the media provider landscape. By receiving the complete, up-to-date list, you can synchronize the state of your app with the current device configuration, eliminating the need to track individual changes and reducing the risk of inconsistencies in the media control ecosystem.
struct MediaControlEndpointLocatorListener
: ApmfBase<MediaControlEndpointLocatorListener, IMediaControlEndpointLocatorListener> {
using OnUpdatedEndpointsCallback =
std::function<void(ArrayView<Ptr<IMediaControlClientAsync> const>)>;
MediaControlEndpointLocatorListener(OnUpdatedEndpointsCallback cb)
: onUpdatedEndpointsCallback{cb}
{
}
void onSubscribed(View<ISubscription> const subscription)
{
// addListener was subscribed
}
void onSubscriptionError(View<IError> error)
{
// addListener failed
}
void onEndpointsChanged(ArrayView<Ptr<IMediaControlClientAsync> const> updatedEndpoints)
{
onUpdatedEndpointsCallback(updatedEndpoints);
}
OnUpdatedEndpointsCallback onUpdatedEndpointsCallback;
};
Ptr<IMediaControlEndpointLocatorListener> listener = Ptr<MediaControlEndpointLocatorListener>::Make(
[this](ArrayView<Ptr<IMediaControlClientAsync> const> endpoints) {
// change detected
discoveredEndpoints = endpoints;
});
endpointLocator->addChangeListener(listener);
The Vega Media Controls Client API provides an additional powerful feature through its addListener
method, which allows client apps to monitor state changes for specific media app endpoints. This state object, is communicated to the Vega Media Controls system through the updateMediaSessionStates
method by the media provider apps. This functionality is crucial for maintaining real-time awareness of media playback status and other relevant state information. When implementing this feature, you can use the addListener
method to register a callback function that will be invoked whenever the state of the specified media app changes. This could include updates to playback status, track information, or other relevant media session data. The addListener
method returns an instance ofISubscription
. This object provides a method to cancel the subscription when it's no longer needed. By calling the cancellation method on the ISubscription
object, you can efficiently manage resources and prevent unnecessary callback executions.
This listening capability enables client apps to react dynamically to changes in media app states without constant polling. It allows for the creation of responsive user interfaces that reflect the current state of media playback accurately and in real-time. Whether it's updating a display, adjusting controls, or synchronizing with other app components, the addListener
method makes sure that your app can stay perfectly in sync with the media playback state of the target endpoint.
// This method can be added to to the MediaAppLocator class we created previously
public fetchMediaAppState(index: number, sessionId: IMediaSessionId): Promise<MediaSessionState[]> {
const mediaApp = this.getMediaApp(index);
if (mediaApp) {
const sessionStates: Promise<MediaSessionState[]> = mediaApp.getSessionState(sessionId);
return sessionStates.then((states) => states);
} else {
return Promise.reject();
}
}
In addition to the event-driven updates through the addListener
methods available in IMediaControlListener
interface, the getSessionState
method offers an explicit way to request the current media session object at any time. This feature operates asynchronously to maintain system responsiveness. If you call the method with a specific media session ID, it returns the session state information for that specific session otherwise it returns information of all available sessions. This allows you to fine-tune your requests based on the app's needs, whether they require a broad overview of all active media sessions or detailed information about a specific one. By combining this explicit request capability with the event-driven updates, the Vega Media Controls system allows you to manage media control in various scenarios, from simple media players to complex media management apps.
struct GetMediaSessionStateResponseListener
: ApmfBase<GetMediaSessionStateResponseListener, IGetMediaSessionStateResponseListener> {
GetMediaSessionStateResponseListener() : future{promise.get_future()} {}
void onResponse(ArrayView<Ptr<MediaSessionState> const> states)
{
// handle response
}
void onError(View<IError> error)
{
// getMediaSessionState failed
}
};
Ptr<IGetMediaSessionStateResponseListener> listener = Ptr<GetMediaSessionStateResponseListener>::Make();
// Get current session state information for all the sessions
activeFocusEndpoint->getSessionState(listener, nullptr);
// Get session state information for session with identifier 1.
Ptr<MediaSessionId> sessionId = MakeBuilder<MediaSessionId>()->id(1)->build().QueryInterface<MediaSessionId>();
activeFocusEndpoint->getSessionState(listener, sessionId);
Vega Media Control attribute and features
You can specify the following attributes for Vega Media Controls.
Optional Attributes
The Vega Media Controls interface does not provide any optional attributes.
Non-Optional Attributes
The attributes that a Vega Media Controls cluster can receive requests for are:
- ActiveAudioTrack
- ActiveMediaId
- ActiveTextTrack
- ActiveVideo
- AvailableActions
- AvailableAudioTracks
- AvailableBackwardSkip
- AvailableForwardSkip
- AvailableRatings
- AvailableSpeeds
- AvailableTextTracks
- CurrentRating
- CurrentState
- CustomControlsState
- Duration
- MediaSessionStates
- PlaybackSpeed
- RepeatMode
- SampledPosition
- SeekRangeEnd
- SeekRangeStart
- ShuffleEnabled
- StartTime
Optional Commands
The commands that a Vega Media Controls cluster can choose to receive requests for are:
- Next
- Previous
- SkipBackward
- SkipForward
- StartOver
Non-Optional Commands
The commands that a Vega Media Controls cluster can receive requests for are:
- ActivateAudioTrack
- ActivateTextTrack
- DeactivateTextTrack
- EnableShuffle
- FastForward
- GetMediaSessionState
- GetMetadata
- Pause
- Play
- Rewind
- Seek
- SendCustomAction
- SetAudioVolume
- SetPlaybackSpeed
- SetRating
- SetRepeatMode
- Stop
- TogglePlayPause
Features
You can specify the following features for a Vega Media Controls:
- AdvancedSeek
- provided attributes
- Duration
- PlaybackSpeed
- SampledPosition
- SeekRangeEnd
- SeekRangeStart
- StartTime
- provided commands
- Seek
- provided attributes
- AudioTracks
- provided attributes
- ActiveAudioTrack
- AvailableAudioTracks
- provided commands
- ActivateAudioTrack
- provided attributes
- TextTracks
- provided attributes
- ActiveTextTrack
- AvailableTextTracks
- provided attributes
- VariableSpeed
- provided commands
- FastForward
- Rewind
- provided commands
Related topics
Last updated: Sep 30, 2025