概述
系統(tǒng)投播功能讓用戶能夠輕松將手機(jī)上的音視頻投放到其他設(shè)備(如PC/2in1設(shè)備、華為智慧屏)上繼續(xù)播放,實(shí)現(xiàn)跨設(shè)備切換,帶來流暢的觀影體驗(yàn)。為簡化開發(fā)流程,系統(tǒng)提供了標(biāo)準(zhǔn)化的音視頻投播解決方案,開發(fā)者僅需配置資源信息、監(jiān)聽投播狀態(tài)并實(shí)現(xiàn)播放控制(如播放、暫停),即可快速集成該功能。
本文將結(jié)合實(shí)際案例,詳細(xì)介紹如何高效利用系統(tǒng)投播組件和接口實(shí)現(xiàn)視頻投播,幫助開發(fā)者提升開發(fā)效率,包含如下關(guān)鍵步驟:
接入播控中心:播控中心系統(tǒng)提供的播放管理模塊,可以后臺管理應(yīng)用播放任務(wù),是投播接入的必備條件。
本端控制遠(yuǎn)端設(shè)備狀態(tài):手機(jī)端實(shí)現(xiàn)遙控器功能,直接控制遠(yuǎn)端設(shè)備的播放狀態(tài)、進(jìn)度、音量等。
遠(yuǎn)端視頻狀態(tài)回傳本端:能夠?qū)崟r同步播放進(jìn)度至手機(jī)端顯示。
視頻資源切換和設(shè)備切換:支持投播過程中集數(shù)的切換及投播設(shè)備的切換。
用戶體驗(yàn)
體驗(yàn)
用戶體驗(yàn)路徑
本文案例提供本端播放和視頻投播兩種播放模式,體驗(yàn)路徑和交互流程圖如下。用戶可以在本端和遠(yuǎn)端播放視頻,在投播模式下,用戶可以通過遙控界面實(shí)現(xiàn)快進(jìn)/快退、切換上下集、音量調(diào)節(jié)(支持物理鍵控制)、進(jìn)度條拖動跳轉(zhuǎn)、選集切換控制功能,應(yīng)用接入時,可根據(jù)實(shí)際需求參考本文實(shí)現(xiàn),并按照應(yīng)用接入播控自檢表完成基礎(chǔ)功能驗(yàn)證,確保應(yīng)用基礎(chǔ)體驗(yàn)。
實(shí)現(xiàn)原理
名詞解釋
投播功能的實(shí)現(xiàn)基于AVSession媒體會話和AVCastController投播控制器的協(xié)同工作:系統(tǒng)通過AVSession建立設(shè)備連接,由AVCastController向Cast+服務(wù)發(fā)送控制指令。開發(fā)者需要聚焦兩個核心環(huán)節(jié)——通過AVSession實(shí)現(xiàn)監(jiān)聽設(shè)備連接,以及使用AVCastController控制遠(yuǎn)端播放并同步狀態(tài),詳見運(yùn)作機(jī)制。
模塊設(shè)計(jì)
建議應(yīng)用封裝三個模塊:
VideoPlayerController:應(yīng)用封裝的本地視頻控制器,控制本端視頻資源的暫停、播放、進(jìn)度、音量、倍速。
VideoSessionController:應(yīng)用封裝的媒體會話控制器,本端視頻播放時用于本應(yīng)用與播控中心的同步、切換設(shè)備發(fā)起投播、結(jié)束投播。
VideoCastController:應(yīng)用封裝的投播視頻控制器,控制遠(yuǎn)端設(shè)備視頻資源的暫停、播放、進(jìn)度、音量、倍速。
完成投播功能,建議參考如下流程接入,其中本端視頻顯示和控制可參考視頻播放組件、使用AVPlayer播放視頻(ArkTS)、使用AVPlayer播放視頻(C/C++)等視頻實(shí)現(xiàn)方案根據(jù)功能訴求自行實(shí)現(xiàn),本文從接入播控中心進(jìn)行介紹。
接入播控中心
投播功能依賴于播控中心,因此必須接入播控中心才能實(shí)現(xiàn)投播功能。播控中心不僅能夠控制本端設(shè)備的播放,還能控制遠(yuǎn)端設(shè)備的播放。本章節(jié)將簡要介紹應(yīng)用接入播控中心的開發(fā)流程。
媒體會話初始化
1.avSession.createAVSession()創(chuàng)建avsession,類型為VIDEO_SESSION。
2.設(shè)置后臺長時播放任務(wù),確保應(yīng)用退至后臺后播放不會停止。
3.videoSession.setLaunchAbility()設(shè)置一個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);
設(shè)置媒體會話元數(shù)據(jù)
videoSession.setAVMetadata()上傳元數(shù)據(jù),從而在播控中心界面進(jìn)行展示。如媒體ID(assetId)、標(biāo)題(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);
本應(yīng)用播放狀態(tài)同步到播控中心
當(dāng)設(shè)置元數(shù)據(jù)后,播控中心會顯示進(jìn)度條并自動計(jì)算播放進(jìn)度,但播放狀態(tài)變更(如暫停、播放、進(jìn)度跳轉(zhuǎn))、音量調(diào)節(jié)和倍速設(shè)置等操作不會自動同步到播控中心。開發(fā)者需要主動監(jiān)聽本地的播放狀態(tài)變化(包括進(jìn)度跳轉(zhuǎn)、倍速調(diào)整、音量修改等事件),并主動將這些狀態(tài)同步到播控中心,以確保兩端狀態(tài)一致。
以下是videoSession狀態(tài)更新的示例代碼,特別注意的是,在更新進(jìn)度狀態(tài)時,需要傳入當(dāng)前時間戳updateTime和視頻播放的時間進(jìn)度elapsedTime。
awaitthis.videoSession.setAVPlaybackState({ state: state ==='playing'? avSession.PlaybackState.PLAYBACK_STATE_PLAY: avSession.PlaybackState.PLAYBACK_STATE_PAUSE, });
播控中心控制應(yīng)用播放
當(dāng)用戶在播控中心進(jìn)行操作(如播放、暫停、停止、進(jìn)度跳轉(zhuǎn)、快進(jìn)、快退等)時,這些操作不會自動同步到應(yīng)用端,開發(fā)者需要主動通過avCastController.on('controlCommand')監(jiān)聽這些事件,并在回調(diào)函數(shù)中主動更新應(yīng)用播放器的狀態(tài)以保持同步,例如在收到播放指令時調(diào)用本地播放器的play()方法,在收到跳轉(zhuǎn)指令時調(diào)整播放進(jìn)度等,確保播控中心與應(yīng)用端的操作狀態(tài)完全一致。
this.videoSession.on('play',() =>avPlayerController.setAVPlayerPlaying()); this.videoSession.on('pause',() =>avPlayerController.setAVPlayerPause());
說明: 這里注冊的交互監(jiān)聽所有on()事件建議在退出播放頁時通過videoSession.off()事件銷毀。
投播基礎(chǔ)功能
為確保投播功能正常使用,應(yīng)用在發(fā)起投播前需要完成播控中心初始化。如未完成此關(guān)鍵步驟,則導(dǎo)致投播功能不可用。
創(chuàng)建投播
在完成創(chuàng)建投播后,遠(yuǎn)端設(shè)備即可正常播放視頻,本端會停止播放并頁面跳轉(zhuǎn)。
創(chuàng)建投播時需要setExtras()告知系統(tǒng)可投播、繪制AVCastPicker、videosession監(jiān)聽設(shè)備改變事件,用戶點(diǎn)擊AVCastPicker組件后會彈出設(shè)備選擇半模態(tài),在選擇設(shè)備后,應(yīng)用需要設(shè)置投播媒體信息,調(diào)用prepare、start啟動播放。時序圖如下,具體實(shí)現(xiàn)見開發(fā)步驟:
時序圖
開發(fā)步驟
1.videosession創(chuàng)建后,創(chuàng)建投播前,聲明當(dāng)前應(yīng)用支持投播。
awaitvideoSession.setExtras({ requireAbilityList: ['url-cast'] })
2.繪制AVCastPicker,AVCastPicker是投播組件,點(diǎn)擊后系統(tǒng)會彈出設(shè)備選擇半模態(tài)。
AVCastPicker({ normalColor: Color.White, pickerStyle:AVCastPickerStyle.STYLE_PANEL, sessionType:'video', // ... })
3.當(dāng)用戶選擇設(shè)備并設(shè)備切換成功后觸發(fā)videoSession.on('outputDeviceChange')事件,應(yīng)用可選擇停止本地播放并跳轉(zhuǎn)到遙控頁面(或保持本端繼續(xù)播放),此時播控中心會自動接管遠(yuǎn)端設(shè)備的播放控制,開發(fā)者無需額外設(shè)置。
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.設(shè)置avCastController資源,完成以下三步后遠(yuǎn)端設(shè)備即可投播視頻,以播放網(wǎng)絡(luò)資源為例。
1.構(gòu)建avSession.AVQueueItem。需要傳入assetId(播放列表媒體ID,應(yīng)用自定義)、title(媒體標(biāo)題)、artist(媒體專輯作者)、mediaUri(媒體URI)、mediaType(媒體類型)、mediaImage(媒體圖片像素?cái)?shù)據(jù))、duration(媒體播放時長)。
2.avCastController.prepare(playItem)準(zhǔn)備播放媒體資源,即進(jìn)行播放資源的加載和緩沖。
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實(shí)現(xiàn)。
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);
設(shè)備切換
設(shè)備切換依賴于videosession監(jiān)聽設(shè)備改變事件,可以通過stopCasting終止投播切換設(shè)備,也可以通過avCastPicker.select()進(jìn)行切換。均會觸發(fā)videoSession.on('outputDeviceChange')事件,當(dāng)切換到遠(yuǎn)端設(shè)備播放,本端應(yīng)該跳轉(zhuǎn)到遙控器界面,當(dāng)切換回本端設(shè)備播放,應(yīng)當(dāng)停止投播并跳轉(zhuǎn)到視頻播放頁面。應(yīng)用時序圖如下,具體實(shí)現(xiàn)見開發(fā)步驟。
時序圖
開發(fā)步驟
可以直接使用AVCastPicker切換設(shè)備,系統(tǒng)會自動彈出設(shè)備選擇半模態(tài)彈窗,用戶可直接選擇目標(biāo)設(shè)備完成切換。開發(fā)者無需額外處理彈窗邏輯。也可以使用avCastPicker.select() 接口切換設(shè)備。
當(dāng)設(shè)備切換時,videoSession.on
('outputDeviceChange')事件將被觸發(fā),開發(fā)者可在回調(diào)中處理設(shè)備切換邏輯:若切換至遠(yuǎn)端設(shè)備則跳轉(zhuǎn)至遙控頁面,若切回本端設(shè)備則恢復(fù)本地播放,實(shí)現(xiàn)播放控制的無縫切換。
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; } } })
遠(yuǎn)端視頻狀態(tài)回傳本端
當(dāng)視頻在遠(yuǎn)端設(shè)備播放時,為了控制遠(yuǎn)端視頻的播放應(yīng)用需要監(jiān)聽遠(yuǎn)端視頻播放狀態(tài)并同步顯示本端,通過遠(yuǎn)端設(shè)備或本端播控中心控制,都會直接改變遠(yuǎn)端設(shè)備的播放狀態(tài),并觸發(fā)avCastController.on('playbackStateChange')。應(yīng)用時序圖如下,具體實(shí)現(xiàn)見開發(fā)步驟。
時序圖
開發(fā)步驟
當(dāng)需要在本地遙控界面同步顯示遠(yuǎn)端視頻的播放狀態(tài)時,可通過avCastController.on
('playbackStateChange') 監(jiān)聽狀態(tài)變化,并使用過濾器篩選目標(biāo)狀態(tài)。
建議使用@Track修飾器標(biāo)記這些經(jīng)常改變的狀態(tài)變量,以便頁面自動響應(yīng)數(shù)據(jù)更新。該機(jī)制可統(tǒng)一獲取播放狀態(tài)(如播放/暫停)、音量、總時長及倍速等信息,以下代碼以獲取已播放時長為例:
@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; } }); // ... } // ... }
本端控制遠(yuǎn)端設(shè)備狀態(tài)
時序圖
開發(fā)步驟
控制遠(yuǎn)端設(shè)備狀態(tài)可通過avCastController.sendControlCommand()接口實(shí)現(xiàn),支持多種播放控制命令,包括:暫停、停止、下一首、上一首、快進(jìn)、快退、跳轉(zhuǎn)、音量調(diào)節(jié)和倍速設(shè)置。只需修改command字段即可切換不同功能,具體命令與功能的對應(yīng)關(guān)系請參考AVCastControlCommandType。
publicasyncsetAVCastPlay(){ letavCommand: avSession.AVCastControlCommand = { command:'play'}; awaitthis.avCastController.sendControlCommand(avCommand); }
在控制跳轉(zhuǎn)、音量調(diào)節(jié)和倍速設(shè)置時,需要傳入時間(單位ms)、音量、倍速參數(shù)。
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); }
資源切換
在完成本集播放/用戶觸發(fā)集數(shù)切換時不需要斷開連接,重新設(shè)置資源即可。
1.構(gòu)建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);
擴(kuò)展功能
懸浮球快捷控制
建議應(yīng)用集成懸浮球快捷控制功能,便于用戶快速返回投播頁面進(jìn)行操作控制,實(shí)現(xiàn)效果如圖:
可以通過為頁面設(shè)置浮層實(shí)現(xiàn)。
.overlay(this.OverlayNode(), { align: Alignment.BottomEnd, offset: {x: -24, y: -136} })
@Builder OverlayNode(){ // ... }
手機(jī)物理音量鍵同步遠(yuǎn)端
音量同步需要通過遙控器頁面的焦點(diǎn)管理和按鍵監(jiān)聽實(shí)現(xiàn),具體流程為:當(dāng)遙控器頁面獲焦時,監(jiān)聽音量加減按鍵事件,在事件回調(diào)中調(diào)用音量調(diào)節(jié)函數(shù)并同步更新播控中心狀態(tài)。典型實(shí)現(xiàn)示例如下:
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); ? } })
-
視頻
+關(guān)注
關(guān)注
6文章
1975瀏覽量
73984 -
華為
+關(guān)注
關(guān)注
216文章
35231瀏覽量
256125 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2127瀏覽量
33261
原文標(biāo)題:HarmonyOS應(yīng)用視頻投播解決方案
文章出處:【微信號:HarmonyOS_Dev,微信公眾號:HarmonyOS開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄

HDTV的完整音視頻解決方案
視頻語音解決方案
[求助]視頻轉(zhuǎn)換器解決方案
杰士安校園網(wǎng)絡(luò)視頻監(jiān)控解決方案
如何構(gòu)建更好的視頻橋接解決方案
【視頻】解決方案第4期:極小硬件方案介紹
Altera的視頻和圖像處理解決方案
HarmonyOS測試技術(shù)與實(shí)戰(zhàn)-分布式應(yīng)用測試解決方案

評論