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

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

一、媒体会话管理开发

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 进行媒体播放。其中有两个重要的方法:


onGetRoot,处理从媒体浏览器AVBrowser发来的连接请求,通过返回一个有效的AVBrowserRoot对象表示连接成功;

onLoadAVElementList,处理从媒体浏览器AVBrowser发来的数据订阅请求,通过 AVBrowserResult.sendAVElementList(List<AVElement>) 方法返回媒体播放数据。

使用onLoadAVElementList(String, AVBrowserResult)的result返回数据前,需要执行detachForRetrieveAsync()。


AVSession

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


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


AVElement

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


接口说明

AVBrowser的主要接口

image.pngimage.png

AVBrowserService的主要接口

image.pngimage.png

AVController的主要接口

image.png

image.png

AVElement的主要接口


image.png

开发步骤

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


创建客户端


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

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。


// 发起连接(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中保存服务端回传的媒体列表。

// 发起订阅信息(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的刷新。


// 服务对客户端的媒体数据或播放状态变更后的回调 
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 {
    // 根媒体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)方法。
    }
}


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


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;
    }
    // 其它回调方法(可选)
    // ...
}


相关文章
|
5月前
|
存储 JavaScript 前端开发
“纯血鸿蒙”要来了,赶紧入手学习吧
1月18日,华为宣布HarmonyOSNEXT鸿蒙星河版面向开发者开放申请,这一最新版本的鸿蒙系统也被喻为“纯血鸿蒙”。赶紧入手学习吧
297 1
|
2月前
|
JavaScript 前端开发 小程序
基于js开发快速学习鸿蒙基础
【8月更文挑战第26天】
39 1
|
3月前
|
存储 开发框架 安全
鸿蒙 HarmonyOS NEXT星河版APP应用开发-阶段一
HarmonyOS NEXT星河版的应用开发标志着华为分布式操作系统的全新篇章,它聚焦于打造原生精致、易用、流畅、安全、智能和互联的极致体验。开发者可以利用其先进的API和工具集,如DevEco Studio,构建高性能、跨设备无缝协同的应用程序,从而充分利用HarmonyOS的分布式能力,为用户带来一致且丰富的多场景数字生活体验。随着“学习强国”、岚图汽车、中国电信等知名企业和应用的加入,鸿蒙生态正迅速扩展,引领着原生应用开发的新趋势。
99 3
鸿蒙 HarmonyOS NEXT星河版APP应用开发-阶段一
|
3月前
|
人工智能 搜索推荐 数据可视化
鸿蒙应用实践:利用扣子API开发起床文案生成器
本文将使用扣子(coze)智能体API开发一个起床文案生成器,用于自己的鸿蒙应用中生成”千人千面“的起床文案。
79 0
|
5月前
|
JavaScript API
鸿蒙开发接口UI界面:【@ohos.mediaquery (媒体查询)】
鸿蒙开发接口UI界面:【@ohos.mediaquery (媒体查询)】
58 1
|
5月前
|
前端开发 JavaScript 开发者
鸿蒙2.0!用 JavaScript 开发鸿蒙应用
鸿蒙2.0!用 JavaScript 开发鸿蒙应用
128 0
|
5月前
|
存储 前端开发 IDE
【华为鸿蒙系统学习】- 如何利用鸿蒙系统进行App项目开发|自学篇
【华为鸿蒙系统学习】- 如何利用鸿蒙系统进行App项目开发|自学篇
251 0
|
5月前
|
数据管理 API 调度
【华为鸿蒙系统学习】- HarmonyOS4.0开发|自学篇
【华为鸿蒙系统学习】- HarmonyOS4.0开发|自学篇
550 0
|
IDE JavaScript API
HarmonyOS开发第一步,熟知开发工具DevEco Studio
本文主要以常见的功能点作为概述希望可以帮助到学习HarmonyOS的开发者。
463 0
|
开发框架 开发者 JavaScript
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 三)
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

热门文章

最新文章

下一篇
无影云桌面