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


相关文章
|
3月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
402 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
3月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
396 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
3月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
857 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
3月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
223 0
|
4月前
|
存储 缓存 5G
鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇
本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。
230 1
|
4月前
|
存储 负载均衡 数据库
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
本文介绍基于华为AGC的端云一体化开发流程,涵盖项目创建、云函数开通、应用配置及DevEco集成。重点讲解云函数的编写、部署、调用与传参,并涉及环境变量设置、负载均衡、重试机制与熔断策略等高阶特性,助力开发者高效构建稳定云端服务。
509 1
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
|
4月前
|
传感器 监控 安全
HarmonyOS NEXT 5.0 的星闪(NearLink)开发应用案例
V哥分享HarmonyOS NEXT 5.0星闪开发实战,涵盖智能车钥匙无感解锁与工业传感器监控。低延迟、高可靠,代码完整,速来学习!
723 0
|
7月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
6月前
|
安全 JavaScript API
鸿蒙开发核心要素
鸿蒙开发核心要素
|
7月前
|
存储 IDE 定位技术
【HarmonyOS 5】鸿蒙组件&模板服务详解 - 助力高效开发的利器
在移动应用开发领域,效率与质量始终是开发者追求的核心目标。鸿蒙系统作为新兴的操作系统,为开发者提供了丰富且强大的开发资源,其中鸿蒙组件&模板服务更是成为开发者快速构建高质量应用的得力助手。
279 0

热门文章

最新文章