华为视频接入播控中心和投播能力概述
华为视频在进入影片详情页播放时,支持在控制中心查看当前播放的视频信息,并进行快进、快退、拖动进度、播放暂停、下一集、调节音量等操作,方便用户通过控制中心来操作当前播放的视频。
当用户希望通过大屏播放当前华为视频的影片时,可以在华为视频或播控中心内进行投播,将影片投播到同一网络下的华为智慧屏等大屏设备进行播放,且通过播控中心来方便地进行播放暂停、快进快退、下一集等操作。
华为视频投播功能需要使用播控中心的能力完成,所以在接入投屏之前,华为视频需要先接入播控中心。
华为视频接入播控中心
华为视频接入播控中心介绍
- 媒体会话(AVSession):本地播放时用于更新媒体资源的信息和响应系统播控中心。在投播时,AVSession作为在本地播放和投播之间切换的“枢纽”接口,把二者联系起来。通过AVSession可以设置和查询应用投播能力,并创建投播控制器。
- 媒体会话控制器(AVSessionController):一般由播控中心提供。如果是应用内的控制器,可用于控制应用的后台播放。
华为视频接入播控中心的交互流程如图所示。
华为视频同步播控中心
- 播放内容信息上报播控中心
这部分功能负责实现在应用播放的时候,通知播控中心当前播放的影片信息。
- 应用冷启动之后,需要调用createAVSession创建会话。应用生命周期结束后会话自动销毁,不需要调用destroy。
说明
下文中代码示例,可能包含重复的函数和导包引入,因此后续代码示例不再重复展示。
- 导入相关模块
// MainAbility.ets import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { AvSessionManager } from '../avsession/AvSessionManager'; import router from '@system.router';
// AvSessionManager.ts import avSession from '@ohos.multimedia.avsession'; import hilog from '@ohos.hilog'; import type { BusinessError } from '@ohos.base'; import type common from '@ohos.app.ability.common'; import WantAgent from '@ohos.app.ability.wantAgent';
- 调用createAVSession创建会话相关示例代码如下:
// MainAbility.ets export default class MainAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { AvSessionManager.getInstance().init(this.context); } }
// AvSessionManager.ts const TAG = 'AvSessionManager'; /** * 对接播控中心管理器 */ export class AvSessionManager { private static readonly instance: AvSessionManager = new AvSessionManager(); private session: avSession.AVSession = null; static getInstance(): AvSessionManager { return this.instance; } init(abilityContext: common.Context): void { avSession.createAVSession(abilityContext, 'himovie', 'video').then(session => { this.session = session; // 创建完成之后,激活会话。 this.session.activate(); hilog.info(0x06666, TAG, 'createAVSession success'); }).catch((error: BusinessError) => { hilog.error(0x06666, TAG, `createAVSession or activate failed, code: ${error?.code}`); }); } }
- 导入相关模块
- 根据当前播放的Volume信息,拼接填写setAVMetadata。
// 业务Index.ets @Entry @Component struct Index { private avsessionMetaData: avSession.AVMetadata | null = null; aboutToAppear(): void { this.setAVSessionMetaData(); } setAVSessionMetaData() { this.avsessionMetaData = { // 影片的id assetId: 'test vod id', subtitle: 'vod subtitle', artist: 'artist name', title: 'vod title', mediaImage: 'media image url', // 仅支持投屏到Cast+ Stream的设备 filter: avSession.ProtocolType.TYPE_CAST_PLUS_STREAM, // 快进快退时间 skipIntervals: avSession?.SkipIntervals?.SECONDS_30 }; AvSessionManager.getInstance().setMetaData(this.avsessionMetaData); } build() { // ... } }
// AvSessionManager.ts export class AvSessionManager { private static readonly instance: AvSessionManager = new AvSessionManager(); private session: avSession.AVSession = null; static getInstance(): AvSessionManager { return this.instance; } /** * 设置metaData并初始化状态 * * @param metadata 影片元数据 */ setMetaData(metadata: avSession.AVMetadata): void { if (this.session) { hilog.info(0x06666, TAG, `setMetaData avMetadata: ${JSON.stringify(metadata)}`); this.session?.setAVMetadata(metadata) .then(() => { hilog.info(0x06666, TAG, `setMetaData success.`); }) .catch((error: BusinessError) => { hilog.error(0x06666, TAG, `setMetaData failed, code: ${error.code}`); }); } } }
- 应用冷启动之后,需要调用createAVSession创建会话。应用生命周期结束后会话自动销毁,不需要调用destroy。
- 播放状态上报播控中心参考以下示例代码,向播控中心上报应用当前的播放状态。即应用中进行播放、暂停、进度调整等行为,通知播控中心进行不同的状态显示。
// AvSessionManager.ts export class AvSessionManager { private static readonly instance: AvSessionManager = new AvSessionManager(); private session: avSession.AVSession = null; /** 播放状态 */ playState?: avSession.AVPlaybackState = { state: avSession.PlaybackState.PLAYBACK_STATE_INITIAL, position: { elapsedTime: 0, updateTime: (new Date()).getTime() } }; static getInstance(): AvSessionManager { return this.instance; } /** * 播放 * * @returns */ play(currentTime?: number): void { hilog.info(0x0666, TAG, `AVSession play, currentTime:${currentTime}, state: ${this.playState?.state}`); this.setPlayOrPauseToAvSession('play', currentTime); } /** * 暂停 * * @returns */ pause(currentTime?: number): void { hilog.info(0x0666, TAG, `AVSession pause, currentTime: ${currentTime}, state: ${this.playState?.state}`); this.setPlayOrPauseToAvSession('pause', currentTime); } /** * 设置播控中心的状态为播放或暂停 * * @param state 状态 * @param elapsedTime 当前进度 */ private setPlayOrPauseToAvSession(state: 'play' | 'pause', elapsedTime: number): void { if (elapsedTime === undefined || elapsedTime < 0) { hilog.warn(0x0666, TAG, `param error, elapsedTime: ${elapsedTime}, do not play or pause.`); return; } if (this.playState === undefined || this.playState.state === avSession.PlaybackState.PLAYBACK_STATE_STOP) { hilog.warn(0x0666, TAG, `playState error, state is PLAYBACK_STATE_STOP or undefined, do not play or pause.`); return; } this.playState.state = state === 'play' ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE; this.playState.position = { elapsedTime: elapsedTime, updateTime: (new Date()).getTime() }; this.setAVPlaybackState(); } /** * 向播控中心设置播放状态 */ private setAVPlaybackState(): void { hilog.info(0x0666, TAG, `setAVPlaybackState state: ${this.playState.state}, updateTime: ${this.playState?.position?.updateTime}, speed: ${this.playState?.speed}`); this.session?.setAVPlaybackState(this.playState); } }
- 详情页退出的特殊逻辑
当用户从详情页退出到应用首页时,需要通知AVSession清除播放信息。
// AvSessionManager.ts export class AvSessionManager { private static readonly instance: AvSessionManager = new AvSessionManager(); private session: avSession.AVSession = null; /** 播放状态 */ playState?: avSession.AVPlaybackState = { state: avSession.PlaybackState.PLAYBACK_STATE_INITIAL, position: { elapsedTime: 0, updateTime: (new Date()).getTime() } }; /** * 向播控中心设置播放状态 */ private setAVPlaybackState(): void { hilog.info(0x0666, TAG, `setAVPlaybackState state: ${this.playState.state}, updateTime: ${this.playState?.position?.updateTime}, speed: ${this.playState?.speed}`); this.session?.setAVPlaybackState(this.playState); } /** * 释放播放器 */ releasePlayer(): void { this.playState.state = avSession.PlaybackState.PLAYBACK_STATE_STOP; this.setAVPlaybackState(); } }
华为视频响应播控中心
当应用处于正常播放的状态时,播放信息和状态同步到播控中心,用户可以在播控中心控制媒体,如暂停、进度调整等。用户在播控中心操作后,需要应用配合响应各种事件,通过AVSession的各种回调完成播放控制。
应用如果已切换到后台,用户点击播控中心,将由播控中心负责拉起华为视频。应用需要配置拉起参数。
同时,应用需要设置监听回调,包括播放、暂停、下一首、进度调整等。只有设置了回调,播控中心侧的按钮才会亮起来,否则按钮将会置灰。
- 拉起华为视频
// AvSessionManager.ts export class AvSessionManager { private static readonly instance: AvSessionManager = new AvSessionManager(); private session: avSession.AVSession = null; /** 播放状态 */ playState?: avSession.AVPlaybackState = { state: avSession.PlaybackState.PLAYBACK_STATE_INITIAL, position: { elapsedTime: 0, updateTime: (new Date()).getTime() } }; static getInstance(): AvSessionManager { return this.instance; } /** * 设置metaData并初始化状态 * * @param metadata 影片元数据 */ setMetaData(metadata: avSession.AVMetadata): void { if (this.session) { hilog.info(0x06666, TAG, `setMetaData avMetadata: ${JSON.stringify(metadata)}`); this.session?.setAVMetadata(metadata) .then(() => { hilog.info(0x06666, TAG, `setMetaData success.`); this.setLaunchAbility(metadata.assetId); }) .catch((error: BusinessError) => { hilog.error(0x06666, TAG, `setMetaData failed, code: ${error.code}`); }); } } /** * 设置一个WantAgent用于拉起会话的Ability * @param vodId 影片Id */ setLaunchAbility(vodId: string): void { const ability: WantAgent.WantAgentInfo = { wants: [ { bundleName: 'com.huawei.hmsapp.himovie', abilityName: 'MainAbility', parameters: { type: 'avsession', routeParams: { vodId, } } } ], requestCode: 0, actionType: WantAgent.OperationType.START_ABILITY, actionFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] } this.session.setLaunchAbility(ability).then(() => { hilog.info(0x0666, TAG, `SetLaunchAbility successfully`); }).catch((err: BusinessError) => { hilog.info(0x0666, TAG, `SetLaunchAbility failed, code: ${err.code}`); }); } }
- 设置监听回调
// AvSessionManager.ts export class AvSessionManager { private session: avSession.AVSession = null; /** * 监听播控中心回调事件,播放 * * @param action 回调方法 */ onPlay(action: () => void): void { if (this.session) { this.session.on('play', action); } } /** * 监听播控中心回调事件,暂停 * * @param action 回调方法 */ onPause(action: () => void): void { if (this.session) { this.session.on('pause', action); } } }
华为视频支持投播
华为视频应用内发起投播
用户使用华为视频播放影片时,通过点击右上角投播组件
,选择需要投播的大屏设备,连接成功后即可完成投播的流程。效果如下图所示。
图1 从华为视频内播放到投播成功
实现投播效果需要完成以下步骤。
- 使用隔空投放组件连接远端设备
用户在播放影片时,右上角会展示一个
图标,它提供了投播能力。用户点击该图标后,播控中心将拉起设备选择的模态窗口,设备的搜索发现、用户选择设备后的连接均由播控中心完成,此过程华为视频不感知。完成连接后,播控中心通过播放设备变化的监听事件outputDeviceChange通知华为视频,华为视频再进行下一步处理。
图2 点击投播组件触发设备选择弹框
应用使用AVSession.on('outputDeviceChange')设置播放设备变化的监听事件,示例代码如下。
远端设备能够投播,需要满足以下条件:
- 设备连接成功,即outputDeviceChange事件监听回调返回connectState为1。
- OutputDeviceInfo中设备列表的第一个设备,必须为远端设备,即castCategory为CATEGORY_REMOTE。
- 投播协议类型必须支持Cast+ Stream。
- 导入相关模块
// CastType.ts import media from '@ohos.multimedia.media'; // 业务Index.ets import avSession from '@ohos.multimedia.avsession'; import { AvSessionManager } from '../avsession/AvSessionManager'; import { CastManager } from '../avsession/CastManager'; import hilog from '@ohos.hilog'; import type { BusinessError } from '@ohos.base'; // CastManager.ets import avSession from '@ohos.multimedia.avsession'; import hilog from '@ohos.hilog'; import type { BusinessError } from '@ohos.base'; import { CastMediaInfo, M3U8Info, CastMediaInfoType, CastErrorType } from './CastType'; import wantAgent from '@ohos.app.ability.wantAgent'; import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager'; import promptAction from '@ohos.promptAction';
- 设置播放设备变化的监听事件示例代码:
// CastManager.ets const TAG = 'CastManager'; /** * 投播管理器 */ export class CastManager { /** 单例 */ private static readonly INSTANCE: CastManager = new CastManager(); /** 播控中心avSession */ private avSession?: avSession.AVSession; /** 投播控制器 */ private avCastController?: avSession.AVCastController; public afterCreateSession(session: avSession.AVSession) { this.avSession = session; // 监听设备连接状态的变化 this.setOutputDeviceChangeListener(); } /** * 设置输出设备变化监听器 */ private setOutputDeviceChangeListener(): void { this.avSession?.on('outputDeviceChange', (connectState: avSession.ConnectionState, device: avSession.OutputDeviceInfo) => { const castCategory = device?.devices?.[0].castCategory; // 成功连接远程设备 if (castCategory === avSession.AVCastCategory.CATEGORY_REMOTE && connectState === avSession.ConnectionState.STATE_CONNECTED) { // 获取cast控制器 this.avSession?.getAVCastController().then(async (controller: avSession.AVCastController) => { hilog.info(0x0666, TAG
- 导入相关模块