HarmonyOS学习路之开发篇—多媒体开发(媒体会话管理开发)

2023-10-15 08:51:24 浏览数 (1)

一、媒体会话管理开发

AVSession是一套媒体播放控制框架,对媒体服务和界面进行解耦,并提供规范的通信接口,使应用可以自由、高效地在不同的媒体之间完成切换。

约束与限制

  • 在使用完AVSession类后,需要及时进行资源释放。
  • 播放器类需要使用ohos.media.player.Player,否则无法正常接收按键事件。

场景介绍

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

  • AVBrowser 媒体浏览器,通常在客户端创建,成功连接媒体服务后,通过媒体控制器AVController向服务端发送播放控制指令。 其主要流程为,调用connect方法向AVBrowserService发起连接请求,连接成功后在回调方法AVConnectionCallback.onConnected中发起订阅数据请求,并在回调方法AVSubscriptionCallback.onAVElementListLoaded中保存请求的媒体播放数据。 调用AVBrowser的subscribeByParentMediaId(String, AVSubscriptionCallback)之前,需要先执行unsubscribeByParentMediaId(String),防止重复订阅。
  • AVController 媒体控制器,在客户端AVBrowser连接服务成功后的回调方法AVConnectionCallback.onConnected中创建,用于向Service发送播放控制指令,并通过实现AVControllerCallback回调来响应服务端媒体状态变化,例如曲目信息变更、播放状态变更等,从而完成UI刷新。
  • AVBrowserService 媒体浏览器服务,通常在服务端,通过媒体会话 AVSession 与媒体浏览器建立连接,并通过实现 Player 进行媒体播放。其中有两个重要的方法:
    1. onGetRoot,处理从媒体浏览器AVBrowser发来的连接请求,通过返回一个有效的AVBrowserRoot对象表示连接成功;
    2. onLoadAVElementList,处理从媒体浏览器AVBrowser发来的数据订阅请求,通过 AVBrowserResult.sendAVElementList(List<AVElement>) 方法返回媒体播放数据。 使用onLoadAVElementList(String, AVBrowserResult)的result返回数据前,需要执行detachForRetrieveAsync()。
  • AVSession 媒体会话,通常在AVBrowserService的onStart中创建,通过setAVToken方法设置到AVBrowserService中,并通过实现AVSessionCallback回调来接收和处理媒体控制器 AVController 发送的播放控制指令,如播放、暂停、跳转至上一曲、跳转至下一曲等。 除了上述四个类,AVSession框架还有AVElement。
  • AVElement 媒体元素,用于将播放列表从AVBrowserService传递给AVBrowser。

接口说明

AVBrowser的主要接口

接口名

描述

AVBrowser(Context context, ElementName name, AVConnectionCallback callback, PacMap options)

构造AVBrowser实例,用于浏览AVBrowserService提供的媒体数据。

connect()

连接AVBrowserService。

disconnect()

与AVBrowserService断开连接。

isConnected()

判断当前是否已经与AVBrowserService连接。

getElementName()

获取AVBrowserService的ohos.bundle.ElementName实例。

getRootMediaId()

获取默认媒体id。

getOptions()

获取AVBrowserService提供的附加数据。

getAVToken()

获取媒体会话的令牌。

getAVElement(String mediaId, AVElementCallback callback)

输入媒体的id,查询对应的ohos.media.common.sessioncore.AVElement信息,查询结果会通过callback返回。

subscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback)

查询指定媒体id包含的所有媒体元素信息,并订阅它的媒体信息更新通知。

subscribeByParentMediaId(String parentMediaId, PacMap options, AVSubscriptionCallback callback)

基于特定于服务的参数来查询指定媒体id中的媒体元素的信息,并订阅它的媒体信息更新通知。

unsubscribeByParentMediaId(String parentMediaId)

取消订阅对应媒体id的信息更新通知。

unsubscribeByParentMediaId(String parentMediaId, AVSubscriptionCallback callback)

取消订阅与指定callback相关的媒体id的信息更新通知。

AVBrowserService的主要接口

接口名

描述

onGetRoot(String callerPackageName, int clientUid, PacMap options)

回调方法,用于返回应用程序的媒体内容的根信息,在AVBrowser.connect()后进行回调。

onLoadAVElementList(String parentMediaId, AVBrowserResult result)

回调方法,用于返回应用程序的媒体内容的结果信息AVBrowserResult,其中包含了子节点的AVElement列表,在AVBrowser的方法subscribeByParentMediaId或notifyAVElementListUpdated执行后进行回调。

onLoadAVElement(String mediaId, AVBrowserResult result)

回调方法,用于获取特定的媒体项目AVElement结果信息,在AVBrowser.getAVElement方法执行后进行回调。

getAVToken()

获取AVBrowser与AVBrowserService之间的会话令牌。

setAVToken(AVToken token)

设置AVBrowser与AVBrowserService之间的会话令牌。

getBrowserOptions()

获取AVBrowser在连接AVBrowserService时设置的服务参数选项。

getCallerUserInfo()

获取当前发送请求的调用者信息。

notifyAVElementListUpdated(String parentMediaId)

通知所有已连接的AVBrowser当前父节点的子节点已经发生改变。

notifyAVElementListUpdated(String parentId, PacMap options)

通知所有已连接的AVBrowser当前父节点的子节点已经发生改变,可设置服务参数。

AVController的主要接口

接口名

描述

AVSession(Context context, String tag)

构造AVSession实例,用于控制媒体播放。

AVSession(Context context, String tag, PacMap sessionInfo)

构造带有附加会话信息的AVSession实例,用于控制媒体播放。

setAVSessionCallback(AVSessionCallback callback)

设置回调函数来控制播放器,控制逻辑由应用实现。如果callback为null则取消控制。

setAVSessionAbility(IntentAgent ia)

给AVSession设置一个IntentAgent,用来启动用户界面。

setAVButtonReceiver(IntentAgent ia)

为媒体按键接收器设置一个IntentAgent,以便应用结束后,可以通过媒体按键重新拉起应用。

enableAVSessionActive(boolean active)

设置是否激活媒体会话。当会话准备接收命令时,将输入参数设置为true。如果会话停止接收命令,则设置为false。

isAVSessionActive()

查询会话是否激活。

sendAVSessionEvent(String event, PacMap options)

向所有订阅此会话的控制器发送事件。

release()

释放资源,应用播放完之后需调用。

getAVToken()

获取应用连接到会话的令牌。此令牌用于创建媒体播放控制器。

getAVController()

获取会话构造时创建的控制器,方便应用使用。

setAVPlaybackState(AVPlaybackState state)

设置当前播放状态。

setAVMetadata(AVMetadata avMetadata)

设置媒体资源元数据ohos.media.common.AVMetadata。

setAVQueue(List<AVQueueElement> queue)

设置播放队列。

setAVQueueTitle(CharSequence queueTitle)

设置播放队列的标题,UI会显示此标题。

setOptions(PacMap options)

设置此会话关联的附加数据。

getCurrentControllerInfo()

获取发送当前请求的媒体控制器信息。

AVElement的主要接口

接口名

描述

AVElement(AVDescription description, int flags)

构造AVElement实例。

getFlags()

获取flags的值。

isScannable()

判断媒体是否可扫描,如:媒体有子节点,则可继续扫描获取子节点内容。

isPlayable()

检查媒体是否可播放。

getAVDescription()

获取媒体的详细信息。

getMediaId()

获取媒体的id。

开发步骤

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

创建客户端

在客户端AVClientAbility中声明avBrowser和avController,通过avBrowser并向服务端发送连接请求,然后将avController注册到ability。

代码语言:javascript复制
public class AVClientAbility extends Ability {
    // 媒体浏览器
    private AVBrowser avBrowser;
    // 媒体控制器
    private AVController avController;
    // 服务端回传的媒体列表
    List<AVElement> avElements;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // 用于指向媒体浏览器服务的包路径和类名
        ElementName elementName = new ElementName("", "com.samples.audioplayer", "com.samples.audioplayer.AVService");
        // connectionCallback在调用avBrowser.connect方法后进行回调。
        avBrowser = new AVBrowser(context, elementName, connectionCallback, null);
        // avBrowser发送对媒体浏览器服务的连接请求,connect方法需要确保当前处于断开连接状态。
        avBrowser.connect();
        // 将媒体控制器注册到ability以接收按键事件。
        AVController.setControllerForAbility(this, avController);
    }
}

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

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

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

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

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

代码语言:javascript复制
// 服务对客户端的媒体数据或播放状态变更后的回调 
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的方法向服务端发送播放控制指令。

代码语言:javascript复制
// 在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: {
            // ...
        }
    }
}

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

代码语言:javascript复制
avController.getPlayControls().playNext();
avController.getPlayControls().playPrevious();
avController.getPlayControls().playFastForward();
avController.getPlayControls().rewind();
avController.getPlayControls().seekTo(1000);
avController.getPlayControls().stop();
// ...

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

代码语言:javascript复制
AVMetadata avMetadata = avController.getAVMetadata();
AVPlaybackState avPlaybackState = avController.getAVPlaybackState();
// ...

创建服务端

在服务端AVService中声明AVSession和Player。

代码语言:javascript复制
public class AVService extends AVBrowserService {
    // 根媒体ID
    private static final String AV_ROOT_ID = "av_root_id";
    // 媒体会话
    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_ID, null);
    }
    @Override
    public void onLoadAVElementList(String parentId, AVBrowserResult result) {
         HiLog.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)方法。
    }
}

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

代码语言:javascript复制
private AVSessionCallback avSessionCallback = new AVSessionCallback() {
    @Override
    public void onPlay() {
        super.onPlay();
        // 当客户端调用avController.getPlayControls().play()时,该方法会被回调。
        // 响应播放请求,开始播放。
        if (avSession.getAVController().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.getSequenceableParam(AVSession.PARAM_KEY_EVENT);
        if (ke == null) {
            HiLog.error(TAG, "getSequenceableParam 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;
    }
    // 其它回调方法(可选)
    // ...
}

0 人点赞