HarmonyOS开发案例:【音乐播放器】

介绍

使用ArkTS语言实现了一个简易的音乐播放器应用,主要包含以下功能:

  1. 播放应用中的音频资源文件,并可进行上一曲、下一曲、播放、暂停、切换播放模式(顺序播放、单曲循环、随机播放)等操作。
  2. 结合后台任务管理模块,实现熄屏后继续播放音频。

相关概念

  • [AVPlayer]:AVPlayer主要工作是将Audio/Video媒体资源转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。
  • [后台任务管理]:针对应用或业务模块处于后台(无可见界面)时,有需要继续执行或者后续执行的业务,可基于业务类型,申请短时任务延迟挂起或者长时任务避免进入挂起状态;如后台播放音乐可使用长时任务避免进入挂起状态。
  • 鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。

约束与限制

  1. 本篇Codelab部分能力依赖于系统API,需下载full-SDK并替换DevEco Studio自动下载的public-SDK。具体操作可参考指南[《如何替换full-SDK》]。
  2. 本篇Codelab使用的部分API仅系统应用可用,需要提升应用等级。

环境搭建

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。

    1. 开始前请参考[工具准备],完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。
    4. HarmonyOS与OpenHarmony鸿蒙文档籽料:mau123789是v直接拿

搜狗高速浏览器截图20240326151450.png

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets               // 代码区
│  ├──common              
│  │  ├──constants                   
│  │  │  └──CommonConstants.ets     // 公共常量
│  │  ├──model                   
│  │  │  └──PlayBarModel            // 播放栏数据模型
│  │  └──utils
│  │     ├──AvSessionUtil.ets  	    // 媒体会话工具类	
│  │     ├──BackgroundTaskUtil.ets  // 后台任务工具类
│  │     ├──CommonUtil.ets  	    // 公共工具类	
│  │     ├──GlobalContext.ets  	    // 公共工具类	
│  │     ├──Logger.ets              // 日志类          
│  │     └──ResourceManagerUtil.ets // 资源管理工具类
│  ├──controller           
│  │  ├──AudioPlayerController.ets  // 音乐播放器控制器
│  │  └──PlayBarController.ets      // 播放栏控制器
│  ├──entryability                    
│  │  └──EntryAbility.ets           // 程序入口类
│  ├──pages                          
│  │  ├──AudioStartUp.ets           // 启动页
│  │  ├──MusicList.ets              // 歌单页
│  │  └──Play.ets                   // 播放页
│  ├──view                         
│  │  ├──MusicCardView.ets          // 播放卡片模块
│  │  ├──MusicView.ets              // 歌单音乐模块
│  │  ├──PlayBarView.ets            // 播放控制模块
│  │  ├──PlayListDialogView.ets     // 弹窗模块
│  │  ├──PlayListMusicView.ets      // 弹窗音乐模块
│  │  └──ProgressView.ets           // 播放页
│  └──viewmodel  
│     ├──MusicItem.ets              // 音乐类
│     └──MusicViewModel.ets         // 歌单音乐模型
└──entry/src/main/resources         // 应用资源目录

实现音频播放

本案例使用播放管理类AVPlayer,实现应用内音频资源的播放,并可进行上一曲、下一曲、播放、暂停、切换播放模式(顺序播放、单曲循环、随机播放)等操作。

使用AVPlayer播放器,需要先创建一个AVPlayer实例。在AudioPlayerController中使用createAVPlayer方法完成音频播放实例的创建。

// AudioPlayerController.ets
initAudioPlayer() {
  media.createAVPlayer((error, video) => {
    if (video === undefined) {
      this.avPlayer = video;
      Logger.error(TAG, `createAVPlayer fail, error: ${error}`);
    } else {
      this.avPlayer = video;
      Logger.info(TAG, 'createAVPlayer success');
    }
  });
}

根据业务需要设置监听事件,搭配播放场景使用。

// AudioPlayerController.ets
// 注册AVPlayer回调函数
setEventCallBack() {
  ...
  // 状态变更回调函数。
  this.avPlayer.on('stateChange', async (state) => {
    ...
    switch (state) {
      case StateEvent.IDLE: // 调用reset成功后触发此状态。
        ...
      case StateEvent.INITIALIZED: // 设置播放源触发此状态。
        ...
      case StateEvent.PREPARED:
        ...
      case StateEvent.PLAYING:
        ...
      case StateEvent.COMPLETED:
        ...
      default:
        Logger.error('unknown state: ' + state);
        break;
    }
  })
}  

设置音频资源,AVPlayer进入initialized状态。在initialized状态回调中,调用prepare方法,准备播放,AVPlayer进入prepared状态。

// AudioPlayerController.ets
async play(src: media.AVFileDescriptor, seekTo: number) {
  Logger.info(TAG, 'audioPlayer play');
  ...
  // 设置播放源
  this.avPlayer.fdSrc = src;
}

setEventCallBack() {
  ...
  this.avPlayer.on('stateChange', async (state) => {
    ...
    switch (state) {
      ...
      case StateEvent.INITIALIZED:// 设置播放源后进入initialized状态
        Logger.info(TAG, 'state initialized called');
        this.avPlayerState = PlayerState.INITIALIZED;
        this.avPlayer.prepare().then(() => {
          Logger.info(TAG, 'prepare success');
        }, (err) => {
          Logger.error(TAG, `prepare failed,error message is: ${err.message}`);
        })
        break;
      ...
    }
  })
}

AVPlayer进入prepared状态,可进行音频播控操作。包括播放play()、跳转至指定位置播放seek()、暂停pause()、停止stop()等操作。

// AudioPlayerController.ets
setEventCallBack() {
  ...
  this.avPlayer.on('stateChange', async (state) => {
    ...
    switch (state) {
      ...
      case StateEvent.PREPARED:
        Logger.info(TAG, 'state prepared called');
        this.avPlayer.play();
        break;
      ...
    }
  })
}

切换歌曲播放时,需调用reset()重置资源。此时AVPlayer重新进入idle状态,允许更换资源。

// AudioPlayerController.ets
async play(src: media.AVFileDescriptor, seekTo: number) {
  ...
  if (this.avPlayerState === PlayerState.INITIALIZED) {
    await this.avPlayer.reset();
    Logger.info(TAG, 'play reset success');
  }
  ...
}

 说明:  只能在initialized/prepared/playing/paused/complete/stopped/error状态调用reset()。

调用release()销毁实例,AVPlayer进入released状态,退出播放。

// AudioPlayerController.ets
async release() {
  Logger.info(TAG, 'audioPlayer release');
  if (typeof (this.avPlayer) !== 'undefined') {
    if (this.timeId === CommonConstants.DEFAULT_TIME_ID) {
      clearInterval(this.timeId);
    }
    await this.avPlayer.release();
    this.avPlayer = undefined;
  }
}

实现熄屏后播放

通过后台任务管理模块申请长时任务,可避免设备熄屏后,应用进入挂起状态。

首先在module.json5文件中配置长时任务权限和后台模式类型。

"module": {
  ...
  "abilities": [
    {
      ...
      "backgroundModes": [
        "audioPlayback"
      ],
      ...
    }
  ],
  "requestPermissions": [
    {
      "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
    }
  ],
}

在播放音乐时,申请长时任务。这样在应用切换至后台或设备熄屏后,仍可以继续播放音乐。

// BackgroundTaskUtil.ets
import wantAgent from '@ohos.app.ability.wantAgent';
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
...
export class BackgroundTaskUtil {
  ...
  public static startContinuousTask(context: Context) {
    if (context === undefined) {
      Logger.info(TAG, 'startContinuousTask fail,context is empty.');
      return;
    }
    let wantAgentInfo = {
      // 点击通知后需要执行的动作
      wants: [
        {
          bundleName: CommonConstants.BUNDLE_NAME,
          abilityName: CommonConstants.ABILITY_NAME
        }
      ],
      // 单击通知后的动作类型
      operationType: wantAgent.OperationType.START_ABILITY,
      // 用户定义的私有属性
      requestCode: CommonConstants.BACKGROUND_REQUEST_CODE
    } as wantAgent.WantAgentInfo;

    // 通过WanAgent模块的方法获取WanAgent对象
    wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj) => {
      try {
        backgroundTaskManager.startBackgroundRunning(context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
          wantAgentObj).then(() => {
          Logger.info(TAG, 'startBackgroundRunning succeeded');
        }).catch((err: Error) => {
          Logger.error(TAG, 'startBackgroundRunning failed, Cause: ' + JSON.stringify(err));
        });
      } catch (error) {
        Logger.error(TAG, `startBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
      }
    });
  }
  ...
}

暂停音乐播放,结束长时任务。

// BackgroundTaskUtil.ets
import wantAgent from '@ohos.app.ability.wantAgent';
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
...
export class BackgroundTaskUtil {
  ...
  public static stopContinuousTask(context: Context) {
    if (context === undefined) {
      Logger.info(TAG, 'stopContinuousTask fail,context is empty.');
      return;
    }
    try {
      backgroundTaskManager.stopBackgroundRunning(context).then(() => {
        Logger.info(TAG, 'stopBackgroundRunning succeeded');
      }).catch((err: Error) => {
        Logger.error(TAG, 'stopBackgroundRunning failed Cause: ' + JSON.stringify(err));
      });
    } catch (error) {
      Logger.error(TAG, `stopBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/569484.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python实现钉钉通讯录导出Excel表

Python工具开源专栏 Py0004 python实现钉钉通讯录导出Excel表 Python工具开源专栏前言目录结构部分演示完整代码已在GitHub上开源 前言 需求来源于公司,需要将钉钉通讯录以Excel表的形式导出到本地,方便定期备份。导出的Excel需要处理钉钉用户兼任多部门…

AppleWatch是真的能够减少我iPhone的使用时长

我应该是比较专情的果粉了,我有一台MacBook Pro、iPad Pro、airpods pro 2和iPhone 15 Pro Max。但我还从来没有用过苹果手表。 然后,我就去买了AppleWatchSeries9蜂窝款,并试用了一周,我想知道它是否能帮助我减少使用iPhone的时间…

Sectigo证书申请流程及价格介绍

Sectigo 是一家全球知名的数字证书颁发机构(Certificate Authority, CA),自1998年起就开始提供 SSL 证书服务,是全球最早的 CA 机构之一。 一 Sectigo证书申请流程 1 确定证书类型 根据自身的需求确定证书的类型,一…

源码篇--Nacos服务--中章(5):Nacos客户端启动-实例注册-grpc连接建立

文章目录 前言一、 前奏:二、客户端连接的建立:2.1 NacosNamingService 创建:2.2 NacosNamingService 初始化:2.3 NamingClientProxyDelegate 长连接建立:2.3.1 grpc 代理对象创建:2.3.2 NamingGrpcClientP…

Meta Llama 3本地部署

感谢阅读 环境安装收尾 环境安装 项目文件 下载完后在根目录进入命令终端(windows下cmd、linux下终端、conda的话activate) 运行 pip install -e .不要控制台,因为还要下载模型。这里挂着是节省时间 模型申请链接 复制如图所示的链接 然后…

每周一算法:多起点最短路

题目描述 有一天,琪琪想乘坐公交车去拜访她的一位朋友。由于琪琪非常容易晕车,所以她想尽快到达朋友家。 现在给定你一张城市交通路线图,上面包含城市的公交站台以及公交线路的具体分布。 已知城市中共包含 n n n个车站(编号 …

Adobe Firefly Image 3:创新步伐与挑战并存的AI图像生成技术升级

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

编写你的第一个java 程序

1.安装 jdk 网址: Java Downloads | Oracle 一般我们安装jdk 17 就行了 自己练习 自己学习 真正的开发中我们使用jdk 8 这个是最适合开发java 应用程序的 当然你也可以选择你的 系统 来安装这个java 在文件资源管理器打开JDK的安装目录的bin目录,会发…

VSCode通过跳板机免密连接远程服务器的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Android Monkey工具介绍与使用

过于爽快的承认失败,就可能发觉不了曾经与正确非常接近。大家好,依旧是在翻看旧文档的时候,发现一篇关于Monkey的介绍和使用,Monkey这款工具在软件测试中主要用于进行压力测试和稳定性测试。它可以模拟大量随机的用户操作&#xf…

618买什么最划算?618买什么东西便宜?必备数码好物清单分享

​只不,马上又到了618购物节咯,数码产品的优惠力度尤为显著,是购买数码产品的绝佳时机。接下来,我将为大家分享几款性价比超高的数码产品,相信总有一款能吸引你的目光。 一、南卡OE MIX开放式蓝牙耳机 在618购物狂欢节…

javaScript中的闭包

什么是闭包 在理解 JavaScript 中的闭包前先了解以下两个知识点: JavaScript 中的作用域和作用域链JavaScript 中的垃圾回收 简单回顾一下这两个知识点: 1. JavaScript 中的作用域和作用域链 作用域就是一个独立的地盘,让变量不会外泄、…

tomcat 配置支持 ssl 附效果图

1、修改tomcat配置文件server.xml: vim ./conf/server.xml 把配置文件&#xff1a; <Connector port"8088" Server" " protocol"HTTP/1.1"connectionTimeout"20000"redirectPort"8443" URIEncoding"UTF-8" …

C++ | Leetcode C++题解之第46题全排列

题目&#xff1a; 题解&#xff1a; class Solution { public:void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){// 所有数都填完了if (first len) {res.emplace_back(output);return;}for (int i first; i &…

逆数对(树状数组的方法)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 5 4 5 1 3 2 输出 7 思路&#xff1a; 根据题意&#xff0c;求逆序对总数。 逆序对含义&#xff1a;如果数组中的两个不同位置&#xff0c;前面的数字比后面的数字严格大&…

投票刷礼物链接怎么弄?最新投票活动创建系统源码 轻松创建活动

投票刷礼物链接怎么弄&#xff1f;投票活动创建系统的作用和功能多种多样&#xff0c;为用户提供一个便捷、高效且功能强大的平台&#xff0c;用于创建、管理和执行各种投票活动。分享一个最新投票活动创建系统源码&#xff0c;源码开源可二开&#xff0c;含完整代码包和详细搭…

SCA-CNN-LSTM多输入回归预测|正余弦优化算法-卷积-长短期神经网络|Matlab

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

[笔试训练](五)

013 游游的you__牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 组成一个you需要一个o且能得2分&#xff0c;而组成相邻字母oo需要两个o&#xff0c;只能得1分。优先考虑组成尽可能多的you&#xff0c;再考虑剩下的o&#xff0c;放一起。 #include <iostream…

【C++】C++的四种类型转换

一、C语言中的类型转换 在C语言中有两种类型转换&#xff0c;隐式类型转换和显示类型转换。 如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与接收返回值类型不一致时&#xff0c;就需要发生类型转化。 隐式类型转换&#…

汽车IVI中控开发入门及进阶(十六):carplay认证

现在有些中控采用高通的芯片如8155、8295等,实现多屏互动等,但是也有一些车型走低成本方案,比如能够实现HiCar、CarLife或者苹果Apple的Carplay等能进行手机投屏就好了。 能实现CarPlay功能通过Carplay认证,也就成了一些必须的过程,国产车规级中控芯片里,开阳有一款ARK1…