距离 Harmony OS 发布已过去了一段时间,为了了解鸿蒙系统的功能与特性,今天我们将准备使用系统 API 实现一个简单的媒体播放器 demo。

大家在阅读本文后会对媒体播放器相关的 API 有一定的了解,并且可以根据文中的步骤一起动手实操,实现在鸿蒙系统上的简单媒体播放器!

VideoPlayerDemo 仓库地址:VideoPlayerDemo: 鸿蒙系统 API 实现简单媒体播放器

话不多说,下面我将带领大家一起看一下媒体播放器的实现流程。

一、媒体资源文件的权限申请

Harmony OS 中所有的应用均在应用沙盒内运行。默认情况下,应用只能访问有限的系统资源,系统负责管理应用对资源的访问权限。

所以要实现媒体播放器功能当然需要访问媒体资源,这时就需要向操作系统申请权限:

1、需要在 config.json 中的 ""reqPermissions" 添加配置:

"reqPermissions": [{"name": "ohos.permission.READ_USER_STORAGE"}
]

2、需要在程序启动时请求读取本地媒体资源的权限:

if (verifyCallingOrSelfPermission(SystemPermission.READ_USER_STORAGE) != IBundleManager.PERMISSION_GRANTED) {requestPermissionsFromUser(new String[]{SystemPermission.READ_USER_STORAGE}, REQUEST_CODE);
}

二、获取本地的媒体资源文件

在申请到媒体资源的权限后,我们就可以去获取本地的媒体资源信息,包括音频及视频。

我们可以通过以下操作,将所有的音视频文件标识为 AVElement 并存入 List<AVElement> 中方便我们做 UI 展示,稍后可通过 AVElement 获取媒体资源文件路径,传递给系统播放器。

private final List<AVElement> avElements = new ArrayList<>();public VideoElementManager(Context context) {loadAudioFromMediaLibrary(context);loadVideoFromMediaLibrary(context);
}// 查找音频
private void loadAudioFromMediaLibrary(Context context) {Uri audioUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;DataAbilityHelper helper = DataAbilityHelper.creator(context, audioUri, false);try {ResultSet resultSet = helper.query(audioUri, null, null);LogUtil.info(TAG, "The audio result size: " + resultSet.getRowCount());processResult(resultSet);resultSet.close();} catch (DataAbilityRemoteException e) {LogUtil.error(TAG, "Query system media failed.");} finally {helper.release();}
}// 查找视频
private void loadVideoFromMediaLibrary(Context context) {Uri videoUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;DataAbilityHelper helper = DataAbilityHelper.creator(context, videoUri, false);try {ResultSet resultSet = helper.query(videoUri, null, null);LogUtil.info(TAG, "The video result size: " + resultSet.getRowCount());processResult(resultSet);resultSet.close();} catch (DataAbilityRemoteException e) {LogUtil.error(TAG, "Query system media failed.");} finally {helper.release();}
}// 处理数据到 List<AVElement>
private void processResult(ResultSet resultSet) {while (resultSet.goToNextRow()) {String path = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DATA));String title = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.TITLE));AVDescription bean =new AVDescription.Builder().setTitle(title).setIMediaUri(Uri.parse(path)).setMediaId(path).build();avElements.add(new AVElement(bean, AVElement.AVELEMENT_FLAG_PLAYABLE));}
}

三、简单的播放功能实现

通过以上的步骤,我们已经获取了媒体资源权限,并且获取到本地的所有音视频,下面我们将通过调用系统 API 实现简单的播放功能。

下图为媒体播放器界面:

通过上面的 UI 界面,我们可以清晰的看出要实现哪些简单功能,例如播放、暂停、恢复、跳转、音量调节、倍速播放和播放进度更新。

下面将给大家详细介绍以上这些功能的具体实现方法。

1、渲染 View 准备

需要播放媒体资源,特别是视频资源,我们当然需要有 View 去渲染,鸿蒙系统 API 需要设置的 View 是 Surface,我们通过以下方法获取 Surface。

// 声明 SurfaceProvider, Surface
private SurfaceProvider surfaceProvider;
private DirectionalLayout playViewLayout;
private Surface surface;private void addSurfaceProvider() {surfaceProvider = new SurfaceProvider(this);if (surfaceProvider.getSurfaceOps().isPresent()) {surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());surfaceProvider.pinToZTop(true);}
}// 生成 Surface 的回调
class SurfaceCallBack implements SurfaceOps.Callback {@Overridepublic void surfaceCreated(SurfaceOps callbackSurfaceOps) {if (surfaceProvider.getSurfaceOps().isPresent()) {surface = surfaceProvider.getSurfaceOps().get().getSurface();LogUtil.info(TAG, "surface set");}}@Overridepublic void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {LogUtil.info(TAG, "surface changed");}@Overridepublic void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {LogUtil.info(TAG, "surface destroyed");}
}
playViewLayout = (DirectionalLayout) findComponentById(ResourceTable.Id_playViewLayout);
playViewLayout.addComponent(surfaceProvider);

2、播放

我们获取到 Surface 后就可以调用系统 API 进行播放了。以下是对系统 API 的简单封装,我们可以调用 play 接口播放媒体资源。

private Player videoPlayer;
private Runnable videoRunnable;public synchronized void play(AVElement avElement, Surface surface) {if (videoPlayer != null) {// 关闭当前播放资源// release 会重置播放状态并关闭定时器videoPlayer.stop();videoPlayer.release();videoPlayer = null;}if (videoRunnable != null) {ThreadPoolManager.getInstance().cancel(videoRunnable);}videoPlayer = new Player(context);setPlayerCallback();videoRunnable = () -> playInner(avElement, surface);ThreadPoolManager.getInstance().execute(videoRunnable);LogUtil.info(TAG, "play");
}private void playInner(AVElement avElement, Surface surface) {Source source = new Source(avElement.getAVDescription().getMediaUri().toString());videoPlayer.setSource(source);videoPlayer.setVideoSurface(surface);LogUtil.info(TAG, source.getUri());videoPlayer.prepare();videoPlayer.play();playbackState = PlaybackState.VIDEO_PLAYER_PLAYING;startTimer();
}// 设置 Callback
private void setPlayerCallback() {videoPlayer.setPlayerCallback(new Player.IPlayerCallback() {@Overridepublic void onPrepared() {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onPrepared();}});}@Overridepublic void onMessage(int type, int extra) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onMessage(type, extra);}});}@Overridepublic void onError(int errorType, int errorCode) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onError(errorType, errorCode);}});}@Overridepublic void onResolutionChanged(int width, int height) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onResolutionChanged(width, height);}});}@Overridepublic void onPlayBackComplete() {playbackState = PlaybackState.VIDEO_PLAYER_PLAY_ENDED;endTimer();if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onPlayBackComplete();}});}@Overridepublic void onRewindToComplete() {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onRewindToComplete();}});}@Overridepublic void onBufferingChange(int percent) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onBufferingChange(percent);}});}@Overridepublic void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onNewTimedMetaData(mediaTimedMetaData);}});}@Overridepublic void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {if (eventHandler == null) {return;}EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onMediaTimeIncontinuity(mediaTimeInfo);}});}});
}

3、暂停、恢复和跳转

实现一个媒体播放器,我们当然是需要暂停、恢复和跳转功能,以下是简单的封装。

这里有一个问题大家需要注意,虽然系统 API 中 getCurrentTime 接口返回值单位是毫秒,但是跳转接口 rewindTo 的接口参数单位是微秒。

// 暂停
public synchronized void pause() {if (videoPlayer == null) {return;}videoPlayer.pause();playbackState = PlaybackState.VIDEO_PLAYER_PAUSING;endTimer();LogUtil.info(TAG, "pause");
}// 恢复
public synchronized void resume() {if (videoPlayer == null) {return;}videoPlayer.play();playbackState = PlaybackState.VIDEO_PLAYER_PLAYING;startTimer();LogUtil.info(TAG, "resume");
}// 跳转
public synchronized void seekTo(long millisecond) {if (videoPlayer == null) {return;}// 注意,rewindTo 接口参数单位是微秒videoPlayer.rewindTo(millisecond * 1000);LogUtil.info(TAG, "seek" + videoPlayer.getCurrentTime());
}

4、时长获取和播放进度显示

我们对获取时长接口做了简单的封装,但是系统 API 中没有相关的播放进度回调,为了进行 UI 展示,我们需要自己维护一个播放状态和定时器去定时获取当前播放进度。

// 获取资源总时长,需要收到 onPrepared 回调后调用
public synchronized int getDuration() {if (videoPlayer == null) {return 0;}return videoPlayer.getDuration();
}private Timer timer;
private PlaybackState playbackState;private void startTimer() {if (timer == null) {timer = new Timer("PlaybackPorgressTimer");timer.schedule(new TimerTask() {@Overridepublic void run() {playbackProgressUpdate();}}, 200L, 200L);}
}private void endTimer() {if (timer == null) {return;}timer.cancel();timer = null;
}private void playbackProgressUpdate() {if (eventHandler == null || videoPlayer == null) {return;}int currentPlaybackProgress = videoPlayer.getCurrentTime();EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());handler.postTask(new Runnable() {@Overridepublic void run() {eventHandler.onPlaybackProgressUpdate(currentPlaybackProgress);}});
}

5、音量调节和倍速播放

鸿蒙播放器系统 API 同样暴露了音量调节和倍速播放的接口,不过需要注意的是音量调节的系统 API 参数范围是 0.0~1.0,改接口仅控制播放器的音量,与手机侧边音量控制无关,有兴趣的话可以自己测试一下。

public synchronized void setPlaybackVolume(int volume) {if (videoPlayer == null) {return;}videoPlayer.setVolume((float) ((float) volume/100.0));
}public synchronized void setPlaybackSpeed(float speed) {if (videoPlayer == null) {return;}videoPlayer.setPlaybackSpeed(speed);
}public synchronized float getPlaybackSpeed() {if (videoPlayer == null) {return 0.0f;}return videoPlayer.getPlaybackSpeed();
}

四、总结

通过调用系统 API 实现一个简单的媒体播放器,相信大家一定对 Harmony OS都有了初步的了解与认识,比如说真正意义上的跑起来一个鸿蒙 APP,中间经过了开发者账号的申请、APP 证书的申请等等。

在11月底,ZEGO 即构科技首发适配鸿蒙系统的 Express SDK 1.0 版本,并正式启动了公测,这其中包括摄像头、麦克风、扬声器等设备的深度兼容与适配,实现了更加稳定、快速的设备管理能力,感兴趣可点击链接了解:ZEGO 即构科技首发适配鸿蒙系统的 Express SDK 1.0 版本,并正式启动公测!

鸿蒙应用开发:实现简单的媒体播放器相关推荐

  1. 一个基于Android开发的简单的音乐播放器

    一个基于Android开发的简单的音乐播放器 记得当时老师让我们写因为播放器时,脑子一头雾水,网上杂七杂八的资料也很少有用,因此索性就自己写一篇,希望对有缘人有用. 因为有好多人问我要源码,所以附上g ...

  2. java实战——简单的媒体播放器

    这个是用jmf来做的,但是由于jmf对视频资源支持的问题所以能用的类型没有那么多,它支持的格式有下面这些. · D indicates the format can be decoded and pr ...

  3. 【开发一个简单的音乐播放器+服务端】【一】

  4. android 播放器 wav 无法播放,对于Android媒体播放器mp3与wav(For android media player mp3 vs. wav)...

    对于Android媒体播放器mp3与wav(For android media player mp3 vs. wav) 我想知道在Android媒体播放器上加载和播放小wav是否比较快的小文件更快. ...

  5. 移植ffplay媒体播放器和sdl2到Android平台

    前言 前述博文<基于FFmpeg和Android的音视频同步播放实现>中,我们按照自己的方法实现了一个简单的媒体播放器,并做了音视频同步.但是这个程序在播放更多码流时,遇到了一些问题,包括 ...

  6. 教程:媒体播放器SKIN制作全攻略[原创]

    因为不太知道怎样的教程比较合用,所以我想把这个教程做成互动式的,一部分一部分地放上来,大家可以对这篇文档提出自己的建议和要求,我根据大家的需求来改,目前拟定的目录如下: 微软媒体播放器SKIN制作教程 ...

  7. Plyr – 简单,灵活的 HTML5 媒体播放器

    Plyr 是一个简单的 HTML5 媒体播放器,包含自定义的控制选项和 WebVTT 字幕.它是只支持现代浏览器,轻量,方便和可定制的媒体播放器.还有的标题和屏幕阅读器的全面支持. 在线演示      ...

  8. C#使用APlayer开发自制媒体播放器

    首先简单来了解下什么是APlayer.下面的内容你都可以通过http://aplayer.open.xunlei.com/轻松地进行查看. 引擎介绍 APlayer 媒体播放引擎是迅雷公司从 2009 ...

  9. 好程序员前端分享使用JS开发简单的音乐播放器

    好程序员前端分享使用JS开发简单的音乐播放器,最近,我们在教学生使用JavaScript,今天就带大家开发一款简单的音乐播放器.首先,最终效果如图所示: 首先,我们来编写html界面index.htm ...

最新文章

  1. BCH(比特币现金)的货币流通速度是BTC的6倍
  2. leetcode算法题--扑克牌中的顺子
  3. 几种用函数指针方式来访问类成员函数的方法总结
  4. SDNU 1429.区间k大数查询(水题)
  5. python3连接数据库失败_python3使用pymysql连接mysql数据库报Keyerror
  6. 下载丨 MySQL运维管理+编程开发大全
  7. 解决Pycharm里远程调试缺乏DISPLAY环境变量的TkAgg报错
  8. Java相关资源下载路径
  9. 【原创翻译】文本编辑器
  10. 象棋将帅不能碰面 java_为何象棋有“将帅不能碰面”的规则?原来这有一个不为人知的典故...
  11. mysql中为啥只显示一条语句_MySQL 笔记整理(19) --为什么我只查一行的语句,也执行这么慢?...
  12. Android MIDI音乐播放/生成相关总结
  13. Struts2基础教程
  14. MG513P30 12V直流减速电机编码器电线与杜邦线焊接教程
  15. 音视频开发(四十四):M3U8边缓存边播放
  16. 机械革命bios升级_¥1500买6年前神舟老战神,3内存+4硬盘升级潜力强,鲁大师15万!...
  17. 台式电脑开机显示蓝屏 检查计算机上的病毒,电脑出现蓝屏问题的诊断以及处理的方法...
  18. GAMES104实录 | 游戏引擎导论(上)
  19. 【GPT-4】立即停止训练比 GPT-4 更强的模型,至少六个月!马斯克、图灵奖得主等数千 AI 专家紧急呼吁
  20. 基于51单片机的红外报警实验

热门文章

  1. z变换判断稳定性和因果性_判断因果性.PPT
  2. Node如何处理模块之间的关系
  3. 指数的增长和衰退问题
  4. sqlite优化简单分析
  5. 一文搞懂「微信支付 Api-v3」接口规则所有知识点
  6. 微信支付接口加密技术详解
  7. Stream流、方法引用知识梳理
  8. 简明扼要:numpy.random.seed()用法
  9. spring bean的实例化
  10. 古时候的汉法,现代社会的红光光浴-种光光学