1.效果展示
2.首页
3.播放页
首页分为2部分,头部和底部。纵向布局,比例1:3。
//头部
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)
底部使用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)
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)//初始化
}
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'),
]
}
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
}
}
当用户点击其中任意一首歌曲时,需要跳转到其播放页。由于数据是死的,我们通过传songList中的索引数据过去,就能实现播放对应的歌曲。
代码:
//歌曲信息组件
Row(){
......
}.width('100%').height(100).borderWidth(1).borderRadius(10).shadow({radius:10}).padding(10)
.onClick(()=>{//歌曲信息组件点击事件
router.pushUrl({url:'pages/Player'})
this.currentIndex = index
})
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%')
}
}
//头部
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()=>{
})
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)
//歌曲信息
@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
//音乐播放状态
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开发音频播放功能(ArkTS) (openharmony.cn)
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
}
//上一首
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()
})
如果此时播放的歌曲是第一首的情况下,那么当用户点击播放上一首的时候,则播放歌曲列表的最后一首歌曲,播放下一首也是同理。
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
}
}