//1.定义接口数据
export interface SongItemType{
img:string
name:string
author:string
url:string
id:string
}
//2.定义类型
import { SongItemType } from "./music"
@ObservedV2
export class GlobalMusic {
@Trace "name": string = ''
@Trace "img": string = ""
@Trace "url": string = ''
@Trace "time": number = 0
@Trace "duration": number = 0
//歌曲列表
@Trace playIndex: number = 0
@Trace playList: SongItemType[] = []
//播放和暂停
@Trace isPlay: boolean = false
}
//3定义工具类
import { media } from '@kit.MediaKit';
import { PlayStatus } from './PlayStatus';
import { BusinessError } from '@kit.BasicServicesKit';
import { VideoListener } from './VideoListener';
import { GlobalMusic } from '../models/GlobalMusic';
import { AppStorageV2 } from '@kit.ArkUI';
import { SongItemType } from '../models/music';
export class AVPlayerUtil {
private durationTime: number = 0
private currentTime: number = 0
private surfaceId: string = '';
private avPlayer: media.AVPlayer | undefined = undefined;
//共享资源 修改
currentSong: GlobalMusic = AppStorageV2.connect(GlobalMusic, 'SONG_KEY', () => new GlobalMusic())!
private aspectCallBack: Function = () => {
};
setSurfaceId(surfaceId: string) {
this.surfaceId = surfaceId;
}
setAVPlayerCallback(avPlayer: media.AVPlayer) {
avPlayer.on('stateChange', async (state: string) => {
switch (state) {
case 'idle':
avPlayer.release();
break;
case 'initialized':
avPlayer.surfaceId = this.surfaceId;
avPlayer.prepare();
break;
case 'prepared':
console.log('---------- this.durationTime--', this.durationTime)
avPlayer.play();
this.currentSong.isPlay = true
break;
case 'playing':
// this.videoListener?.onPlayStatus(PlayStatus.PLAY)
break;
case 'paused':
// this.videoListener?.onPlayStatus(PlayStatus.PAUSE)
break;
case 'completed':
avPlayer.pause()
// this.videoListener?.onPlayStatus(PlayStatus.COMPLETE)
break;
case 'stopped':
avPlayer.reset();
break;
case 'released':
break;
default:
break;
}
})
// 监听seek生效的事件
this.avPlayer!.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
avPlayer.play()
this.currentSong.isPlay = true
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
this.avPlayer!.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
})
// 用于进度条,监听进度条长度,刷新资源时长
avPlayer.on('durationUpdate', (duration: number) => {
console.info('-----AVPlayer state durationUpdate called. current time: ', duration);
// 获取视频总时长
this.currentSong.duration = duration
// this.durationTime=duration
})
// 用于进度条,监听进度条当前位置,刷新当前时间
avPlayer.on('timeUpdate', (time) => {
console.info('---------AVPlayer state timeUpdate called. current time: ', time);
// 获取当前播放时长
this.currentSong.time = time
// this.currentTime=time
})
// get video height and width
avPlayer.on('videoSizeChange', (width: number, height: number) => {
this.aspectCallBack(height / width);
})
}
/**
* 初始化* @param url
* @param callBack
*/
async initPlayer(url: string, callBack: Function) {
this.avPlayer = await media.createAVPlayer();
this.aspectCallBack = callBack;
this.setAVPlayerCallback(this.avPlayer);
this.avPlayer.url = url
}
/**
* 切换视频*/
playChange(url: string) {
if (this.avPlayer) {
this.avPlayer.stop()
this.avPlayer.release()
this.initPlayer(url, this.aspectCallBack)
}
}
/**
* 跳转播放*/
seek(timeMs: number) {
if (this.avPlayer) {
this.avPlayer.seek(timeMs)
}
}
/**
* 暂停播放*/
pause() {
if (this.avPlayer) {
this.avPlayer.pause()
this.currentSong.isPlay = false
}
}
/**
* 播放*
* 设置资源*/
play(url: string) {
if (this.avPlayer) {
this.avPlayer.url = url
this.currentSong.img = ''
this.avPlayer.play()
this.currentSong.isPlay = true
}
}
/**
* 播放*/
// singPlay(song: SongItemType) {
// this.avPlayer!.url = song.url
// this.currentSong.img = song.img
//
// }
singPlay2(song: SongItemType) {
const inList = this.currentSong.playList.some(item => item.id === song.id)
if (inList) {
if (song.url === this.currentSong.url) {
this.avPlayer?.play()
this.currentSong.isPlay = true
} else {
//设置新的索引
this.currentSong.playIndex = this.currentSong.playList.findIndex(item => item.id === song.id)
// 切歌
this.chSong()
}
} else {
// add
this.currentSong.playList.unshift(song)
this.currentSong.playIndex = 0
// 切歌
this.chSong()
}
}
//
async chSong() {
await this.avPlayer?.reset()
this.currentSong.duration = 0
this.currentSong.time = 0
this.currentSong.img = this.currentSong.playList[this.currentSong.playIndex].img
this.currentSong.url = this.currentSong.playList[this.currentSong.playIndex].url
this.currentSong.name = this.currentSong.playList[this.currentSong.playIndex].name
this.avPlayer!.url = this.currentSong.url
}
/**
* 停止播放*/
stop() {
if (this.avPlayer) {
this.avPlayer.stop()
this.avPlayer.release()
}
}
/**
* 上一首* @param listener
*/
prevPlay() {
this.currentSong.playIndex--
if (this.currentSong.playIndex < 0) {
//去最后一首
this.currentSong.playIndex = this.currentSong.playList.length - 1
}
this.singPlay2(this.currentSong.playList[this.currentSong.playIndex])
}
/**
* 下一首* @param listener
*/
nextPlay() {
this.currentSong.playIndex++
if (this.currentSong.playIndex >= this.currentSong.playList.length) {
//去第一一首
this.currentSong.playIndex = 0
}
this.singPlay2(this.currentSong.playList[this.currentSong.playIndex])
}
setListener(listener: VideoListener) {
// this.videoListener = listener;
}
getDurationTime(): number {
return this.durationTime;
}
getCurrentTime(): number {
return this.currentTime;
}
}
//5页面布局
import { media } from '@kit.MediaKit';
import { AppStorageV2, display } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { AVPlayerUtil } from '../video/AVPlayerUtil';
import { PlayStatus } from '../video/PlayStatus';
import { timeConvert } from '../utils/TimeUtils';
import { VideoListener } from '../video/VideoListener';
import { SongItemType } from '../models/music';
import { GlobalMusic } from '../models/GlobalMusic';
@Entry
@ComponentV2
struct VideoPlayer {
pathStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'navStack', () => new NavPathStack())!
// XComponent控制器
// AVPlayer实例
private avPlayer: media.AVPlayer | null = null;
// 播放表面ID
private surfaceID: string = '';
// 播放状态相关变量
@Local isPlaying: boolean = false;
@Local currentTime: string = '11';
@Local durationTime: string = '222';
@Local progressValue: number = 0;
private player?: AVPlayerUtil
xComponentController: XComponentController = new XComponentController()
@Local videoUrl: string = 'https://www.w3schools.com/html/movie.mp4'
@Local timeFormat: string = '00:00'
@Local duration: number = 0
@Local time: number = 0
@Local durationFormat: string = '00:00'
@Local playStatus: PlayStatus = PlayStatus.INIT;
songs: SongItemType[] = [{
img: "http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimacloudMusic/0.jpg",
name: '直到世界的尽头',
author: "www",
url: "http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.m4a",
id: '0000'
},
{
img: "http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimacloudMusic/1.jpg",
name: '直到世界的尽头',
author: "www",
url: "http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.m4a",
id: '0000'
}
]
// @Local playState:SongItemType=this.songs[0]
@Local playState: GlobalMusic = AppStorageV2.connect(GlobalMusic, 'SONG_KEY', () => new GlobalMusic())!
// @Local
// playState:
// 数据共享变量
// 数据共享变量@Provide videoData: { url: string, title: string } = {
// url: 'http://example.com/video.mp4',
// title: '示例视频'
// }
number2time(number: number) {
}
build() {
// Navigation(this.pathStack) {
Column() {
Text(this.playState.img)
// 视频显示区域
XComponent({
id: 'videoSurface',
type: 'surface',
controller: this.xComponentController
}).height(200)
.onLoad(() => {
// this.initPlayer();
this.player = new AVPlayerUtil();
this.player.setSurfaceId(this.xComponentController.getXComponentSurfaceId());
this.player.initPlayer(this.videoUrl, (aspect: number) => {
})
})
// 控制面板
Column() {
Button(this.playState.isPlay ? '暂停' : '播放').onClick(() => {
this.playState.isPlay ? this.avPlayer?.pause() :
this.player?.singPlay2(this.playState.playList[this.playState.playIndex])
})
// .onClick(() => this.togglePlay())
Flex({ justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
Row() {
Text(this.formatDuration(this.playState.time))
.fontWeight(400)
.fontColor(Color.Red)
.fontSize(14)
}
.width(100)
.justifyContent(FlexAlign.End)
Slider({
value: this.playState.time,
step: 0.1,
min: 0,
max: this.playState.duration,
style: SliderStyle.OutSet
})
.margin({ left: 10, right: 10 })// .width('90%')
.trackColor('rgba(0,0,0,0.1)')// 滑轨背景颜色
.selectedColor('#3a8074')// 滑轨的已滑动部分颜色
.showSteps(false)// 是否显示步长刻度
// .showTips(true)
.blockSize({ width: 12, height: 12 })// 滑块大小
.blockColor('#E48E13')// 滑块颜色
.trackThickness(3)// 滑轨粗细
.trackBorderRadius(2)// 底板圆角半径
.selectedBorderRadius(2)// 已滑动部分圆角半径
// .margin({ bottom: 30 })
.onChange((value: number, mode: SliderChangeMode) => {
this.player?.seek(value);
// if (mode == SliderChangeMode.Begin) {
// // this.isSwiping = true;
// this.player?.pause();
// }
// this.player?.seek(value);
// // this.currentTime = value;
// if (mode == SliderChangeMode.End) {
// // this.isSwiping = false;
// // this.flag = true;
// this.player?.play('');
// }
})
Row() {
Text(this.formatDuration(this.playState.duration))
.fontWeight(400)
.fontColor(Color.Red)
.fontSize(14)
}
.width(100)
.justifyContent(FlexAlign.Start)
}
// Slider({
// value: this.progressValue,
// min: 0,
// max: 100
// })
// .onChange((value: number) => {
// this.seekVideo(value);
// })
// }
}
}
// }.onAppear(() => {
// this.pathStack.pushPathByName("Stark", null, null)
// }).hideNavBar(true)
//
}
/**
* 视频时长转换,格式:totalFormat true: 00:00:00;false:00:00(如果超过60分钟,格式为 "hh:mm:ss")* @param milliseconds
* @returns
*/
formatDuration(milliseconds: number, totalFormat: boolean = false): string {
const totalSeconds = Math.floor(milliseconds / 1000); // 将毫秒转换为秒
const hrs = Math.floor(totalSeconds / 3600);
const mins = Math.floor((totalSeconds % 3600) / 60);
const secs = totalSeconds % 60;
if (!totalFormat) {
// 如果超过60分钟,格式为"hh:mm:ss"
if (hrs > 0) {
const formattedHrs = String(hrs).padStart(2, '0');
const formattedMins = String(mins).padStart(2, '0');
const formattedSecs = String(secs).padStart(2, '0');
return `${formattedHrs}:${formattedMins}:${formattedSecs}`;
}
// 如果少于60分钟,格式为"mm:ss"
const formattedMins = String(mins).padStart(2, '0');
const formattedSecs = String(secs).padStart(2, '0');
return `${formattedMins}:${formattedSecs}`;
} else {
// 使用padStart来确保每个单位是两位数
const formattedHrs = String(hrs).padStart(2, '0');
const formattedMins = String(mins).padStart(2, '0');
const formattedSecs = String(secs).padStart(2, '0');
return `${formattedHrs}:${formattedMins}:${formattedSecs}`;
}
}
}