HarmonyOS 鸿蒙Next 分享一个封装画中画视频组件
HarmonyOS 鸿蒙Next 分享一个封装画中画视频组件
import { media } from ‘@kit.MediaKit’
import { PiPWindow } from ‘@kit.ArkUI’
@Entry
@Component
export struct XComponentCase {
// 视频源
mediaUrl: string = ‘https://video19.ifeng.com/video09/2024/05/23/p7199260608686989961-0-122707.mp4’
// 自动播放
autoPlay: boolean = false
// 自动浮窗
autoPip: boolean = true
// avplayer控制器
private videoPlayer?: media.AVPlayer
// 视频时长
@State private duration: number = 0
// 播放时长
@State private time: number = 0
// 播放器状态
@State private playState: media.AVPlayerState = ‘idle’
// 是否画中画
@State pipWindow: boolean = false
// 控制栏位置
@State private sliderY: string | number = 0
// 防抖定时器
private timer: number = -1
// XComponent控制器
private controller: XComponentController = new XComponentController()
// 画中画控制器
private pipController?: PiPWindow.PiPController
// 进度条的展示隐藏
changeSlider() {
// 如果显示,防抖倒计时关闭
// 否则先显示,倒计时关闭
if (this.sliderY === 0) {
clearInterval(this.timer)
this.timer = setTimeout(() => {
this.sliderY = ‘100%’
}, 3000)
} else {
this.sliderY = 0
this.timer = setTimeout(() => {
this.sliderY = ‘100%’
}, 3000)
}
}
// 切换浮窗
async changePipWindow() {
this.pipWindow = !this.pipWindow
if (!this.pipController) {
return
}
if (this.pipWindow) {
await this.pipController.startPiP()
} else {
await this.pipController.stopPiP()
}
<span class="hljs-keyword">this</span>.changeSlider()
}
// 时长数字(ms)转字符串
number2time(number: number) {
const ms: number = number % 1000
const second = (number - ms) / 1000
const s: number = second % 60
if (second > 60) {
const m: number = (second - s) / 60 % 60
return m.toString().padStart(2, ‘0’) + ‘:’ + s.toString().padStart(2, ‘0’)
}
return ‘00:’ + s.toString().padStart(2, ‘0’)
}
// 初始化
async init(){
this.videoPlayer = await media.createAVPlayer()
this.videoPlayer.url = this.mediaUrl
this.videoPlayer.on(“stateChange”, async (state) => {
if (state === ‘initialized’) {
if (this.videoPlayer) {
// 只有设置了src才能给播放器设置输出源的id否则无效
this.videoPlayer.surfaceId = this.controller.getXComponentSurfaceId()
this.videoPlayer.prepare()
}
} else if (state === ‘prepared’) {
if (this.videoPlayer && this.autoPlay) {
this.videoPlayer.play()
}
} else if (state === ‘completed’) {
if (this.videoPlayer) {
this.videoPlayer.play()
}
}
this.playState = state
})
this.videoPlayer.on(“timeUpdate”, time => {
this.time = time
})
this.videoPlayer.on(“durationUpdate”, duration => {
this.duration = duration
})
PiPWindow.create({
context: getContext(this),
componentController: this.controller,
templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY, // 对于视频通话、视频会议等场景,需要设置相应的模板类型
}).then(control => {
this.pipController = control
this.pipController.on(“stateChange”,(state)=>{
if(state===PiPWindow.PiPState.STARTED){
this.pipWindow = true
}else if(state === PiPWindow.PiPState.STOPPED){
this.pipWindow = false
}
})
// 自动开启
control.setAutoStartEnabled(this.autoPip)
})
}
build() {
Column() {
Row() {
Row() {
XComponent({
id: ‘xComponent’,
type: XComponentType.SURFACE,
controller: this.controller
})
.onLoad(() => {
// 初始化
this.init()
})
.id(‘xComponent’)
.renderFit(RenderFit.RESIZE_CONTAIN)
.onClick(() => {
this.changeSlider()
})
// 进度条
Row({ space: 12 }) {
Image(this.playState === ‘playing’ ? $r(‘sys.media.ohos_ic_public_pause’) :
$r(‘sys.media.ohos_ic_public_play’))
.width(20)
.aspectRatio(1)
.fillColor(Color.White)
.onClick(() => {
this.playState === ‘playing’ ? this.videoPlayer?.pause() : this.videoPlayer?.play()
this.changeSlider()
})
Text(this.number2time(this.time))
.fontSize(12)
.fontColor(Color.White)
Slider({
value: this.time,
min: 0,
max: this.duration
})
.layoutWeight(1)
.blockColor(Color.White)
.selectedColor(Color.White)
.trackColor(’#ccc5c5c5’)
.trackThickness(2)
.onChange((val) => {
this.videoPlayer?.seek(val, 2)
this.changeSlider()
})
Text(this.number2time(this.duration))
.fontSize(12)
.fontColor(Color.White)
Image($r(‘sys.media.arrowshape_3_triangle_path’))
.width(20)
.aspectRatio(1)
.fillColor(Color.White)
.rotate({
angle: this.pipWindow ? 0 : 180
})
.animation({
duration: 300
})
.onClick(() => {
this.changePipWindow()
})
}
.width(‘100%’)
.padding({
left: 12,
right: 12
})
.linearGradient({
direction: GradientDirection.Bottom,
colors: [[’#33000000’, 0], [’#77000000’, 0.15], [’#aa000000’, 0.75], [’#aa000000’, 1]]
})
.position({
bottom: 0
})
.translate({
y: this.sliderY
})
.animation({
duration: 300
})
}
.width(‘100%’)
.height(‘100%’)
.clip(true)
}.width(‘100%’)
}
.height(‘100%’)
.width(‘100%’)
}
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>
基于xcomponent+avplayer封装的视频组件,支持画中画格式,有视频开发需求的小伙伴可以做个参考~
更多关于HarmonyOS 鸿蒙Next 分享一个封装画中画视频组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS 鸿蒙Next 分享一个封装画中画视频组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS 鸿蒙Next中,封装画中画(Picture-in-Picture,PiP)视频组件涉及使用系统的多媒体框架和UI布局技术。以下是一个简要的封装过程:
-
创建PiP服务: 定义一个PiP服务,该服务负责处理视频的播放和PiP窗口的管理。在服务的
onCreate
方法中,初始化媒体播放器并设置视频源。 -
实现PiP窗口: 使用鸿蒙提供的窗口管理API创建并配置PiP窗口。设置窗口大小、位置以及视频内容的显示。
-
UI布局: 在应用的布局文件中,定义一个容器用于放置PiP窗口。确保容器可以响应触摸事件,以便用户可以拖动或调整PiP窗口的位置和大小。
-
生命周期管理: 在应用的生命周期回调中,管理PiP服务的启动和停止。确保在应用进入后台或用户离开当前页面时,PiP窗口能够正确显示或隐藏。
-
事件处理: 处理用户与PiP窗口的交互,如拖动、缩放以及播放控制(播放/暂停、音量调节等)。
-
权限管理: 确保应用具有必要的权限,如悬浮窗权限、多媒体播放权限等。
通过上述步骤,可以在HarmonyOS 鸿蒙Next中封装一个功能完善的画中画视频组件。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html