概述
系統投播功能讓用戶能夠輕松將手機上的音視頻投放到其他設備(如PC/2in1設備、華為智慧屏)上繼續播放,實現跨設備切換,帶來流暢的觀影體驗。為簡化開發流程,系統提供了標準化的音視頻投播解決方案,開發者僅需配置資源信息、監聽投播狀態并實現播放控制(如播放、暫停),即可快速集成該功能。
本文將結合實際案例,詳細介紹如何高效利用系統投播組件和接口實現視頻投播,幫助開發者提升開發效率,包含如下關鍵步驟:
接入播控中心:播控中心系統提供的播放管理模塊,可以后臺管理應用播放任務,是投播接入的必備條件。
本端控制遠端設備狀態:手機端實現遙控器功能,直接控制遠端設備的播放狀態、進度、音量等。
遠端視頻狀態回傳本端:能夠實時同步播放進度至手機端顯示。
視頻資源切換和設備切換:支持投播過程中集數的切換及投播設備的切換。
用戶體驗
體驗
用戶體驗路徑
本文案例提供本端播放和視頻投播兩種播放模式,體驗路徑和交互流程圖如下。用戶可以在本端和遠端播放視頻,在投播模式下,用戶可以通過遙控界面實現快進/快退、切換上下集、音量調節(支持物理鍵控制)、進度條拖動跳轉、選集切換控制功能,應用接入時,可根據實際需求參考本文實現,并按照應用接入播控自檢表完成基礎功能驗證,確保應用基礎體驗。

實現原理
名詞解釋

投播功能的實現基于AVSession媒體會話和AVCastController投播控制器的協同工作:系統通過AVSession建立設備連接,由AVCastController向Cast+服務發送控制指令。開發者需要聚焦兩個核心環節——通過AVSession實現監聽設備連接,以及使用AVCastController控制遠端播放并同步狀態,詳見運作機制。

模塊設計
建議應用封裝三個模塊:
VideoPlayerController:應用封裝的本地視頻控制器,控制本端視頻資源的暫停、播放、進度、音量、倍速。
VideoSessionController:應用封裝的媒體會話控制器,本端視頻播放時用于本應用與播控中心的同步、切換設備發起投播、結束投播。
VideoCastController:應用封裝的投播視頻控制器,控制遠端設備視頻資源的暫停、播放、進度、音量、倍速。
完成投播功能,建議參考如下流程接入,其中本端視頻顯示和控制可參考視頻播放組件、使用AVPlayer播放視頻(ArkTS)、使用AVPlayer播放視頻(C/C++)等視頻實現方案根據功能訴求自行實現,本文從接入播控中心進行介紹。

接入播控中心
投播功能依賴于播控中心,因此必須接入播控中心才能實現投播功能。播控中心不僅能夠控制本端設備的播放,還能控制遠端設備的播放。本章節將簡要介紹應用接入播控中心的開發流程。
媒體會話初始化
1.avSession.createAVSession()創建avsession,類型為VIDEO_SESSION。
2.設置后臺長時播放任務,確保應用退至后臺后播放不會停止。
3.videoSession.setLaunchAbility()設置一個WantAgent用于拉起會話的Ability。
4.videoSession.activate()激活videoSession。
letvideoSession =awaitavSession.createAVSession(context,'VIDEO_SESSION','video'); // Set up a background task. BackgroundTaskManager.startContinuousTask(context); constwantAgentInfo: wantAgent.WantAgentInfo= { wants: [ { bundleName: context.abilityInfo.bundleName, abilityName: context.abilityInfo.name } ], operationType: wantAgent.OperationType.START_ABILITIES, requestCode:0, wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] }; letagent = wantAgent.getWantAgent(wantAgentInfo); videoSession.setLaunchAbility(agent); videoSession.activate(); returnnewVideoSessionController(videoSession);
設置媒體會話元數據
videoSession.setAVMetadata()上傳元數據,從而在播控中心界面進行展示。如媒體ID(assetId)、標題(title)、播控中心顯示的圖片(mediaImage)、媒體時長(duration)。
letmetadata: avSession.AVMetadata= {
assetId:`${curSource.index}`,
title: curSource.name,
mediaImage: headPixel,
duration: duration,
filter: avSession.ProtocolType.TYPE_DLNA| avSession.ProtocolType.TYPE_CAST_PLUS_STREAM
};
awaitthis.videoSession.setAVMetadata(metadata);
本應用播放狀態同步到播控中心
當設置元數據后,播控中心會顯示進度條并自動計算播放進度,但播放狀態變更(如暫停、播放、進度跳轉)、音量調節和倍速設置等操作不會自動同步到播控中心。開發者需要主動監聽本地的播放狀態變化(包括進度跳轉、倍速調整、音量修改等事件),并主動將這些狀態同步到播控中心,以確保兩端狀態一致。
以下是videoSession狀態更新的示例代碼,特別注意的是,在更新進度狀態時,需要傳入當前時間戳updateTime和視頻播放的時間進度elapsedTime。
awaitthis.videoSession.setAVPlaybackState({
state: state ==='playing'? avSession.PlaybackState.PLAYBACK_STATE_PLAY:
avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
});
播控中心控制應用播放
當用戶在播控中心進行操作(如播放、暫停、停止、進度跳轉、快進、快退等)時,這些操作不會自動同步到應用端,開發者需要主動通過avCastController.on('controlCommand')監聽這些事件,并在回調函數中主動更新應用播放器的狀態以保持同步,例如在收到播放指令時調用本地播放器的play()方法,在收到跳轉指令時調整播放進度等,確保播控中心與應用端的操作狀態完全一致。
this.videoSession.on('play',() =>avPlayerController.setAVPlayerPlaying());
this.videoSession.on('pause',() =>avPlayerController.setAVPlayerPause());
說明: 這里注冊的交互監聽所有on()事件建議在退出播放頁時通過videoSession.off()事件銷毀。
投播基礎功能
為確保投播功能正常使用,應用在發起投播前需要完成播控中心初始化。如未完成此關鍵步驟,則導致投播功能不可用。
創建投播
在完成創建投播后,遠端設備即可正常播放視頻,本端會停止播放并頁面跳轉。
創建投播時需要setExtras()告知系統可投播、繪制AVCastPicker、videosession監聽設備改變事件,用戶點擊AVCastPicker組件后會彈出設備選擇半模態,在選擇設備后,應用需要設置投播媒體信息,調用prepare、start啟動播放。時序圖如下,具體實現見開發步驟:
時序圖

開發步驟
1.videosession創建后,創建投播前,聲明當前應用支持投播。
awaitvideoSession.setExtras({
requireAbilityList: ['url-cast']
})
2.繪制AVCastPicker,AVCastPicker是投播組件,點擊后系統會彈出設備選擇半模態。
AVCastPicker({
normalColor: Color.White,
pickerStyle:AVCastPickerStyle.STYLE_PANEL,
sessionType:'video',
// ...
})
3.當用戶選擇設備并設備切換成功后觸發videoSession.on('outputDeviceChange')事件,應用可選擇停止本地播放并跳轉到遙控頁面(或保持本端繼續播放),此時播控中心會自動接管遠端設備的播放控制,開發者無需額外設置。
videoSession.on('outputDeviceChange',async(connectState: avSession.ConnectionState,
device: avSession.OutputDeviceInfo) => {
hilog.info(0x0000,TAG,`device${JSON.stringify(device)}`);
hilog.info(0x0000,TAG,`connectState${JSON.stringify(connectState)}`);
// The linked device is a remote device.
if(device.devices[0].castCategory=== avSession.AVCastCategory.CATEGORY_REMOTE&&
connectState === avSession.ConnectionState.STATE_CONNECTED) {
// Page jump
this.remoteControlPathStack.replacePath({name:'detail',param:this.currentTime});
this.castingList.push(this.videoType);
awaitthis.releaseAVPlayer();
// The linked device is the local device.
}elseif(device.devices[0].castCategory=== avSession.AVCastCategory.CATEGORY_REMOTE&&
connectState === avSession.ConnectionState.STATE_DISCONNECTED) {
if(this.avCastController) {
awaitthis.avCastController.releaseAVCast();
awaitthis.avSessionController!.stopCasting();
this.avCastController=undefined;
}
}
// ...
})
4.設置avCastController資源,完成以下三步后遠端設備即可投播視頻,以播放網絡資源為例。
1.構建avSession.AVQueueItem。需要傳入assetId(播放列表媒體ID,應用自定義)、title(媒體標題)、artist(媒體專輯作者)、mediaUri(媒體URI)、mediaType(媒體類型)、mediaImage(媒體圖片像素數據)、duration(媒體播放時長)。
2.avCastController.prepare(playItem)準備播放媒體資源,即進行播放資源的加載和緩沖。
3.avCastController.start(playItem)啟動播放媒體資源。
letplayItem: avSession.AVQueueItem= {
itemId: videoIndex,
description: {
assetId:'VIDEO-'+JSON.stringify(videoIndex),
title:this.videoDataArray[videoIndex].name,
artist:'ExampleArtist',
mediaUri:this.videoDataArray[videoIndex].urlasstring,
mediaType:'VIDEO',
mediaImage: imgPixel,
mediaSize:1000,
startPosition: startPosition,
duration:this.videoDataArray[videoIndex].duration
}
};
awaitthis.avCastController.prepare(playItem);
awaitthis.avCastController.start(playItem);
若需要投播本地資源,需要打開沙箱文件,并在fdSrc中傳入文件fd實現。
try{
letfile =awaitfileIo.open(context.filesDir+'/'+this.videoDataArray[videoIndex].url);
letavFileDescriptor: media.AVFileDescriptor= {fd: file.fd};
letplayItem: avSession.AVQueueItem= {
itemId: videoIndex,
description: {
assetId:'VIDEO-'+JSON.stringify(videoIndex),
title:this.videoDataArray[videoIndex].name,
artist:'ExampleArtist',
mediaType:'VIDEO',
mediaImage: imgPixel,
mediaSize:1000,
fdSrc: avFileDescriptor,
startPosition: startPosition,
duration:this.videoDataArray[videoIndex].duration
}
};
awaitthis.avCastController.prepare(playItem);
awaitthis.avCastController.start(playItem);
設備切換
設備切換依賴于videosession監聽設備改變事件,可以通過stopCasting終止投播切換設備,也可以通過avCastPicker.select()進行切換。均會觸發videoSession.on('outputDeviceChange')事件,當切換到遠端設備播放,本端應該跳轉到遙控器界面,當切換回本端設備播放,應當停止投播并跳轉到視頻播放頁面。應用時序圖如下,具體實現見開發步驟。
時序圖

開發步驟
可以直接使用AVCastPicker切換設備,系統會自動彈出設備選擇半模態彈窗,用戶可直接選擇目標設備完成切換。開發者無需額外處理彈窗邏輯。也可以使用avCastPicker.select() 接口切換設備。
當設備切換時,videoSession.on
('outputDeviceChange')事件將被觸發,開發者可在回調中處理設備切換邏輯:若切換至遠端設備則跳轉至遙控頁面,若切回本端設備則恢復本地播放,實現播放控制的無縫切換。
videoSession.on('outputDeviceChange', async (connectState: avSession.ConnectionState,
device: avSession.OutputDeviceInfo) => {
hilog.info(0x0000, TAG, `device ${JSON.stringify(device)}`);
hilog.info(0x0000, TAG, `connectState ${JSON.stringify(connectState)}`);
// The linked device is a remote device.
if(device.devices[0].castCategory === avSession.AVCastCategory.CATEGORY_REMOTE &&
connectState === avSession.ConnectionState.STATE_CONNECTED) {
// Page jump
this.remoteControlPathStack.replacePath({ name:'detail', param:this.currentTime });
this.castingList.push(this.videoType);
awaitthis.releaseAVPlayer();
// The linked device is the local device.
}elseif(device.devices[0].castCategory === avSession.AVCastCategory.CATEGORY_LOCAL) {
this.remoteControlPathStack.clear();
let videoType =this.castingList[0];
this.castingList = [];
let videoPlayParam = new VideoPlayParam(videoType,0,this.avplayerContinueIndex);
this.videoPlayPathStack.replacePath({ name:'detail', param: videoPlayParam });
if(this.avCastController) {
awaitthis.avCastController.releaseAVCast();
awaitthis.avSessionController!.stopCasting();
this.avCastController = undefined;
}
}
})
遠端視頻狀態回傳本端
當視頻在遠端設備播放時,為了控制遠端視頻的播放應用需要監聽遠端視頻播放狀態并同步顯示本端,通過遠端設備或本端播控中心控制,都會直接改變遠端設備的播放狀態,并觸發avCastController.on('playbackStateChange')。應用時序圖如下,具體實現見開發步驟。
時序圖

開發步驟
當需要在本地遙控界面同步顯示遠端視頻的播放狀態時,可通過avCastController.on
('playbackStateChange') 監聽狀態變化,并使用過濾器篩選目標狀態。
建議使用@Track修飾器標記這些經常改變的狀態變量,以便頁面自動響應數據更新。該機制可統一獲取播放狀態(如播放/暫停)、音量、總時長及倍速等信息,以下代碼以獲取已播放時長為例:
@Observed
exportclassVideoCastController{
@Trackstate: avSession.PlaybackState= avSession.PlaybackState.PLAYBACK_STATE_INITIAL;
// ...
/**
* Sets up AV cast playback state change callbacks.
* Handles playback completion, position updates, volume changes and errors.
*/
setAVCastCallback() {
this.avCastController.on('playbackStateChange', ['state'],async(playbackState: avSession.AVPlaybackState) => {
if(playbackState.state) {
this.state= playbackState.state;
}
});
// ...
}
// ...
}
本端控制遠端設備狀態
時序圖

開發步驟
控制遠端設備狀態可通過avCastController.sendControlCommand()接口實現,支持多種播放控制命令,包括:暫停、停止、下一首、上一首、快進、快退、跳轉、音量調節和倍速設置。只需修改command字段即可切換不同功能,具體命令與功能的對應關系請參考AVCastControlCommandType。
publicasyncsetAVCastPlay(){
letavCommand: avSession.AVCastControlCommand = { command:'play'};
awaitthis.avCastController.sendControlCommand(avCommand);
}
在控制跳轉、音量調節和倍速設置時,需要傳入時間(單位ms)、音量、倍速參數。
publicasyncsetAVCastSeek(timeMS:number) { letavCommand: avSession.AVCastControlCommand= {command:'seek',parameter: timeMS }; awaitthis.avCastController.sendControlCommand(avCommand); } publicasyncsetAVCastVolume(volume:number) { letavCommand: avSession.AVCastControlCommand= {command:'setVolume',parameter: volume }; awaitthis.avCastController.sendControlCommand(avCommand); } publicasyncsetAVCastSpeed(speed: media.PlaybackSpeed) { letavCommand: avSession.AVCastControlCommand= {command:'setSpeed',parameter: speed }; awaitthis.avCastController.sendControlCommand(avCommand); }
資源切換
在完成本集播放/用戶觸發集數切換時不需要斷開連接,重新設置資源即可。
1.構建avSession.AVQueueItem。
2.avCastController.prepare(playItem)。
3.avCastController.start(playItem)。
letplayItem: avSession.AVQueueItem= {
itemId: videoIndex,
description: {
assetId:'VIDEO-'+JSON.stringify(videoIndex),
title:this.videoDataArray[videoIndex].name,
subtitle:'video',
mediaUri:this.videoDataArray[videoIndex].urlasstring,
mediaType:'VIDEO',
mediaImage: imgPixel,
startPosition: startPosition,
duration:this.videoDataArray[videoIndex].duration
}
};
awaitthis.avCastController.prepare(playItem);
awaitthis.avCastController.start(playItem);
擴展功能
懸浮球快捷控制
建議應用集成懸浮球快捷控制功能,便于用戶快速返回投播頁面進行操作控制,實現效果如圖:
可以通過為頁面設置浮層實現。
.overlay(this.OverlayNode(), {
align: Alignment.BottomEnd,
offset: {x: -24,
y: -136}
})
@Builder
OverlayNode(){
// ...
}
手機物理音量鍵同步遠端
音量同步需要通過遙控器頁面的焦點管理和按鍵監聽實現,具體流程為:當遙控器頁面獲焦時,監聽音量加減按鍵事件,在事件回調中調用音量調節函數并同步更新播控中心狀態。典型實現示例如下:
letupOptions: inputConsumer.KeyPressedConfig= {
key:KeyCode.KEYCODE_VOLUME_UP,
action:1,
isRepeat:true,
}
inputConsumer.on('keyPressed', upOptions,async()=> {
if(this.avCastPlayerController) {
console.log('currentVolume'+JSON.stringify(this.currentVolume));
letvolume =this.currentVolume+10;
awaitthis.avCastPlayerController.setAVCastVolume(volume);
}
})
letdownOptions: inputConsumer.KeyPressedConfig= {
key:KeyCode.KEYCODE_VOLUME_DOWN,
action:1,
isRepeat:true,
}
inputConsumer.on('keyPressed', downOptions,async()=> {
if(this.avCastPlayerController) {
letvolume =this.currentVolume-10;
if(volume 0){
? ? ??await?this.avCastPlayerController.setAVCastVolume(0);
? ? }
? ??await?this.avCastPlayerController.setAVCastVolume(volume);
? }
})
-
視頻
+關注
關注
6文章
2005瀏覽量
74954 -
華為
+關注
關注
218文章
36002瀏覽量
262069 -
HarmonyOS
+關注
關注
80文章
2153瀏覽量
36037
原文標題:HarmonyOS應用視頻投播解決方案
文章出處:【微信號:HarmonyOS_Dev,微信公眾號:HarmonyOS開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
HarmonyOS應用視頻投播解決方案
評論