手撸了一个视频播放器,功能包含快进、快退、暂停、播放、进度条、全屏
难点
既然有视频播放器,那肯定需要全屏播放,如果按照常规思路,在全屏播放时需要将应用设置为横屏,用到的方法为:
// 切换屏幕方向(横屏/竖屏)
private async switchOrientation(landscape: boolean) {
try {
// 获取当前窗口实例
const topWindow = await window.getTopWindow()getContext(this);
if (landscape) {
// 设置为横屏(左右横屏均可)
await topWindow.setPreferredOrientation(window.Orientation.LANDSCAPE);
this.isLandscape = true;
} else {
// 恢复竖屏
await topWindow.setPreferredOrientation(window.Orientation.PORTRAIT);
this.isLandscape = false;
}
} catch (err) {
hilog.error(0x0000, 'VideoDemo', `切换方向失败: ${(err as BusinessError).message}`);
}
}
这种方法的弊端在于会横竖屏切换时会重新走声明周期,这样就导致播放进度、是否展示了控制面板这些的状态失效,而如果想保留状态,就需要进行一大堆操作,非常麻烦,因此咱们放弃这种方法,改成直接将播放器组件旋转90度,然后将播放器组件的宽度设置为屏幕的高度,这样也能达到效果,而且无需重走生命周期,非常简单,下面是完整demo
import { window } from '@kit.ArkUI';
import display from '@ohos.display'
@Entry
@Component
export struct VideoPlayer {
@State isPlaying: boolean = false;
@State currentTime: number = 0; // 当前播放时间(秒)
@State duration: number = 0; // 总时长(秒)
@State isFullScreen: boolean = false; // 是否全屏
@State showControls: boolean = true; // 是否显示控制栏
@State controlTimer: number = 0; // 控制栏自动隐藏计时器
private videoController: VideoController = new VideoController();
private totalWidth: number = 0 //横屏时的宽度
private totalHeight: number = 0 //横屏时的高度
// 视频源(支持网络URL或本地文件路径)
videoSrc: string = ''
aboutToAppear(): void {
this.getScreenSize()
}
aboutToDisappear(): void {
this.clearControlTimer(); // 组件消失时清理计时器
}
private async getScreenSize() {
const win = await window.getLastWindow(getContext(this));
const size = await win.getWindowProperties();
//屏幕宽高除以屏幕密度,得到vp;高度需要+5进行微调,否则横屏时顶部会有一个小白条
this.totalWidth = size.windowRect.width / display.getDefaultDisplaySync().densityPixels
this.totalHeight = size.windowRect.height / display.getDefaultDisplaySync().densityPixels + 5
}
build() {
Row() {
RelativeContainer() {
this.VideoView()
this.ControlView()
this.RemindView()
}
.width('100%')
.height(this.isFullScreen ? this.totalWidth : 300)
.rotate({
x: 0,
y: 0,
z: 1,
centerX: '50%',
centerY: '50%',
angle: this.isFullScreen ? 90 : 0 // 旋转角度:90度
})
}
.alignItems(VerticalAlign.Center)
.width('100%')
.height('100%')
.backgroundColor('#80000000')
}
//播放器
@Builder
VideoView() {
// 视频播放区域
Video({
src: this.videoSrc,
controller: this.videoController
})
.width(this.isFullScreen ? this.totalHeight : '100%')
.height(this.isFullScreen ? this.totalWidth : 300)
.objectFit(ImageFit.Contain)
.controls(false)
.onStart(() => {
this.isPlaying = true;
this.startControlTimer(); // 播放时启动控制栏隐藏计时器
})
.onPause(() => {
this.showControls = true; // 暂停时显示控制栏
})
.onFinish(() => {
this.isPlaying = false;
this.currentTime = 0;
this.showControls = true;
})
.onPrepared((callBackDuration) => {
this.duration = callBackDuration.duration; // 更新总时长
})
.autoPlay(true)
.onUpdate((callBackPlayTime) => {
this.currentTime = callBackPlayTime.time; // 更新当前播放时间
})
.onClick(() => {
this.toggleControls(); // 点击视频切换控制栏显示状态
})
.alignRules({
middle: { anchor: '__container__', align: HorizontalAlign.Center },
center: { anchor: '__container__', align: VerticalAlign.Center },
})
}
//快进、快退、暂停、播放、进度条
@Builder
ControlView() {
// 控制栏(底部显示)
Column() {
// 控制按钮区域
Row() {
// 快退按钮(10秒)
Button('快退10s')
.fontSize(14)
.padding({ left: 12, right: 12 })
.margin({ left: 10 })
.onClick(() => this.rewind(10));
Text().layoutWeight(1)
// 快进按钮(10秒)
Button('快进10s')
.fontSize(14)
.padding({ left: 12, right: 12 })
.margin({ left: 10 })
.onClick(() => this.forward(10));
}
//进度条
Row() {
if (this.isFullScreen) {
Text().width(15)
}
Text(`${this.formatTime(this.currentTime)}`)
.fontSize(14)
.margin({ left: 5, right: 5 })
.fontColor('#fff')
Slider({
value: this.currentTime,
min: 0,
max: this.duration,
step: 1
})
.layoutWeight(1)
.blockColor('#ffffff')
.selectedColor('#ffffff')
.trackColor('#cccccc')
.onChange((value) => {
this.videoController.setCurrentTime(value); // 拖动进度条跳转播放位置
})
.onTouch((event) => {
if (event.type === TouchType.Down) {
this.clearControlTimer(); // 触摸进度条时暂停隐藏计时器
} else if (event.type === TouchType.Up) {
this.startControlTimer(); // 结束触摸后重启计时器
}
})
Text(`${this.formatTime(this.duration)}`)
.fontSize(14)
.margin({ left: 5, right: 5 })
.fontColor('#fff')
Button(this.isFullScreen ? '退出全屏' : '全屏')
.fontSize(14)
.margin({ left: 5, right: 5 })
.onClick(() => this.toggleFullScreen())
if (this.isFullScreen) {
Text().width(15)
}
}
}
.alignItems(HorizontalAlign.Start)
.width(this.isFullScreen ? this.totalHeight : '100%')
.backgroundColor('#80000000')
.visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
.padding({ top: 5, bottom: 5 })
.alignRules({
middle: { anchor: '__container__', align: HorizontalAlign.Center },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
})
}
//加载中
@Builder
RemindView() {
// 加载提示--暂不需要
// if (this.duration == 0) {
// Text('加载中...')
// .fontSize(14)
// .fontColor('#fff')
// .alignRules({
// middle: { anchor: '__container__', align: HorizontalAlign.Center },
// center: { anchor: '__container__', align: VerticalAlign.Center },
// })
// }
if (this.isPlaying) {
Button('暂停')
.fontSize(14)
.padding({ left: 12, right: 12 })
.onClick(() => this.togglePlay())
.visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
.alignRules({
middle: { anchor: '__container__', align: HorizontalAlign.Center },
center: { anchor: '__container__', align: VerticalAlign.Center },
})
} else {
Button('播放')
.fontSize(14)
.padding({ left: 12, right: 12 })
.onClick(() => this.togglePlay())
.visibility(this.showControls ? Visibility.Visible : Visibility.Hidden)
.alignRules({
middle: { anchor: '__container__', align: HorizontalAlign.Center },
center: { anchor: '__container__', align: VerticalAlign.Center },
})
}
}
// 切换播放/暂停
private togglePlay() {
if (this.duration == 0) {
return
}
if (this.isPlaying) {
this.videoController.pause();
} else {
this.videoController.start();
}
this.isPlaying = !this.isPlaying;
}
// 快进n秒
private forward(seconds: number) {
if (this.duration == 0) {
return
}
const newTime = this.currentTime + seconds;
this.videoController.setCurrentTime(Math.min(newTime, this.duration)); // 不超过总时长
}
// 快退n秒
private rewind(seconds: number) {
if (this.duration == 0) {
return
}
const newTime = this.currentTime - seconds;
this.videoController.setCurrentTime(Math.max(newTime, 0)); // 不小于0
}
// 切换全屏/窗口模式
private async toggleFullScreen() {
this.isFullScreen = !this.isFullScreen
}
// 切换控制栏显示状态
private toggleControls() {
this.showControls = !this.showControls;
if (this.showControls && this.isPlaying) {
this.startControlTimer(); // 显示时启动计时器(仅在播放中)
} else {
this.clearControlTimer();
}
}
// 启动控制栏自动隐藏计时器(3秒后隐藏)
private startControlTimer() {
this.clearControlTimer();
this.controlTimer = setTimeout(() => {
this.showControls = false;
}, 3000)
}
// 清除计时器
private clearControlTimer() {
if (this.controlTimer) {
clearTimeout(this.controlTimer);
this.controlTimer = 0;
}
}
// 格式化时间(秒 -> mm:ss)
private formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
}