鸿蒙音乐播放器(超详细)

基于API9的音乐播放器,可播放,暂停,上,下一首歌曲切换等功能

文章目录

        1.效果展示

        2.首页

        3.播放页


注:需要使用模拟器真机调试运行

一、效果展示

1.首页样式:

2.播放样式:

 

二、首页功能代码

1: UI代码

首页分为2部分,头部和底部。纵向布局,比例1:3。

1.1:头部

//头部
     Row(){
       Column(){
         Image('https://tse2-mm.cn.bing.net/th/id/OIP-C.fNF8owgmIwYhU9KINmt2dAAAAA?w=217&h=217&c=7&r=0&o=5&dpr=1.5&pid=1.7')
           .width(80).height(80).borderRadius(50)
         Column({space:5}){
           //获取时间
           Text(new Date().toDateString()).fontSize(20).fontColor(Color.White).fontWeight(800)
           Text('1 YEAR ACO TODAY').fontSize(16).opacity(0.6)//透明度
         }
       }.width('100%').height('100%').justifyContent(FlexAlign.SpaceAround)
     }.width('100%').layoutWeight(1).backgroundColor('#4FE3C1').justifyContent(FlexAlign.Center)

1.2:底部

底部使用foreach循环渲染每个歌曲的信息,另外当所需渲染的歌曲过多的时候,我们需要使用Scroll组件,让他歌曲可以滚动以便展示完整歌曲。

//歌单列表
     Row(){
       //滚动组件
       Scroll(){
         Column({space:20}){
           //this.songList==》歌曲所有信息,songInfo==》歌曲信息类
           ForEach(this.songList,(item:songInfo,index:number)=>{
             //歌曲信息组件
             Row(){
               Row({space:10}){
                 Text(item.id.toString()).fontSize(24)//歌曲id
                 Image(item.pic).width(60).height(60)//歌曲图片
                 Column(){
                   //歌曲名称(限定歌曲长度为1行,超出则显示...)
                   Text(item.name).width('60%').fontSize(18).fontWeight(700).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis })
                   //歌曲作者
                   Text(item.author).fontSize(15).opacity(0.6)
                 }.height(80).alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceEvenly)
               }.width('80%')
               Row(){
                 Text(item.play+'Plays').fontSize(12).fontColor(Color.Gray)//歌曲播放数
               }.width('20%').height(80).justifyContent(FlexAlign.End).alignItems(VerticalAlign.Bottom)
             }.width('100%').height(100).borderWidth(1).borderRadius(10).shadow({radius:10}).padding(10)
             
           })
         }.padding({top:20,bottom:20,left:5,right:5})
       }.scrollBar(BarState.Off).width('100%').height('100%')
     }.width('100%').layoutWeight(3)

2:其他代码

2.1:首页变量数据

 import { song } from '../mock/song'
 import { songInfo } from '../model/songInfo'
  //播放的歌曲信息
  @StorageLink('song') songList:songInfo[]=[]
  //播放的歌曲索引
  @StorageLink('i') currentIndex:number = -1


  aboutToAppear(){
    this.songList = song.songList//赋值歌曲信息
    AppStorage.SetOrCreate('i',-1)//初始化
  }

2.2:song文件代码

import { songInfo } from '../model/songInfo'
export class song{
  static songList:songInfo[]=[
    new songInfo(1,$rawfile('OnlyLovers.png'),'Only Lovers','Trademark',24,541,999,'onlyLovers.mp3'),
    new songInfo(2,$rawfile('NeverReallyEasy.png'),'Never Really Easy','SSerafim',33,779,999,'NeverReallyEasy.mp3'),
    new songInfo(3,$rawfile('BumpingUpandDown.png'),'Bumping Upand Down','MCND',40,956,999,'BumpingUpandDown.mp3'),
    new songInfo(4,$rawfile('BattleField.png'),'Battle Field','JordanFisher',57,978,999,'BattleField.mp3'),
    new songInfo(5,$rawfile('CanYouFeelIt.png'),'Can You Feel It','JeanRoch',61,988,999,'CanYouFeelIt.mp3'),
    new songInfo(6,$rawfile('GodIsAGirl.png'),'God Is A Girl','Groove',75,990,999,'GodIsAGirl.mp3'),
    new songInfo(7,$rawfile('TuViviNell‘aria.png'),'Tu Vivi Nell','Miani',95,999,999,'TuViviNell‘aria.mp3'),
  ]
}

歌曲文件在以下目录中:

 

2.3:songInfo文件代码

export class songInfo{
  id:number//歌曲id
  pic:Resource//歌曲图片
  name:string//歌曲姓名
  author:string//歌曲作者
  play:number//播放次数
  like:number//喜欢总数
  comment:number//评论数
  src:string//播放地址
  constructor(id:number,pic:Resource,name:string,author:string,play:number,like:number,comment:number,src:string) {
    this.id = id
    this.pic = pic
    this.name = name
    this.author = author
    this.play = play
    this.like = like
    this.comment = comment
    this.src = src
  }
}

 3:点击事件

当用户点击其中任意一首歌曲时,需要跳转到其播放页。由于数据是死的,我们通过传songList中的索引数据过去,就能实现播放对应的歌曲。

代码:

 //歌曲信息组件
  Row(){
             ......    
    }.width('100%').height(100).borderWidth(1).borderRadius(10).shadow({radius:10}).padding(10)
     .onClick(()=>{//歌曲信息组件点击事件
           router.pushUrl({url:'pages/Player'})
           this.currentIndex = index
     })

 4:完整代码

import router from '@ohos.router'
import { song } from '../mock/song'
import { songInfo } from '../model/songInfo'
@Entry
@Component
struct Index {
  //播放的歌曲信息
  @StorageLink('song') songList:songInfo[]=[]
  //播放的歌曲索引
  @StorageLink('i') currentIndex:number = -1
  
  aboutToAppear(){
    this.songList = song.songList
    AppStorage.SetOrCreate('i',-1)
  }

  build() {
   Column(){
     //头部
     Row(){
       Column(){
         Image('https://tse2-mm.cn.bing.net/th/id/OIP-C.fNF8owgmIwYhU9KINmt2dAAAAA?w=217&h=217&c=7&r=0&o=5&dpr=1.5&pid=1.7')
           .width(80).height(80).borderRadius(50)
         Column({space:5}){
           //获取时间
           Text(new Date().toDateString()).fontSize(20).fontColor(Color.White).fontWeight(800)
           Text('1 YEAR ACO TODAY').fontSize(16).opacity(0.6)//透明度
         }
       }.width('100%').height('100%').justifyContent(FlexAlign.SpaceAround)
     }.width('100%').layoutWeight(1).backgroundColor('#4FE3C1').justifyContent(FlexAlign.Center)
     //歌单列表
     Row(){
       //滚动组件
       Scroll(){
         Column({space:20}){
           //this.songList==》歌曲所有信息,songInfo==》歌曲信息类
           ForEach(this.songList,(item:songInfo,index:number)=>{
             //歌曲信息组件
             Row(){
               Row({space:10}){
                 Text(item.id.toString()).fontSize(24)//歌曲id
                 Image(item.pic).width(60).height(60)//歌曲图片
                 Column(){
                   //歌曲名称(限定歌曲长度为1行,超出则显示...)
                   Text(item.name).width('60%').fontSize(18).fontWeight(700).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis })
                   //歌曲作者
                   Text(item.author).fontSize(15).opacity(0.6)
                 }.height(80).alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceEvenly)
               }.width('80%')
               Row(){
                 Text(item.play+'Plays').fontSize(12).fontColor(Color.Gray)//歌曲播放数
               }.width('20%').height(80).justifyContent(FlexAlign.End).alignItems(VerticalAlign.Bottom)
             }.width('100%').height(100).borderWidth(1).borderRadius(10).shadow({radius:10}).padding(10)
             .onClick(()=>{//歌曲信息组件点击事件
                router.pushUrl({url:'pages/Player'})
                this.currentIndex = index
             })
           })
         }.padding({top:20,bottom:20,left:5,right:5})
       }.scrollBar(BarState.Off).width('100%').height('100%')
     }.width('100%').layoutWeight(3)
   }
    .width('100%')
    .height('100%')
  }
}

三 、播放功能代码

1:UI代码

1.1:头部

//头部
      Row(){
        Column({space:5}){
          Text(new Date().toDateString()).fontSize(22).fontWeight(800)
          Text('1 YEAR ACO TODAY').fontSize(15).fontColor(Color.Gray).opacity(0.6)
        }
      }.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)

1.2:空白占位区

//空白占位
      Row(){

      }.width('100%').layoutWeight(1)

 1.3:歌曲图片及播放进度

//歌曲图片和播放进度
      Row(){
        Stack(){
          Image(this.songList[this.currentIndex].pic)
            .width(170).height(170).borderRadius(100)
          Progress({value:this.value,total:this.total,type:ProgressType.Ring})
            .width(190).height(190).color("#ff49d7b8")
            .style({strokeWidth:8})
        }
      }.width('100%').layoutWeight(4).justifyContent(FlexAlign.Center)

 1.4:歌曲信息

//歌曲信息
      Row(){
        Column({space:10}){
          Text(this.songList[this.currentIndex].name).fontSize(25).fontWeight(900)
          Text(this.songList[this.currentIndex].author).fontSize(18).opacity(0.6)
          //喜欢,评论,分享
          Row(){
            Badge({count:this.songList[this.currentIndex].like,position:BadgePosition.RightTop,style:{badgeSize:20,badgeColor:Color.Red}}){
              Image($r('app.media.tool_like')).width(40).height(40)
            }
            Badge({count:this.songList[this.currentIndex].comment,position:BadgePosition.RightTop,style:{badgeSize:20,badgeColor:Color.Gray}}){
              Image($r('app.media.tool_comment')).width(40).height(40)
            }
            Image($r('app.media.nav_share')).width(40).height(40)
          }.width('100%').justifyContent(FlexAlign.SpaceAround).margin({top:10})
        }.width('100%').height('100%').justifyContent(FlexAlign.Center)
      }.width('100%').layoutWeight(2)

1.5:歌曲菜单控制

 //歌曲菜单
      Row(){
        Row(){
          Image($r('app.media.control_last')).width(50).height(50)
            .onClick(async()=>{
              
            })
          Image(this.isPlaying ? $r('app.media.control_pause') : $r('app.media.control_play')).width(50).height(50)
            .onClick(()=>{
              this.isPlaying ? this.avPlayer.pause() : this.avPlayer.play()
            })
          Image($r('app.media.control_next')).width(50).height(50)
            .onClick(async()=>{
             
            })
        }.width('100%').justifyContent(FlexAlign.SpaceEvenly).margin({top:10})
      }.width('100%').layoutWeight(1)

2:其他代码(重要)

2.1:播放页变量数据

//歌曲信息
  @StorageLink('song') songList:songInfo[]=[]
  //播放的歌曲索引
  @StorageLink('i') currentIndex:number = -1
  //现在播放的时间
  @State value:number = 0
  //总共播放的时间
  @State total:number = 0
  //是否开启定时器标志
  @State flag:boolean = false
  //定时器id
  @State time:number=0
  //是否正在播放
  @State isPlaying:boolean = false
  //音乐播放对象
  avPlayer:media.AVPlayer

2.2:音乐播放状态及事件

//音乐播放状态
  setAVPlayerCallback(avPlayer:media.AVPlayer){
    // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
    avPlayer.on('error', (err) => {
      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
      avPlayer.reset(); // 调用reset重置资源,触发idle状态
    })
    avPlayer.on('stateChange',async(state:string)=>{
      switch (state){
        case 'idle': // 成功调用reset接口后触发该状态机上报
          avPlayer.release(); // 调用release接口销毁实例对象
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          avPlayer.prepare(); // 调用prepare接口销毁实例对象
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          this.value = avPlayer.currentTime // 播放的时间进度
          this.total = avPlayer.duration  //  歌曲总共时间
          avPlayer.play();  // 调用play接口开始播放
          break;
        case 'playing':
          this.isPlaying = true //  开始播放
          this.flag = true   // 状态标记
          this.setTimer()   //  启动定时器
          break;
        case 'paused':
          this.isPlaying = false //未开始播放
          this.deleteTimer()  //暂停定时器
          avPlayer.pause()  //  调用pause接口暂停播放
          break;
        case 'stopped':
          avPlayer.reset(); // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          break;
      }
    })
  }

 2.3:获取音乐歌曲播放路径

 //获取音乐播放路径
  async getAVPlayerUrl(){
    //创建播放实例对象
    let avPlayer = await media.createAVPlayer()
    this.setAVPlayerCallback(avPlayer)
    //获取上下文对象
    let context = getContext(this) as common.UIAbilityContext
    //获取播放文件
    let file = await context.resourceManager.getRawFd(this.songList[this.currentIndex].src)
    let avFile:media.AVFileDescriptor = {fd:file.fd,offset:file.offset,length:file.length}
    avPlayer.fdSrc = avFile
    return avPlayer
  }

 这里的代码不懂的可以参考官网文档,即在我们获取到播放状态时候,添加了处理事件。 

官网链接:使用AVPlayer开发音频播放功能(ArkTS) (openharmony.cn)

2.4:初始化

  async aboutToAppear(){
    this.avPlayer = await this.getAVPlayerUrl()
  }

2.5:定时器功能

//定时器(持续增加播放的时间)
  setTimer(){
    if(this.flag){
      this.time = setInterval(()=>{
        //如果当值大于总的播放时间的时候,那么就取消定时
        if(this.value++ > this.total){
          this.deleteTimer()
        }else {
          this.value++
        }
      },1)
    }
  }

  //取消定时器
  deleteTimer(){
    clearInterval(this.time)
    this.flag = false
  }

  //重置时间
  resetTime(){
    this.deleteTimer()
    this.value = 0
    this.total = 0
  }

3:点击事件

          //上一首
          Image($r('app.media.control_last')).width(50).height(50)
            .onClick(async()=>{
              this.avPlayer.release()
              if(this.currentIndex-1 < 0){
                this.currentIndex = this.songList.length -1
              }else {
                this.currentIndex--
              }
              this.resetTime()
              this.avPlayer = await this.getAVPlayerUrl()
              this.avPlayer.play()
            })
          //暂停/播放
          Image(this.isPlaying ? $r('app.media.control_pause') : $r('app.media.control_play')).width(50).height(50)
            .onClick(()=>{
              this.isPlaying ? this.avPlayer.pause() : this.avPlayer.play()
            })
          //下一首
          Image($r('app.media.control_next')).width(50).height(50)
            .onClick(async()=>{
              this.avPlayer.release()
              if(this.currentIndex+1 > song.songList.length){
                this.currentIndex = 0
              }else {
                this.currentIndex++
              }
              this.resetTime()
              this.avPlayer = await this.getAVPlayerUrl()
              this.avPlayer.play()
            })

 如果此时播放的歌曲是第一首的情况下,那么当用户点击播放上一首的时候,则播放歌曲列表的最后一首歌曲,播放下一首也是同理。

4:完整代码

import media from '@ohos.multimedia.media'
import { songInfo } from '../model/songInfo'
import common from '@ohos.app.ability.common'
import { song } from '../mock/song'
@Entry
@Component
struct Player {
  //歌曲信息
  @StorageLink('song') songList:songInfo[]=[]
  //播放的歌曲索引
  @StorageLink('i') currentIndex:number = -1
  //现在播放的时间
  @State value:number = 0
  //总共播放的时间
  @State total:number = 0
  //是否开启定时器标志
  @State flag:boolean = false
  //定时器id
  @State time:number=0
  //是否正在播放
  @State isPlaying:boolean = false
  //音乐播放对象
  avPlayer:media.AVPlayer

  async aboutToAppear(){
    this.avPlayer = await this.getAVPlayerUrl()
  }

  //定时器(持续增加播放的时间)
  setTimer(){
    if(this.flag){
      this.time = setInterval(()=>{
        if(this.value++ > this.total){
          this.deleteTimer()
        }else {
          this.value++
        }
      },1)
    }
  }

  //取消定时器
  deleteTimer(){
    clearInterval(this.time)
    this.flag = false
  }

  //重置时间
  resetTime(){
    this.deleteTimer()
    this.value = 0
    this.total = 0
  }


  build() {
    Column(){
      //头部
      Row(){
        Column({space:5}){
          Text(new Date().toDateString()).fontSize(22).fontWeight(800)
          Text('1 YEAR ACO TODAY').fontSize(15).fontColor(Color.Gray).opacity(0.6)
        }
      }.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
      //空白占位
      Row(){

      }.width('100%').layoutWeight(1)
      //歌曲图片和播放进度
      Row(){
        Stack(){
          Image(this.songList[this.currentIndex].pic)
            .width(170).height(170).borderRadius(100)
          Progress({value:this.value,total:this.total,type:ProgressType.Ring})
            .width(190).height(190).color("#ff49d7b8")
            .style({strokeWidth:8})
        }
      }.width('100%').layoutWeight(4).justifyContent(FlexAlign.Center)
      //歌曲信息
      Row(){
        Column({space:10}){
          Text(this.songList[this.currentIndex].name).fontSize(25).fontWeight(900)
          Text(this.songList[this.currentIndex].author).fontSize(18).opacity(0.6)
          //喜欢,评论,分享
          Row(){
            Badge({count:this.songList[this.currentIndex].like,position:BadgePosition.RightTop,style:{badgeSize:20,badgeColor:Color.Red}}){
              Image($r('app.media.tool_like')).width(40).height(40)
            }
            Badge({count:this.songList[this.currentIndex].comment,position:BadgePosition.RightTop,style:{badgeSize:20,badgeColor:Color.Gray}}){
              Image($r('app.media.tool_comment')).width(40).height(40)
            }
            Image($r('app.media.nav_share')).width(40).height(40)
          }.width('100%').justifyContent(FlexAlign.SpaceAround).margin({top:10})
        }.width('100%').height('100%').justifyContent(FlexAlign.Center)
      }.width('100%').layoutWeight(2)
      //歌曲菜单
      Row(){
        Row(){
          //上一首
          Image($r('app.media.control_last')).width(50).height(50)
            .onClick(async()=>{
              this.avPlayer.release()
              if(this.currentIndex-1 < 0){
                this.currentIndex = this.songList.length -1
              }else {
                this.currentIndex--
              }
              this.resetTime()
              this.avPlayer = await this.getAVPlayerUrl()
              this.avPlayer.play()
            })
          //暂停/播放
          Image(this.isPlaying ? $r('app.media.control_pause') : $r('app.media.control_play')).width(50).height(50)
            .onClick(()=>{
              this.isPlaying ? this.avPlayer.pause() : this.avPlayer.play()
            })
          //下一首
          Image($r('app.media.control_next')).width(50).height(50)
            .onClick(async()=>{
              this.avPlayer.release()
              if(this.currentIndex+1 > song.songList.length){
                this.currentIndex = 0
              }else {
                this.currentIndex++
              }
              this.resetTime()
              this.avPlayer = await this.getAVPlayerUrl()
              this.avPlayer.play()
            })
        }.width('100%').justifyContent(FlexAlign.SpaceEvenly).margin({top:10})
      }.width('100%').layoutWeight(1)
      //占位
      Row(){

      }.width('100%').layoutWeight(1).backgroundColor("#ff49d7b8")
    }
    .width('100%')
    .height('100%')
  }

  //音乐播放状态
  setAVPlayerCallback(avPlayer:media.AVPlayer){
    // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
    avPlayer.on('error', (err) => {
      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
      avPlayer.reset(); // 调用reset重置资源,触发idle状态
    })
    avPlayer.on('stateChange',async(state:string)=>{
      switch (state){
        case 'idle': // 成功调用reset接口后触发该状态机上报
          avPlayer.release(); // 调用release接口销毁实例对象
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          avPlayer.prepare(); // 调用prepare接口销毁实例对象
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          this.value = avPlayer.currentTime // 播放的时间进度
          this.total = avPlayer.duration  //  歌曲总共时间
          avPlayer.play();  // 调用play接口开始播放
          break;
        case 'playing':
          this.isPlaying = true //  开始播放
          this.flag = true   // 状态标记
          this.setTimer()   //  启动定时器
          break;
        case 'paused':
          this.isPlaying = false //未开始播放
          this.deleteTimer()  //暂停定时器
          avPlayer.pause()  //  调用pause接口暂停播放
          break;
        case 'stopped':
          avPlayer.reset(); // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          break;
      }
    })
  }

  //获取音乐播放路径
  async getAVPlayerUrl(){
    //创建播放实例对象
    let avPlayer = await media.createAVPlayer()
    this.setAVPlayerCallback(avPlayer)
    //获取上下文对象
    let context = getContext(this) as common.UIAbilityContext
    //获取播放文件
    let file = await context.resourceManager.getRawFd(this.songList[this.currentIndex].src)
    let avFile:media.AVFileDescriptor = {fd:file.fd,offset:file.offset,length:file.length}
    avPlayer.fdSrc = avFile
    return avPlayer
  }
}

总结

利用AVPlayer开发音频播放,重点是掌握如何获取播放路径(本地地址,网络地址)等,再根据歌曲的状态编写对应函数即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值