刚开始学习鸿蒙开发,经验分享
最近有做到视频播放这一块的需求,在此记录,标有相应注释。
主要是avplayer和横竖屏事件,页面示例如下。
1.横竖屏事件代码
// 是否开启临时横屏事件,13-竖屏;14-横屏
setOrientation(orientation: number) {
window.getLastWindow(getContext(this)).then((win) => {
win.setPreferredOrientation(orientation).then((data) => {
console.log('setWindowOrientation: ' + orientation + ' Succeeded. Data: ' + JSON.stringify(data));
}).catch((err: string) => {
console.log('setWindowOrientation: Failed. Cause: ' + JSON.stringify(err));
});
}).catch((err: string) => {
console.log('setWindowOrientation: Failed to obtain the top window. Cause: ' + JSON.stringify(err));
});
}
调用
if(this.isFull) {
this.setOrientation(window.Orientation.USER_ROTATION_LANDSCAPE)
} else {
this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT)
}
2.创建和使用avplayer
播放视频可以用自带的video组件,也可以使用avplayer实现。
2.1定义参数
@State vol: number = 1 // 音量
@State sateVideoState: boolean = false
private xComponentController: XComponentController = new XComponentController(); // 播放窗口
@State isFull: boolean = false // 是否全屏
@State showController:boolean = false // 是否展示播放器控制器
@State playing: boolean = true // 是否播放中
@State surfaceID: string = '';
@State videoSrc: string = ''//视频地址
@State videoTime:number = 0 // 当前播放时间
@State endTime: number = 0 // 视频总时长
//视频比例
@State videoProportion: number = 0
@State videoHeight: number = 1
@State videoWidth: number = 1
@State windowHeight: number = 1
@State windowWidth: number = 1
2.2创建视频播放器
// 播放器
private avPlayer: media.AVPlayer | null = null;
// avplayer状态机
private setAVPlayerCallback(avPlayer: media.AVPlayer) {
console.log('状态机初始化')
// startRenderFrame首帧渲染回调函数
avPlayer.on('startRenderFrame', () => {
console.info(`AVPlayer start render frame`);
})
// 音量
avPlayer.on('volumeChange', (vol:number) => {
this.vol=vol
})
// seek操作结果回调函数
avPlayer.on('seekDone', async (seekDoneTime: number) => {
console.info('zzz=== avPlayer seek操作',` seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
avPlayer.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
emitter.emit('VedioError')
})
// 时间更新 监听资源播放当前时间,单位为毫秒(ms),用于刷新进度条当前位置,默认间隔100ms时间上报,因用户操作(seek)产生的时间变化会立刻上报。
avPlayer.on('timeUpdate', async (time:number)=>{
this.videoTime = time
})
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
console.log('zzz=== avPlayer状态变化', `当前状态${state}`)
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info('AVPlayer state idle called.');
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.log('avPlayer状态机',"设置播放源后")
avPlayer.url
avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置
//监听
emitter.emit('VideoInitialized')
avPlayer.prepare();
break;
case 'prepared': // prepare调用成功后上报该状态机已准备状态,在initialized状态调用prepare()方法,AVPlayer会进入prepared状态,此时播放引擎的资源已准备就绪。
console.log('avPlayer状态机','进入准备状态');
this.endTime = avPlayer.duration
avPlayer.videoScaleType = 1
avPlayer.setVolume(1)
this.VideoSize(this.windowHeight,this.windowWidth)
// this.listenVieoVoice()
this.doPlay()
break;
case 'playing': // play成功调用后触发该状态机上报
console.info('AVPlayer state playing called.');
this.playing = true
//监听
emitter.emit('VideoPlaying')
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info('AVPlayer state paused called.');
this.playing = false
//监听
emitter.emit('VideoPaused')
break;
case 'completed': // 播放结束后触发该状态机上报
console.info('AVPlayer state completed called.');
this.playing = false
//监听
emitter.emit('VideoCompleted')
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态
//监听
emitter.emit('VideoStopped')
break;
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
//视频尺寸变化
avPlayer.on('videoSizeChange',(width:number,height:number)=>{
this.videoProportion = (width/height)
console.log( 'myTag', '获取视频比例', this.videoProportion)
this.VideoSize(this.windowHeight,this.windowWidth)
})
}
2.3视频播放相关方法
横竖屏变化修改视频窗口宽高
//视频比例适应
VideoSize(height: number, width: number) {
console.log('myTag', '视频比例调整')
console.log('视频高',height);
this.videoHeight = height
if(this.isFull) {
this.videoWidth = height*this.videoProportion
} else {
this.videoWidth = width
}
}
播放暂停
async doPlay(){
let mp = this.avPlayer
console.log('zzz=== doPlay方法',`mp状态:${mp?.state};`)
if (mp?.state && (mp?.state == "prepared" || mp?.state == "paused" || mp?.state == "completed")) {
mp.play()
} else {
// ToastUtil.showToast('视频播放出现了点问题')
}
}
async doPause(){
let mp = this.avPlayer
console.log('zzz=== doPause方法',`mp状态:${mp?.state};`)
if (mp?.state == 'playing' ){
mp.pause()
}
}
2.4页面结构
2.4.1播放窗口
build() {
NavDestination() {
Column() {
Stack({alignContent: Alignment.Top}) {
// 视频播放组件
XComponent({type:XComponentType.SURFACE,controller:this.xComponentController})
.onLoad(async e => {
this.surfaceID = this.xComponentController.getXComponentSurfaceId()
})
.height(this.videoHeight).width(this.videoWidth)
.zIndex(11)
.onClick(()=> {
animateTo({ duration: 600 }, () => {
this.showController = !this.showController
// 2秒后隐藏控件
setTimeout(()=> {
this.changeController()
}, 2000)
})
})
}
.width('100%').zIndex(11)
.backgroundColor('#333333')
.height(this.isFull ? '100%' : '422lpx')
.onAreaChange((oldValue: Area, newValue: Area) => {
this.windowHeight = Number(newValue.height)
this.windowWidth = Number(newValue.width)
this.VideoSize(Number(newValue.height), Number(newValue.width))
})
}
.width('100%')
.height('100%')
.padding({top: this.isFull ? '0lpx' : '80lpx', bottom: '0lpx'})
.backgroundColor(Color.White)
}
.hideTitleBar(true)
.onHidden(()=>{
this.onPageHide()
})
.onReady((context: NavDestinationContext) => {
})
}
2.4.2播放控件以及对应事件
// 播放器自定义控件
if(this.showController) {
Row() {
Image($r('app.media.ic_arrow_left_02')).width('44lpx').onClick(()=>{
if(this.isFull) {
this.isFull = !this.isFull
this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT)
} else {
this.pathStack.pop()
}
// this.pathStack.pop()
})
}.padding({left: '25lpx', right: '25lpx', top: '25lpx'}).width('100%')
.zIndex(11)
.transition({ type: TransitionType.Delete, opacity: 0 })
Row() {
if(this.playing) {
Image($r('app.media.ic_play_on')).width('36lpx').margin({right: '24lpx'}).onClick(()=> {
this.playing = false
this.doPause()
})
} else {
Image($r('app.media.ic_play_off')).width('36lpx').margin({right: '24lpx'}).onClick(()=> {
this.playing = true
this.doPlay()
})
}
Text(this.getTimeString(this.videoTime)).fontColor(Color.White).fontSize('22lpx')
Stack() {
Slider({
value:this.videoTime,
min:0,
max:this.endTime,
step: 0.01,
})
.width('438lpx')
.videoSlider()
.showSteps(false)
//控制播放进度
.onChange((value: number, mode: SliderChangeMode) => {
if (mode==0) {
console.log('zzz=== 进度条点击事件')
this.sateVideoState = this.playing
this.doPause()
}
if (mode==1) {
this.avPlayer?.seek(value,media.SeekMode.SEEK_CLOSEST)
}
if (mode==2){
console.log('zzz=== 进度条松开事件')
this.avPlayer?.seek(value,media.SeekMode.SEEK_CLOSEST)
if (this.sateVideoState){
this.doPlay()
}
}
})
// Column().width('438lpx').height('4lpx').borderRadius('2lpx').backgroundColor('#fff')
}
Text(this.getTimeString(this.endTime)).fontColor(Color.White).fontSize('22lpx')
Image($r('app.media.ic_full_screen')).width('36lpx').margin({left: '24lpx'}).onClick(()=> {
console.log('全屏事件')
if (this.avPlayer) {
this.isFull = !this.isFull
if(this.isFull) {
this.setOrientation(window.Orientation.USER_ROTATION_LANDSCAPE)
} else {
this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT)
}
}
})
}.width('100%').height('68lpx').position({bottom: 0, left: 0})
.backgroundColor('rgba(23,23,26,0.8)').alignItems(VerticalAlign.Center)
.padding({left: '24lpx', right: '24lpx'})
.justifyContent(FlexAlign.SpaceBetween)
.zIndex(11)
.transition({ type: TransitionType.Insert, opacity: 0, translate: { y: '0lpx', x: 0 } })
.transition({ type: TransitionType.Delete, opacity: 0, translate: { y: '0lpx', x: 0 } })
}
2.4.3播放进度条样式
// 进度条样式
@Extend(Slider)
function videoSlider() {
.trackColor('#ffffff')
.trackThickness('5lpx')
.selectedColor('#2ACF6F')
.blockBorderColor('rgba(44, 211, 215, 0.3)')
.blockBorderWidth('8lpx')
.blockColor('#2CD3D7')
.blockSize({ width: '25lpx', height: '25lpx' })
}
2.5初始化并播放
async aboutToAppear() {
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback(this.avPlayer);
setTimeout(()=>{
this.init()
},300)
}
init() {
if(this.avPlayer) {
this.avPlayer.url = "http:www.abc.link.mp4" // 赋值你的视频地址
this.doPlay() // 执行播放
}
}
注意事项
退出页面需要暂停播放器
async onPageHide() {
this.doPause()
this.setOrientation(window.Orientation.USER_ROTATION_PORTRAIT) // 回复竖屏
}
切换视频源,也需要重置播放状态
this.videoPlay = false
// 切换视频,对播放器初始化
this.doPause()
this.avPlayer?.reset()
this.avPlayer.url = "http:www.abc.link.mp4" // 重新赋值地址
setTimeout(()=>{
this.init()
},300)