HarmonyOS Next 中Media Kit使用说明及其封装音乐播放器及其背景音乐播放组件 原创
前言
工具帖子,有需要直接用的可以直接拿
通过Media Kit封装音乐播放器以及封装背景音乐使用组件
效果如下:
实现步骤
播放媒体文件的流程为:创建AVPlayer->设置播放资源->设置播放参数(音量/倍速/焦点模式)->播放控制(播放/暂停/跳转/停止)->重置->销毁资源。
我们可以通过AVPlayer的state属性主动获取当前状态或使用on(‘stateChange’)方法监听状态变化。
播放状态变化示意图如下
下方例子为步骤讲解,完整代码放到最后
1.创建AVPlayer
我们通过createAVPlayer()方法用于创建我们的媒体播放组件
if (this.avPlayer !== null) {
this.avPlayer?.reset()
}
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 绑定回调函数
this.setAVPlayerCallback(this.avPlayer);
同时我们需要绑定我们的回调函数,我们通过这个回调函数,在回调函数中可以获取到组件中需要的信息,比如播放时长、等内容,如下方展示
事件类型 | 说明 |
---|---|
stateChange | 必要事件,监听播放器的state属性改变。 |
error | 必要事件,监听播放器的错误信息。 |
durationUpdate | 用于进度条,监听进度条长度,刷新资源时长。 |
timeUpdate | 用于进度条,监听进度条当前位置,刷新当前时间。 |
seekDone | 响应API调用,监听seek()请求完成情况。当使用seek()跳转到指定播放位置后,如果seek操作成功,将上报该事件。 |
speedDone | 响应API调用,监听setSpeed()请求完成情况。当使用setSpeed()设置播放倍速后,如果setSpeed操作成功,将上报该事件。 |
volumeChange | 响应API调用,监听setVolume()请求完成情况。当使用setVolume()调节播放音量后,如果setVolume操作成功,将上报该事件。 |
bufferingUpdate | 用于网络播放,监听网络播放缓冲信息,用于上报缓冲百分比以及缓存播放进度。 |
audioInterrupt | 监听音频焦点切换信息,搭配属性audioInterruptMode使用。如果当前设备存在多个音频正在播放,音频焦点被切换(即播放其他媒体如通话等)时将上报该事件,应用可以及时处理。 |
添加这些事件的格式为:
avPlayer.on('事件', (参数) => {
// 事件逻辑
})
我们这里的回调函数作为样例,只选择通过在加上stateChange和error,保证能够正常播放之外,再添加timeUpdate和durationUpdate
所以我们的回调函数为下方样式
setAVPlayerCallback(avPlayer: media.AVPlayer) {
if (avPlayer !== null) {
avPlayer.on('error', (err) => {
console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`);
avPlayer?.reset();
});
avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'initialized':
console.info('资源初始化完成');
avPlayer?.prepare();
break;
case 'prepared':
console.info('资源准备完成');
avPlayer?.play();
break;
case 'completed':
console.info('播放完成');
avPlayer?.stop();
break;
}
});
// 获取进度条长度
avPlayer.on('timeUpdate', (time:number) => {
this.outSetValue = time
console.info('timeUpdate success,and new time is :' + time)
})
// 获取时间长度
avPlayer.on('durationUpdate', (duration: number) => {
this.SliderLength = duration;
console.info('durationUpdate success,new duration is :' + duration)
})
}
}
可以根据自己的需要进行添加
2.设置播放资源
我们播放音乐肯定要设置播放资源,我把音乐名称,作者和文件都封装成一个类中,如下方展示
interface song_item{
// 歌曲名称
Title:string
// 作者
Author:string
// 歌曲文件
SongFile:string
}
首先我们需要把mp3文件放到rawfile目录下
我们将歌曲列表封装好
aboutToAppear(): void {
this.songs.push({Title:"花日",Author:"CMJ",SongFile:"CMJ.mp3"});
this.songs.push({Title:"踊り子",Author:"Vaundy",SongFile:"Vaundy.mp3"});
}
并将歌曲文件地址装入组件
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(item.SongFile);
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
// this.isSeek = true; // 支持seek操作
// 为fdSrc赋值触发initialized状态机上报
this.avPlayer.fdSrc = avFileDescriptor;
就可以对播放控制进行封装了
3.播放控制
我们通过timeUpdate和durationUpdate,可以获取到播放时长信息,将信息封装入进度条组件
// 进度条
Row() {
Slider({
value: this.outSetValue,
min: 0,
max: this.SliderLength,
step: 1,
style: SliderStyle.OutSet
})
.width("80%")
.blockColor('#FFFFFF')
.trackColor('#182431')
.selectedColor('#cbf1f5')
.showSteps(true)
.showTips(false)
.onChange((value: number, mode: SliderChangeMode) => {
this.outSetValue = value
// 调节长度
this.avPlayer?.seek(this.outSetValue)
})
Text(this.invertTime(this.outSetValue)).fontSize(16)
}
因为我们通过回调函数获得的时长为毫秒
这个时候我们要将时长转化为 hh:mm:ss的格式,就像这个样子
我们还需要添加一个函数用来转化时间:
// 时间格式转换
invertTime(time:number):string{
let totalSeconds = Math.floor(time / 1000);
let hours = Math.floor(totalSeconds / 3600);
let minutes = Math.floor((totalSeconds % 3600) / 60);
let seconds = totalSeconds % 60;
// 格式化为两位数
let formattedHours = String(hours).padStart(2, '0');
let formattedMinutes = String(minutes).padStart(2, '0');
let formattedSeconds = String(seconds).padStart(2, '0');
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
}
4.添加页面
一切准备完整,我们只需要把页面加上,就会变成完整的样子
完整代码:
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct Index {
private avPlayer: media.AVPlayer|null=null;
// 存放歌曲信息的列表
private songs:song_item[] = [];
@State titlenumber:number=-1
// 判断是否播放
@State isBFplaying:Boolean=true
// 进度条的值
@State outSetValue: number = 0
// 进度条长度
@State SliderLength:number = 0
aboutToAppear(): void {
this.songs.push({Title:"花日",Author:"CMJ",SongFile:"CMJ.mp3"});
this.songs.push({Title:"踊り子",Author:"Vaundy",SongFile:"Vaundy.mp3"});
}
// 播放设置
async Play(item:song_item){
if (this.avPlayer !== null) {
this.avPlayer?.reset()
}
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 绑定回调函数
this.setAVPlayerCallback(this.avPlayer);
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(item.SongFile);
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
// this.isSeek = true; // 支持seek操作
// 为fdSrc赋值触发initialized状态机上报
this.avPlayer.fdSrc = avFileDescriptor;
}
build() {
Column(){
Row(){
Row(){
Text('我的音乐')
.fontColor(Color.White).fontSize(32)
}.margin({left:20})
}.backgroundColor("#71c9ce").height('8%').width('100%')
Column(){
List(){
ForEach(this.songs,(item:song_item,index)=>{
ListItem(){
Row(){
Button({type:ButtonType.Normal}){
Row(){
Text((index+1)+' ')
.fontSize(32)
Column(){
Text(item.Title).fontSize(20).fontWeight(700)
Text(item.Author).fontSize(14)
}.alignItems(HorizontalAlign.Start)
}.justifyContent(FlexAlign.Start)
.width('90%')
}
.backgroundColor(Color.White)
.width("100%").height(50)
.margin({top:10})
.onClick(()=>{
this.isBFplaying=true
this.titlenumber=index
this.Play(item);
})
}
}
})
}.width('100%')
}.height('84%')
Row(){
Column(){
// 音乐信息
Row(){
if (this.titlenumber==-1){
Text('点击歌曲开始播放')
.fontSize(20).fontColor(Color.White)
}else {
Column(){
Text(this.songs[this.titlenumber].Title)
.fontSize(20).fontColor(Color.White)
}.width('70%').alignItems(HorizontalAlign.Start)
Column(){
Button({ type: ButtonType.Normal, stateEffect: true }){
Text(this.isBFplaying?"暂停":"继续")
.fontSize(20).fontColor(Color.White)
}.borderRadius(8).height(26).width(70).backgroundColor("#40514e")
.onClick(()=>{
// this.Play()
if (this.avPlayer !== null && this.isBFplaying==true) {
this.avPlayer.pause()
this.isBFplaying=!this.isBFplaying
}else{
this.avPlayer?.play()
this.isBFplaying=!this.isBFplaying
}
})
}.width('20%')
}
}.width('99%').margin({left:15})
// 进度条
Row() {
Slider({
value: this.outSetValue,
min: 0,
max: this.SliderLength,
step: 1,
style: SliderStyle.OutSet
})
.width("80%")
.blockColor('#FFFFFF')
.trackColor('#182431')
.selectedColor('#cbf1f5')
.showSteps(true)
.showTips(false)
.onChange((value: number, mode: SliderChangeMode) => {
this.outSetValue = value
// 调节长度
this.avPlayer?.seek(this.outSetValue)
})
Text(this.invertTime(this.outSetValue)).fontSize(16)
}
}
}.backgroundColor("#71c9ce").height('13%').width('100%')
}.height('100%').width('100%')
}
setAVPlayerCallback(avPlayer: media.AVPlayer) {
if (avPlayer !== null) {
avPlayer.on('error', (err) => {
console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`);
avPlayer?.reset();
});
avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'initialized':
console.info('资源初始化完成');
avPlayer?.prepare();
break;
case 'prepared':
console.info('资源准备完成');
avPlayer?.play();
break;
case 'completed':
console.info('播放完成');
avPlayer?.stop();
break;
}
});
// 获取进度条长度
avPlayer.on('timeUpdate', (time:number) => {
this.outSetValue = time
console.info('timeUpdate success,and new time is :' + time)
})
// 获取时间长度
avPlayer.on('durationUpdate', (duration: number) => {
this.SliderLength = duration;
console.info('durationUpdate success,new duration is :' + duration)
})
}
}
// 时间格式转换
invertTime(time:number):string{
let totalSeconds = Math.floor(time / 1000);
let hours = Math.floor(totalSeconds / 3600);
let minutes = Math.floor((totalSeconds % 3600) / 60);
let seconds = totalSeconds % 60;
// 格式化为两位数
let formattedHours = String(hours).padStart(2, '0');
let formattedMinutes = String(minutes).padStart(2, '0');
let formattedSeconds = String(seconds).padStart(2, '0');
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
}
}
interface song_item{
// 歌曲名称
Title:string
// 作者
Author:string
// 歌曲文件
SongFile:string
}
封装的背景音乐工具类
除此之外,我还封装了一个直接给我们的页面添加背景音乐的工具类
步骤都差不多,所以说实现过程不一一描述了
完整代码:
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
export class backgroundMusic{
private avPlayer: media.AVPlayer|null=null;
// 初始化函数
public async init(music:string){
if (this.avPlayer !== null) {
this.avPlayer?.reset()
}
// this.musicFile = music;
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 绑定回调函数
this.setAVPlayerCallback(this.avPlayer);
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(music);
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
// this.isSeek = true; // 支持seek操作
// 为fdSrc赋值触发initialized状态机上报
this.avPlayer.fdSrc = avFileDescriptor;
}
// 播放函数
public play(){
this.avPlayer?.play();
}
// 暂停函数
public paused(){
this.avPlayer?.pause();
}
// 其他函数
setAVPlayerCallback(avPlayer: media.AVPlayer) {
if (avPlayer !== null) {
avPlayer.on('error', (err) => {
console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`);
avPlayer?.reset();
});
avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'initialized':
console.info('资源初始化完成');
avPlayer?.prepare();
break;
case 'prepared':
console.info('资源准备完成');
avPlayer?.play();
break;
case 'completed':
console.info('播放完成');
avPlayer?.stop();
break;
}
});
}
}
}
使用方法:
在我们需要添加背景音乐的页面进行初始化操作
backgroundmusic:backgroundMusic = new backgroundMusic();
aboutToAppear(): void {
this.backgroundmusic.init("CMJ.mp3")
}
如果需要暂停或播放可以使用
this.backgroundmusic.paused()
或
this.backgroundmusic.play()
页面完整代码和展示:
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';
import { backgroundMusic } from './backgroundMusic'
@Entry
@Component
struct Index {
@State isPlay:boolean = true
backgroundmusic:backgroundMusic = new backgroundMusic();
aboutToAppear(): void {
this.backgroundmusic.init("CMJ.mp3")
}
build() {
Column(){
Row(){
Text('背景音乐')
.fontColor(Color.White).fontSize(32)
.margin({left:20,right:20})
Image(this.isPlay?$r("app.media.paused"):$r("app.media.play"))
.height("40vp")
.margin({left:20,right:20})
.onClick(()=>{
if (this.isPlay == true) {
this.backgroundmusic.paused()
}else {
this.backgroundmusic.play()
}
this.isPlay = !this.isPlay
})
}.backgroundColor("#71c9ce").height('8%').width('100%')
.justifyContent(FlexAlign.SpaceBetween)
Column(){
Text(this.isPlay?"正在播放":"正在暂停")
.fontSize(32)
}
.justifyContent(FlexAlign.Center)
.height('92%').width('100%')
}.height('100%').width('100%')
}
}
展示效果
结尾
小工具及其Media Kit的解释,需要的直接复制过来用