阅读(2112) (16)

鸿蒙OS 媒体会话开发指导

2020-09-17 09:22:56 更新

场景介绍

AVSession 框架有四个主要的类,控制着整个框架的核心,下图简单的说明四个核心媒体框架控制类的关系。

img

  • AVBrowser

媒体浏览器,通常在客户端创建,成功连接媒体服务后,通过媒体控制器 AVBrowse r向服务端发送播放控制指令。

其主要流程为,调用 connect 方法向 AVBrowserService 发起连接请求,连接成功后在回调方法 AVConnectionCallback.onConnected 中发起订阅数据请求,并在回调方法 AVSubscriptionCallback.onAVElementListLoaded 中保存请求的媒体播放数据。

  • AVController

媒体控制器,在客户端 AVBrowser 连接服务成功后的回调方法 AVConnectionCallback.onConnected 中创建,用于向 Service 发送播放控制指令,并通过实现 AVControllerCallback 回调来响应服务端媒体状态变化,例如曲目信息变更、播放状态变更等,从而完成UI刷新。

  • AVBrowser

Service

媒体浏览器服务,通常在服务端,通过媒体会话 AVSession 与媒体浏览器建立连接,并通过实现 Player 进行媒体播放。其中有两个重要的方法:

  1. onGetRoot,处理从媒体浏览器 AVBrowser 发来的连接请求,通过返回一个有效的 AVBrowserRoot 对象表示连接成功;
  2. onLoadAVElementList,处理从媒体浏览器 AVBrowser 发来的数据订阅请求,通过 AVBrowserResult.sendAVElementList(List<AVElement>) 方法返回媒体播放数据。

  • AVSession

媒体会话,通常在 AVBrowserService 的 onStart 中创建,通过 setAVToken 方法设置到 AVBrowserService 中,并通过实现 AVSessionCallback 回调来接收和处理媒体控制器 AVController 发送的播放控制指令,如播放、暂停、跳转至上一曲、跳转至下一曲等。

除了上述四个类,AVSession 框架还有 AVElement。

  • AVElement

媒体元素,用于将播放列表从 AVBrowserService 传递给 AVBrowser。

接口说明

接口名 描述
AVBrowser(Context context, ElementName name, AVConnectionCallback callback, PacMap options) 构造 AVBrowser 实例,用于浏览 AVBrowserService 提供的媒体数据。
void connect() 连接 AVBrowserService。
void disconnect() 与 AVBrowserService 断开连接。
boolean isConnected() 判断当前是否已经与 AVBrowserService 连接。
ElementName getElementName() 获取 AVBrowserService 的ohos.bundle.ElementName实例。
String getRootMediaId() 获取默认媒体 id。
PacMap getOptions() 获取 AVBrowserService 提供的附加数据。
AVToken getAVToken() 获取媒体会话的令牌。
void getAVElement(String mediaId, AVElementCallback callback) 输入媒体的 id,查询对应的 ohos.media.common.sessioncore.AVElement 信息,查询结果会通过 callback 返回。
void subscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback) 查询指定媒体 id 包含的所有媒体元素信息,并订阅它的媒体信息更新通知。
void subscribeByParentMediaId(String parentMediaId, PacMap options, AVSubscriptionCallback callback) 基于特定于服务的参数来查询指定媒体 id 中的媒体元素的信息,并订阅它的媒体信息更新通知。
void unsubscribeByParentMediaId(String parentMediaId) 取消订阅对应媒体 id 的信息更新通知。
void unsubscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback) 取消订阅与指定 callback 相关的媒体 id 的信息更新通知。
接口名 描述
abstract AVBrowserRoot onGetRoot(String callerPackageName, int clientUid, PacMap options) 回调方法,用于返回应用程序的媒体内容的根信息,在 AVBrowser.connect()后进行回调。
abstract void onLoadAVElementList(String parentMediaId, AVBrowserResult result) 回调方法,用于返回应用程序的媒体内容的结果信息 AVBrowserResult,其中包含了子节点的 AVElement 列表,在 AVBrowser 的方法 subscribeByParentMediaId 或 notifyAVElementListUpdated 执行后进行回调。
abstract void onLoadAVElement(String mediaId, AVBrowserResult result) 回调方法,用于获取特定的媒体项目 AVElement 结果信息,在 AVBrowser.getAVElement 方法执行后进行回调。
AVToken getAVToken() 获取 AVBrowser 与 AVBrowserService 之间的会话令牌。
void setAVToken(AVToken token) 设置 AVBrowser 与 AVBrowserService 之间的会话令牌。
final PacMap getBrowserOptions() 获取 AVBrowser 在连接 AVBrowserService 时设置的服务参数选项。
final AVRemoteUserInfo getCallerUserInfo() 获取当前发送请求的调用者信息。
void notifyAVElementListUpdated(String parentMediaId) 通知所有已连接的 AVBrowser 当前父节点的子节点已经发生改变。
void notifyAVElementListUpdated(String parentId, PacMap options) 通知所有已连接的 AVBrowser 当前父节点的子节点已经发生改变,可设置服务参数。
接口名 描述
AVController(Context context, AVToken avToken) 构造 AVController 实例,用于应用程序与 AVSession 进行交互以控制媒体播放。
static boolean setControllerForAbility(Ability ability, AVController controller) 将媒体控制器注册到 ability 以接收按键事件。
boolean setAVControllerCallback(AVControllerCallback callback) 注册一个回调以接收来自 AVSession 的变更,例如元数据和播放状态变更。
boolean releaseAVControllerCallback(AVControllerCallback callback) 释放与 AVSession 之间的回调实例。
List<AVQueueElement> getAVQueueElement() 获取播放队列。
CharSequence getAVQueueTitle() 获取播放队列的标题。
AVPlaybackState getAVPlaybackState() 获取播放状态。
boolean dispatchAVKeyEvent(KeyEvent keyEvent) 应用分发媒体按键事件给会话以控制播放。
void sendCustomCommand(String command, PacMap pacMap, GeneralReceiver receiverCb) 应用向 AVSession 发送自定义命令,参考ohos.media.common.sessioncore.AVSessionCallback.onCommand。
IntentAgent getAVSessionAbility() 获取启动用户界面的IntentAgent。
AVToken getAVToken() 获取应用连接到会话的令牌。此令牌用于创建媒体播放控制器。
void adjustAVPlaybackVolume(int direction, int flags) 调节播放音量。
void setAVPlaybackVolume(int value, int flags) 设置播放音量,要求支持绝对音量控制。
PacMap getOptions() 获取与此控制器连接的AVSession的附加数据。
long getFlags() 获取 AVSession 的附加标识,标记在 AVSession 中的定义。
AVMetadata getAVMetadata() 获取媒体资源的元数据ohos.media.common.AVMetadata。
AVPlaybackInfo getAVPlaybackInfo() 获取播放信息。
String getSessionOwnerPackageName() 获得 AVSession 实例的应用程序的包名称。
PacMap getAVSessionInfo() 获取会话的附加数据。
PlayControls getPlayControls() 获取一个 PlayControls 实例,将用于控制播放,比如控制媒体播放、停止、下一首等。
接口名 描述
AVSession(Context context, String tag) 构造 AVSession 实例,用于控制媒体播放。
AVSession(Context context, String tag, PacMap sessionInfo) 构造带有附加会话信息的 AVSession 实例,用于控制媒体播放。
void setAVSessionCallback(AVSessionCallback callback) 设置回调函数来控制播放器,控制逻辑由应用实现。如果 callback 为 null 则取消控制。
boolean setAVSessionAbility(IntentAgent ia) 给 AVSession 设置一个IntentAgent,用来启动用户界面。
boolean setAVButtonReceiver(IntentAgent ia) 为媒体按键接收器设置一个 IntentAgent,以便应用结束后,可以通过媒体按键重新拉起应用。
void enableAVSessionActive(boolean active) 设置是否激活媒体会话。当会话准备接收命令时,将输入参数设置为 true。如果会话停止接收命令,则设置为 false。
boolean isAVSessionActive() 查询会话是否激活。
void sendAVSessionEvent(String event, PacMap options) 向所有订阅此会话的控制器发送事件。
void release() 释放资源,应用播放完之后需调用。
AVToken getAVToken() 获取应用连接到会话的令牌。此令牌用于创建媒体播放控制器。
AVController getAVController() 获取会话构造时创建的控制器,方便应用使用。
void setAVPlaybackState(AVPlaybackState state) 设置当前播放状态。
void setAVMetadata(AVMetadata avMetadata) 设置媒体资源元数据ohos.media.common.AVMetadata。
void setAVQueue(List<AVQueueElement> queue) 设置播放队列。
void setAVQueueTitle(CharSequence queueTitle) 设置播放队列的标题,UI会显示此标题。
void setOptions(PacMap options) 设置此会话关联的附加数据。
AVCallerUserInfo getCurrentControllerInfo() 获取发送当前请求的媒体控制器信息。
接口名 描述
AVElement(AVDescription description, int flags) 构造 AVElement 实例。
int getFlags() 获取flags的值。
boolean isScannable() 判断媒体是否可扫描,如:媒体有子节点,则可继续扫描获取子节点内容。
boolean isPlayable() 检查媒体是否可播放。
AVDescription getAVDescription() 获取媒体的详细信息。
String getMediaId() 获取媒体的id。

开发步骤

使用 AVSession 媒体框架创建一个播放器示例,分为创建客户端和创建服务端。

创建客户端

在客户端 AVClientAbility 中声明 AVBrowser 和 AVController,并向服务端发送连接请求。

public class AVClientAbility extends Ability {
    // 媒体浏览器
    private AVBrowser avBrowser;
    // 媒体控制器
    private AVController avController;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // 用于指向媒体浏览器服务的包路径和类名
        ElementName elementName = new ElementName("", "com.huawei.samples.audioplayer", "com.huawei.samples.audioplayer.AVService");
        // connectionCallback 在调用 avBrowser.connect 方法后进行回调。
        avBrowser = new AVBrowser(context, elementName, connectionCallback, null);
        // avBrowser 发送对媒体浏览器服务的连接请求。
        avBrowser.connect();
        // 将媒体控制器注册到 ability 以接收按键事件。
        AVController.setControllerForAbility(this, avController);
    }
}

AVConnectionCallback 回调接口中的方法为可选实现,通常需要会在 onConnected 中订阅媒体数据和创建媒体控制器 AVController。

// 发起连接(avBrowser.connect)后的回调方法实现
private AVConnectionCallback connectionCallback = new AVConnectionCallback() {
    @Override
    public void onConnected() {
        // 成功连接媒体浏览器服务时回调该方法,否则回调 onConnectionFailed()。
        // 重复订阅会报错,所以先解除订阅。
        avBrowser.unsubscribeByParentMediaId(avBrowser.getRootMediaId());
        // 第二个参数 AVSubscriptionCallback,用于处理订阅信息的回调。
        avBrowser.subscribeByParentMediaId(AV_ROOTavBrowser.getRootMediaId(), avSubscriptionCallback);
        AVToken token = avBrowser.getAVToken();
        avController = new AVController(AVClient.this, token); // AVController第一个参数为当前类的context
        // 参数 AVControllerCallback,用于处理服务端播放状态及信息变化时回调。
        avController.setAVControllerCallback(avControllerCallback);
        // ...
    }
    // 其它回调方法(可选)
    // ...
};

通常在订阅成功时,在 AVSubscriptionCallback 回调接口 onAVElementListLoaded 中保存服务端回传的媒体列表。

// 发起订阅信息(avBrowser.subscribeByParentMediaId)后的回调方法实现
private AVSubscriptionCallback avSubscriptionCallback = new AVSubscriptionCallback() {
    @Override
    public void onAVElementListLoaded(String parentId, List<AVElement> children) {
        // 订阅成功时回调该方法,parentID 为标识,children 为服务端回传的媒体列表
        super.onAVElementListLoaded(parentId, children);
        list.addAll(children);
        // ...
    }
};

AVControllerCallback 回调接口中的方法均为可选方法,主要用于服务端播放状态及信息的变化后对客户端的回调,客户端可在这些方法中实现UI的刷新。

// 服务对客户端的媒体数据或播放状态变更后的回调 
 private AVControllerCallback     avControllerCallback = new  AVControllerCallback() {
    @Override
    public void onAVMetadataChanged(AVMetadata metadata) {
        // 当服务端调用 avSession.setAVMetadata(avMetadata)时,此方法会被回调。
        super.onAVMetadataChanged(metadata);
        AVDescription description = metadata.getAVDescription();
        String title = description.getTitle().toString();
        PixelMap pixelMap = description.getIcon();
        // ...
    }
    @Override
    public void onAVPlaybackStateChanged(AVPlaybackState playbackState) {
        // 当服务端调用avSession.setAVPlaybackState(...)时,此方法会被回调。
        super.onAVPlaybackStateChanged(playbackState);
        long position = playbackState.getCurrentPosition();
        // ...
    }
    // 其它回调方法(可选)
    // ...
};

完成以上实现后,则应用可以在UI事件中调用avController的方法向服务端发送播放控制指令。

// 在 UI 播放与暂停按钮的点击事件中向服务端发送播放或暂停指令
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: {
            // ...
        }
    }
}

其它播放控制根据业务是否需要实现,比如:

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

也可以主动获取媒体信息、播放状态等数据:

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

创建服务端

在服务端 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());
        // 设置 sessioncallback,用于响应客户端的媒体控制器发起的播放控制指令。
        avSession.setAVSessionCallback(avSessionCallback);
        // 设置播放状态初始状态为 AVPlaybackState.PLAYBACK_STATE_NONE。
        AVPlaybackState playbackState = new AVPlaybackState.Builder().setAVPlaybackState(AVPlaybackState.PLAYBACK_STATE_NONE, 0, 1.0f).build();
        avSession.setAVPlaybackState(playbackState);
        // 完成播放器的初始化,如果使用多个  Player,也可以在执行播放时初始化。
        player = new Player(this);
    }
    @Override
    public AVBrowserRoot onGetRoot(String clientPackageName, int clientUid, PacMap rootHints) {
        // 响应客户端 avBrowser.connect()方法。若同意连接,则返回有效的AVBrowserRoot实例,否则返回null
        return new AVBrowserRoot(AV_ROOT, null);
    }
    @Override
    public void onLoadAVElementList(String parentId, AVBrowserResult result) {
         LogUtil.info(TAG, "onLoadChildren");
         // 响应客户端avBrowser.subscribeByParentMediaId(...)方法。
         // 先执行该方法detachForRetrieveAsync() 
         result.detachForRetrieveAsync();
         // externalAudioItems缓存媒体文件,请开发者自行实现。
         result.sendAVElementList(externalAudioItems.getAudioItems());
    }
    @Override
    public void onLoadAVElementList(String s, AVBrowserResult avBrowserResult, PacMap pacMap) {
         // 响应客户端avBrowser.subscribeByParentMediaId(String, PacMap, AVSubscriptionCallback)方法。
    }
    @Override
    public void onLoadAVElement(String s, AVBrowserResult avBrowserResult) {
        // 响应客户端avBrowser.getAVElement(String, AVElementCallback)方法。
    }
}

响应客户端的媒体控制器发起的播放控制指令的回调实现。

private AVSessionCallback avSessionCallback = new AVSessionCallback() {
    @Override
    public void onPlay() {
        super.onPlay();
        // 当客户端调用avController.getPlayControls().play()时,该方法会被回调。
        // 响应播放请求,开始播放。
        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();
        // 当客户端调用avController.getPlayControls().pause()时,该方法会被回调。
        // 响应暂停请求,暂停播放。
    }
    @Override
    public void onPlayNext() {
        super.onPlayNext();
        // 当客户端调用avController.getPlayControls().playNext()时,该方法会被回调。
        // 响应播放下一曲请求,通过avSession.setAVMetadata 设置下一曲曲目的信息。
        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;
    }
    // 其它回调方法(可选)
    // ...
}