Vega Media Controls (VMC) is a unified system for handling media input across all interaction methods. Instead of each app implementing custom remote button handling, voice command processing, and system UI synchronization, VMC provides a centralized service.
The service is designed to handle the following:
Routes Input Automatically: Remote button presses, Alexa voice commands (“Alexa, pause”), Bluetooth headphones and system UI interactions are captured by Fire TV OS and routed to your app through standardized callback methods.
Manages State Centrally: VMC automatically synchronizes your app’s playback state with the seek bar, playback indicators, and Alexa integration - no manual state management required.
Handles Focus Logic: VMC automatically determines which app should receive media commands based on focus, so developers don’t need to specify which app to control.
How VMC Integration Works
The integration happens through two main patterns:
W3C Media Integration (Automatic): For apps using the W3C Media API, VMC integration is automatic for basic playback controls (play, pause, seek). The W3C Media API internally creates the VMC server, publishes server states, and handles basic commands.
To opt-in to W3C Media integration:
- Enable VMC - Add the necessary manifest entries to your
manifest.tomlfile:
[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",
]
attribute_options = ["AudioAdvanceMuted"]
features = ["AdvancedSeek", "VariableSpeed", "AudioTracks", "TextTracks"]
- Update dependencies - Add to your
package.json:
{
"@amzn/kepler-media-controls": "~1.0.0",
"@amzn/react-native-w3cmedia": "~1.0.0"
}
- Get the component instance and set media control focus:
import { useComponentInstance, IComponentInstance } from '@amzn/react-native-kepler';
import { W3CMediaPlayer } from '@amzn/react-native-w3cmedia';
const componentInstance: IComponentInstance = useComponentInstance();
const player = new W3CMediaPlayer();
// This enables VMC for basic controls automatically
player.setMediaControlFocus(componentInstance);
Now your player instance will be enabled with VMC and will support basic functionality of pause/play and seek remote control and Alexa voice controls.
Override W3C Media Behavior: You can extend the default W3C Media handler for custom control logic:
import { KeplerMediaControlHandler } from "@amzn/react-native-w3cmedia";
import { IMediaSessionId } from '@amzn/kepler-media-controls';
class AppOverrideMediaControlHandler extends KeplerMediaControlHandler {
async handlePlay(mediaSessionId?: IMediaSessionId) {
if (shouldOverride) {
// Custom handling (e.g., disable during live content)
} else {
super.handlePlay(mediaSessionId);
}
}
}
// Pass custom handler as second parameter
player.setMediaControlFocus(componentInstance, new AppOverrideMediaControlHandler());
When properly integrated, user input flows like this:
User Input Fire TV OS VMC Media Player
| | | |
| | | |
| Press FF Button | | |
|------------------------>| | |
| | | |
| | Forward FF Event | |
| |-------------------->| |
| | | |
| | | handleSkipForward() |
| | |------------------------->|
| | | |
| | | |
| | | Seek Operation |
| | | |
| | | (Media Advances) |
| | | |
*This integration is only applicable for use cases where the player is integrated with an interactive component. If the app handle VMCs itself, it need not use W3C Media integration. Apps shouldn’t call player.setMediaControlFocus(componentInstance) or implement override functionality.
Fire TV Vega Media Controls Troubleshooting Guide
Overview
Purpose
This guide provides practical solutions for Fire TV developers experiencing Vega Media Controls (VMC) integration issues. It focuses on rapid diagnosis and resolution of common problems without requiring deep platform knowledge.
Target Audience
- Fire TV app developers
- QA engineers testing media functionality
Common Symptoms
- Unresponsive fast-forward/rewind buttons
- Ignored Alexa voice commands
- Out-of-sync seek bars
- Intermittent media control functionality
How to Use This Guide
- Start with Quick Triage - Determine if you have a platform or app-specific issue
- Identify your symptoms in the Common Issues section
- Run diagnostic tools to confirm the root cause
- Apply the recommended solution or escalate as needed
Quick Triage
Platform vs. App Issue Assessment
Before investigating app-specific code, evaluate these indicators:
| Question | Platform Issue Indicator |
|---|---|
| Does the problem only occur on specific Fire TV device models? | ✓ Yes |
| Do some input methods work while others don’t (e.g., D-pad works, media buttons don’t)? | ✓ Yes |
| Did the issue appear after a Fire TV OS update? | ✓ Yes |
Result: If you answered “yes” to 2+ questions, you likely have a platform issue requiring escalation to Amazon support.
Common Issues & Solutions
Issue #1: Fast Forward/Rewind Buttons Not Responding
Symptoms:
- FF/RW buttons on Fire TV remote don’t respond
- D-pad left/right still work for seeking
- Play/pause buttons work normally
- Issue affects multiple streaming apps
Root Cause: W3C Media + Vega Media Controls integration failure
Diagnostic Test:
// Test if your app is properly registered with VMC
const testKMCRegistration = async () => {
const locator = MediaControlComponentAsync. makeMediaControlEndpointLocator();
const endpoints = await locator.getMediaControlEndpoints();
console.log('Registered apps:', endpoints.length);
endpoints.forEach((endpoint, index) => {
console.log(`App ${index}:`, endpoint.displayName);
});
};
Solution:
- Verify W3C Media Player Configuration:
const player = new W3CMediaPlayer();
const componentInstance = useComponentInstance();
// Critical: This must be called BEFORE playing content
player.setMediaControlFocus(componentInstance);
- Check Manifest Configuration:
[[components.interactive]]
categories = ["com.amazon.category.kepler.media"]
Issue #2: Intermittent Media Control Functionality
Symptoms:
- Sometimes FF/RW works, sometimes doesn’t
- Controls work after app restart
- Issue appears random
Root Cause: VMC registration timing issues
Solution:
export const App = () => {
const componentInstance = useComponentInstance();
const [player, setPlayer] = useState(null);
useEffect(() => {
if (componentInstance) {
const mediaPlayer = new W3CMediaPlayer();
// Wait for component to be fully initialized
setTimeout(() => {
mediaPlayer.setMediaControlFocus(componentInstance);
setPlayer(mediaPlayer);
}, 100);
}
}, [componentInstance]);
// Only start playback after VMC registration
const startPlayback = () => {
if (player) {
player.play(videoUrl);
}
};
};
Issue #3: Seek Bar Synchronization Problems
Symptoms:
- FF/RW buttons work but seek bar doesn’t move
- Playback position gets out of sync
- User can’t see current position
Root Cause: Not updating VMC server state
Solution:
class MediaControlHandler implements IMediaControlHandlerAsync {
private mediaPlayerState: MediaPlayerState;
async handleSkipForward(sessionId?: IMediaSessionId): Promise<void> {
// Update your player position
const newPosition = this.player.currentTime + 10;
this.player.currentTime = newPosition;
// CRITICAL: Update VMC state
this.mediaPlayerState.currentPosition = {
seconds: Math.floor(newPosition),
nanoseconds: (newPosition % 1) * 1000000000
};
this.mediaPlayerState.playbackPosition = {
updatedAtTime: { seconds: Date.now() / 1000, nanoseconds: 0 },
position: this.mediaPlayerState.currentPosition
};
// Notify VMC of state change
const sessionState = this.mediaPlayerState.getServerState();
this.mediaControlServer.updateMediaSessionStates([sessionState]);
}
}
Diagnostic Tools
Tool #1: VMC Health Check
const runKMCDiagnostics = async () => {
console.log('=== KMC Diagnostics ===');
try {
// Test 1: Check if KMC is available
const locator = MediaControlComponentAsync.m[REDACTED:AWS_ACCESS_KEY]tLocator();
console.log('✅ KMC locator created');
// Test 2: Get registered endpoints
const endpoints = await locator.getMediaControlEndpoints();
console.log(`✅ Found ${endpoints.length} registered apps`);
// Test 3: Test command routing
if (endpoints.length > 0) {
const firstApp = endpoints[0];
await firstApp.play();
console.log('✅ Command routing works');
}
} catch (error) {
console.error('❌ KMC Diagnostics failed:', error.message);
console.log('Possible causes:');
console.log('- KMC not properly initialized');
console.log('- Missing manifest entries');
console.log('- Component not registered');
}
};
Tool #2: W3C Media Integration Test
const testW3CMediaIntegration = () => {
const componentInstance = useComponentInstance();
if (!componentInstance) {
console.error('❌ Component instance not available');
return;
}
const player = new W3CMediaPlayer();
try {
player.setMediaControlFocus(componentInstance);
console.log('✅ W3C Media + KMC integration successful');
// Verify registration after a delay
setTimeout(async () => {
const locator = MediaControlComponentAsync.m[REDACTED:AWS_ACCESS_KEY]tLocator();
const endpoints = await locator.getMediaControlEndpoints();
const ourApp = endpoints.find(e => e.componentId === componentInstance.id);
if (ourApp) {
console.log('✅ App successfully registered with KMC');
} else {
console.error('❌ App not found in KMC registry');
}
}, 1000);
} catch (error) {
console.error('❌ W3C Media integration failed:', error.message);
}
};
Common Pitfalls
Pitfall #1: Late Media Control Focus Registration
Wrong:
const startVideo = () => {
player.play(videoUrl);
player.setMediaControlFocus(componentInstance); // Too late!
};
Correct:
useEffect(() => {
if (componentInstance && player) {
player.setMediaControlFocus(componentInstance);
}
}, [componentInstance, player]);
Pitfall #2: Ignoring Session Management
Wrong:
async handlePlay(): Promise<void> {
this.player.play(); // Assumes single session
}
Correct:
async handlePlay(sessionId?: IMediaSessionId): Promise<void> {
const targetPlayer = sessionId ?
this.getPlayerForSession(sessionId.id) :
this.defaultPlayer;
targetPlayer.play();
}
Pitfall #3: Missing VMC State Updates
Wrong:
const seekTo = (position: number) => {
player.currentTime = position; // Only updates local player
};
Correct:
const seekTo = (position: number) => {
player.currentTime = position;
// Update VMC state
this.mediaPlayerState.currentPosition = {
seconds: Math.floor(position),
nanoseconds: (position % 1) * 1000000000
};
const sessionState = this.mediaPlayerState.getServerState();
this.mediaControlServer.updateMediaSessionStates([sessionState]);
};
Pitfall #4: Inadequate Error Handling
Wrong:
player.setMediaControlFocus(componentInstance); // No error handling
Correct:
try {
player.setMediaControlFocus(componentInstance);
console.log('KMC integration successful');
} catch (error) {
console.error('KMC integration failed:', error);
// Fallback: App still works, just no remote control
this.fallbackToManualControls();
}
Step-by-Step Troubleshooting
Step 1: Verify Basic Setup
Check Dependencies:
{
"@amzn/kepler-media-controls": "~1.0.0",
"@amzn/kepler-media-types": "~1.0.0",
"@amzn/react-native-w3cmedia": "~1.0.0"
}
Verify Manifest Configuration:
[components]
[[components.interactive]]
categories = ["com.amazon.category.kepler.media"]
[[extras]]
key = "interface.provider"
component-id = "com.yourapp.main"
Important: When using W3C Media integration, avoid implementing custom VMC handlers or calling
setMediaControlFocus()with custom handlers unless you need to override default behavior.
Step 2: Test VMC Registration
const verifyKMCSetup = async () => {
console.log('Verifying KMC setup...');
// Check component instance
const componentInstance = useComponentInstance();
if (!componentInstance) {
console.error('❌ No component instance - check your React setup');
return;
}
// Check W3C Media player
const player = new W3CMediaPlayer();
if (!player.setMediaControlFocus) {
console.error('❌ setMediaControlFocus not available - check dependencies');
return;
}
// Test registration
try {
player.setMediaControlFocus(componentInstance);
console.log('✅ KMC registration successful');
} catch (error) {
console.error('❌ KMC registration failed:', error);
}
};
Step 3: Test Media Controls
const testMediaControls = async () => {
console.log('Testing media controls...');
const locator = MediaControlComponentAsync.m[REDACTED:AWS_ACCESS_KEY]tLocator();
const endpoints = await locator.getMediaControlEndpoints();
if (endpoints.length === 0) {
console.error('❌ No apps registered with KMC');
return;
}
const ourApp = endpoints[0]; // Assuming your app is focused
// Test each control
const tests = [
{ name: 'Play', action: () => ourApp.play() },
{ name: 'Pause', action: () => ourApp.pause() },
{ name: 'Skip Forward', action: () => ourApp.skipForward() },
{ name: 'Skip Backward', action: () => ourApp.skipBackward() }
];
for (const test of tests) {
try {
await test.action();
console.log(`✅ ${test.name} works`);
} catch (error) {
console.error(`❌ ${test.name} failed:`, error.message);
}
}
};
Reference Implementation
Minimal Working Implementation
import { useComponentInstance } from '@amzn/react-native-kepler';
import { W3CMediaPlayer } from '@amzn/react-native-w3cmedia';
import { useEffect } from 'react';
export const App = () => {
const componentInstance = useComponentInstance();
useEffect(() => {
if (componentInstance) {
const player = new W3CMediaPlayer();
player.setMediaControlFocus(componentInstance);
}
}, [componentInstance]);
return <YourVideoPlayerUI />;
};
