代码拜见
gitee: https://gitee.com/jimmy_enjoy/hamonyos_video_demo/tree/master
结果
鸿蒙模拟器-视频轮播结果
数据定义
- export interface VideoItem{
- name: string
- url?: string // 网址播放
- video?: string // rawfile 播放
- head: Resource
- index: number
- }
复制代码 组件
包含Swiper的父组件
- Swiper(this.swiperController) {
- ForEach(videoDataList, (item: VideoItem, index: number) => {
- VideoPlayer({
- curSource: item,
- isPageShow: this.isPageShow,
- curTitle: item.name,
- curIndex: this.curVideoIndex,
- index: index,
- total: this.total,
- onShowPrevious: () => this.showPrevious(index),
- onShowNext: () => this.showNext(index)
- })
- })
- }
- .cachedCount(2)
- // .disableSwipe(true)
- .vertical(true)
- .indicator(false)
- .loop(false)
- .curve(Curve.Ease)
- .duration(300)
- .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
- Logger.info(TAG,
- `onAnimationStart index: ${index},curIndex: ${targetIndex},extraInfo: ${JSON.stringify(extraInfo)}.`);
- this.curVideoIndex = targetIndex;
- // this.curTitle = this.data.getData(this.curVideoIndex)?.name ?? ''
- })
- .width('100%')
- .height('100%')
复制代码 SwiperItem 子组件
- Stack({alignContent: Alignment.BottomStart}){
- Column(){
- XComponent({
- id: 'XComponent',
- type: XComponentType.SURFACE,
- controller: this.xComponentController
- })
- .onLoad(async () => {
- // this.xComponentController.setXComponentSurfaceRect({
- // surfaceWidth: CommonConstants.SURFACE_WIDTH,
- // surfaceHeight: CommonConstants.SURFACE_HEIGHT
- // })
- this.surfaceID = this.xComponentController.getXComponentSurfaceId()
- hilog.info(0x0000, TAG,
- 'surfaceID=' + this.surfaceID + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- this.initAVPlayer()
- })
- .aspectRatio(CommonConstants.ASPECT)
- .width('100%')
- .height(240)
- }
- .justifyContent(FlexAlign.Center)
- .width(CommonConstants.WIDTH_FULL_PERCENT)
- .height(CommonConstants.HEIGHT_FULL_PERCENT)
- .zIndex(CommonConstants.Z_INDEX_BASE)
- // 播放控制,这里需要设置更高的Z-Index,防止事件被最上层的手势监听覆盖
- Column(){
- this.PlayControl()
- }
- .zIndex(CommonConstants.Z_INDEX_MAX)
- }
- .gesture(
- // 监听滑动手势
- PanGesture({direction: PanDirection.Horizontal})
- .onActionStart((event: GestureEvent) => {
- this.isSliderGesture = true
- this.panStartX = event.offsetX
- this.panStartTime = this.currentTime
- this.sliderOnchange(this.panStartTime, SliderChangeMode.Begin)
- })
- .onActionUpdate((event: GestureEvent) => {
- this.isSliderGesture = true
- let panTime = this.panStartTime +
- (this.panStartX + event.offsetX) / this.slideWidth * this.durationTime
- this.panEndTime = Math.min(Math.max(0, panTime), this.durationTime)
- this.sliderOnchange(this.panEndTime, SliderChangeMode.Moving)
- })
- .onActionEnd(() => {
- this.sliderOnchange(this.panEndTime, SliderChangeMode.End)
- this.isSliderGesture = false
- })
- )
- @Builder
- PlayControl(){
- Column(){
- // 滑动时显示,当前时长/总时长
- Row(){
- Text(this.currentStringTime)
- .fontSize($r('app.float.font_size_14'))
- .fontColor(Color.White)
- .opacity($r('app.float.opacity_9'))
- .margin({ left: CommonConstants.TEXT_MARGIN_LEFT })
- .width(CommonConstants.TEXT_LEFT_WIDTH)
- .textAlign(TextAlign.End)
- .zIndex(CommonConstants.SLIDER_INDEX)
- Divider()
- .vertical(true)
- .height($r('app.float.padding_14'))
- .width(CommonConstants.DIVIDER_WIDTH)
- .backgroundBlurStyle(BlurStyle.Regular, { colorMode: ThemeColorMode.LIGHT })
- .color(Color.White)
- .opacity($r('app.float.opacity_9'))
- .margin({ left: $r('app.float.margin_small'), right: $r('app.float.margin_small') })
- .rotate({
- x: CommonConstants.DIVIDER_X,
- y: CommonConstants.DIVIDER_Y,
- z: CommonConstants.DIVIDER_Z,
- centerX: CommonConstants.DIVIDER_CENTER_X,
- centerY: CommonConstants.DIVIDER_CENTER_Y,
- angle: CommonConstants.DIVIDER_ANGLE
- })
- Text(this.durationStringTime)
- .fontSize($r('app.float.font_size_14'))
- .fontColor(Color.White)
- .margin({ right: CommonConstants.TEXT_MARGIN_LEFT })
- .width(CommonConstants.TEXT_LEFT_WIDTH)
- .textAlign(TextAlign.Start)
- .opacity($r('app.float.opacity_4'))
- .zIndex(CommonConstants.SLIDER_INDEX)
- }
- .margin({ bottom: $r('app.float.margin_small') })
- .alignItems(VerticalAlign.Center)
- .opacity(this.isTimeDisplay)
- Column() {
- Row() {
- Image(this.isPlaying ? $r('sys.media.ohos_ic_public_pause') :
- $r('app.media.ic_video_menu_play'))
- .width($r('app.float.size_24'))
- .height($r('app.float.size_24'))
- .fillColor(Color.White)
- .margin({ right: $r('app.float.margin_small') })
- .onClick(() => {
- this.iconOnclick();
- })
- .visibility(
- // this.isFloatWindow || this.isFullLandscapeScreen || this.isFullScreen ||
- this.isSliderDragging ? Visibility.None : Visibility.Visible)
- Slider({
- value: this.isSliderGesture ? this.panEndTime : this.currentTime,
- step: CommonConstants.SLIDER_STEP,
- min: CommonConstants.SLIDER_MIN,
- max: this.durationTime,
- style: this.sliderStyle
- })
- .id('video_slider')
- .height(this.isSliderDragging ? $r('app.float.side_width') : $r('app.float.size_24'))
- .trackColor($r('app.color.white_opacity_1_color'))
- .showSteps(false)
- .blockSize({ width: this.blockSize, height: this.blockSize })
- .blockColor($r('sys.color.background_primary'))
- .layoutWeight(1)
- .trackThickness(this.trackThicknessSize)
- .trackBorderRadius(CommonConstants.TRACK_BORDER_RADIUS)
- .selectedBorderRadius(CommonConstants.TRACK_BORDER_RADIUS)
- .zIndex(CommonConstants.SLIDER_INDEX)
- .onAreaChange(() => {
- let videoSlider: componentUtils.ComponentInfo = componentUtils.getRectangleById('video_slider');
- this.slideWidth = px2vp(videoSlider.size.width);
- // this.offsetY = px2vp(videoSlider.localOffset.y);
- // this.beginX = px2vp(videoSlider.localOffset.x);
- })
- .onChange((value: number, mode: SliderChangeMode) => {
- this.sliderOnchange(value, mode);
- })
- }
- .width(CommonConstants.WIDTH_FULL_PERCENT)
- }
- }
- }
复制代码 AVPlayer
创建AVPlayer
- async initAVPlayer(){
- hilog.info(0x0000, TAG, 'createAVPlayer begin')
- media.createAVPlayer().then((video: media.AVPlayer) => {
- if(video !== null){
- Logger.info(TAG, 'initAVPlayer video')
- this.avPlayer = video
- this.avPlayerStart(this.curSource, this.avPlayer)
- this.setAVPlayerCallback(this.avPlayer)
- }
- }).catch((error: BusinessError) => {
- Logger.error(TAG, `AVPlayer catchCallback, error message:${error.message}`);
- })
- }
复制代码 注册AVPlayer回调函数
- setAVPlayerCallback(avPlayer: media.AVPlayer){
- // 更新视频当前时间状态
- avPlayer.on('timeUpdate', (time: number) => {
- if(time > this.currentTime * 1000){
- animateTo({duration: 1000, curve: Curve.Linear}, () => {
- this.currentTime = Math.floor(time / 1000)
- })
- } else {
- this.currentTime = Math.floor(time / 1000)
- }
- this.currentStringTime = secondToTime(Math.floor(time / CommonConstants.SECOND_TO_MS))
- })
- // 错误监听
- avPlayer.on('error', (err: BusinessError) => {
- hilog.error(0x0000, TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}` +
- `----state:${avPlayer.state} this.curIndex:${this.curIndex} this.index: ${this.index}`);
- avPlayer.reset();
- })
- this.setAVPlayerStateListen(avPlayer)
- }
- setAVPlayerStateListen(avPlayer: media.AVPlayer){
- // 监听状态变化
- avPlayer.on('stateChange', async (state: string) => {
- Logger.info(TAG, 'avplayer stateChange')
- switch (state){
- case 'idle': // call reset into idle state
- hilog.info(0x0000, TAG, 'AVPlayer state idle called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`)
- break
- case 'initialized':
- hilog.info(0x0000, TAG,
- 'AVPlayer state initialized called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- avPlayer.surfaceId = this.surfaceID;
- avPlayer.prepare();
- break;
- case 'prepared':
- avPlayer.audioInterruptMode = audio.InterruptMode.INDEPENDENT_MODE;
- hilog.info(0x0000, TAG,
- 'AVPlayer state prepared called.' +
- ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- this.flag = true;
- avPlayer.loop = true;
- this.duration = avPlayer.duration;
- this.durationTime = Math.floor(this.duration / CommonConstants.SECOND_TO_MS);
- this.durationStringTime = secondToTime(this.durationTime);
- if (this.curIndex === this.index) {
- this.playVideo()
- // this.firstFlag = false;
- }
- break;
- case 'completed':
- hilog.info(0x0000, TAG,
- 'AVPlayer state completed called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- this.isPlaying = false;
- break;
- case 'playing':
- this.isPlaying = true;
- hilog.info(0x0000, TAG,
- 'AVPlayer state playing called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}` +
- ', source=' + this.curSource);
- break;
- case 'paused':
- this.isPlaying = false
- Logger.info(TAG,
- 'AVPlayer state paused called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- break;
- case 'stopped':
- Logger.info(TAG,
- 'AVPlayer state stopped called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- break;
- case 'released':
- Logger.info(TAG,
- 'AVPlayer state released called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- break;
- case 'error':
- Logger.info(TAG, 'AVPlayer state error called' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- // avPlayer.reset();
- break;
- default:
- Logger.info(TAG,
- 'AVPlayer state unknown called.' + ` this.curIndex:${this.curIndex} this.index:${this.index}`);
- break;
- }
- })
- }
复制代码 AVSession会话控制
- 需要申请长时使命
- 在通知栏可以由系统视频控制器举行视频的控制,包括进度条滑动、快进、快退、暂停、下一首、上一首。
后台长时使命申请
需要在module.json中注册
- {
- "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
- "reason": "$string:reason_background",
- "usedScene": {
- "abilities": [
- "EntryAbility"
- ],
- "when": "always"
- }
- }
复制代码- export class BackgroundTaskManager{
- public static startContinuousTask(context?: common.UIAbilityContext): void{
- if(!context) return
- let wantAgentInfo: wantAgent.WantAgentInfo = {
- wants: [
- {
- bundleName: context.abilityInfo.bundleName,
- abilityName: context.abilityInfo.name
- }
- ],
- actionType: wantAgent.OperationType.START_ABILITY,
- requestCode: 0,
- actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
- }
- wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj) => {
- try {
- // 启动后台任务
- backgroundTaskManager.startBackgroundRunning(context, backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK,
- wantAgentObj).then(() => {
- Logger.info(TAG, 'startBackgroundRunning succeeded');
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `startBackgroundRunning failed Cause: ${JSON.stringify(err)}`);
- })
- }catch (error){
- Logger.error(TAG, `Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
- }
- })
- }
- public static stopContinuousTask(context?: common.UIAbilityContext){
- try {
- if (!context) {
- return
- }
- backgroundTaskManager.stopBackgroundRunning(context).then(() => {
- Logger.info(TAG, 'stopBackgroundRunning succeeded');
- }).catch((err: BusinessError) => {
- Logger.error(TAG, `stopBackgroundRunning failed Cause: ${JSON.stringify(err)}`);
- });
- } catch (error) {
- Logger.error(TAG, `stopBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
- }
- }
- }
复制代码 AVSession
AVSession 在整个应用中,全局共享实例。
创建
- export class AvSessionController{
- private static instance: AvSessionController | null
- private context: common.UIAbilityContext | undefined = undefined;
- private avSession: avSession.AVSession | undefined = undefined;
- private avSessionMetadata: avSession.AVMetadata | undefined = undefined;
- private constructor() {
- this.initAvSession()
- }
- public getAvSession() {
- return this.avSession;
- }
- public getAvSessionMetadata() {
- return this.avSessionMetadata;
- }
- public static getInstance(): AvSessionController{
- if(!AvSessionController.instance){
- AvSessionController.instance = new AvSessionController()
- }
- return AvSessionController.instance
- }
- // 初始化
- public initAvSession(){
- this.context = AppStorage.get('context')
- if(!this.context){
- Logger.info(TAG, `session create failed : conext is undefined`)
- return
- }
- avSession.createAVSession(this.context, "SHORT_AUDIO_SESSION", 'video').then(async (avSession) => {
- this.avSession = avSession
- Logger.info(TAG, `session create successed : sessionId : ${this.avSession.sessionId}`);
- // 启动后台长任务
- BackgroundTaskManager.startContinuousTask(this.context);
- // 设置点击后启动的activity
- this.setLaunchAbility();
- this.avSession.activate();
- })
- }
-
- private setLaunchAbility(){
- if(!this.context) return
- const wantAgentInfo: wantAgent.WantAgentInfo = {
- wants: [
- {
- bundleName: this.context.abilityInfo.bundleName,
- abilityName: this.context.abilityInfo.name
- }
- ],
- actionType: wantAgent.OperationType.START_ABILITIES,
- requestCode: 0,
- actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
- }
- wantAgent.getWantAgent(wantAgentInfo).then((agent) => {
- if(this.avSession) this.avSession.setLaunchAbility(agent)
- })
- }
复制代码 在AVPlayer中结合AVSession
初始化
- async initAVPlayer(){
- hilog.info(0x0000, TAG, 'createAVPlayer begin')
- media.createAVPlayer().then((video: media.AVPlayer) => {
- if(video !== null){
- // ...
- if(this.curIndex === this.curSource.index){
- // 设置媒体显示信息
- this.avSessionController.setAVMetadata(this.curSource, 0)
- }
- this.setAvSessionListener()
- }
- }).catch((error: BusinessError) => {
- Logger.error(TAG, `AVPlayer catchCallback, error message:${error.message}`);
- })
- }
- // 设置会话控制的监听,进行相应媒体播放器调整
- public async setAvSessionListener(){
- if(!this.avSessionController) return
- this.avSessionController.getAvSession()?.on('play', () => this.sessionPlayCallback())
- this.avSessionController.getAvSession()?.on('pause', () => this.sessionPauseCallback())
- this.avSessionController.getAvSession()?.on('stop', () => this.sessionStopCallback())
- this.avSessionController.getAvSession()?.on('fastForward',
- (time?: number) => this.sessionFastForwardCallback(time))
- this.avSessionController.getAvSession()?.on('rewind', (time?: number) => this.sessionRewindCallback(time))
- this.avSessionController.getAvSession()?.on('seek', (seekTime: number) => this.sessionSeekCallback(seekTime))
- if(this.index < this.total - 1){
- this.avSessionController.getAvSession()?.on('playNext', () => this.sessionPlayNextCallback())
- }else {
- this.avSessionController.getAvSession()?.off('playNext')
- }
- if(this.index > 0){
- this.avSessionController.getAvSession()?.on('playPrevious', () => this.sessionPlayPreviousCallback())
- }else {
- this.avSessionController.getAvSession()?.off('playPrevious')
- }
- }
复制代码 上一首和下一首实现
通过父容器传递的showNext 和 showPrevious,具体为通过SwiperController实现切换
- showNext(index: number): void{
- if(this.swiperController && index < this.total - 1){
- this.swiperController.changeIndex(index + 1, true)
- getContext(this).eventHub.emit('videoShow', index + 1)
- }
- }
- showPrevious(index: number): void{
- if(this.swiperController && index > 0){
- this.swiperController.changeIndex(index - 1, true)
- getContext(this).eventHub.emit('videoShow', index + 1)
- }
- }
复制代码 重要公共方法实现
- // 播放视频
- playVideo(){
- if(this.avPlayer && this.curIndex === this.index){
- if(this.avPlayer.state != AVPlayerState.PREPARED && this.avPlayer.state != AVPlayerState.PAUSED &&
- this.avPlayer.state != AVPlayerState.COMPLETED){
- return
- }
- // 重置会话监听状态
- this.setAvSessionListener()
- this.avSessionController.setAVMetadata(this.curSource, this.duration)
- this.updateIsPlay(true)
- this.avPlayer.play((err: BusinessError) => {
- if(err){
- this.updateIsPlay(false)
- Logger.error(TAG, `playVideo failed, code is ${err.code}, message is ${err.message}`);
- } else {
- Logger.info(TAG, `playVideo success , this.curIndex:${this.curIndex}`);
- }
- })
- }
- }
- // 更新播放状态
- updateIsPlay(isPlay: boolean){
- if(this.curIndex !== this.curSource.index) return
- this.isPlaying = isPlay
- // 更新会话控制中,媒体播放状态。
- this.avSessionController.setAvSessionPlayState({
- state: isPlay ? avSession.PlaybackState.PLAYBACK_STATE_PLAY : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
- position: {
- elapsedTime: this.currentTime * 1000, // 已播放时长
- updateTime: new Date().getTime() // 更新时间
- },
- duration: this.duration
- })
- }
复制代码 注
- 完备代码
- 可以用LazyForEach结合Swiper的cachecount实现懒加载与缓存。
- 这里没有实现屏幕旋转的环境。
- 更完备的示例可以参考 鸿蒙官方示例
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |