鴻蒙OS 媒體會話開發(fā)指導(dǎo)

2020-09-18 14:39 更新

場景介紹

AVSession 框架有四個(gè)主要的類,控制著整個(gè)框架的核心,下圖簡單的說明四個(gè)核心媒體框架控制類的關(guān)系。

img

  • AVBrowser

媒體瀏覽器,通常在客戶端創(chuàng)建,成功連接媒體服務(wù)后,通過媒體控制器 AVBrowse r向服務(wù)端發(fā)送播放控制指令。

其主要流程為,調(diào)用 connect 方法向 AVBrowserService 發(fā)起連接請求,連接成功后在回調(diào)方法 AVConnectionCallback.onConnected 中發(fā)起訂閱數(shù)據(jù)請求,并在回調(diào)方法 AVSubscriptionCallback.onAVElementListLoaded 中保存請求的媒體播放數(shù)據(jù)。

  • AVController

媒體控制器,在客戶端 AVBrowser 連接服務(wù)成功后的回調(diào)方法 AVConnectionCallback.onConnected 中創(chuàng)建,用于向 Service 發(fā)送播放控制指令,并通過實(shí)現(xiàn) AVControllerCallback 回調(diào)來響應(yīng)服務(wù)端媒體狀態(tài)變化,例如曲目信息變更、播放狀態(tài)變更等,從而完成UI刷新。

  • AVBrowser

Service

媒體瀏覽器服務(wù),通常在服務(wù)端,通過媒體會話 AVSession 與媒體瀏覽器建立連接,并通過實(shí)現(xiàn) Player 進(jìn)行媒體播放。其中有兩個(gè)重要的方法:

  1. onGetRoot,處理從媒體瀏覽器 AVBrowser 發(fā)來的連接請求,通過返回一個(gè)有效的 AVBrowserRoot 對象表示連接成功;
  2. onLoadAVElementList,處理從媒體瀏覽器 AVBrowser 發(fā)來的數(shù)據(jù)訂閱請求,通過 AVBrowserResult.sendAVElementList(List<AVElement>) 方法返回媒體播放數(shù)據(jù)。

  • AVSession

媒體會話,通常在 AVBrowserService 的 onStart 中創(chuàng)建,通過 setAVToken 方法設(shè)置到 AVBrowserService 中,并通過實(shí)現(xiàn) AVSessionCallback 回調(diào)來接收和處理媒體控制器 AVController 發(fā)送的播放控制指令,如播放、暫停、跳轉(zhuǎn)至上一曲、跳轉(zhuǎn)至下一曲等。

除了上述四個(gè)類,AVSession 框架還有 AVElement。

  • AVElement

媒體元素,用于將播放列表從 AVBrowserService 傳遞給 AVBrowser。

接口說明

接口名 描述
AVBrowser(Context context, ElementName name, AVConnectionCallback callback, PacMap options) 構(gòu)造 AVBrowser 實(shí)例,用于瀏覽 AVBrowserService 提供的媒體數(shù)據(jù)。
void connect() 連接 AVBrowserService。
void disconnect() 與 AVBrowserService 斷開連接。
boolean isConnected() 判斷當(dāng)前是否已經(jīng)與 AVBrowserService 連接。
ElementName getElementName() 獲取 AVBrowserService 的ohos.bundle.ElementName實(shí)例。
String getRootMediaId() 獲取默認(rèn)媒體 id。
PacMap getOptions() 獲取 AVBrowserService 提供的附加數(shù)據(jù)。
AVToken getAVToken() 獲取媒體會話的令牌。
void getAVElement(String mediaId, AVElementCallback callback) 輸入媒體的 id,查詢對應(yīng)的 ohos.media.common.sessioncore.AVElement 信息,查詢結(jié)果會通過 callback 返回。
void subscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback) 查詢指定媒體 id 包含的所有媒體元素信息,并訂閱它的媒體信息更新通知。
void subscribeByParentMediaId(String parentMediaId, PacMap options, AVSubscriptionCallback callback) 基于特定于服務(wù)的參數(shù)來查詢指定媒體 id 中的媒體元素的信息,并訂閱它的媒體信息更新通知。
void unsubscribeByParentMediaId(String parentMediaId) 取消訂閱對應(yīng)媒體 id 的信息更新通知。
void unsubscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback) 取消訂閱與指定 callback 相關(guān)的媒體 id 的信息更新通知。
接口名 描述
abstract AVBrowserRoot onGetRoot(String callerPackageName, int clientUid, PacMap options) 回調(diào)方法,用于返回應(yīng)用程序的媒體內(nèi)容的根信息,在 AVBrowser.connect()后進(jìn)行回調(diào)。
abstract void onLoadAVElementList(String parentMediaId, AVBrowserResult result) 回調(diào)方法,用于返回應(yīng)用程序的媒體內(nèi)容的結(jié)果信息 AVBrowserResult,其中包含了子節(jié)點(diǎn)的 AVElement 列表,在 AVBrowser 的方法 subscribeByParentMediaId 或 notifyAVElementListUpdated 執(zhí)行后進(jìn)行回調(diào)。
abstract void onLoadAVElement(String mediaId, AVBrowserResult result) 回調(diào)方法,用于獲取特定的媒體項(xiàng)目 AVElement 結(jié)果信息,在 AVBrowser.getAVElement 方法執(zhí)行后進(jìn)行回調(diào)。
AVToken getAVToken() 獲取 AVBrowser 與 AVBrowserService 之間的會話令牌。
void setAVToken(AVToken token) 設(shè)置 AVBrowser 與 AVBrowserService 之間的會話令牌。
final PacMap getBrowserOptions() 獲取 AVBrowser 在連接 AVBrowserService 時(shí)設(shè)置的服務(wù)參數(shù)選項(xiàng)。
final AVRemoteUserInfo getCallerUserInfo() 獲取當(dāng)前發(fā)送請求的調(diào)用者信息。
void notifyAVElementListUpdated(String parentMediaId) 通知所有已連接的 AVBrowser 當(dāng)前父節(jié)點(diǎn)的子節(jié)點(diǎn)已經(jīng)發(fā)生改變。
void notifyAVElementListUpdated(String parentId, PacMap options) 通知所有已連接的 AVBrowser 當(dāng)前父節(jié)點(diǎn)的子節(jié)點(diǎn)已經(jīng)發(fā)生改變,可設(shè)置服務(wù)參數(shù)。
接口名 描述
AVController(Context context, AVToken avToken) 構(gòu)造 AVController 實(shí)例,用于應(yīng)用程序與 AVSession 進(jìn)行交互以控制媒體播放。
static boolean setControllerForAbility(Ability ability, AVController controller) 將媒體控制器注冊到 ability 以接收按鍵事件。
boolean setAVControllerCallback(AVControllerCallback callback) 注冊一個(gè)回調(diào)以接收來自 AVSession 的變更,例如元數(shù)據(jù)和播放狀態(tài)變更。
boolean releaseAVControllerCallback(AVControllerCallback callback) 釋放與 AVSession 之間的回調(diào)實(shí)例。
List<AVQueueElement> getAVQueueElement() 獲取播放隊(duì)列。
CharSequence getAVQueueTitle() 獲取播放隊(duì)列的標(biāo)題。
AVPlaybackState getAVPlaybackState() 獲取播放狀態(tài)。
boolean dispatchAVKeyEvent(KeyEvent keyEvent) 應(yīng)用分發(fā)媒體按鍵事件給會話以控制播放。
void sendCustomCommand(String command, PacMap pacMap, GeneralReceiver receiverCb) 應(yīng)用向 AVSession 發(fā)送自定義命令,參考o(jì)hos.media.common.sessioncore.AVSessionCallback.onCommand。
IntentAgent getAVSessionAbility() 獲取啟動用戶界面的IntentAgent。
AVToken getAVToken() 獲取應(yīng)用連接到會話的令牌。此令牌用于創(chuàng)建媒體播放控制器。
void adjustAVPlaybackVolume(int direction, int flags) 調(diào)節(jié)播放音量。
void setAVPlaybackVolume(int value, int flags) 設(shè)置播放音量,要求支持絕對音量控制。
PacMap getOptions() 獲取與此控制器連接的AVSession的附加數(shù)據(jù)。
long getFlags() 獲取 AVSession 的附加標(biāo)識,標(biāo)記在 AVSession 中的定義。
AVMetadata getAVMetadata() 獲取媒體資源的元數(shù)據(jù)ohos.media.common.AVMetadata。
AVPlaybackInfo getAVPlaybackInfo() 獲取播放信息。
String getSessionOwnerPackageName() 獲得 AVSession 實(shí)例的應(yīng)用程序的包名稱。
PacMap getAVSessionInfo() 獲取會話的附加數(shù)據(jù)。
PlayControls getPlayControls() 獲取一個(gè) PlayControls 實(shí)例,將用于控制播放,比如控制媒體播放、停止、下一首等。
接口名 描述
AVSession(Context context, String tag) 構(gòu)造 AVSession 實(shí)例,用于控制媒體播放。
AVSession(Context context, String tag, PacMap sessionInfo) 構(gòu)造帶有附加會話信息的 AVSession 實(shí)例,用于控制媒體播放。
void setAVSessionCallback(AVSessionCallback callback) 設(shè)置回調(diào)函數(shù)來控制播放器,控制邏輯由應(yīng)用實(shí)現(xiàn)。如果 callback 為 null 則取消控制。
boolean setAVSessionAbility(IntentAgent ia) 給 AVSession 設(shè)置一個(gè)IntentAgent,用來啟動用戶界面。
boolean setAVButtonReceiver(IntentAgent ia) 為媒體按鍵接收器設(shè)置一個(gè) IntentAgent,以便應(yīng)用結(jié)束后,可以通過媒體按鍵重新拉起應(yīng)用。
void enableAVSessionActive(boolean active) 設(shè)置是否激活媒體會話。當(dāng)會話準(zhǔn)備接收命令時(shí),將輸入?yún)?shù)設(shè)置為 true。如果會話停止接收命令,則設(shè)置為 false。
boolean isAVSessionActive() 查詢會話是否激活。
void sendAVSessionEvent(String event, PacMap options) 向所有訂閱此會話的控制器發(fā)送事件。
void release() 釋放資源,應(yīng)用播放完之后需調(diào)用。
AVToken getAVToken() 獲取應(yīng)用連接到會話的令牌。此令牌用于創(chuàng)建媒體播放控制器。
AVController getAVController() 獲取會話構(gòu)造時(shí)創(chuàng)建的控制器,方便應(yīng)用使用。
void setAVPlaybackState(AVPlaybackState state) 設(shè)置當(dāng)前播放狀態(tài)。
void setAVMetadata(AVMetadata avMetadata) 設(shè)置媒體資源元數(shù)據(jù)ohos.media.common.AVMetadata。
void setAVQueue(List<AVQueueElement> queue) 設(shè)置播放隊(duì)列。
void setAVQueueTitle(CharSequence queueTitle) 設(shè)置播放隊(duì)列的標(biāo)題,UI會顯示此標(biāo)題。
void setOptions(PacMap options) 設(shè)置此會話關(guān)聯(lián)的附加數(shù)據(jù)。
AVCallerUserInfo getCurrentControllerInfo() 獲取發(fā)送當(dāng)前請求的媒體控制器信息。
接口名 描述
AVElement(AVDescription description, int flags) 構(gòu)造 AVElement 實(shí)例。
int getFlags() 獲取flags的值。
boolean isScannable() 判斷媒體是否可掃描,如:媒體有子節(jié)點(diǎn),則可繼續(xù)掃描獲取子節(jié)點(diǎn)內(nèi)容。
boolean isPlayable() 檢查媒體是否可播放。
AVDescription getAVDescription() 獲取媒體的詳細(xì)信息。
String getMediaId() 獲取媒體的id。

開發(fā)步驟

使用 AVSession 媒體框架創(chuàng)建一個(gè)播放器示例,分為創(chuàng)建客戶端和創(chuàng)建服務(wù)端。

創(chuàng)建客戶端

在客戶端 AVClientAbility 中聲明 AVBrowser 和 AVController,并向服務(wù)端發(fā)送連接請求。

public class AVClientAbility extends Ability {
    // 媒體瀏覽器
    private AVBrowser avBrowser;
    // 媒體控制器
    private AVController avController;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // 用于指向媒體瀏覽器服務(wù)的包路徑和類名
        ElementName elementName = new ElementName("", "com.huawei.samples.audioplayer", "com.huawei.samples.audioplayer.AVService");
        // connectionCallback 在調(diào)用 avBrowser.connect 方法后進(jìn)行回調(diào)。
        avBrowser = new AVBrowser(context, elementName, connectionCallback, null);
        // avBrowser 發(fā)送對媒體瀏覽器服務(wù)的連接請求。
        avBrowser.connect();
        // 將媒體控制器注冊到 ability 以接收按鍵事件。
        AVController.setControllerForAbility(this, avController);
    }
}

AVConnectionCallback 回調(diào)接口中的方法為可選實(shí)現(xiàn),通常需要會在 onConnected 中訂閱媒體數(shù)據(jù)和創(chuàng)建媒體控制器 AVController。

// 發(fā)起連接(avBrowser.connect)后的回調(diào)方法實(shí)現(xiàn)
private AVConnectionCallback connectionCallback = new AVConnectionCallback() {
    @Override
    public void onConnected() {
        // 成功連接媒體瀏覽器服務(wù)時(shí)回調(diào)該方法,否則回調(diào) onConnectionFailed()。
        // 重復(fù)訂閱會報(bào)錯(cuò),所以先解除訂閱。
        avBrowser.unsubscribeByParentMediaId(avBrowser.getRootMediaId());
        // 第二個(gè)參數(shù) AVSubscriptionCallback,用于處理訂閱信息的回調(diào)。
        avBrowser.subscribeByParentMediaId(AV_ROOTavBrowser.getRootMediaId(), avSubscriptionCallback);
        AVToken token = avBrowser.getAVToken();
        avController = new AVController(AVClient.this, token); // AVController第一個(gè)參數(shù)為當(dāng)前類的context
        // 參數(shù) AVControllerCallback,用于處理服務(wù)端播放狀態(tài)及信息變化時(shí)回調(diào)。
        avController.setAVControllerCallback(avControllerCallback);
        // ...
    }
    // 其它回調(diào)方法(可選)
    // ...
};

通常在訂閱成功時(shí),在 AVSubscriptionCallback 回調(diào)接口 onAVElementListLoaded 中保存服務(wù)端回傳的媒體列表。

// 發(fā)起訂閱信息(avBrowser.subscribeByParentMediaId)后的回調(diào)方法實(shí)現(xiàn)
private AVSubscriptionCallback avSubscriptionCallback = new AVSubscriptionCallback() {
    @Override
    public void onAVElementListLoaded(String parentId, List<AVElement> children) {
        // 訂閱成功時(shí)回調(diào)該方法,parentID 為標(biāo)識,children 為服務(wù)端回傳的媒體列表
        super.onAVElementListLoaded(parentId, children);
        list.addAll(children);
        // ...
    }
};

AVControllerCallback 回調(diào)接口中的方法均為可選方法,主要用于服務(wù)端播放狀態(tài)及信息的變化后對客戶端的回調(diào),客戶端可在這些方法中實(shí)現(xiàn)UI的刷新。

// 服務(wù)對客戶端的媒體數(shù)據(jù)或播放狀態(tài)變更后的回調(diào) 
 private AVControllerCallback     avControllerCallback = new  AVControllerCallback() {
    @Override
    public void onAVMetadataChanged(AVMetadata metadata) {
        // 當(dāng)服務(wù)端調(diào)用 avSession.setAVMetadata(avMetadata)時(shí),此方法會被回調(diào)。
        super.onAVMetadataChanged(metadata);
        AVDescription description = metadata.getAVDescription();
        String title = description.getTitle().toString();
        PixelMap pixelMap = description.getIcon();
        // ...
    }
    @Override
    public void onAVPlaybackStateChanged(AVPlaybackState playbackState) {
        // 當(dāng)服務(wù)端調(diào)用avSession.setAVPlaybackState(...)時(shí),此方法會被回調(diào)。
        super.onAVPlaybackStateChanged(playbackState);
        long position = playbackState.getCurrentPosition();
        // ...
    }
    // 其它回調(diào)方法(可選)
    // ...
};

完成以上實(shí)現(xiàn)后,則應(yīng)用可以在UI事件中調(diào)用avController的方法向服務(wù)端發(fā)送播放控制指令。

// 在 UI 播放與暫停按鈕的點(diǎn)擊事件中向服務(wù)端發(fā)送播放或暫停指令
public void toPlayOrPause() {
    switch (avController.getAVPlaybackState().getAVPlaybackState()) {
        case AVPlaybackState.PLAYBACK_STATE_NONE: {
            avController.getPlayControls().prepareToPlay();
            avController.getPlayControls().play();
            break;
        }
        case AVPlaybackState.PLAYBACK_STATE_PLAYING: {
            avController.getPlayControls().pause();
            break;
        }
        case AVPlaybackState.PLAYBACK_STATE_PAUSED: {
            avController.getPlayControls().play();
            break;
        }
        default: {
            // ...
        }
    }
}

其它播放控制根據(jù)業(yè)務(wù)是否需要實(shí)現(xiàn),比如:

avController.getPlayControls().playNext();
avController.getPlayControls().playPrevious();
avController.getPlayControls().playFastForward();
avController.getPlayControls().rewind();
avController.getPlayControls().seekTo(1000);
avController.getPlayControls().stop();
// ...

也可以主動獲取媒體信息、播放狀態(tài)等數(shù)據(jù):

AVMetadata avMetadata = avController.getAVMetadata();
AVPlaybackState avPlaybackState = avController.getAVPlaybackState();
// ...

創(chuàng)建服務(wù)端

在服務(wù)端 AVService 中聲明 AVSession 和 Player。

public class AVService extends AVBrowserService {
    // 媒體會話
    private AVSession avSession;
    // 媒體播放器
    private Player player;

 
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        avSession = new AVSession(this, "AVService");
        setAVToken(avSession.getAVToken());
        // 設(shè)置 sessioncallback,用于響應(yīng)客戶端的媒體控制器發(fā)起的播放控制指令。
        avSession.setAVSessionCallback(avSessionCallback);
        // 設(shè)置播放狀態(tài)初始狀態(tài)為 AVPlaybackState.PLAYBACK_STATE_NONE。
        AVPlaybackState playbackState = new AVPlaybackState.Builder().setAVPlaybackState(AVPlaybackState.PLAYBACK_STATE_NONE, 0, 1.0f).build();
        avSession.setAVPlaybackState(playbackState);
        // 完成播放器的初始化,如果使用多個(gè)  Player,也可以在執(zhí)行播放時(shí)初始化。
        player = new Player(this);
    }
    @Override
    public AVBrowserRoot onGetRoot(String clientPackageName, int clientUid, PacMap rootHints) {
        // 響應(yīng)客戶端 avBrowser.connect()方法。若同意連接,則返回有效的AVBrowserRoot實(shí)例,否則返回null
        return new AVBrowserRoot(AV_ROOT, null);
    }
    @Override
    public void onLoadAVElementList(String parentId, AVBrowserResult result) {
         LogUtil.info(TAG, "onLoadChildren");
         // 響應(yīng)客戶端avBrowser.subscribeByParentMediaId(...)方法。
         // 先執(zhí)行該方法detachForRetrieveAsync() 
         result.detachForRetrieveAsync();
         // externalAudioItems緩存媒體文件,請開發(fā)者自行實(shí)現(xiàn)。
         result.sendAVElementList(externalAudioItems.getAudioItems());
    }
    @Override
    public void onLoadAVElementList(String s, AVBrowserResult avBrowserResult, PacMap pacMap) {
         // 響應(yīng)客戶端avBrowser.subscribeByParentMediaId(String, PacMap, AVSubscriptionCallback)方法。
    }
    @Override
    public void onLoadAVElement(String s, AVBrowserResult avBrowserResult) {
        // 響應(yīng)客戶端avBrowser.getAVElement(String, AVElementCallback)方法。
    }
}

響應(yīng)客戶端的媒體控制器發(fā)起的播放控制指令的回調(diào)實(shí)現(xiàn)。

private AVSessionCallback avSessionCallback = new AVSessionCallback() {
    @Override
    public void onPlay() {
        super.onPlay();
        // 當(dāng)客戶端調(diào)用avController.getPlayControls().play()時(shí),該方法會被回調(diào)。
        // 響應(yīng)播放請求,開始播放。
        if (avSession.getAVController().getAVPlaybackState().getAVPlaybackState() == AVPlaybackState.PLAYBACK_STATE_PAUSED) {
            if (player.play()) {
                AVPlaybackState playbackState = new AVPlaybackState.Builder().setAVPlaybackState(
                    AVPlaybackState.PLAYBACK_STATE_PLAYING, player.getCurrentTime(),
                    player.getPlaybackSpeed()).build();
                avSession.setAVPlaybackState(playbackState);
            }
        }
    }
    @Override
    public void onPause() {
        super.onPause();
        // 當(dāng)客戶端調(diào)用avController.getPlayControls().pause()時(shí),該方法會被回調(diào)。
        // 響應(yīng)暫停請求,暫停播放。
    }
    @Override
    public void onPlayNext() {
        super.onPlayNext();
        // 當(dāng)客戶端調(diào)用avController.getPlayControls().playNext()時(shí),該方法會被回調(diào)。
        // 響應(yīng)播放下一曲請求,通過avSession.setAVMetadata 設(shè)置下一曲曲目的信息。
        avSession.setAVMetadata(avNextMetadata);
    }
    // 重寫以處理按鍵事件
    @Override
    public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
        KeyEvent ke = mediaButtonIntent.getParcelableParam(AVSession.PARAM_KEY_EVENT);
        if (ke == null) {
            LogUtil.error("onMediaButtonEvent", "getParcelableParam failed");
            return false;
        }
        if (ke.isKeyDown()) {
            // 只處理按鍵抬起事件
            return true;
        }

 
        switch (ke.getKeyCode()) {
            case KeyEvent.KEY_MEDIA_PLAY_PAUSE: {
                if (playbackState.getAVPlaybackState() == AVPlaybackState.PLAYBACK_STATE_PAUSED) {
                    onPlay();
                    break;
                }
                if (playbackState.getAVPlaybackState() == AVPlaybackState.PLAYBACK_STATE_PLAYING) {
                    onPause();
                    break;
                }
                break;
            }
            case KeyEvent.KEY_MEDIA_PLAY: {
                onPlay();
                break;
            }
            case KeyEvent.KEY_MEDIA_PAUSE: {
                onPause();
                break;
            }
            case KeyEvent.KEY_MEDIA_STOP: {
                onStop();
                break;
            }
            case KeyEvent.KEY_MEDIA_NEXT: {
                onPlayNext();
                break;
            }
            case KeyEvent.KEY_MEDIA_PREVIOUS: {
                onPlayPrevious();
                break;
            }
            default: {
                break;
            }
        }
        return true;
    }
    // 其它回調(diào)方法(可選)
    // ...
}
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號