鸿蒙实战开发:视频播放器实现(下)

鸿蒙实战开发:视频播放器实现(下)

码农世界 2024-05-27 后端 61 次浏览 0个评论

鸿蒙实战开发:视频播放器实现(上)

鸿蒙实战开发:视频播放器实现(中) 

鸿蒙实战开发:视频播放器实现(下) 


经过前面两篇文章的分享,我们实现了视频播放器的播放功能,在本文中,我们继续讲解视频播放器暂停/播放、切换进度、切换视频功能的实现。

1、暂停/播放

目前已经可以播放视频了,但是并不能暂停,因此我们继续给播放器添加暂停/播放功能。

  • 暂停

    给播放器添加一个 pause 的方法,在该方法调用AVPlayer的pause()来实现视频的暂停,同时将播放状态设置为 false,并通过updateState()将播放信息同步给页面

    import media from '@ohos.multimedia.media'
    export class VideoAVPlayerClass {
      // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
      static player: media.AVPlayer | null = null
      // 当前播放器播放视频的总时长
      static duration: number = 0
      // 当前播放器播放的时长
      static time: number = 0
      // 当前播放器是否播放
      static isPlay: boolean = false
        // 当前播放器的播放列表
      static playList: videoItemType[] = []
      
      // 当前播放的视频索引
      static playIndex: number = 0
      // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
      static surfaceId: string = ''
      
      // 创建播放器的方法
      static async init(initParams: InitParams) {
        // 存储属性SurfaceID,用于设置播放窗口,显示画面
        VideoAVPlayerClass.surfaceId = initParams.surfaceId
        // 创建播放器实例
        VideoAVPlayerClass.player = await media.createAVPlayer()
        // ----------------------- 事件监听 --------------------------------------------------------------
        // 用于进度条,监听进度条长度,刷新资源时长
        VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
            console.info('AVPlayer state durationUpdate called. current time: ', duration);
            // 获取视频总时长
            VideoAVPlayerClass.duration = duration
        })
        // 用于进度条,监听进度条当前位置,刷新当前时间
        VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
          console.info('AVPlayer state timeUpdate called. current time: ', time);
          // 获取当前播放时长
          VideoAVPlayerClass.time = time
          // 更新信息到页面
          VideoAVPlayerClass.updateState()
        })
        // 监听seek生效的事件
        VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
          console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
          VideoAVPlayerClass.avPlayer.play()
          VideoAVPlayerClass.isPlay = true
        })
        // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
        VideoAVPlayerClass.avPlayer.on('error', (err) => {
          console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
          // 调用reset重置资源,触发idle状态
          VideoAVPlayerClass.avPlayer.reset()
        })
        // 监听播放状态机AVPlayerState切换的事件
        VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
          switch (state) {
          // 成功调用reset接口后触发该状态机上报
            case 'idle':
              console.info('AVPlayer state idle called.');
              break
           // avplayer 设置播放源后触发该状态上报
            case 'initialized':
              console.info('AVPlayerstate initialized called.');
              // 设置显示画面,当播放的资源为纯音频时无需设置
              VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
              break
          // prepare调用成功后上报该状态机
            case 'prepared':
              console.info('AVPlayer state prepared called.');
              break
          // play成功调用后触发该状态机上报
            case 'playing':
              console.info('AVPlayer state playing called.');
              break
          // pause成功调用后触发该状态机上报
            case 'paused':
              console.info('AVPlayer state paused called.');
              break
          // 播放结束后触发该状态机上报
            case 'completed':
              console.info('AVPlayer state completed called.');
              break
          // stop接口成功调用后触发该状态机上报
            case 'stopped':
              console.info('AVPlayer state stopped called.');
            // 调用reset接口初始化avplayer状态
              VideoAVPlayerClass.avPlayer.reset()
              break
            case 'released':
              console.info('AVPlayer state released called.');
              break;
            default:
              console.info('AVPlayer state unknown called.');
              break;
          }
        })
      }
      // 视频暂停
      static pause() {
        VideoAVPlayerClass.avPlayer.pause()
        VideoAVPlayerClass.isPlay = false
        VideoAVPlayerClass.updateState()
      }
      static async changePlay() {
        // 将播放状态置为闲置
        await VideoAVPlayerClass.avPlayer.reset()
        VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
        VideoAVPlayerClass.updateState()
      }
      // 更新页面状态
      static async updateState() {
        const data = {
          playState: JSON.stringify({
            duration: VideoAVPlayerClass.duration,
            time: VideoAVPlayerClass.time,
            isPlay: VideoAVPlayerClass.isPlay,
            playIndex: VideoAVPlayerClass.playIndex,
            playList: VideoAVPlayerClass.playList,
          })
        }
        // 更新页面
        emitter.emit({
          eventId: EmitEventType.UPDATE_STATE
        }, {
          data
        })
      }
      
    }
    
    • 播放

      给播放器添加一个 play 方法,在该方法中,在该方法调用AVPlayer的play()来实现视频的播放,同时将播放状态设置为 true,并通过updateState()将播放信息同步给页面

      import media from '@ohos.multimedia.media'
      export class VideoAVPlayerClass {
        // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
        static player: media.AVPlayer | null = null
        // 当前播放器播放视频的总时长
        static duration: number = 0
        // 当前播放器播放的时长
        static time: number = 0
        // 当前播放器是否播放
        static isPlay: boolean = false
          // 当前播放器的播放列表
        static playList: videoItemType[] = []
        
        // 当前播放的视频索引
        static playIndex: number = 0
        // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
        static surfaceId: string = ''
        
        // 创建播放器的方法
        static async init(initParams: InitParams) {
          // 存储属性SurfaceID,用于设置播放窗口,显示画面
          VideoAVPlayerClass.surfaceId = initParams.surfaceId
          // 创建播放器实例
          VideoAVPlayerClass.player = await media.createAVPlayer()
          // ----------------------- 事件监听 --------------------------------------------------------------
          // 用于进度条,监听进度条长度,刷新资源时长
          VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
              console.info('AVPlayer state durationUpdate called. current time: ', duration);
              // 获取视频总时长
              VideoAVPlayerClass.duration = duration
          })
          // 用于进度条,监听进度条当前位置,刷新当前时间
          VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
            console.info('AVPlayer state timeUpdate called. current time: ', time);
            // 获取当前播放时长
            VideoAVPlayerClass.time = time
            // 更新信息到页面
            VideoAVPlayerClass.updateState()
          })
          // 监听seek生效的事件
          VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
            console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
            VideoAVPlayerClass.avPlayer.play()
            VideoAVPlayerClass.isPlay = true
          })
          // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
          VideoAVPlayerClass.avPlayer.on('error', (err) => {
            console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
            // 调用reset重置资源,触发idle状态
            VideoAVPlayerClass.avPlayer.reset()
          })
          // 监听播放状态机AVPlayerState切换的事件
          VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
            switch (state) {
            // 成功调用reset接口后触发该状态机上报
              case 'idle':
                console.info('AVPlayer state idle called.');
                break
             // avplayer 设置播放源后触发该状态上报
              case 'initialized':
                console.info('AVPlayerstate initialized called.');
                // 设置显示画面,当播放的资源为纯音频时无需设置
                VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
                break
            // prepare调用成功后上报该状态机
              case 'prepared':
                console.info('AVPlayer state prepared called.');
                break
            // play成功调用后触发该状态机上报
              case 'playing':
                console.info('AVPlayer state playing called.');
                break
            // pause成功调用后触发该状态机上报
              case 'paused':
                console.info('AVPlayer state paused called.');
                break
            // 播放结束后触发该状态机上报
              case 'completed':
                console.info('AVPlayer state completed called.');
                break
            // stop接口成功调用后触发该状态机上报
              case 'stopped':
                console.info('AVPlayer state stopped called.');
              // 调用reset接口初始化avplayer状态
                VideoAVPlayerClass.avPlayer.reset()
                break
              case 'released':
                console.info('AVPlayer state released called.');
                break;
              default:
                console.info('AVPlayer state unknown called.');
                break;
            }
          })
        }
        // 视频播放
        static async play() {
          VideoAVPlayerClass.avPlayer.play()
          VideoAVPlayerClass.isPlay = true
          VideoAVPlayerClass.updateState()
        }
        // 视频暂停
        static pause() {
          VideoAVPlayerClass.avPlayer.pause()
          VideoAVPlayerClass.isPlay = false
          VideoAVPlayerClass.updateState()
        }
        static async changePlay() {
          // 将播放状态置为闲置
          await VideoAVPlayerClass.avPlayer.reset()
          VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
          VideoAVPlayerClass.updateState()
        }
        // 更新页面状态
        static async updateState() {
          const data = {
            playState: JSON.stringify({
              duration: VideoAVPlayerClass.duration,
              time: VideoAVPlayerClass.time,
              isPlay: VideoAVPlayerClass.isPlay,
              playIndex: VideoAVPlayerClass.playIndex,
              playList: VideoAVPlayerClass.playList,
            })
          }
          // 更新页面
          emitter.emit({
            eventId: EmitEventType.UPDATE_STATE
          }, {
            data
          })
        }
        
      }
      
      • 页面调用

        在Index.ets页面中,根据播放状态,在点击播放窗口时调用 play() 或 pause() 方法进行视频的播放/暂停。

        import emitter from '@ohos.events.emitter';
        import PlayingAnimation from '../components/PlayingAnimation';
        import { EmitEventType } from '../constants/EventContants';
        import { VideoListData } from '../constants/VideoConstants';
        import { PlayStateType, PlayStateTypeModel } from '../models/playState';
        import { videoItemType } from '../models/video';
        import { VideoPlayStateType, VideoPlayStateTypeModel } from '../models/videoPlayState';
        import { VideoAVPlayerClass } from '../utils/VideoAVPlayerClass';
        @Preview
        @Component
        struct Index {
          @State
          playState: VideoPlayStateType = new VideoPlayStateTypeModel({} as VideoPlayStateType)
          xComController: XComponentController = new XComponentController()
          surfaceId: string = "" // 定义surfaceId
          videoList: videoItemType[] = VideoListData
          async aboutToAppear() {
            // 从播放器订阅数据
            emitter.on({ eventId: EmitEventType.UPDATE_STATE }, (data) => {
              this.playState = new VideoPlayStateTypeModel(JSON.parse(data.data.playState))
            })
          }
          aboutToDisappear(){
            // 销毁播放器
            VideoAVPlayerClass.avPlayer.release()
          }
          // 时长数字(ms)转字符串
          number2time(number: number) {
            if (!number) {
              return '00:00'
            }
            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')
          }
          build() {
            Row() {
              Column({ space: 10 }) {
                Stack() {
                  Column() {
                    Row(){
                      // 视频播放窗口
                      XComponent({
                        id: 'videoXComponent',
                        type: 'surface',
                        controller: this.xComController
                      })
                        .width('100%')
                        .height(200)
                        .onLoad(async () => {
                          this.xComController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 });
                          this.surfaceId = this.xComController.getXComponentSurfaceId()
                          if (this.surfaceId) {
                            await VideoAVPlayerClass.init({surfaceId: this.surfaceId, playList: this.videoList, context: getContext(this)})
                            await VideoAVPlayerClass.singlePlay()
                          }
                        })
                    }
                    .onClick(() => {
                      this.playState.isPlay ? VideoAVPlayerClass.pause() : VideoAVPlayerClass.play()
                    })
                    // 进度条
                    Row({space: 6}){
                      // 当前播放时长
                      Text(this.number2time(this.playState?.time))
                        .fontColor($r('app.color.white'))
                        .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                      // 进度条
                      Slider({
                        value: this.playState.time,
                        min: 0,
                        max: this.playState.duration,
                      })
                        .trackColor($r('app.color.white'))
                        .width("70%")
                      // 视频总时长
                      Text(this.number2time(this.playState?.duration))
                        .fontColor($r('app.color.white'))
                        .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                    }
                    .width('100%')
                    .height(20)
                    .margin({
                      top: 10
                    })
                    .justifyContent(FlexAlign.Center)
                  }
                  .width('100%')
                  .height(270)
                  .padding({
                    top: 30,
                    bottom:30
                  })
                  .backgroundColor($r('app.color.black'))
                  .justifyContent(FlexAlign.Start)
                  // 播放按钮
                  if (!this.playState.isPlay) {
                    Image($r('app.media.ic_play'))
                      .width(48)
                      .height(48)
                      .fillColor($r('app.color.white'))
                      .onClick(() => {
                        VideoAVPlayerClass.play()
                      })
                  }
                }
                // 视频列表缩略图
                List({ space: 10, initialIndex: 0 }) {
                  ForEach(this.videoList, (item: videoItemType, index: number) => {
                    ListItem() {
                      Stack({alignContent: Alignment.Center}){
                        Image(item.imgUrl)
                          .width(100)
                          .height(80)
                        // .objectFit(ImageFit.Contain)
                        if (this.playState.playIndex === index) {
                          Row(){
                            PlayingAnimation({ recordIng: true })
                          }
                        }
                      }
                    }
                    .width(100)
                    .onClick(() => {
                      VideoAVPlayerClass.singlePlay(item)
                    })
                  }, item => item)
                }
                .height(100)
                .listDirection(Axis.Horizontal) // 排列方向
                .edgeEffect(EdgeEffect.Spring) // 滑动到边缘无效果
                .onScrollIndex((firstIndex: number, lastIndex: number) => {
                  console.info('first' + firstIndex)
                  console.info('last' + lastIndex)
                })
              }
              .width('100%')
              .height('100%')
            }
            .height('100%')
            .width('100%')
          }
        }
        export default Index
        

        2、切换播放进度

        给播放器添加一个seekTime方法,指定播放进度。在该方法中,调用AVPlayer的 seek() 方法可以跳转到指定播放位置。

        import media from '@ohos.multimedia.media'
        export class VideoAVPlayerClass {
          // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
          static player: media.AVPlayer | null = null
          // 当前播放器播放视频的总时长
          static duration: number = 0
          // 当前播放器播放的时长
          static time: number = 0
          // 当前播放器是否播放
          static isPlay: boolean = false
            // 当前播放器的播放列表
          static playList: videoItemType[] = []
          
          // 当前播放的视频索引
          static playIndex: number = 0
          // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
          static surfaceId: string = ''
          
          // 创建播放器的方法
          static async init(initParams: InitParams) {
            // 存储属性SurfaceID,用于设置播放窗口,显示画面
            VideoAVPlayerClass.surfaceId = initParams.surfaceId
            // 创建播放器实例
            VideoAVPlayerClass.player = await media.createAVPlayer()
            // ----------------------- 事件监听 --------------------------------------------------------------
            // 用于进度条,监听进度条长度,刷新资源时长
            VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
                console.info('AVPlayer state durationUpdate called. current time: ', duration);
                // 获取视频总时长
                VideoAVPlayerClass.duration = duration
            })
            // 用于进度条,监听进度条当前位置,刷新当前时间
            VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
              console.info('AVPlayer state timeUpdate called. current time: ', time);
              // 获取当前播放时长
              VideoAVPlayerClass.time = time
              // 更新信息到页面
              VideoAVPlayerClass.updateState()
            })
            // 监听seek生效的事件
            VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
              console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
              VideoAVPlayerClass.avPlayer.play()
              VideoAVPlayerClass.isPlay = true
            })
            // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
            VideoAVPlayerClass.avPlayer.on('error', (err) => {
              console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
              // 调用reset重置资源,触发idle状态
              VideoAVPlayerClass.avPlayer.reset()
            })
            // 监听播放状态机AVPlayerState切换的事件
            VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
              switch (state) {
              // 成功调用reset接口后触发该状态机上报
                case 'idle':
                  console.info('AVPlayer state idle called.');
                  break
               // avplayer 设置播放源后触发该状态上报
                case 'initialized':
                  console.info('AVPlayerstate initialized called.');
                  // 设置显示画面,当播放的资源为纯音频时无需设置
                  VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
                  break
              // prepare调用成功后上报该状态机
                case 'prepared':
                  console.info('AVPlayer state prepared called.');
                  break
              // play成功调用后触发该状态机上报
                case 'playing':
                  console.info('AVPlayer state playing called.');
                  break
              // pause成功调用后触发该状态机上报
                case 'paused':
                  console.info('AVPlayer state paused called.');
                  break
              // 播放结束后触发该状态机上报
                case 'completed':
                  console.info('AVPlayer state completed called.');
                  break
              // stop接口成功调用后触发该状态机上报
                case 'stopped':
                  console.info('AVPlayer state stopped called.');
                // 调用reset接口初始化avplayer状态
                  VideoAVPlayerClass.avPlayer.reset()
                  break
                case 'released':
                  console.info('AVPlayer state released called.');
                  break;
                default:
                  console.info('AVPlayer state unknown called.');
                  break;
              }
            })
          }
          // 视频播放
          static async play() {
            VideoAVPlayerClass.avPlayer.play()
            VideoAVPlayerClass.isPlay = true
            VideoAVPlayerClass.updateState()
          }
          // 视频暂停
          static pause() {
            VideoAVPlayerClass.avPlayer.pause()
            VideoAVPlayerClass.isPlay = false
            VideoAVPlayerClass.updateState()
          }
            // 跳转到指定播放位置
          static seekTime(time: number) {
            VideoAVPlayerClass.time = time
            VideoAVPlayerClass.avPlayer.seek(VideoAVPlayerClass.time)
            // 如果是暂停状态下跳转到指定播放位置,则开启播放
            if (!VideoAVPlayerClass.isPlay) {
              VideoAVPlayerClass.isPlay = true
              VideoAVPlayerClass.avPlayer.play()
            }
          }
          static async changePlay() {
            // 将播放状态置为闲置
            await VideoAVPlayerClass.avPlayer.reset()
            VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
            VideoAVPlayerClass.updateState()
          }
          // 更新页面状态
          static async updateState() {
            const data = {
              playState: JSON.stringify({
                duration: VideoAVPlayerClass.duration,
                time: VideoAVPlayerClass.time,
                isPlay: VideoAVPlayerClass.isPlay,
              })
            }
            // 更新页面
            emitter.emit({
              eventId: EmitEventType.UPDATE_STATE
            }, {
              data
            })
          }
          
        }
        

        在页面的进度条中调用seekTime(),实现播放进度的切换。

        import emitter from '@ohos.events.emitter';
        import PlayingAnimation from '../components/PlayingAnimation';
        import { EmitEventType } from '../constants/EventContants';
        import { VideoListData } from '../constants/VideoConstants';
        import { PlayStateType, PlayStateTypeModel } from '../models/playState';
        import { videoItemType } from '../models/video';
        import { VideoPlayStateType, VideoPlayStateTypeModel } from '../models/videoPlayState';
        import { VideoAVPlayerClass } from '../utils/VideoAVPlayerClass';
        @Preview
        @Component
        struct Index {
          @State
          playState: VideoPlayStateType = new VideoPlayStateTypeModel({} as VideoPlayStateType)
          xComController: XComponentController = new XComponentController()
          surfaceId: string = "" // 定义surfaceId
          videoList: videoItemType[] = VideoListData
          async aboutToAppear() {
            // 从播放器订阅数据
            emitter.on({ eventId: EmitEventType.UPDATE_STATE }, (data) => {
              this.playState = new VideoPlayStateTypeModel(JSON.parse(data.data.playState))
            })
          }
          aboutToDisappear(){
            // 销毁播放器
            VideoAVPlayerClass.avPlayer.release()
          }
          // 时长数字(ms)转字符串
          number2time(number: number) {
            if (!number) {
              return '00:00'
            }
            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')
          }
          build() {
            Row() {
              Column({ space: 10 }) {
                Stack() {
                  Column() {
                    Row(){
                      // 视频播放窗口
                      XComponent({
                        id: 'videoXComponent',
                        type: 'surface',
                        controller: this.xComController
                      })
                        .width('100%')
                        .height(200)
                        .onLoad(async () => {
                          this.xComController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 });
                          this.surfaceId = this.xComController.getXComponentSurfaceId()
                          if (this.surfaceId) {
                            await VideoAVPlayerClass.init({surfaceId: this.surfaceId, playList: this.videoList, context: getContext(this)})
                            await VideoAVPlayerClass.singlePlay()
                          }
                        })
                    }
                    .onClick(() => {
                      this.playState.isPlay ? VideoAVPlayerClass.pause() : VideoAVPlayerClass.play()
                    })
                    // 进度条
                    Row({space: 6}){
                      // 当前播放时长
                      Text(this.number2time(this.playState?.time))
                        .fontColor($r('app.color.white'))
                        .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                      // 进度条
                      Slider({
                        value: this.playState.time,
                        min: 0,
                        max: this.playState.duration,
                      })
                        .trackColor($r('app.color.white'))
                        .onChange((value: number, mode: SliderChangeMode) => {
                          // 切换播放进度
                          VideoAVPlayerClass.seekTime(value)
                        })
                        .width("70%")
                      // 视频总时长
                      Text(this.number2time(this.playState?.duration))
                        .fontColor($r('app.color.white'))
                        .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                    }
                    .width('100%')
                    .height(20)
                    .margin({
                      top: 10
                    })
                    .justifyContent(FlexAlign.Center)
                  }
                  .width('100%')
                  .height(270)
                  .padding({
                    top: 30,
                    bottom:30
                  })
                  .backgroundColor($r('app.color.black'))
                  .justifyContent(FlexAlign.Start)
                  // 播放按钮
                  if (!this.playState.isPlay) {
                    Image($r('app.media.ic_play'))
                      .width(48)
                      .height(48)
                      .fillColor($r('app.color.white'))
                      .onClick(() => {
                        VideoAVPlayerClass.play()
                      })
                  }
                }
                // 视频列表缩略图
                List({ space: 10, initialIndex: 0 }) {
                  ForEach(this.videoList, (item: videoItemType, index: number) => {
                    ListItem() {
                      Stack({alignContent: Alignment.Center}){
                        Image(item.imgUrl)
                          .width(100)
                          .height(80)
                        // .objectFit(ImageFit.Contain)
                        if (this.playState.playIndex === index) {
                          Row(){
                            PlayingAnimation({ recordIng: true })
                          }
                        }
                      }
                    }
                    .width(100)
                  }, item => item)
                }
                .height(100)
                .listDirection(Axis.Horizontal) // 排列方向
                .edgeEffect(EdgeEffect.Spring) // 滑动到边缘无效果
                .onScrollIndex((firstIndex: number, lastIndex: number) => {
                  console.info('first' + firstIndex)
                  console.info('last' + lastIndex)
                })
              }
              .width('100%')
              .height('100%')
            }
            .height('100%')
            .width('100%')
          }
        }
        export default Index
        

        3、切换视频

        • 手动切换

          当我们点击页面的视频列表中的某个视频时,切换到当前视频进行播放。我们在播放器类中添加一个singlePlay()方法来实现视频的切换。

          在切换视频时,如果要切换的视频已经在播放列表中,则根据播放索引,播放当前切换的视频。如果要切换的视频不在播放列表中,就将当前切换的视频添加到播放列表中,然后播放当前切换的视频。

          在切换视频前,我们还需要做一件事情,就是将当前播放时长和视频时长重置为0。

          import media from '@ohos.multimedia.media'
          export class VideoAVPlayerClass {
            // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
            static player: media.AVPlayer | null = null
            // 当前播放器播放视频的总时长
            static duration: number = 0
            // 当前播放器播放的时长
            static time: number = 0
            // 当前播放器是否播放
            static isPlay: boolean = false
              // 当前播放器的播放列表
            static playList: videoItemType[] = []
            
            // 当前播放的视频索引
            static playIndex: number = 0
            // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
            static surfaceId: string = ''
            
            // 创建播放器的方法
            static async init(initParams: InitParams) {
              // 存储属性SurfaceID,用于设置播放窗口,显示画面
              VideoAVPlayerClass.surfaceId = initParams.surfaceId
              // 创建播放器实例
              VideoAVPlayerClass.player = await media.createAVPlayer()
              // ----------------------- 事件监听 --------------------------------------------------------------
              // 用于进度条,监听进度条长度,刷新资源时长
              VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
                  console.info('AVPlayer state durationUpdate called. current time: ', duration);
                  // 获取视频总时长
                  VideoAVPlayerClass.duration = duration
              })
              // 用于进度条,监听进度条当前位置,刷新当前时间
              VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
                console.info('AVPlayer state timeUpdate called. current time: ', time);
                // 获取当前播放时长
                VideoAVPlayerClass.time = time
                // 更新信息到页面
                VideoAVPlayerClass.updateState()
              })
              // 监听seek生效的事件
              VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
                console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
                VideoAVPlayerClass.avPlayer.play()
                VideoAVPlayerClass.isPlay = true
              })
              // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
              VideoAVPlayerClass.avPlayer.on('error', (err) => {
                console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
                // 调用reset重置资源,触发idle状态
                VideoAVPlayerClass.avPlayer.reset()
              })
              // 监听播放状态机AVPlayerState切换的事件
              VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
                switch (state) {
                // 成功调用reset接口后触发该状态机上报
                  case 'idle':
                    console.info('AVPlayer state idle called.');
                    break
                 // avplayer 设置播放源后触发该状态上报
                  case 'initialized':
                    console.info('AVPlayerstate initialized called.');
                    // 设置显示画面,当播放的资源为纯音频时无需设置
                    VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
                    break
                // prepare调用成功后上报该状态机
                  case 'prepared':
                    console.info('AVPlayer state prepared called.');
                    break
                // play成功调用后触发该状态机上报
                  case 'playing':
                    console.info('AVPlayer state playing called.');
                    break
                // pause成功调用后触发该状态机上报
                  case 'paused':
                    console.info('AVPlayer state paused called.');
                    break
                // 播放结束后触发该状态机上报
                  case 'completed':
                    console.info('AVPlayer state completed called.');
                    break
                // stop接口成功调用后触发该状态机上报
                  case 'stopped':
                    console.info('AVPlayer state stopped called.');
                  // 调用reset接口初始化avplayer状态
                    VideoAVPlayerClass.avPlayer.reset()
                    break
                  case 'released':
                    console.info('AVPlayer state released called.');
                    break;
                  default:
                    console.info('AVPlayer state unknown called.');
                    break;
                }
              })
            }
            // 视频播放
            static async play() {
              VideoAVPlayerClass.avPlayer.play()
              VideoAVPlayerClass.isPlay = true
              VideoAVPlayerClass.updateState()
            }
            // 视频暂停
            static pause() {
              VideoAVPlayerClass.avPlayer.pause()
              VideoAVPlayerClass.isPlay = false
              VideoAVPlayerClass.updateState()
            }
            // 切换视频
            static singlePlay(video?: videoItemType) {
              if (video) {
                let index = VideoAVPlayerClass.playList.findIndex((item: videoItemType) => item.id === video.id)
                if (index > -1) {
                  // 当前要播放的视频在播放列表里
                  VideoAVPlayerClass.playIndex = index
                } else {
                  // 当前要播放的视频不在播放列表里
                  VideoAVPlayerClass.playList.push(video)
                  VideoAVPlayerClass.playIndex = VideoAVPlayerClass.playList.length - 1
                }
              }
              VideoAVPlayerClass.changePlay()
            }
            static async changePlay() {
              // 将播放状态置为闲置
              await VideoAVPlayerClass.avPlayer.reset()
              // 重置当前播放时长和视频时长
              VideoAVPlayerClass.time = 0
              VideoAVPlayerClass.duration = 0
              VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
              VideoAVPlayerClass.updateState()
            }
            // 更新页面状态
            static async updateState() {
              const data = {
                playState: JSON.stringify({
                  duration: VideoAVPlayerClass.duration,
                  time: VideoAVPlayerClass.time,
                  isPlay: VideoAVPlayerClass.isPlay,
                  playIndex: VideoAVPlayerClass.playIndex,
                  playList: VideoAVPlayerClass.playList,
                })
              }
              // 更新页面
              emitter.emit({
                eventId: EmitEventType.UPDATE_STATE
              }, {
                data
              })
            }
            
          }
          
          • 自动切换

            当前视频播放完之后,自动播放下一个视频。监听播放器的stateChange事件,当播放器状态为completed状态时,获取下一个视频的索引,调用singlePlay()方法播放下一个视频。

            import media from '@ohos.multimedia.media'
            export class VideoAVPlayerClass {
              // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
              static player: media.AVPlayer | null = null
              // 当前播放器播放视频的总时长
              static duration: number = 0
              // 当前播放器播放的时长
              static time: number = 0
              // 当前播放器是否播放
              static isPlay: boolean = false
                // 当前播放器的播放列表
              static playList: videoItemType[] = []
              
              // 当前播放的视频索引
              static playIndex: number = 0
              // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
              static surfaceId: string = ''
              
              // 创建播放器的方法
              static async init(initParams: InitParams) {
                // 存储属性SurfaceID,用于设置播放窗口,显示画面
                VideoAVPlayerClass.surfaceId = initParams.surfaceId
                // 创建播放器实例
                VideoAVPlayerClass.player = await media.createAVPlayer()
                // ----------------------- 事件监听 --------------------------------------------------------------
                // 用于进度条,监听进度条长度,刷新资源时长
                VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
                    console.info('AVPlayer state durationUpdate called. current time: ', duration);
                    // 获取视频总时长
                    VideoAVPlayerClass.duration = duration
                })
                // 用于进度条,监听进度条当前位置,刷新当前时间
                VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
                  console.info('AVPlayer state timeUpdate called. current time: ', time);
                  // 获取当前播放时长
                  VideoAVPlayerClass.time = time
                  // 更新信息到页面
                  VideoAVPlayerClass.updateState()
                })
                // 监听seek生效的事件
                VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
                  console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
                  VideoAVPlayerClass.avPlayer.play()
                  VideoAVPlayerClass.isPlay = true
                })
                // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
                VideoAVPlayerClass.avPlayer.on('error', (err) => {
                  console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
                  // 调用reset重置资源,触发idle状态
                  VideoAVPlayerClass.avPlayer.reset()
                })
                // 监听播放状态机AVPlayerState切换的事件
                VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
                  switch (state) {
                  // 成功调用reset接口后触发该状态机上报
                    case 'idle':
                      console.info('AVPlayer state idle called.');
                      break
                   // avplayer 设置播放源后触发该状态上报
                    case 'initialized':
                      console.info('AVPlayerstate initialized called.');
                      // 设置显示画面,当播放的资源为纯音频时无需设置
                      VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
                      break
                  // prepare调用成功后上报该状态机
                    case 'prepared':
                      console.info('AVPlayer state prepared called.');
                      break
                  // play成功调用后触发该状态机上报
                    case 'playing':
                      console.info('AVPlayer state playing called.');
                      break
                  // pause成功调用后触发该状态机上报
                    case 'paused':
                      console.info('AVPlayer state paused called.');
                      break
                  // 播放结束后触发该状态机上报
                    case 'completed':
                      console.info('AVPlayer state completed called.');
                      // 当前视频播放完成,自动播放下一个视频哦
                      if (VideoAVPlayerClass.autoPlayList && VideoAVPlayerClass.playIndex < VideoAVPlayerClass.playList.length) {
                        VideoAVPlayerClass.playIndex++
                        VideoAVPlayerClass.playIndex = (VideoAVPlayerClass.playIndex + VideoAVPlayerClass.playList.length) % VideoAVPlayerClass.playList.length
                        VideoAVPlayerClass.singlePlay(VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex])
                        VideoAVPlayerClass.isPlay = true
                      } else {
                        VideoAVPlayerClass.isPlay = false
                        // 停止播放
                        VideoAVPlayerClass.avPlayer.stop()
                      }
                      
                      break
                  // stop接口成功调用后触发该状态机上报
                    case 'stopped':
                      console.info('AVPlayer state stopped called.');
                    // 调用reset接口初始化avplayer状态
                      VideoAVPlayerClass.avPlayer.reset()
                      break
                    case 'released':
                      console.info('AVPlayer state released called.');
                      break;
                    default:
                      console.info('AVPlayer state unknown called.');
                      break;
                  }
                })
              }
              // 视频播放
              static async play() {
                VideoAVPlayerClass.avPlayer.play()
                VideoAVPlayerClass.isPlay = true
                VideoAVPlayerClass.updateState()
              }
              // 视频暂停
              static pause() {
                VideoAVPlayerClass.avPlayer.pause()
                VideoAVPlayerClass.isPlay = false
                VideoAVPlayerClass.updateState()
              }
              // 切换视频
              static singlePlay(video?: videoItemType) {
                if (video) {
                  let index = VideoAVPlayerClass.playList.findIndex((item: videoItemType) => item.id === video.id)
                  if (index > -1) {
                    // 当前要播放的视频在播放列表里
                    VideoAVPlayerClass.playIndex = index
                  } else {
                    // 当前要播放的视频不在播放列表里
                    VideoAVPlayerClass.playList.push(video)
                    VideoAVPlayerClass.playIndex = VideoAVPlayerClass.playList.length - 1
                  }
                }
                VideoAVPlayerClass.changePlay()
              }
              static async changePlay() {
                // 将播放状态置为闲置
                await VideoAVPlayerClass.avPlayer.reset()
                // 重置当前播放时长和视频时长
                VideoAVPlayerClass.time = 0
                VideoAVPlayerClass.duration = 0
                VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
                VideoAVPlayerClass.updateState()
              }
              // 更新页面状态
              static async updateState() {
                const data = {
                  playState: JSON.stringify({
                    duration: VideoAVPlayerClass.duration,
                    time: VideoAVPlayerClass.time,
                    isPlay: VideoAVPlayerClass.isPlay,
                    playIndex: VideoAVPlayerClass.playIndex,
                    playList: VideoAVPlayerClass.playList,
                  })
                }
                // 更新页面
                emitter.emit({
                  eventId: EmitEventType.UPDATE_STATE
                }, {
                  data
                })
              }
              
            }
            
            • 页面调用

              在页面的视频列表中调用播放器的singlePlay()方法,实现下一个视频的播放。

              import emitter from '@ohos.events.emitter';
              import PlayingAnimation from '../components/PlayingAnimation';
              import { EmitEventType } from '../constants/EventContants';
              import { VideoListData } from '../constants/VideoConstants';
              import { PlayStateType, PlayStateTypeModel } from '../models/playState';
              import { videoItemType } from '../models/video';
              import { VideoPlayStateType, VideoPlayStateTypeModel } from '../models/videoPlayState';
              import { VideoAVPlayerClass } from '../utils/VideoAVPlayerClass';
              @Preview
              @Component
              struct Index {
                @State
                playState: VideoPlayStateType = new VideoPlayStateTypeModel({} as VideoPlayStateType)
                xComController: XComponentController = new XComponentController()
                surfaceId: string = "" // 定义surfaceId
                videoList: videoItemType[] = VideoListData
                async aboutToAppear() {
                  // 从播放器订阅数据
                  emitter.on({ eventId: EmitEventType.UPDATE_STATE }, (data) => {
                    this.playState = new VideoPlayStateTypeModel(JSON.parse(data.data.playState))
                  })
                }
                aboutToDisappear(){
                  // 销毁播放器
                  VideoAVPlayerClass.avPlayer.release()
                }
                // 时长数字(ms)转字符串
                number2time(number: number) {
                  if (!number) {
                    return '00:00'
                  }
                  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')
                }
                build() {
                  Row() {
                    Column({ space: 10 }) {
                      Stack() {
                        Column() {
                          Row(){
                            // 视频播放窗口
                            XComponent({
                              id: 'videoXComponent',
                              type: 'surface',
                              controller: this.xComController
                            })
                              .width('100%')
                              .height(200)
                              .onLoad(async () => {
                                this.xComController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 });
                                this.surfaceId = this.xComController.getXComponentSurfaceId()
                                if (this.surfaceId) {
                                  await VideoAVPlayerClass.init({surfaceId: this.surfaceId, playList: this.videoList, context: getContext(this)})
                                  await VideoAVPlayerClass.singlePlay()
                                }
                              })
                          }
                          .onClick(() => {
                            this.playState.isPlay ? VideoAVPlayerClass.pause() : VideoAVPlayerClass.play()
                          })
                          // 进度条
                          Row({space: 6}){
                            // 当前播放时长
                            Text(this.number2time(this.playState?.time))
                              .fontColor($r('app.color.white'))
                              .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                            // 进度条
                            Slider({
                              value: this.playState.time,
                              min: 0,
                              max: this.playState.duration,
                            })
                              .trackColor($r('app.color.white'))
                              .onChange((value: number, mode: SliderChangeMode) => {
                                // 切换播放进度
                                VideoAVPlayerClass.seekTime(value)
                              })
                              .width("70%")
                            // 视频总时长
                            Text(this.number2time(this.playState?.duration))
                              .fontColor($r('app.color.white'))
                              .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                          }
                          .width('100%')
                          .height(20)
                          .margin({
                            top: 10
                          })
                          .justifyContent(FlexAlign.Center)
                        }
                        .width('100%')
                        .height(270)
                        .padding({
                          top: 30,
                          bottom:30
                        })
                        .backgroundColor($r('app.color.black'))
                        .justifyContent(FlexAlign.Start)
                        // 播放按钮
                        if (!this.playState.isPlay) {
                          Image($r('app.media.ic_play'))
                            .width(48)
                            .height(48)
                            .fillColor($r('app.color.white'))
                            .onClick(() => {
                              VideoAVPlayerClass.play()
                            })
                        }
                      }
                      // 视频列表缩略图
                      List({ space: 10, initialIndex: 0 }) {
                        ForEach(this.videoList, (item: videoItemType, index: number) => {
                          ListItem() {
                            Stack({alignContent: Alignment.Center}){
                              Image(item.imgUrl)
                                .width(100)
                                .height(80)
                              // .objectFit(ImageFit.Contain)
                              if (this.playState.playIndex === index) {
                                Row(){
                                  PlayingAnimation({ recordIng: true })
                                }
                              }
                            }
                          }
                          .width(100)
                          .onClick(() => {
                            // 切换视频
                            VideoAVPlayerClass.singlePlay(item)
                          })
                        }, item => item)
                      }
                      .height(100)
                      .listDirection(Axis.Horizontal) // 排列方向
                      .edgeEffect(EdgeEffect.Spring) // 滑动到边缘无效果
                      .onScrollIndex((firstIndex: number, lastIndex: number) => {
                        console.info('first' + firstIndex)
                        console.info('last' + lastIndex)
                      })
                    }
                    .width('100%')
                    .height('100%')
                  }
                  .height('100%')
                  .width('100%')
                }
              }
              export default Index
              

              4、缓存播放信息

              我们的播放器已经可以正常进行视频的播放和切换了,当我们不小心退出了当前页面,再进入播放页面时,你会发现我们当前播放的视频信息没有了,这是为什么呢?

              在当前的实现中,播放页面的信息是通过订阅播放器得到的,如果不播放了,就没有了信息来源的渠道,所以页面的播放信息就没有了。因此我们需要再建立一个信息收集渠道,即使不在播放时,也能获取到最后的播放信息数据。为此,我们使用**@ohos.data.preferences(用户首选项)**来持久化播放信息。

              1、首先我们实现一个存储和读取首选项的工具类

              import preferences from '@ohos.data.preferences'
              import { videoDefaultState, VideoPlayStateType } from '../models/videoPlayState'
              export class PreferencesClass {
                StoreName = 'VIDEO_PLAYER'
                context: Context
                VideoPlayStateKey = "VIDEO_PLAY_STATE"
                constructor(context: Context) {
                  this.context = context
                }
                // 获取store
                async getStore() {
                  return await preferences.getPreferences(this.context,this.StoreName)
                }
                // 存储视频播放状态
                async setVideoPlayState(playState:VideoPlayStateType){
                  const store = await this.getStore()
                  await store.put(this.PlayStateKey,JSON.stringify(playState))
                  await store.flush()
                }
                // 读取视频播放状态
                async getVideoPlayState(): Promise {
                  const store = await this.getStore()
                  return JSON.parse(await store.get(this.VideoPlayStateKey,JSON.stringify(videoDefaultState)) as string) as VideoPlayStateType
                }
              }
              

              2、播放器每次发布事件时,更新播放信息到首选项中,由于需要使用上下文,所以我们需要在初始化播放器的时候将上下文保存到播放器类中。需要注意的是,我们需要在页面中将上下文传给播放器。

              import media from '@ohos.multimedia.media'
              export class VideoAVPlayerClass {
                // 创建的播放器应该存在我们的工具类上,这样才能被导出使用
                static player: media.AVPlayer | null = null
                // 当前播放器播放视频的总时长
                static duration: number = 0
                // 当前播放器播放的时长
                static time: number = 0
                // 当前播放器是否播放
                static isPlay: boolean = false
                  // 当前播放器的播放列表
                static playList: videoItemType[] = []
                
                // 当前播放的视频索引
                static playIndex: number = 0
                // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取
                static surfaceId: string = ''
                // 播放器上记录上下文
                static context: Context|null = null
                
                
                // 创建播放器的方法
                static async init(initParams: InitParams) {
                  // 存储属性SurfaceID,用于设置播放窗口,显示画面
                  VideoAVPlayerClass.surfaceId = initParams.surfaceId
                  // 将上下文 context 存储到播放器类上
                  VideoAVPlayerClass.context = initParams.context
                  // 创建播放器实例
                  VideoAVPlayerClass.player = await media.createAVPlayer()
                  // ----------------------- 事件监听 --------------------------------------------------------------
                  // 用于进度条,监听进度条长度,刷新资源时长
                  VideoAVPlayerClass.avPlayer.on('durationUpdate', (duration: number) => {
                      console.info('AVPlayer state durationUpdate called. current time: ', duration);
                      // 获取视频总时长
                      VideoAVPlayerClass.duration = duration
                  })
                  // 用于进度条,监听进度条当前位置,刷新当前时间
                  VideoAVPlayerClass.avPlayer.on('timeUpdate', (time) =>{
                    console.info('AVPlayer state timeUpdate called. current time: ', time);
                    // 获取当前播放时长
                    VideoAVPlayerClass.time = time
                    // 更新信息到页面
                    VideoAVPlayerClass.updateState()
                  })
                  // 监听seek生效的事件
                  VideoAVPlayerClass.avPlayer.on('seekDone', (seekDoneTime: number) => {
                    console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
                    VideoAVPlayerClass.avPlayer.play()
                    VideoAVPlayerClass.isPlay = true
                  })
                  // 监听视频播放错误事件,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
                  VideoAVPlayerClass.avPlayer.on('error', (err) => {
                    console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
                    // 调用reset重置资源,触发idle状态
                    VideoAVPlayerClass.avPlayer.reset()
                  })
                  // 监听播放状态机AVPlayerState切换的事件
                  VideoAVPlayerClass.avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {
                    switch (state) {
                    // 成功调用reset接口后触发该状态机上报
                      case 'idle':
                        console.info('AVPlayer state idle called.');
                        break
                     // avplayer 设置播放源后触发该状态上报
                      case 'initialized':
                        console.info('AVPlayerstate initialized called.');
                        // 设置显示画面,当播放的资源为纯音频时无需设置
                        VideoAVPlayerClass.avPlayer.surfaceId = VideoAVPlayerClass.surfaceId
                        break
                    // prepare调用成功后上报该状态机
                      case 'prepared':
                        console.info('AVPlayer state prepared called.');
                        break
                    // play成功调用后触发该状态机上报
                      case 'playing':
                        console.info('AVPlayer state playing called.');
                        break
                    // pause成功调用后触发该状态机上报
                      case 'paused':
                        console.info('AVPlayer state paused called.');
                        break
                    // 播放结束后触发该状态机上报
                      case 'completed':
                        console.info('AVPlayer state completed called.');
                        // 当前视频播放完成,自动播放下一个视频哦
                        if (VideoAVPlayerClass.autoPlayList && VideoAVPlayerClass.playIndex < VideoAVPlayerClass.playList.length) {
                          VideoAVPlayerClass.playIndex++
                          VideoAVPlayerClass.playIndex = (VideoAVPlayerClass.playIndex + VideoAVPlayerClass.playList.length) % VideoAVPlayerClass.playList.length
                          VideoAVPlayerClass.singlePlay(VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex])
                          VideoAVPlayerClass.isPlay = true
                        } else {
                          VideoAVPlayerClass.isPlay = false
                          // 停止播放
                          VideoAVPlayerClass.avPlayer.stop()
                        }
                        
                        break
                    // stop接口成功调用后触发该状态机上报
                      case 'stopped':
                        console.info('AVPlayer state stopped called.');
                      // 调用reset接口初始化avplayer状态
                        VideoAVPlayerClass.avPlayer.reset()
                        break
                      case 'released':
                        console.info('AVPlayer state released called.');
                        break;
                      default:
                        console.info('AVPlayer state unknown called.');
                        break;
                    }
                  })
                }
                // 视频播放
                static async play() {
                  VideoAVPlayerClass.avPlayer.play()
                  VideoAVPlayerClass.isPlay = true
                  VideoAVPlayerClass.updateState()
                }
                // 视频暂停
                static pause() {
                  VideoAVPlayerClass.avPlayer.pause()
                  VideoAVPlayerClass.isPlay = false
                  VideoAVPlayerClass.updateState()
                }
                // 切换视频
                static singlePlay(video?: videoItemType) {
                  if (video) {
                    let index = VideoAVPlayerClass.playList.findIndex((item: videoItemType) => item.id === video.id)
                    if (index > -1) {
                      // 当前要播放的视频在播放列表里
                      VideoAVPlayerClass.playIndex = index
                    } else {
                      // 当前要播放的视频不在播放列表里
                      VideoAVPlayerClass.playList.push(video)
                      VideoAVPlayerClass.playIndex = VideoAVPlayerClass.playList.length - 1
                    }
                  }
                  VideoAVPlayerClass.changePlay()
                }
                static async changePlay() {
                  // 将播放状态置为闲置
                  await VideoAVPlayerClass.avPlayer.reset()
                  // 重置当前播放时长和视频时长
                  VideoAVPlayerClass.time = 0
                  VideoAVPlayerClass.duration = 0
                  VideoAVPlayerClass.avPlayer.url = VideoAVPlayerClass.playList[VideoAVPlayerClass.playIndex].url
                  VideoAVPlayerClass.updateState()
                }
                // 更新页面状态
                static async updateState() {
                  const data = {
                    playState: JSON.stringify({
                      duration: VideoAVPlayerClass.duration,
                      time: VideoAVPlayerClass.time,
                      isPlay: VideoAVPlayerClass.isPlay,
                      playIndex: VideoAVPlayerClass.playIndex,
                      playList: VideoAVPlayerClass.playList,
                    })
                  }
                  // 更新页面
                  emitter.emit({
                    eventId: EmitEventType.UPDATE_STATE
                  }, {
                    data
                  })
                  
                  // 存储首选项
                  const preferences:PreferencesClass = new PreferencesClass(VideoAVPlayerClass.context)
                  await preferences.setVideoPlayState(JSON.parse(data.playState))
                  
                }
                
              }
              

              3、最后,在页面首次加载的时候,从首选项中读取播放信息,存储到playState中。

              import emitter from '@ohos.events.emitter';
              import PlayingAnimation from '../components/PlayingAnimation';
              import { EmitEventType } from '../constants/EventContants';
              import { VideoListData } from '../constants/VideoConstants';
              import { PlayStateType, PlayStateTypeModel } from '../models/playState';
              import { videoItemType } from '../models/video';
              import { VideoPlayStateType, VideoPlayStateTypeModel } from '../models/videoPlayState';
              import { PreferencesClass } from '../utils/PreferencesClass';
              import { VideoAVPlayerClass } from '../utils/VideoAVPlayerClass';
              @Preview
              @Component
              struct Index {
                @State
                playState: VideoPlayStateType = new VideoPlayStateTypeModel({} as VideoPlayStateType)
                xComController: XComponentController = new XComponentController()
                surfaceId: string = "" // 定义surfaceId
                videoList: videoItemType[] = VideoListData
                async aboutToAppear() {
                  // 从播放器订阅数据
                  emitter.on({ eventId: EmitEventType.UPDATE_STATE }, (data) => {
                    this.playState = new VideoPlayStateTypeModel(JSON.parse(data.data.playState))
                  })
                  // 从首选项加载数据
                  const preferences:PreferencesClass = new PreferencesClass(getContext(this))
                  this.playState = await preferences.getVideoPlayState()
                }
                aboutToDisappear(){
                  // 销毁播放器
                  VideoAVPlayerClass.avPlayer.release()
                }
                // 时长数字(ms)转字符串
                number2time(number: number) {
                  if (!number) {
                    return '00:00'
                  }
                  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')
                }
                build() {
                  Row() {
                    Column({ space: 10 }) {
                      Stack() {
                        Column() {
                          Row(){
                            // 视频播放窗口
                            XComponent({
                              id: 'videoXComponent',
                              type: 'surface',
                              controller: this.xComController
                            })
                              .width('100%')
                              .height(200)
                              .onLoad(async () => {
                                this.xComController.setXComponentSurfaceSize({ surfaceWidth: 1080, surfaceHeight: 1920 });
                                this.surfaceId = this.xComController.getXComponentSurfaceId()
                                if (this.surfaceId) {
                                  await VideoAVPlayerClass.init({surfaceId: this.surfaceId, playList: this.videoList, context: getContext(this)})
                                  await VideoAVPlayerClass.singlePlay()
                                }
                              })
                          }
                          .onClick(() => {
                            this.playState.isPlay ? VideoAVPlayerClass.pause() : VideoAVPlayerClass.play()
                          })
                          // 进度条
                          Row({space: 6}){
                            // 当前播放时长
                            Text(this.number2time(this.playState?.time))
                              .fontColor($r('app.color.white'))
                              .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                            // 进度条
                            Slider({
                              value: this.playState.time,
                              min: 0,
                              max: this.playState.duration,
                            })
                              .trackColor($r('app.color.white'))
                              .onChange((value: number, mode: SliderChangeMode) => {
                                // 切换播放进度
                                VideoAVPlayerClass.seekTime(value)
                              })
                              .width("70%")
                            // 视频总时长
                            Text(this.number2time(this.playState?.duration))
                              .fontColor($r('app.color.white'))
                              .visibility(this.playState?.duration ? Visibility.Visible : Visibility.Hidden)
                          }
                          .width('100%')
                          .height(20)
                          .margin({
                            top: 10
                          })
                          .justifyContent(FlexAlign.Center)
                        }
                        .width('100%')
                        .height(270)
                        .padding({
                          top: 30,
                          bottom:30
                        })
                        .backgroundColor($r('app.color.black'))
                        .justifyContent(FlexAlign.Start)
                        // 播放按钮
                        if (!this.playState.isPlay) {
                          Image($r('app.media.ic_play'))
                            .width(48)
                            .height(48)
                            .fillColor($r('app.color.white'))
                            .onClick(() => {
                              VideoAVPlayerClass.play()
                            })
                        }
                      }
                      // 视频列表缩略图
                      List({ space: 10, initialIndex: 0 }) {
                        ForEach(this.videoList, (item: videoItemType, index: number) => {
                          ListItem() {
                            Stack({alignContent: Alignment.Center}){
                              Image(item.imgUrl)
                                .width(100)
                                .height(80)
                              // .objectFit(ImageFit.Contain)
                              if (this.playState.playIndex === index) {
                                Row(){
                                  PlayingAnimation({ recordIng: true })
                                }
                              }
                            }
                          }
                          .width(100)
                          .onClick(() => {
                            VideoAVPlayerClass.singlePlay(item)
                          })
                        }, item => item)
                      }
                      .height(100)
                      .listDirection(Axis.Horizontal) // 排列方向
                      .edgeEffect(EdgeEffect.Spring) // 滑动到边缘无效果
                      .onScrollIndex((firstIndex: number, lastIndex: number) => {
                        console.info('first' + firstIndex)
                        console.info('last' + lastIndex)
                      })
                    }
                    .width('100%')
                    .height('100%')
                  }
                  .height('100%')
                  .width('100%')
                }
              }
              export default Index
              

              此时,我们完成了播放信息的缓存,即使退出了播放页面,我们依然能通过首选项获取到播放信息。

              至此,我们整个播放器的功能就算完成了


              最后

              有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

              这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

              希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

              如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料

               获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

              鸿蒙(HarmonyOS NEXT)最新学习路线

              鸿蒙实战开发:视频播放器实现(下)

              •  HarmonOS基础技能

                鸿蒙实战开发:视频播放器实现(下)

                • HarmonOS就业必备技能 鸿蒙实战开发:视频播放器实现(下)
                •  HarmonOS多媒体技术

                  鸿蒙实战开发:视频播放器实现(下)

                  • 鸿蒙NaPi组件进阶

                    鸿蒙实战开发:视频播放器实现(下)

                    • HarmonOS高级技能

                      鸿蒙实战开发:视频播放器实现(下)

                      • 初识HarmonOS内核 鸿蒙实战开发:视频播放器实现(下)
                      • 实战就业级设备开发

                        鸿蒙实战开发:视频播放器实现(下)

                         有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

                        获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

                        《鸿蒙 (OpenHarmony)开发入门教学视频》

                        鸿蒙实战开发:视频播放器实现(下)

                        《鸿蒙生态应用开发V2.0白皮书》

                        鸿蒙实战开发:视频播放器实现(下)

                        《鸿蒙 (OpenHarmony)开发基础到实战手册》

                        OpenHarmony北向、南向开发环境搭建

                        鸿蒙实战开发:视频播放器实现(下)

                         《鸿蒙开发基础》

                        • ArkTS语言
                        • 安装DevEco Studio
                        • 运用你的第一个ArkTS应用
                        • ArkUI声明式UI开发
                        • .……

                          鸿蒙实战开发:视频播放器实现(下)

                           《鸿蒙开发进阶》

                          • Stage模型入门
                          • 网络管理
                          • 数据管理
                          • 电话服务
                          • 分布式应用开发
                          • 通知与窗口管理
                          • 多媒体技术
                          • 安全技能
                          • 任务管理
                          • WebGL
                          • 国际化开发
                          • 应用测试
                          • DFX面向未来设计
                          • 鸿蒙系统移植和裁剪定制
                          • ……

                            鸿蒙实战开发:视频播放器实现(下)

                            《鸿蒙进阶实战》

                            • ArkTS实践
                            • UIAbility应用
                            • 网络案例
                            • ……

                              鸿蒙实战开发:视频播放器实现(下)

                               获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

                              总结

                              总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

                              鸿蒙实战开发:视频播放器实现(下)

转载请注明来自码农世界,本文标题:《鸿蒙实战开发:视频播放器实现(下)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,61人围观)参与讨论

还没有评论,来说两句吧...

Top