开始使用Vega媒体控制
本指南为实现Vega媒体控制功能的应用提供了实现示例和指导。
Vega媒体控制API参考
有关此API的参考文档,请参阅amzn/kepler-media-controls。
Vega媒体控制的先决条件
以下先决条件仅适用于媒体提供方应用的开发阶段,不适用于使用客户端方法时。在开始使用这些方法之前,您必须更新应用清单,明确声明您打算使用Vega媒体控制API。修改清单条目时,请记住将com.amazondeveloper.media.sample替换为应用的实际程序包ID,如以下示例所示。此步骤可确保应用已正确配置,可以调用Vega媒体控制API。
清单配置控制您的应用可以接收哪些命令和属性。有三个类别:
- 核心命令和属性 — 始终无需任何清单配置即可交付给您的应用。其中包括基本的播放命令,例如
Play、Pause、Stop和TogglePlayPause,以及一些属性,例如CurrentState、AvailableActions和MediaSessionStates。有关完整列表,请参阅核心命令和核心属性。 - 可选命令 (
command_options) — 您可以通过在command_options数组中列出从而单独选择使用的命令。如果您未在此处列出命令,则您的应用将不会收到对该命令的请求。有关完整列表,请参阅可选命令。 - 功能受限命令和属性 (
features) — 每个功能都启用一组相关的命令和属性。需要向features数组添加功能才能接收其提供的命令和属性。如果没有该功能,即使您实现了处理程序,这些命令和属性也不会传送到您的应用。例如,如果您想接收FastForward和Rewind事件,则必须在features数组中纳入"VariableSpeed"。有关每项功能及其启用的内容的详细信息,请参阅功能。
[package]
title = "<应用标题>"
id = "com.amazondeveloper.media.sample"
[components]
[[components.interactive]]
id = "com.amazondeveloper.media.sample.main"
launch-type = "singleton"
# 类别“com.amazon.category.kepler.media”仅对主要组件是必需的,
# 该组件在清单的extras部分中使用“interface.provider”键进行标识。
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",
]
# 功能支持相关命令和属性组。
# 如果没有该功能,即使您实现了处理程序,
# 这些命令和属性也不会传送到您的应用。
#
# - AdvancedSeek: 搜索命令 + Duration、PlaybackSpeed、SampledPosition、
# SeekRangeEnd、SeekRangeStart、StartTime属性
# - VariableSpeed: FastForward和Rewind命令
# - AudioTracks: ActivateAudioTrack命令 + ActiveAudioTrack,
# AvailableAudioTracks属性
# - TextTracks: ActivateTextTrack、DeactivateTextTrack命令 +
# ActiveTextTrack、AvailableTextTracks属性
features = ["AdvancedSeek", "VariableSpeed", "AudioTracks", "TextTracks"]
安装和设置Vega媒体控制
要使用API,请将以下依赖项添加到package.json中。
"@amazon-devices/kepler-media-controls": "~1.0.0",
"@amazon-devices/kepler-media-types": "~1.0.0"
Vega媒体控制常见使用案例
适用于媒体内容提供方应用
要在提供方应用中管理媒体会话状态,请实现一个MediaPlayerState类。该类封装了媒体会话的所有必要属性,包括播放状态、曲目信息、时长、当前位置、播放速率和可用媒体操作。
MediaPlayerState类提供了一些方法,可以在媒体会话状态变化时更新这些属性。此外,还包括像getServerState()这样的方法,可以根据当前属性值构造和返回有效的MediaSessionState对象。通过在媒体会话发生变化时持续更新MediaPlayerState实例,可以确保MediaSessionState对象准确反映媒体播放器的当前状态,从而集中管理状态并简化同步操作。
对于支持同时播放多个视频的媒体提供方,请为每个会话维护一个单独的MediaPlayerState对象,确保正确更新每个实例的mSessionId。这种方法可以有效地管理应用中的多个媒体会话。
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;
}
}
要实现媒体控制功能,请创建一个实现IMediaControlHandlerAsync接口的类MediaControlHandlerAsync。该类包含handlePlay、handlePause等回调函数,以及其他用于管理媒体播放控制的函数。这些函数使用基于TypeScript Promise的方法来确保非阻塞执行。该类还实现了一个回调函数,使用之前创建的MediaPlayerState类异步返回媒体会话状态。
提供的代码示例仅实现了该方法的子集。完整的实现包括来自IMediaControlHandlerAsync接口的所有方法。大多数回调函数接收IMediaSessionId接口对象的实例,表示执行所请求操作的会话标识符。当媒体提供方应用支持多个会话时,这一点尤其重要。如果sessionId的值为空或未定义,则提供方应用默认对默认会话执行操作。这种方法可确保在应用内正确处理单个或多个媒体会话中的媒体控制请求。
MediaControlHandlerAsync类必须实现handleGetMetadataInfo方法,该方法是IMediaControlHandlerAsync接口中定义的方法之一。此方法负责返回给定会话中当前正在播放的媒体的元数据。
媒体元数据信息使用IMediaMetadata数据对象表示,该数据对象包括各种属性,例如媒体编号、作曲家、艺术家、播放源等。在提供的代码示例中,handleGetMetadataInfo的实现演示了如何创建和返回IMediaMetadata对象。为简单起见,该示例使用行内对象来表示元数据。但是,在生产环境中,更正确的方法是创建一个单独的类来实现IMediaMetadata数据对象。这个单独的类被设计为一个专用的数据对象,用于处理媒体的元数据,从而优化代码结构,提高代码可维护性,同时还可以更全面地描述媒体属性。
MediaControlHandlerAsync类构造函数在设置媒体控制功能方面起着至关重要的作用。它会创建一个MediaControlServerComponentAsync唯一实例,该实例是异步处理媒体控制操作的核心组件。
这个构造函数中的一个关键步骤是注册IComponentInstance对象。该对象作为参数传递给构造函数,然后注册到MediaControlServerComponentAsync实例。这个注册过程至关重要,因为它在媒体控制处理程序和应用中接收媒体控制回调的特定组件之间建立了链接。
在包含多个组件的应用中,这种注册的重要性尤为突出。通过将正确的IComponentInstance传递给MediaControlHandlerAsync构造函数,可以确保将媒体控制回调定向到对应的组件。这种有目标的方法可以防止混淆,并确保媒体控制仅影响应用的预期部分。
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',
},
],
});
}
}
在交互式应用程序中,您可以使用useComponentInstance方法获取IComponentInstance实例,然后在React Native的useEffect挂钩中创建MediaControlHandlerAsync实例。下面介绍如何实现这种方法:
- 使用
useComponentInstance方法获取一个IComponentInstance实例。 - 然后,在React组件中,使用
useEffect挂钩创建MediaControlHandlerAsync实例,并将该组件实例作为构造函数参数传入。此设置可确保在组件挂载后,MediaControlHandlerAsync对象立即使用正确的IComponentInstance对象进行初始化。
通过这种方法,媒体提供方应用在应用初始化并启动图形用户界面后,就能立即处理媒体控制播放命令,将媒体控制功能与组件的生命周期和IComponentInstance对象的可用性紧密结合起来。
import { useComponentInstance , IComponentInstance } from '@amazon-devices/react-native-kepler';
export const App = () => {
const componentInstance: IComponentInstance = useComponentInstance();
useEffect(() => {
const mediaControlHandler: IMediaControlHandlerAsync =
new MediaControlHandlerAsync(componentInstance);
}, []);
// 在此处为媒体提供方应用创建用户界面
};
适用于媒体控制客户端应用
Vega媒体控制API的客户端方法在媒体生态系统中起着至关重要的作用,其主要功能是向媒体提供方应用传输各种命令,如play、pause和其他受支持的操作。借助这些方法,您可以灵活地创建不同的应用,包括可以从单个界面控制多个媒体源的集中式媒体中心解决方案。
此外,您可以在测试中使用这些方法,验证媒体提供方应用是否正确接收和处理诸如handlePlay和handlePause之类的命令以及相关参数。进行此类测试可确保媒体提供方应用对用户输入做出恰当的响应,提供一个顺畅灵敏的媒体播放体验。
要使用Vega媒体控制客户端API,请创建一个名为MediaAppLocator的帮助程序类(单例类)。在这个类中,通过调用MediaControlComponentAsync.makeMediaControlEndpointLocator()来获取一个媒体应用定位器实例,它返回一个实现IMediaControlEndpointLocatorAsync接口的对象。
使用此实例获取使用Vega媒体控制API并安装在相关设备上的媒体提供方应用。终端节点定位器接口检索媒体控制提供方应用的排序列表,列表中的排序由应用的焦点决定。列表中的第一个应用通常是当前可见且获得焦点的应用。使用基于TypeScript Promise的方法异步获取终端节点,确保主线程在检索过程中不会被阻塞,从而保持应用能够快速响应。
Vega媒体控制API的客户端方法提供了一项附加功能,可增强其功能和响应能力。通过调用addChangeListener方法,客户端应用可以注册一个侦听器,以监控设备上可用媒体终端节点的变化。借助这种侦听功能,可以近乎实时地更新媒体应用列表。
安装新的媒体应用或卸载现有媒体应用时,设备的媒体终端节点格局可能会发生变化。通过实现此侦听器,客户端应用可以自动检测到此类更改,无需手动轮询或刷新。这可确保客户端应用始终拥有可用媒体应用的最新信息。
可以将侦听器设置为在发生更改时触发相应的操作,例如更新用户界面、刷新可用媒体控件列表,或根据新的一组可用媒体终端节点调整应用的行为。这种实时响应能力显著改善了用户体验,并使客户端应用与设备的媒体生态系统保持同步。
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[]) => {
// 我们必须手动释放这些资源
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();
}
}
}
Vega媒体控制客户端方法以IMediaControlClientAsync对象的排序列表形式返回终端节点。列表中的排序基于应用焦点,列表中的第一个终端节点代表当前获得焦点的应用。每个IMediaControlClientAsync对象都允许您与特定的媒体应用进行交互。这些对象允许客户端应用将诸如play、pause等控制命令直接发送给特定的媒体应用。
Vega媒体控制客户端方法支持多个媒体会话。此功能旨在支持一些高级功能,例如多个视频播放或画中画模式等。要使用此功能,所有客户端方法都接受一个可选的会话ID参数。使用这些方法时,如果您省略会话ID,则提供方应用应该对默认会话执行操作。但是,如果您指定会话ID,则应用将针对这个特定会话执行操作。这种灵活性允许在单个应用中精确控制多个并发媒体会话。
let appLocator = MediaAppLocator.getInstance();
// 向当前焦点应用的默认会话发送播放命令
let focusedMediaApp = appLocator.getMediaApp(0);
focusedMediaApp?.play().then(() => {
console.info("play action succeeded");
}).catch((error: Error) => {
console.error("play action failed. error: " + error.message);
});
// 向当前焦点应用中标识符为2的会话发送暂停命令
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);
});
Vega媒体控制API允许您监控可用媒体提供方应用的变化。此功能通过IGetMediaControlEndpointsResponseListener接口实现。要使用此功能,您需要创建一个实现IMediaControlEndpointLocatorListener接口的类或结构。每当媒体提供方应用列表发生变化时,例如安装新的提供方应用或卸载现有提供方应用,都会自动调用此侦听器。触发回调时,它不会仅仅提供发生的特定更改的信息,而是发送更新后的提供方应用完整列表。这种方法可确保客户端应用始终拥有最新、最全面的媒体提供方格局视图。通过接收最新的完整列表,您可以将应用状态与当前设备配置同步,从而无需跟踪单个更改,降低媒体控制生态系统中出现不一致的风险。
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已订阅
}
void onSubscriptionError(View<IError> error)
{
// addListener失败
}
void onEndpointsChanged(ArrayView<Ptr<IMediaControlClientAsync> const> updatedEndpoints)
{
onUpdatedEndpointsCallback(updatedEndpoints);
}
OnUpdatedEndpointsCallback onUpdatedEndpointsCallback;
};
Ptr<IMediaControlEndpointLocatorListener> listener = Ptr<MediaControlEndpointLocatorListener>::Make(
[this](ArrayView<Ptr<IMediaControlClientAsync> const> endpoints) {
// 检测到变化
discoveredEndpoints = endpoints;
});
endpointLocator->addChangeListener(listener);
通过addListener方法,Vega媒体控制客户端API提供了一项额外的强大功能,允许客户端应用监控特定媒体应用终端节点的状态变化。该状态对象由媒体提供方应用通过updateMediaSessionStates方法传送到Vega媒体控制系统。
这一功能对于实时了解媒体播放状态和其他相关状态信息至关重要。实现此功能时,您可以使用addListener方法注册一个回调函数,只要指定媒体应用的状态发生变化,就会调用该回调函数。这些更新可能包括播放状态、曲目信息或其他相关媒体会话数据的更改。
addListener方法返回一个ISubscription实例。此对象提供了一个方法,让您在不再需要订阅时取消订阅。通过调用ISubscription对象上的取消方法,您可以高效地管理资源,避免不必要的回调执行。
借助这种监听功能,客户端应用能够对媒体应用状态的变化做出动态反应,而无需持续轮询。它让您可以创建响应式用户界面,准确、实时地反映媒体播放的当前状态。无论是更新显示、调整控制还是与其他应用组件同步,addListener方法都可确保您的应用能够与目标终端节点的媒体播放状态完美保持同步。
// 这个方法可以添加到我们之前创建的MediaAppLocator类中
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();
}
}
除了通过IMediaControlListener接口中提供的addListener方法实现事件驱动的更新外,getSessionState方法还提供了一种明确的方法,可以随时请求当前媒体会话对象的状态。此功能异步运行,避免影响系统响应能力。如果您使用特定的媒体会话ID调用该方法,它将返回该特定会话的会话状态信息。否则,它会返回所有可用会话的信息。这使您可以根据应用需求对请求进行微调,无论是需要全部活跃媒体会话的概览,还是需要特定会话的详细信息。
通过将这种明确的请求功能与事件驱动的更新相结合,Vega媒体控制系统让您在各种场景中方便地管理媒体控制,从简单的媒体播放器应用到复杂的媒体管理应用。
struct GetMediaSessionStateResponseListener
: ApmfBase<GetMediaSessionStateResponseListener, IGetMediaSessionStateResponseListener> {
GetMediaSessionStateResponseListener() : future{promise.get_future()} {}
void onResponse(ArrayView<Ptr<MediaSessionState> const> states)
{
// 处理响应
}
void onError(View<IError> error)
{
// getMediaSessionState失败
}
};
Ptr<IGetMediaSessionStateResponseListener> listener = Ptr<GetMediaSessionStateResponseListener>::Make();
// 获取所有会话的当前会话状态信息
activeFocusEndpoint->getSessionState(listener, nullptr);
// 获取标识符为1的会话的会话状态信息。
Ptr<MediaSessionId> sessionId = MakeBuilder<MediaSessionId>()->id(1)->build().QueryInterface<MediaSessionId>();
activeFocusEndpoint->getSessionState(listener, sessionId);
相关主题
Last updated: 2026年3月31日

