一:根据鸿蒙官方的avplayer 封装的播放器,根据我自己的理解进行的封装使用,如果您有不同个的见解请多交流哦!!!!
好了,主要代码分了两个ets文件。
1:首先是工具类的封装,不多说,上代码
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
export default class avplayer{
private url:string = ''
private surfaceId:string = ''
private mineAvPlayer:media.AVPlayer|null = null
private mineseekTime:number = 0
DructionGetCallBack?:(druction:number)=>void
NowDructionCallback?:(nowDruction:number)=>void
private nowState:string = ''
seturl(url:string){
this.url = url
}
setsurfaceId(surfaceId:string){
this.surfaceId = surfaceId
}
async prepare(callBack:(prepare:boolean)=>void){
this.mineAvPlayer = await media.createAVPlayer()
if (this.mineAvPlayer == null) {
return
}
//avplayer 创建成功state状态为 idle 这之后才可设置URL
if (this.mineAvPlayer.state ==='idle') {
if (this.mineAvPlayer == null) {
return
}
this.mineAvPlayer.url = this.url;
}
this.mineAvPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
// 开发者根据需要写入业务逻辑。
console.info('获取当前状态state:',state)
this.nowState = state
//设置URL后state状态会转为 initialized 然后设置 surfaceid 调用prepare 即可进行播放
if (state === 'initialized') {
if (this.mineAvPlayer == null) {
return
}
if (this.surfaceId === '') {
return
}
this.mineAvPlayer.surfaceId = this.surfaceId
//准备播放:调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置 、音量等。
this.mineAvPlayer?.prepare((err: BusinessError,data) => {
if (err) {
console.error('获取 Failed to prepare,error message is :' + err.message);
} else {
console.info('获取 Succeeded in preparing');
if (this.mineAvPlayer == null){
return
}
}
});
}else if(state === 'prepared'){
// 调用prepare 后如果成功 state 会变为 prepared 此时即可返回到类调用组件进行 play调用(这也不可以用反馈给调用的组件 直接调用play也行)
callBack(true)
}
});
//视频总时长
this.mineAvPlayer.on('durationUpdate',async (fullDruction:number)=>{
if (this.DructionGetCallBack) {
this.DructionGetCallBack(fullDruction)
}
})
//更新当前的时间
this.mineAvPlayer.on('timeUpdate',async (nowDruction:number)=>{
if (this.NowDructionCallback) {
this.NowDructionCallback(nowDruction)
}
})
}
//播放
play(){
if (this.mineAvPlayer == null) {
return
}
// 播放操作。
this.mineAvPlayer.play().then(() => {
console.info('Succeeded in playing');
}, (err: BusinessError) => {
console.error('Failed to play,error message is :' + err.message);
});
}
pause(){
// 暂停操作。
this.mineAvPlayer?.pause((err: BusinessError) => {
if (err) {
console.error('Failed to pause,error message is :' + err.message);
} else {
console.info('Succeeded in pausing');
}
});
}
//跳转到某个时间
seekTime(seektime:number){
//拖动之前先暂停播放
this.mineseekTime = seektime
this.mineAvPlayer?.seek(this.mineseekTime, media.SeekMode.SEEK_PREV_SYNC);
}
stop(){
// 停止操作。
this.mineAvPlayer?.stop((err: BusinessError) => {
if (err) {
console.error('Failed to stop,error message is :' + err.message);
} else {
console.info('Succeeded in stopping');
}
});
}
//如果切换视频源 需先调用reset
reset(){
this.mineAvPlayer?.reset()
if (this.mineAvPlayer == null) {
return
}
}
//退出
out(){
this.mineAvPlayer?.release((err: BusinessError) => {
if (err) {
console.error('Failed to release,error message is :' + err.message);
} else {
console.info('Succeeded in releasing');
}
});
}
getState():string{
return this.nowState
}
//设置声音
setvolume(volume:number){
this.mineAvPlayer?.setVolume(volume)
}
}
2: 接下来是播放界面的建议封装和对工具类的调用,其中很遗憾的是弹幕功能并为开发完毕,有些思路(感觉一般般),还没想到一个好的方法,
(1)说实话,这次的封转并不完美,简单应用还是没问题的,如果您有更好的方法,请积极交流,感谢
继续上代码!!!这是第二个ets文件
import avplayer from './utils/avplayerUtil'
import { JSON } from '@kit.ArkTS';
import { formatDuration } from './utils/toolsUtill'; //哦 这里是一个将视频时长转为时分秒的工具 这个ets文件后,下面会贴出代码
import { audio } from '@kit.AudioKit'; //这里是对声音控制的
import { window } from '@kit.ArkUI'; //这里是对应用内亮度控制的
// @Entry 设置成组件 这个要去掉
//这里直接设置成了组件 这样就可以之直接在其他页面应用了 @ComponentV2 和 @Component 的区别请去官方查看吧
@ComponentV2
export struct avPlayer {
myXComponentController: XComponentController = new XComponentController();
private mineAvplayer:avplayer = new avplayer()
@Local nowPlayTime:number = 0
@Local fulldruction:number = 0
@Local nowState:string = 'paused'
@Local nowPlayTimestr:string = ''
@Local fullDrucStr:string = ''
@Local currentVolume$:number = 0
@Local currentLight$:number = 0
@Local MAX_VOLUME:number = 100
@Local MIN_VOLUME:number = 0
@Local showlight:boolean = false
@Local showVolume:boolean = false
@Local offsetY: number = 0;
@Local hideViews:boolean = false
@Local showTextinut:boolean = false
//这里就是我为什么用@ComponentV2 的原因 :
// @Param @Require videourl:string
// @Monitor('videourl')
//检测videourl 变化进而执行 onurlChanged 用于视频源改变后 播放相关操作
@Param @Require videourl:string
@Monitor('videourl')
onurlChanged(monitor:IMonitor){
//这里是查看是否改变了
monitor.dirty.forEach((path: string) => {
console.info(`${path} 获取 changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
});
//url 发生变化 则 进行reset
this.mineAvplayer.reset()
//reset 后 再次 load 进行视频播放
this.reonload()
}
windowStage: window.WindowStage = AppStorage.get('windowStage') as window.WindowStage;
// 获取主窗口的方式
mainWin: window.Window = this.windowStage.getMainWindowSync();
controller: TextInputController = new TextInputController();
@Local textinputtext:string = ''
aboutToAppear(): void {
this.currentLight$ = this.mainWin.getWindowProperties().brightness
}
//获取应用音量
getVolume(){
let audioManager = audio.getAudioManager();
let audioVolumeManager = audioManager.getVolumeManager();
// 查询应用音量。
audioVolumeManager.getAppVolumePercentage().then((value: number) => {
console.info(`获取 app volume is: ${value}.`);
this.currentVolume$ = value
});
}
getState(){
this.nowState = this.mineAvplayer.getState()
}
//设置应用亮度
setlight(){
// 获取最上层窗口的方式
window.getLastWindow(getContext(this));
try {
this.mainWin.setWindowBrightness(this.currentLight$, (err) => {
if (err.code) {
console.error('获取 Failed to set the brightness. Cause: ' + JSON.stringify(err));
return;
}
console.info('获取 Succeeded in setting the brightness.');
});
} catch (exception) {
console.error('获取 Failed to set the brightness. Cause: ' + JSON.stringify(exception));
}
}
//屏幕亮度相关
@Builder
lightpanSliderView(color:Color|string){
Stack(){
Slider({
value:$$this.currentLight$,
min:0,
max:1,
style: SliderStyle.NONE,
direction: Axis.Vertical,
reverse:true //是否取反向
})
//定义滑动条的宽度
.trackThickness('100%')
.height('100%')
.trackColor('#ADD8E6')
.selectedColor('#4169E1')
.showTips(false)
.onChange((value:number)=>{
this.setlight()
})
}
.width(50)
.height(200)
.opacity(this.showlight? 1:0)
}
//设置声音
setVolume(){
this.mineAvplayer.setvolume(this.currentVolume$)
}
//声音相关
@Builder
VolumpanSliderView(color:Color|string){
Stack(){
Slider({
value:$$this.currentVolume$,
min:0,
max:1,
style: SliderStyle.NONE,
direction: Axis.Vertical,
reverse:true //是否取反向
})
//定义滑动条的宽度
.trackThickness('100%')
.height('100%')
.trackColor('#ADD8E6')
.selectedColor('#4169E1')
.showTips(false)
.onChange((value:number)=>{
this.setVolume()
})
}
.width(50)
.height(200)
.opacity(this.showVolume? 1:0)
}
reonload(){
let surfaceId: string = this.myXComponentController.getXComponentSurfaceId();
console.info("XComponent SurfaceId: " + surfaceId);
this.mineAvplayer.seturl(this.videourl)
this.mineAvplayer.setsurfaceId(surfaceId)
this.mineAvplayer.prepare((prepare:boolean)=>{
if (prepare) {
this.mineAvplayer.play()
this.getVolume() //获取音量
}
})
this.mineAvplayer.DructionGetCallBack = (druction)=>{
this.fulldruction = druction
console.info('获取总时长:',JSON.stringify(druction))
//转为时分秒
this.fullDrucStr = formatDuration(this.fulldruction)
}
this.mineAvplayer.NowDructionCallback = (nowdruction)=>{
this.nowPlayTime = nowdruction
console.info('播放当前时间:',JSON.stringify(nowdruction))
//转为时分秒
this.nowPlayTimestr = formatDuration(this.nowPlayTime)
}
}
build() {
Stack(){
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
XComponent({
type: XComponentType.SURFACE,
controller: this.myXComponentController
})
.onLoad(() => {
this.reonload()
})
}
//操作界面
Column(){
if (!this.hideViews){
Row(){
Button('back').onClick((event: ClickEvent) => {
this.mineAvplayer.out()
this.getUIContext().getRouter().back()
})
}
.backgroundColor('rgba(0,0,0,0.7)')
.width('100%')
.height(48)
}
//滑动改变亮度和声音
Row(){
//左侧滑动区域 控制亮度
Column(){
this.lightpanSliderView('')
}
.width('50%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.padding({left:20})
.gesture(
PanGesture()
.onActionStart(() => {
console.info("获取 Pan start");
this.showlight = true
this.offsetY = 0
})
.onActionUpdate((event: GestureEvent) => {
// 单指上下滑动
if (event.source === SourceType.TouchScreen) {
this.offsetY = event.offsetY
console.info("获取 sss offset "+`${this.offsetY}`+'和'+`${this.currentLight$}`);
this.currentLight$ += -this.offsetY*0.0001
}
})
.onActionEnd(()=>{
console.info("获取 offset "+`${this.offsetY}`+'和'+`${this.currentLight$}`);
this.showlight = false
this.offsetY = 0
this.setlight()
})
)
//右侧滑动区域 控制音量
Column(){
this.VolumpanSliderView('')
}
.width('50%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.End)
.padding({right:20})
.gesture(
PanGesture()
.onActionStart(() => {
console.info("获取 Pan start");
this.showVolume = true
this.offsetY = 0
})
.onActionUpdate((event: GestureEvent) => {
// 单指上下滑动
if (event.source === SourceType.TouchScreen) {
console.info("获取 finger move triggered PanGesture");
this.offsetY = event.offsetY
this.currentVolume$ += -this.offsetY*0.0001
}
})
.onActionEnd(()=>{
this.offsetY = 0
this.showVolume = false
this.setVolume()
})
)
}
.backgroundColor(Color.Transparent)
.width('100%')
.layoutWeight(1)
.gesture(
//组合手势的三种中的其中一种mode: Exclusive:互斥识别,注册的手势同时识别,若有一个手势识别成功,则结束手势识别,其他手势识别均失败。
//所以 count :2 放在前面 否则先识别 count:1 的然后就结束
GestureGroup(GestureMode.Exclusive,
//双击暂停、开始
TapGesture({count:2})
.onAction((event:GestureEvent|undefined)=>{
if (event) {
this.getState()
if (this.nowState === 'playing') {
this.mineAvplayer.pause()
}else{
this.mineAvplayer.play()
}
}
}),
TapGesture({count:1})
.onAction((event:GestureEvent|undefined)=>{
if (event) {
this.hideViews = ! this.hideViews
}
})
)
)
if (!this.hideViews){
//播放控制
Row(){
if (!this.showTextinut) {
Button(this.nowState == 'paused'? '暂停':'开始').onClick((event: ClickEvent) => {
if (this.mineAvplayer.getState() == 'paused') {
this.mineAvplayer.play()
}else{
this.mineAvplayer.pause()
}
this.getState()
})
.width(100)
.height(50)
.fontColor(Color.White)
//进度条
Slider({
value:this.nowPlayTime,
min:0,
max:this.fulldruction
})
.height(10)
.layoutWeight(1)
.onChange((value:number,mode:SliderChangeMode)=>{
if (mode === SliderChangeMode.Begin) {
this.mineAvplayer.pause(); // 开始拖拽时暂停播放
}
this.mineAvplayer.seekTime(this.nowPlayTime)
// 执行跳转
this.nowPlayTime = value; // 更新当前时间显示
if (mode === SliderChangeMode.End) {
this.mineAvplayer.play(); // 拖拽结束恢复播放
}
})
//时间显示
Text(`${this.nowPlayTimestr}/${this.fullDrucStr}`)
.fontColor(Color.White)
.width(160)
.height(50)
.textAlign(TextAlign.Center)
Button('发弹幕').onClick((event:ClickEvent)=>{
this.showTextinut = true
})
.width('80')
.height(50)
}else{
Row(){
TextInput({text:this.textinputtext,placeholder:'请输入文字',controller:this.controller})
.backgroundColor(Color.White)
.height(40)
.layoutWeight(1)
.padding({left:10,right:10})
.onSubmit(()=>{
this.showTextinut = false
})
.onEditChange((isedit:boolean)=>{
if (isedit) {
}else{
this.showTextinut = false
}
})
}
}
}
.backgroundColor('rgba(0,0,0,0.7)')
.width('100%')
.height(68)
}
}
.justifyContent(FlexAlign.SpaceEvenly)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}
3:这个是时间转时分秒的工具代码
export function formatDuration(durationMs: number): string {
// 1. 计算总秒数(毫秒转秒)
const totalSeconds = Math.floor(durationMs / 1000);
// 2. 提取小时、分钟、秒
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
// 3. 格式化为两位数字符串(补零)
const pad = (num: number) => num.toString().padStart(2, '0');
// 4. 拼接结果
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
4:接下来就是对封转的avplayer组件的调用了
代码上!!!
import { avPlayer } from './avPlayer'
@Entry
@ComponentV2
struct AvplayerUse{
@Local videourl:string = 'https://yunceyan.oss-cn-beijing.aliyuncs.com/nhii-archive/webmagic-homework/images-video/2025-09-14/c283b8/1.1-319821-1.mp4'
build() {
Column(){
Stack(){
avPlayer({
videourl:this.videourl
})
.width('100%')
.height('100%')
Column(){
Button('切换视频源').onClick((event: ClickEvent) => {
this.videourl = 'http://vjs.zencdn.net/v/oceans.mp4'
})
Button('切换视频源22').onClick((event: ClickEvent) => {
this.videourl = 'https://yunceyan.oss-cn-beijing.aliyuncs.com/nhii-archive/webmagic-homework/images-video/2025-09-14/c283b8/1.1-319821-1.mp4'
})
}
}
}
.width('100%')
.height('100%')
}
}
OK!!!就这些,有些地方我遇到的问题有简单写注释,在代码里
OK!!!!!!!!可以的话点个关注赞和收藏把!!!谢谢咯~~~
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END
















暂无评论内容