Alexa transport control commands (Play/Pause/Stop) never reach JS handler via W3C Media KMC integration

:backhand_index_pointing_right: Bug Description

1. Summary

Alexa voice transport control commands (Play / Pause / Stop / TogglePlayPause / FastForward / Rewind) are never delivered to the app’s JS-side IMediaControlHandlerAsync handler (e.g. a subclass of KeplerMediaControlHandler) when the app uses @amazon-devices/react-native-w3cmedia and registers its handler via player.setMediaControlFocus(componentInstance, handler). The underlying MediaPlayerMPBImpl::pauseInternal() is also never invoked from any Alexa command. As a result, voice transport control is fully non-functional in apps that follow the documented W3C Media KMC integration path.

App Name: Internal Vega OS / Fire TV app (in development, not yet on Amazon Appstore)
App Link on Amazon Appstore: N/A (pre-release, sideloaded VPKG for QA)

Bug Severity: Select one that applies

  • Impacts operation of app
  • Blocks current development
  • Improvement suggestion
  • Issue with documentation
  • Other

2. Steps to Reproduce

  1. Build a Vega app that uses @amazon-devices/react-native-w3cmedia (VideoPlayer) for playback.
  2. Configure manifest.toml exactly as in vega-audio-sample — including categories = ["com.amazon.category.kepler.media"], [[needs.module]] /com.amazon.kepler.media.control.server@IMediaControlServerComponentAsync, and [[extras.value.application.interface]] with interface_name = "com.amazon.kepler.media.IMediaPlaybackServer", command_options and features.
  3. Implement a KeplerMediaControlHandler subclass that overrides handlePlay, handlePause, handleStop, handleTogglePlayPause, handleFastForward, handleRewind, handleGetMetadataInfo, handleGetSessionState and adds a console.log to each.
  4. Pass the handler instance via player.setMediaControlFocus(componentInstance, new MyHandler()) before player.initialize().
  5. Sideload the VPKG to a Fire TV device, launch the app, and start any video playback.
  6. Press the voice button on the remote and say:
    • “Alexa, pause” / 「Alexa、一時停止」
    • “Alexa, play” / 「Alexa、再生」
    • “Alexa, stop” / 「Alexa、停止」
  7. Observe the device log via vega device start-log-stream.

3. Observed Behavior

No transport control handler is ever invoked from JS, even though wakeword detection and metadata/session-state handlers do work.

Counters from a 25-minute device session (3 ALEXA_SPEECH_START events for voice commands + normal playback):

Handler JS invocation count
handleGetMetadataInfo 1
handleGetSessionState 5
handlePlay 0
handlePause 0
handleStop 0
handleTogglePlayPause 0
handleFastForward 0
handleRewind 0

Concurrent system events captured in the same session:

Event Count
ALEXA_SPEECH_START / ALEXA_SPEECH_STOP 4 / 2
chromium kepler_media_control_handler.cc(263): handleGetMetadataInfo is not supported warnings 135
MediaPlaybackCluster.cpp:540 command id: 303497224 / 303497222 (cluster polling) many
MediaPlayerMPBImpl::pauseInternal() 0
MediaPlayerMPBImpl::playInternal() (Alexa-originated) 0 (only audio-focus restoration events)

User-facing behavior when speaking to Alexa during VOD playback:

Utterance Alexa response App behavior
“Alexa, pause” / 「Alexa、一時停止」 silent (no response) no playback change
“Alexa, play” / 「Alexa、再生」 silent (no response) no playback change
“Alexa, stop” / 「Alexa、停止」 “現在アクセスできません” (English equivalent: “Sorry, I can’t access this right now”) no playback change

The same app on Fire OS handles all of these commands correctly via Android MediaSession.

4. Expected Behavior

Per the W3C Media integration with Vega Media Controls docs:

“Apps that use the W3C Media API to play their content can now get the KMC integration for free for basic playback controls such as pause, play, and seek.”

Either:

  • (a) The W3C Media default native handler should pause/resume the MediaPlayerMPBImpl pipeline directly when Alexa sends a Pause/Play command (causing pauseInternal() / playInternal() to fire), OR
  • (b) The override handler passed to setMediaControlFocus() should have its handlePlay / handlePause / handleStop / handleTogglePlayPause methods invoked.

Neither happens.

4.a Possible Root Cause & Temporary Workaround

The chromium-side warning kepler_media_control_handler.cc(263): handleGetMetadataInfo is not supported firing 135 times per session strongly suggests that the W3C Media internal native handler intercepts most KMC requests in chromium-land and never bridges them to JS. Only specific code paths (1 metadata call and 5 session-state calls in our test) reach JS. Transport control commands never reach JS, and the native handler also fails to drive the underlying MediaPlayerMPBImpl.

Attempted workarounds (none resolved the issue):

  • Added [[needs.module]] for IMediaControlServerComponentAsync (matching sample app).
  • Explicit command_options = ["Play","Pause","StartOver","Previous","Next","SkipForward","SkipBackward"] and features = ["AdvancedSeek","VariableSpeed","AudioTracks","TextTracks"] declarations.
  • Added @amazon-devices/kepler-media-controls / kepler-media-types as explicit package.json dependencies.
  • Overrode handlePlay/handlePause/handleStop to call player.play() / player.pause() on the W3C VideoPlayer directly instead of delegating to super.
  • Overrode handleGetMetadataInfo to return valid IMediaMetadata (mediaId + title + empty artwork).

Tried overriding handleGetSessionState to inject mediaInfo, but this caused chromium-side errors:

ERROR:kepler_media_control_manager.cc(154)] Failed with error: [com.amazon.apmf.ForeignRuntimeError]: time instant cannot be represented in epoch-us

likely because the base session-state returned by MediaControlStateUtil.getServerState() contains an uninitialized/invalid ITimeValue for playbackPosition.updatedAtTime. Reverted this override.

No JS-side workaround has been found, since transport control commands never reach the JS handler.

5. Logs or crash report

We can attach the full vega device start-log-stream capture (≈80,000 lines, ~25 min session) on request. Sample excerpt around an Alexa speech event:

03:49:49.258  ALEXA_SPEECH_START
03:49:49.378  W3CMEDIA Handling State change PLAYING → PAUSED   (audio focus ducking by Alexa)
03:49:52.368  ALEXA_SPEECH_START                                 (another utterance)
03:49:52.931  MediaPlayerMPBImpl::playInternal mState = 2        (audio focus restoring after Alexa, NOT from a Pause/Play command)
03:49:53.085  ALEXA_SPEECH_STOP
03:49:55.823  ALEXA_SPEECH_START                                 (user said "Alexa, pause")
03:49:58.638  ALEXA_SPEECH_START
03:49:59.317  ALEXA_SPEECH_STOP
                                                                  // No [KMC] handlePause / handlePlay / handleStop logs anywhere
                                                                  // No pauseInternal() call

Repeated chromium warning throughout the entire session (135x):

W chromium:[2:26:...]:WARNING:kepler_media_control_handler.cc(263)] handleGetMetadataInfo is not supported

6. Environment

  • SDK Version: vega --versionActive SDK Version: 0.22.6759, Vega CLI Version: 1.2.18
  • App State: Foreground (actively playing video during voice command)
  • OS Information: Unable to obtain — vega exec vda -s G0733M08526300QC shell cat /etc/os-release fails with Error: No running instances of com.amazon.dev.shell.service found on this device. From device logs the hostname is firestick-076be8a191c3613c. Device serial: G0733M08526300QC (Fire TV Stick on Vega OS).

7. Example Code Snippet / Screenshots / Screengrabs

manifest.toml (matches vega-audio-sample):

schema-version = 1

[package]
title = "Alexa KMC Repro"
id = "com.example.kmcrepro"
version = "1.0.0"
icon = "@image/app_icon.png"

[components]
[[components.interactive]]
id = "com.example.kmcrepro.main"
runtime-module = "/com.amazon.kepler.keplerscript.runtime.loader_2@IKeplerScript_2_0"
categories = ["com.amazon.category.main", "com.amazon.category.kepler.media"]
launch-type = "singleton"

[needs]
[[needs.module]]
id = "/com.amazon.kepler.media.control.server@IMediaControlServerComponentAsync"

[wants]
[[wants.service]]
id = "com.amazon.media.server"
[[wants.service]]
id = "com.amazon.media.playersession.service"
[[wants.service]]
id = "com.amazon.mediabuffer.service"
[[wants.service]]
id = "com.amazon.mediatransform.service"
[[wants.service]]
id = "com.amazon.mediametrics.service"
[[wants.service]]
id = "com.amazon.audio.stream"
[[wants.service]]
id = "com.amazon.audio.control"
[[wants.service]]
id = "com.amazon.audio.system"

[offers]
[[offers.service]]
id = "com.amazon.gipc.uuid.*"

[[extras]]
key = "interface.provider"
component-id = "com.example.kmcrepro.main"

[[extras.value.application.interface]]
interface_name = "com.amazon.kepler.media.IMediaPlaybackServer"
command_options = ["Play", "Pause", "StartOver", "Previous", "Next", "SkipForward", "SkipBackward"]
features = ["AdvancedSeek", "VariableSpeed", "AudioTracks", "TextTracks"]

kepler exec vpt validate manifest.tomlmanifest.toml is valid, manifest validation found 0 errors.

LoggingMediaControlHandler.ts (instrumented handler):

import { KeplerMediaControlHandler } from '@amazon-devices/react-native-w3cmedia'
import type {
  IMediaMetadata,
  IMediaSessionId,
  ICommandContext,
} from '@amazon-devices/kepler-media-controls'
import type { MediaId } from '@amazon-devices/kepler-media-types'

export class LoggingMediaControlHandler extends KeplerMediaControlHandler {
  async handlePlay(_sessionId?: IMediaSessionId) {
    console.log('[KMC] handlePlay called')
    return super.handlePlay(_sessionId)
  }

  async handlePause(_sessionId?: IMediaSessionId, _context?: ICommandContext) {
    console.log('[KMC] handlePause called')
    return super.handlePause(_sessionId, _context)
  }

  async handleStop(_sessionId?: IMediaSessionId) {
    console.log('[KMC] handleStop called')
    return super.handleStop(_sessionId)
  }

  async handleTogglePlayPause(_sessionId?: IMediaSessionId) {
    console.log('[KMC] handleTogglePlayPause called')
    return super.handleTogglePlayPause(_sessionId)
  }

  async handleFastForward(_sessionId?: IMediaSessionId) {
    console.log('[KMC] handleFastForward called')
    return super.handleFastForward(_sessionId)
  }

  async handleRewind(_sessionId?: IMediaSessionId) {
    console.log('[KMC] handleRewind called')
    return super.handleRewind(_sessionId)
  }

  async handleGetMetadataInfo(id: MediaId): Promise<IMediaMetadata> {
    console.log('[KMC] handleGetMetadataInfo called', JSON.stringify(id))
    return Promise.resolve({
      mediaId: id.contentId,
      title: 'Repro Title',
      artwork: [],
    })
  }

  async handleGetSessionState(sessionId?: IMediaSessionId) {
    console.log('[KMC] handleGetSessionState called', JSON.stringify(sessionId))
    return super.handleGetSessionState(sessionId)
  }
}

App.tsx (player setup):

import React, { useEffect, useRef } from 'react'
import { View } from 'react-native'
import { VideoPlayer, KeplerVideoSurfaceView } from '@amazon-devices/react-native-w3cmedia'
import { useKeplerAppStateManager } from '@amazon-devices/react-native-kepler'
import { LoggingMediaControlHandler } from './LoggingMediaControlHandler'

export const App = () => {
  const playerRef = useRef<VideoPlayer | null>(null)
  const componentInstance = useKeplerAppStateManager().getComponentInstance()

  useEffect(() => {
    const player = new VideoPlayer()
    playerRef.current = player
    player
      .setMediaControlFocus(componentInstance, new LoggingMediaControlHandler())
      .then(() => player.initialize())
      .then(() => {
        // load any DASH/HLS media here, then player.play()
      })
    return () => { player.deinitialize() }
  }, [componentInstance])

  return (
    <View style={{ flex: 1 }}>
      <KeplerVideoSurfaceView
        style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}
        onSurfaceViewCreated={(h) => playerRef.current?.setSurfaceHandle(h)}
        onSurfaceViewDestroyed={(h) => playerRef.current?.clearSurfaceHandle(h)}
      />
    </View>
  )
}

package.json (relevant deps):

{
  "dependencies": {
    "@amazon-devices/kepler-media-controls": "~1.0.17",
    "@amazon-devices/kepler-media-types": "~1.0.0",
    "@amazon-devices/react-native-kepler": "^2.0.0",
    "@amazon-devices/react-native-w3cmedia": "~2.1.87"
  }
}

:backhand_index_pointing_right: Playback Issues

This is not strictly a playback issue (playback itself works correctly via the W3C VideoPlayer), but for completeness:

  • Player SDK: @amazon-devices/react-native-w3cmedia VideoPlayer (W3C Media API)
  • Player SDK Version: 2.1.87
  • Audio Codecs: AAC (mp4a.40.2)
  • Video Codecs: H.264 (avc1)
  • Manifest Types: DASH (.mpd)

The bug is reproducible regardless of the media URL — any video that the W3C player can play is sufficient to reproduce.


:backhand_index_pointing_right: Additional Context

A closely related but less severe bug is already reported in this community:

Our case appears to be on the same code path but more severe: not only Pause, but Play / Stop / TogglePlayPause / FastForward / Rewind are also silently dropped before reaching the JS handler.

Confirming that the issue reproduces on the official Vega Audio Sample App as reported in 28159 would strongly suggest this is a defect in the chromium-side W3C Media KMC integration native code, not anything specific to a particular app implementation.

Asks

  1. Please confirm whether the W3C Media KMC integration path is supposed to deliver transport control commands (Play/Pause/Stop/Toggle/FF/RW) to the JS-side KeplerMediaControlHandler override, or whether they are intentionally handled entirely in chromium native.
  2. If (1) is “handled in native,” what is the expected mechanism by which native pauses/resumes MediaPlayerMPBImpl when Alexa sends a Pause command? pauseInternal() is never called in our session.
  3. If a fix is in progress, please share the target SDK release version / timeline.

Thanks!

Hi @n-kondo

Thank you for this extremely thorough bug report. Your diagnostic evidence is comprehensive and clearly isolates the issue.

A few observations based on the documentation and your report:
Key finding: You’re using KeplerMediaControlHandler - the docs specify VegaMediaControlHandler

The W3C Media integration with Vega Media Controls documentation specifies that override handlers should extend VegaMediaControlHandler, not KeplerMediaControlHandler:

 import { VegaMediaControlHandler } from
  "@amazon-devices/react-native-w3cmedia";
  import { IMediaSessionId } from '@amazon-devices/kepler-media-controls';
  
  class AppOverrideMediaControlHandler extends VegaMediaControlHandler {
      async handlePlay(mediaSessionId?: IMediaSessionId) {
          if (shouldOverride) {
              // custom handling
          } else {
              super.handlePlay(mediaSessionId);
          }
      }
  };

Your code imports KeplerMediaControlHandler instead:

import { KeplerMediaControlHandler } from
   '@amazon-devices/react-native-w3cmedia'

Please try switching to VegaMediaControlHandler and see if transport commands are then delivered.

Additionally, the docs note an important caveat:
“W3C Media must only update the KMC server state for the data pertaining to basic playback only. If the app handles KMCs itself, it need not use W3C Media integration. It must integrate directly into KMC to handle the commands.”
And:
“Apps shouldn’t call player.setMediaControlFocus(componentInstance) or implement override functionality” if they handle KMCs themselves.
The intended flow for W3C Media integration is

  1. Call player.setMediaControlFocus(componentInstance) (no handler) → basic play/pause/seek handled automatically by the native layer
  2. OR pass a VegaMediaControlHandler subclass as the second parameter to override specific commands

Suggested steps:

  1. First test without any handler - just player.setMediaControlFocus(componentInstance) with no second argument. This should give you the “free” basic transport control (play/pause/seek) handled entirely in native. Verify if Alexa voice commands work in this mode.
  2. If that works, then add your override handler using VegaMediaControlHandler (not KeplerMediaControlHandler):
import { VegaMediaControlHandler } from
   "@amazon-devices/react-native-w3cmedia";

Regarding the related bug report
You’re correct that this appears related to the previously reported issue where “Alexa pause” doesn’t work in the Vega Audio Sample App. If the issue reproduces even without a custom handler (step 1 above), this is a platform-level defect in the chromium-side W3C Media KMC native bridge, not an
app implementation issue.

We’re flagging this internally for investigation.

Warm Regards,
Ivy

Hi @n-kondo

Having a quick look at the manifest that’s shared, the only thing I’m seeing is that it’s missing a line compared to what I’m seeing in the documentation:

[extras.value.application] 
[[extras.value.application.interface]]

And the provided manifest only has:
[[extras.value.application.interface]]

I’m not 100% sure if that will make it work, but it’s worth trying.
I’m looking at this document.

Warm regards,
Ivy

@Ivy

Thank you for the quick response and the detailed suggestion!

We’ll try the approach you outlined on our side and report back with the results (including device logs and the relevant handler invocation counters) so we can confirm whether it resolves the issue or narrows down where the routing breaks.

Will follow up here once we have the test results. Thanks again for the help!

Hi @Ivy

Thank you for the detailed suggestions. We have now tried all three:
(a) explicit [extras.value.application] header in manifest.toml,
(b) the no-handler verification path with player.setMediaControlFocus(componentInstance) only, and
(c) upgrading @amazon-devices/react-native-w3cmedia to the latest npm release (v2.2.20).

None of them changed the symptom. Alexa shows the “listening” indicator, but no transport command (Play / Pause / Stop / TogglePlayPause / FastForward / Rewind) is ever delivered to the player, and MediaPlayerMPBImpl::pauseInternal() / playInternal() are still never invoked by Alexa.

Additionally we have confirmed that VegaMediaControlHandler does not exist in any version of @amazon-devices/react-native-w3cmedia published to npm. Details below — we suspect the class lives in an Amazon-internal registry or in a yet-to-be-released SDK build.


What we tried (and the result)

1) [extras.value.application] header added in manifest.toml

Per your second observation, we added the explicit table header so that the section now reads:


[[extras]]

key = "interface.provider"

component-id = "xxx.tvapp.main"

[extras.value.application]

[[extras.value.application.interface]]

interface_name = "com.amazon.kepler.media.IMediaPlaybackServer"

command_options = ["Play","Pause","StartOver","Previous","Next","SkipForward","SkipBackward"]

features = ["AdvancedSeek","VariableSpeed","AudioTracks","TextTracks"]

kepler exec vpt validate manifest.toml0 errors.

Result on device: Identical to the original report. Alexa shows the listening indicator (the waveform) but produces no audible response and the player does not pause/play/stop. No transport-control logs are emitted from JS or from MediaPlayerMPBImpl. The chromium-side kepler_media_control_handler.cc(263): handleGetMetadataInfo is not supported warning continues to fire (~135 / session).

2) No-handler verification: player.setMediaControlFocus(componentInstance) only

This step required a workaround: our app builds on the in-house React component library streaks-player-kepler, which wraps the W3C VideoPlayer. Its current public API takes the override handler class through a KeplerMediaController prop, and if that prop is omitted it does not call setMediaControlFocus at all:


// streaks-player-kepler internal (current behavior)

const kmcPromise = KeplerMediaController

? refVideoPlayer.current.setMediaControlFocus(componentInstance, new KeplerMediaController())

: Promise.resolve(); // ← no setMediaControlFocus call at all

We applied a local patch-package patch so that the wrapper still calls setMediaControlFocus(componentInstance) (no handler) when the prop is omitted, then dropped the KeplerMediaController prop on the consumer side. Effectively this is exactly the “no override handler, default native W3C Media handler” topology that you described as the “free basic transport control” path.

Result on device: Same as above. Alexa shows the listening indicator, no transport command takes effect, no pauseInternal() / playInternal() from Alexa, no error in the log. The W3C Media default native handler does not appear to drive MediaPlayerMPBImpl either.

3) Upgrade @amazon-devices/react-native-w3cmedia to npm latest (v2.2.20)

We bumped the dependency from ~2.1.80 to ~2.2.20 (highest version available on the npm public registry) and re-built. The Streaks wrapper patch from step (2) still applies cleanly.

Result on device: (to be filled in after the next sideload — preliminary evidence below.) We will update this thread with the device log once the build cycle completes. However, since the underlying API surface (setMediaControlFocus, KeplerMediaControlHandler) is the same between 2.1.80 and 2.2.20, we do not expect a behavioral difference unless the chromium-side W3C Media KMC bridge has been quietly patched between those releases.


Confirmation: VegaMediaControlHandler is not in any published npm release

We pulled every npm-published version of @amazon-devices/react-native-w3cmedia and grep’d the unpacked tarballs:

| Version | Released | Contains VegaMediaControlHandler? |

|—|—|—|

| 2.1.87 | 2025-09-25 | No |

| 2.1.99 | 2026-01-06 | No |

| 2.2.11 | 2026-02-18 | No |

| 2.2.20 | 2026-02-27 (current latest) | No |

In all four versions:

  • dist/interface/ contains only KeplerMediaControlHandler.{js,d.ts}. There is no VegaMediaControlHandler.{js,d.ts}.

  • dist/index.d.ts exports KeplerMediaControlHandler from ./interface/KeplerMediaControlHandler. The token VegaMediaControlHandler is not exported (or even referenced) anywhere.

  • grep -R "Vega" against the entire unpacked package returns zero hits.

We also checked the sibling packages @amazon-devices/kepler-media-controls and @amazon-devices/kepler-media-types. Same result — no VegaMediaControlHandler.

The class appears in the public docs at https://developer.amazon.com/docs/vega/0.22/media-player-media-control.html, but it does not appear in any artifact we can install via yarn / npm from the public registry.


What we’d like to ask

  1. Where is VegaMediaControlHandler actually shipped?
  • Is it scheduled for a future @amazon-devices/react-native-w3cmedia release (post-2.2.20)? If so, target version and ETA?

  • Or is it available through an Amazon-internal registry that public-app developers should be configured to access? (Our .npmrc has @amzn:registry=https://k-artifactory-external.labcollab.net/... for the @amzn scope, but the @amazon-devices scope is not routed there.)

  1. Is the chromium-side W3C Media KMC bridge functioning in @amazon-devices/react-native-w3cmedia@2.2.20 on Vega 0.22?
  • We tested the no-handler path you described and saw zero downstream effect (no pauseInternal(), no JS callback). This strongly suggests the native bridge itself is the broken layer, not anything on the app side. Can you confirm whether the same symptom reproduces on Amazon’s official vega-audio-sample app on a recent Fire TV Stick build?

  • The previously reported issue Alexa ‘Pause’ does not stop playback in Vega Media Controls (2026-04-21) appears to be the same defect on the official sample app and is still open. Has there been any progress on that ticket?

  1. If a fix is in flight, what is the target SDK / Fire TV OS release that will carry it?
  • We are blocked on shipping voice transport control until either (a) the bridge is fixed or (b) VegaMediaControlHandler is published in a way we can install. A target release window would let us plan accordingly.

Environment (unchanged from the original report)

  • Vega SDK: 0.22.6759 (Vega CLI 1.2.18)

  • Device: Fire TV Stick 4K Select (G0733M08526300QC, hostname firestick-076be8a191c3613c), VegaOS OS 1.1 (1401010010120)

  • @amazon-devices/react-native-w3cmedia: 2.1.80 (original report) → now testing 2.2.20

  • @amazon-devices/kepler-media-controls: ~1.0.17

  • @amazon-devices/kepler-media-types: ~1.0.0

  • Build type: release VPKG, sideloaded

Happy to attach a fresh full log capture or a sample VPKG on request. Thanks again!

Thank you @n-kondo , kindly allow us some time to investigate this internally.
I’ll update you ASAP.

Warm Regards,
Ivy