HarmonyOS VideoController缺少相应方法

VideoController提供了播放控制功能,比如开始播放、暂停播放,停止播放,设置播放位置,切换还原全屏模式。但是无法获取相关属性状态,如当前是否在播放中、当前播放时刻是否全屏模式。

HarmonyOS
2天前
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
aquaa

参考示例如下:

1、AVPlayerDemo.ets

import media from '@ohos.multimedia.media';
import { BusinessError } from '@ohos.base';
import pipWindow from '@ohos.PiPWindow';
import { display, window } from '@kit.ArkUI';
import { GlobalContext } from '../util/GlobalContext';
import { avSession } from '@kit.AVSessionKit';
import { audio } from '@kit.AudioKit';
import { image } from '@kit.ImageKit';
import { abilityAccessCtrl, PermissionRequestResult, Permissions, WantAgent, wantAgent } from '@kit.AbilityKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';

@Entry
@Component
struct AVPlayerDemo {
  @Watch('setWindowLayOut') @State isFullScreen: boolean = false;
  @State isLandscape: boolean = false;
  @State isVideo: boolean = true;
  @State isOpacity: boolean = false;
  @State isPlay: boolean = false;
  @State currentTime: number = 0;
  @State durationTime: number = 0;
  @State durationStringTime: string = '00:00';
  @State currentStringTime: string = '00:00';
  @State flag: boolean = false;
  @State videoFiles: media.AVFileDescriptor[] = [];
  @State audioFiles: media.AVFileDescriptor[] = [];
  @State sourceFiles: media.AVFileDescriptor[] = [];
  @State currentIndex: number = 0;
  @State speed: number = media.PlaybackSpeed.SPEED_FORWARD_1_00_X;
  private avPlayer: media.AVPlayer | undefined = undefined;
  private xComponentController = new XComponentController();
  private surfaceID: string = '';
  private readonly OPERATE_STATE: Array<string> = ['prepared', 'playing', 'paused', 'completed'];
  private pipController: pipWindow.PiPController | undefined = undefined;
  private windowClass: window.Window = GlobalContext.getContext().getObject('windowClass') as window.Window;
  private currentAVSession: avSession.AVSession | undefined = undefined;
  private curSessionId: string = '';
  private avsessionController: avSession.AVSessionController | undefined = undefined;
  private curPixelMap: image.PixelMap | undefined = undefined;
  private playbackState: avSession.AVPlaybackState = {
    state: avSession.PlaybackState.PLAYBACK_STATE_PLAY, // 播放状态
    position: {
      elapsedTime: 0, // 已经播放的位置,以ms为单位
      updateTime: 0, // 应用更新当前位置的时间戳,以ms为单位
    },
    speed: this.speed, // 可选,默认是1.0,播放的倍速,按照应用内支持的speed进行设置,系统不做校验
    // bufferedTime: 14000, // 可选,资源缓存的时间,以ms为单位
    duration: 0, // 资源的时长,以ms为单位
    loopMode: avSession.LoopMode.LOOP_MODE_SEQUENCE, // 循环模式
  };

  setWindowLayOut() {
    // 监听全屏事件,设置沉浸式窗口
    this.windowClass.setWindowLayoutFullScreen(this.isFullScreen);
  }

  aboutToAppear(): void {
    this.initFiles();
    // 初始化AVPlayer
    this.createAVPlayer();
    // 初始化画中画
    this.createPipWindow();
    // 初始化AVSession
    this.createAVSession();
    this.saveRawFileToPixelMap('first.png')
    this.reset(true)
    try {
      // 监听窗口尺寸变化
      this.windowClass.on('windowSizeChange', (data) => {
        console.info('AVPlayerDemo Succeeded in enabling the listener for window size changes. Data: ' +
        JSON.stringify(data));
        let orientation = display.getDefaultDisplaySync().orientation;
        // 横屏
        if (orientation == display.Orientation.LANDSCAPE || orientation == display.Orientation.LANDSCAPE_INVERTED) {
          this.isLandscape = true;
        }
        // 竖屏
        if (orientation == display.Orientation.PORTRAIT || orientation == display.Orientation.PORTRAIT_INVERTED) {
          this.isLandscape = false;
        }
      });
    } catch (exception) {
      console.error('AVPlayerDemo Failed to enable the listener for window size changes. Cause: ' +
      JSON.stringify(exception));
    }
  }

  aboutToDisappear(): void {
    if (this.avPlayer) {
      this.avPlayer.off('timeUpdate');
      this.avPlayer.off('seekDone');
      this.avPlayer.off('error');
      this.avPlayer.off('stateChange');
      this.avPlayer.release();
    }
    if (this.currentAVSession) {
      this.currentAVSession.off('play');
      this.currentAVSession.off('pause');
      this.currentAVSession.off('stop');
      this.currentAVSession.off('playNext');
      this.currentAVSession.off('playPrevious');
      this.currentAVSession.off('fastForward');
      this.currentAVSession.off('rewind');
      this.currentAVSession.off('playFromAssetId');
      this.currentAVSession.off('seek');
      this.currentAVSession.off('setSpeed');
      this.currentAVSession.deactivate();
      this.currentAVSession.destroy();
    }
    try {
      this.windowClass.off('windowSizeChange');
    } catch (exception) {
      console.error('AVPlayerDemo Failed to disable the listener for window size changes. Cause: ' +
      JSON.stringify(exception));
    }
    this.stopContinuousTask();
  }

  build() {
    Column() {
      if (!this.isFullScreen) {
        Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) {
          Column() {
            Text('视频播放')
              .fontColor(this.isVideo ? Color.Blue : Color.Black)
              .fontSize(16)
              .fontWeight(this.isVideo ? 500 : 400)
              .lineHeight(22)
              .margin({ top: 17, bottom: 7 })
            Divider()
              .strokeWidth(2)
              .color('#007DFF')
              .opacity(this.isVideo ? 1 : 0)
          }
          .onClick(() => {
            this.isVideo = true
            this.reset(true);
          })

          Column() {
            Text('音频播放')
              .fontColor(!this.isVideo ? Color.Blue : Color.Black)
              .fontSize(16)
              .fontWeight(!this.isVideo ? 500 : 400)
              .lineHeight(22)
              .margin({ top: 17, bottom: 7 })
            Divider()
              .strokeWidth(2)
              .color('#007DFF')
              .opacity(!this.isVideo ? 1 : 0)
          }
          .onClick(() => {
            this.isVideo = false
            this.reset(true);
          })
        }
        .margin({ bottom: '8vp' })
      }

      Flex({
        direction: FlexDirection.Column,
        justifyContent: this.isFullScreen ? FlexAlign.Center : FlexAlign.Start
      }) {
        if (this.isVideo) {
          this.VideoPlayer()
        } else {
          this.AudioPlayer()
        }

        if (!this.isFullScreen) {
          this.Buttons();
        }
      }
      .width('100%')
      .height('100%')
      .backgroundColor(this.isFullScreen ? Color.Black : Color.White)

    }
    .width('100%')
    .height('100%')
  }

  @Builder
  Buttons() {
    Column() {
      Scroll() {
        GridRow({
          columns: 2,
          gutter: { x: 5, y: 10 },
          direction: GridRowDirection.Row
        }) {
          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('播放').width(140).onClick(() => {
              this.play();
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('暂停').width(140).onClick(() => {
              this.isPlay = false;
              if (this.avPlayer) {
                this.avPlayer.pause();
              }
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('停止').width(140).onClick(() => {
              this.stopPlay();
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('跳转5秒位置').width(140).onClick(() => {
              this.setSeek(5, 0);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('进入全屏').width(140).onClick(() => {
              this.isFullScreen = true;
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('0.75倍速').width(140).onClick(() => {
              this.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_0_75_X);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('1倍速').width(140).onClick(() => {
              this.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('1.25倍速').width(140).onClick(() => {
              this.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('1.75倍速').width(140).onClick(() => {
              this.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('2倍速').width(140).onClick(() => {
              this.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('快进5秒').width(140).onClick(() => {
              this.setSeek(5);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('快退5秒').width(140).onClick(() => {
              this.setSeek(-5);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('上一个').width(140).onClick(() => {
              this.goToNextOrPre(-1);
            });
          }

          GridCol({ span: 1, offset: 0, order: 0 }) {
            Button('下一个').width(140).onClick(() => {
              this.goToNextOrPre(1);
            });
          }

          if (this.isVideo) {
            GridCol({ span: 1, offset: 0, order: 0 }) {
              Button('开启画中画').width(140).onClick(() => {
                this.startPipWindow();
              });
            }

            GridCol({ span: 1, offset: 0, order: 0 }) {
              Button('关闭画中画').width(140).onClick(() => {
                this.stopPipWindow();
              });
            }
          }
        }
        .margin({ bottom: 20, top: 20 })
        .borderRadius(20)
      }
      .scrollBar(BarState.Off)
    }
  }

  @Builder
  VideoPlayer() {
    Stack({
      alignContent: this.isFullScreen ? (this.isLandscape ? Alignment.Bottom : Alignment.Center) : Alignment.Bottom
    }) {
      Stack() {
        if (!this.isPlay) {
          Image($r('app.media.ic_public_play'))
            .width(50)
            .height(50)
            .zIndex(2)
            .onClick(() => {
              this.play();
            });
        }

        Column() {
          XComponent({
            id: '',
            type: XComponentType.SURFACE,
            libraryname: '',
            controller: this.xComponentController
          })
            .onLoad(() => {
              this.xComponentController.setXComponentSurfaceSize({
                surfaceWidth: 1920,
                surfaceHeight: 1080
              });
              this.surfaceID = this.xComponentController.getXComponentSurfaceId();
            })
            .width('100%')
            .height('100%');
        }
        .zIndex(1)
        .onClick(() => {
          this.playOrPause();
        })
      }
      .width('100%')
      .height(this.isFullScreen ? (this.isLandscape ? '100%' : 260) : '100%')

      this.PlayControl()
    }
    .height(this.isFullScreen ? '100%' : 260)
    .backgroundColor(Color.Black)
    .width('100%')
  }

  @Builder
  AudioPlayer() {
    Stack({ alignContent: this.isFullScreen ? Alignment.Center : Alignment.Bottom }) {
      Image($r('app.media.ic_camera_story_playing')).objectFit(ImageFit.Contain)
      this.PlayControl()
    }
    .height(260)
    .backgroundImage($r('app.media.background'), ImageRepeat.NoRepeat)
    .width('100%')
  }

  @Builder
  PlayControl() {
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Image($r('app.media.ic_previous'))
        .width('20vp')
        .height('20vp')
        .onClick(() => {
          this.goToNextOrPre(-1)
        });
      Image(this.isPlay ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
        .width('20vp')
        .height('20vp')
        .onClick(() => {
          this.iconOnclick();
        });
      Image($r('app.media.ic_next'))
        .width('20vp')
        .height('20vp')
        .onClick(() => {
          this.goToNextOrPre(1)
        });
      Text(this.currentStringTime)
        .fontSize('14vp')
        .fontColor(Color.White)
        .margin({ left: '2vp' })
      Slider({
        value: this.currentTime,
        step: 1,
        min: 0,
        max: this.durationTime,
        style: SliderStyle.OutSet
      })
        .blockColor(Color.White)
        .width('50%')
        .trackColor(Color.Gray)
        .selectedColor(Color.White)
        .showSteps(false)
        .showTips(false)
        .trackThickness(this.isOpacity ? 2 : 4)
        .onChange((value: number, mode: SliderChangeMode) => {
          this.sliderOnchange(value, mode);
        });
      Text(this.durationStringTime)
        .fontSize('14vp')
        .fontColor(Color.White)
        .margin({ left: '2vp', right: '2vp' })
      Image($r('app.media.ic_public_reduce'))
        .width('20vp')
        .height('20vp')
        .onClick(() => {
          this.isFullScreen = !this.isFullScreen
        });
    }
    .zIndex(2)
    .padding({ right: '2vp' })
    .opacity(this.isOpacity ? 0.7 : 1)
    .width('100%')
    .offset({ x: 0, y: this.isFullScreen ? (this.isLandscape ? 0 : 110) : 0 })
    .backgroundBlurStyle(BlurStyle.Thin, { colorMode: ThemeColorMode.DARK })
  }

  createPipWindow() {
    let config: pipWindow.PiPConfiguration = {
      context: getContext(this),
      componentController: this.xComponentController,
      // navigationId: navId,
      templateType: pipWindow.PiPTemplateType.VIDEO_PLAY,
      contentWidth: 800,
      contentHeight: 600,
    };

    pipWindow.create(config).then((data: pipWindow.PiPController) => {
      console.info(`AVPlayerDemo Succeeded in creating pip controller. Data:${data}`);
      this.pipController = data;
      this.setPipWindowCallback(data)
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo Failed to create pip controller. Cause:${err.code}, message:${err.message}`);
    });
  }

  // 注册PipWindow回调函数
  setPipWindowCallback(pipController: pipWindow.PiPController) {
    // 注册PipWindow生命周期状态监听
    pipController.on('stateChange', (state: pipWindow.PiPState, reason: string) => {
      let curState: string = '';
      switch (state) {
        case pipWindow.PiPState.ABOUT_TO_START:
          curState = 'ABOUT_TO_START';
          break;
        case pipWindow.PiPState.STARTED:
          curState = 'STARTED';
          break;
        case pipWindow.PiPState.ABOUT_TO_STOP:
          curState = 'ABOUT_TO_STOP';
          break;
        case pipWindow.PiPState.STOPPED:
          pipController.setAutoStartEnabled(false)
          curState = 'STOPPED';
          break;
        case pipWindow.PiPState.ABOUT_TO_RESTORE:
          curState = 'ABOUT_TO_RESTORE';
          break;
        case pipWindow.PiPState.ERROR:
          curState = 'ERROR';
          break;
        default:
          break;
      }
      console.info('AVPlayerDemo pipController stateChange:' + curState + ' reason:' + reason);
    });
    // 注册PipWindow控制事件监听
    pipController.on('controlPanelActionEvent', (event: pipWindow.PiPActionEventType) => {
      switch (event) {
        case 'playbackStateChanged':
        // 开始或停止视频
          this.playOrPause();
          break;
        case 'nextVideo':
        // 切换到下一个视频
          this.goToNextOrPre(1);
          break;
        case 'previousVideo':
        // 切换到上一个视频
          this.goToNextOrPre(-1);
          break;
        default:
          break;
      }
      console.info('AVPlayerDemo pipController registerActionEventCallback, event:' + event);
    });
  }

  startPipWindow() {
    if (this.pipController) {
      // 设置是否需要在返回桌面时自动启动画中画
      this.pipController.setAutoStartEnabled(true)
      this.pipController.startPiP().then(() => {
        // this.play();
        console.info(`AVPlayerDemo Succeeded in starting pip.`);
      }).catch((err: BusinessError) => {
        console.error(`AVPlayerDemo Failed to start pip. Cause:${err.code}, message:${err.message}`);
      });
    }
  }

  stopPipWindow() {
    if (this.pipController) {
      this.pipController.stopPiP().then(() => {
        console.info(`AVPlayerDemo Succeeded in stop pip.`);
      }).catch((err: BusinessError) => {
        console.error(`AVPlayerDemo Failed to stop pip. Cause:${err.code}, message:${err.message}`);
      });
    }
  }

  // 注册avplayer回调函数
  setAVPlayerCallback(avPlayer: media.AVPlayer) {
    avPlayer.on('timeUpdate', (time: number) => {
      console.info(`AVPlayerDemo AVPlayer timeUpdate. time is ${time}`);
      this.currentTime = Math.floor(time * this.durationTime / avPlayer.duration);
      console.info(`AVPlayerDemo this.currentTime. time is ${this.currentTime}`);
      this.currentStringTime = this.secondToTime(Math.floor(time / 1000));
      if (this.currentAVSession) {
        this.playbackState.position = {
          elapsedTime: time, // 已经播放的位置,以ms为单位
          updateTime: (new Date()).getTime(), // 应用更新当前位置的时间戳,以ms为单位
        };
        this.currentAVSession.setAVPlaybackState(this.playbackState);
      }
    })

    // seek操作结果回调函数
    avPlayer.on('seekDone', (seekDoneTime: number) => {
      console.info(`AVPlayerDemo AVPlayer seekDone succeeded, seek time is ${seekDoneTime}`);
      if (this.currentAVSession) {
        this.playbackState.position = {
          elapsedTime: avPlayer.currentTime, // 已经播放的位置,以ms为单位
          updateTime: (new Date()).getTime(), // 应用更新当前位置的时间戳,以ms为单位
        };
        this.currentAVSession.setAVPlaybackState(this.playbackState);
      }
    })

    // 监听setSpeed生效的事件
    avPlayer.on('speedDone', (speed: number) => {
      console.info(`AVPlayerDemo AVPlayer speedDone succeeded, speed is ${speed}`);
      if (this.currentAVSession) {
        this.playbackState.speed = speed;
        this.currentAVSession.setAVPlaybackState(this.playbackState);
      }
    })

    // error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
    avPlayer.on('error', (err: BusinessError) => {
      console.error(`AVPlayerDemo Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
      avPlayer.reset(); // 调用reset重置资源,触发idle状态
    })

    // 状态机变化回调函数
    avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info('AVPlayerDemo AVPlayer state idle called.');
          if (avPlayer && this.sourceFiles.length > this.currentIndex) {
            // 网络视频使用avPlayer.url赋值
            avPlayer.fdSrc = this.sourceFiles[this.currentIndex];
          }
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info('AVPlayerDemo AVPlayer state initialized called.');
          this.reset()
          if (this.isVideo) {
            avPlayer.surfaceId = this.surfaceID;
          }
          avPlayer.prepare();
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info('AVPlayerDemo AVPlayer state prepared called.');
          this.flag = true;
          this.durationTime = Math.floor(avPlayer.duration / 1000);
          this.durationStringTime = this.secondToTime(this.durationTime);
        // avPlayer.loop = true;
          avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X)
          avPlayer.seek(1, media.SeekMode.SEEK_PREV_SYNC)
          await this.startAVSession();
          break;
        case 'completed': // prepare调用成功后上报该状态机
          console.info('AVPlayerDemo AVPlayer state completed called.');
          this.isPlay = false;
          if (this.currentAVSession) {
            // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间,资源的时长
            this.playbackState.state = avSession.PlaybackState.PLAYBACK_STATE_COMPLETED;
            this.currentAVSession.setAVPlaybackState(this.playbackState);
          }
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info('AVPlayerDemo AVPlayer state playing called.');
          if (this.currentAVSession) {
            // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间,资源的时长
            this.playbackState.state = avSession.PlaybackState.PLAYBACK_STATE_PLAY;
            this.currentAVSession.setAVPlaybackState(this.playbackState);
          }
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info('AVPlayerDemo AVPlayer state paused called.');
          if (this.currentAVSession) {
            // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间,资源的时长
            this.playbackState.state = avSession.PlaybackState.PLAYBACK_STATE_PAUSE;
            this.playbackState.position = {
              elapsedTime: avPlayer.currentTime, // 已经播放的位置,以ms为单位
              updateTime: (new Date()).getTime(), // 应用更新当前位置的时间戳,以ms为单位
            };
            this.currentAVSession.setAVPlaybackState(this.playbackState);
          }
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info('AVPlayerDemo AVPlayer state stopped called.');
          if (this.currentAVSession) {
            // 设置状态: 播放状态,进度位置,播放倍速,缓存的时间,资源的时长
            this.playbackState.state = avSession.PlaybackState.PLAYBACK_STATE_STOP;
            this.currentAVSession.setAVPlaybackState(this.playbackState);
          }
          break;
        case 'released':
          console.info('AVPlayerDemo AVPlayer state released called.');
          break;
        default:
          console.info('AVPlayerDemo AVPlayer state unknown called.');
          break;
      }
    })
  }

  startContinuousTask() {
    let wantAgentInfo: wantAgent.WantAgentInfo = {
      // 点击通知后,将要执行的动作列表
      // 添加需要被拉起应用的bundleName和abilityName
      wants: [
        {
          bundleName: "com.xxx.xxx",
          abilityName: "EntryAbility"
        }
      ],
      // 指定点击通知栏消息后的动作是拉起ability
      actionType: wantAgent.OperationType.START_ABILITY,
      // 使用者自定义的一个私有值
      requestCode: 0,
      // 点击通知后,动作执行属性
      actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    };

    // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
    wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
      backgroundTaskManager.startBackgroundRunning(getContext(this),
        backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK, wantAgentObj).then(() => {
        console.info(`AVPlayerDemo Succeeded in operationing startBackgroundRunning.`);
      }).catch((err: BusinessError) => {
        console.error(`AVPlayerDemo Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
      });
    });
  }

  stopContinuousTask() {
    backgroundTaskManager.stopBackgroundRunning(getContext(this)).then(() => {
      console.info(`AVPlayerDemo Succeeded in operationing stopBackgroundRunning.`);
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
    });
  }

  reset(sourceFlag?: boolean) {
    this.isPlay = false;
    this.currentTime = 0;
    this.durationTime = 0;
    this.durationStringTime = '00:00';
    this.currentStringTime = '00:00';
    this.flag = false;
    if (sourceFlag) {
      this.currentIndex = 0;
      this.isFullScreen = false;
      if (this.isVideo) {
        this.sourceFiles = this.videoFiles;
      } else {
        this.sourceFiles = this.audioFiles;
      }
      if (this.avPlayer) {
        this.avPlayer.reset()
      }
    }

    if (this.pipController) {
      this.pipController.stopPiP();
    }
  }

  setSpeed(playSpeed: number) {
    if (!this.avPlayer || this.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
      console.error('AVPlayerDemo setSpeed failed. no avPlayer or state is not prepared/playing/paused/completed')
      return;
    }
    this.avPlayer.setSpeed(playSpeed);
  }

  async play() {
    if (!this.avPlayer || this.OPERATE_STATE.indexOf(this.avPlayer.state) === -1 ||
      this.OPERATE_STATE.indexOf(this.avPlayer.state) === 1) {
      console.error('AVPlayerDemo play failed. no avPlayer or state is not prepared/paused/completed')
      return;
    }
    this.isPlay = true;
    if (this.avPlayer.state === 'completed') {
      this.currentTime = 0
      this.currentStringTime = '00:00'
      this.avPlayer.seek(1, media.SeekMode.SEEK_PREV_SYNC);
    }
    this.avPlayer.play();
  }

  async startAVSession() {
    if (!this.currentAVSession) {
      console.error('AVPlayerDemo currentAVSession is undefined.')
      return;
    }
    let metadata: avSession.AVMetadata = this.generateAVMetadata();
    await this.currentAVSession.setAVMetadata(metadata).then(() => {
      console.info(`AVPlayerDemo SetAVMetadata successfully`);
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo SetAVMetadata BusinessError: code: ${err.code}, message: ${err.message}`);
    });
    this.playbackState.state = avSession.PlaybackState.PLAYBACK_STATE_PREPARE;
    this.playbackState.duration = this.avPlayer?.duration;
    this.currentAVSession.setAVPlaybackState(this.playbackState);
    // 通过按钮申请长时任务
    this.startContinuousTask();
    this.currentAVSession.getController().then((avcontroller: avSession.AVSessionController) => {
      this.avsessionController = avcontroller;
      console.info(`AVPlayerDemo GetController : SUCCESS : sessionid : ${avcontroller.sessionId}`);
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo GetController BusinessError: code: ${err.code}, message: ${err.message}`);
    });
    await this.currentAVSession.activate().then(() => {
      console.info(`AVPlayerDemo Activate : SUCCESS `);
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo Activate BusinessError: code: ${err.code}, message: ${err.message}`);
    });
  }

  private generateAVMetadata() {
    let previousIndex = this.addStepIndex(-1);
    let nextIndex = this.addStepIndex(1);
    let metadata: avSession.AVMetadata = {
      assetId: `${this.sourceFiles[this.currentIndex].fd}`, // 必须,媒体ID。歌曲的唯一标识,由应用自定义。
      title: `${this.sourceFiles[this.currentIndex].fd}`, // 标题
      artist: `艺术家${this.currentIndex}`, // 艺术家
      author: `专辑作者${this.currentIndex}`, // 专辑作者
      // avQueueId: "", // 歌单(歌曲列表)唯一标识Id
      // avQueueImage: "", // 歌单(歌曲列表)封面图,图片的像素数据或者图片路径地址(本地路径或网络路径)
      album: `专辑名称${this.currentIndex}`, // 专辑名称
      writer: `词作者${this.currentIndex}`, // 词作者
      composer: `作曲者${this.currentIndex}`, // 作曲者
      duration: this.avPlayer?.duration, // 媒体时长,单位毫秒(ms)
      mediaImage: this.curPixelMap, // 图片的像素数据或者图片路径地址(本地路径或网络路径)
      publishDate: new Date(), // 发行日期
      subtitle: `子标题${this.currentIndex}`, // 子标题
      description: `媒体描述${this.currentIndex}`, // 媒体描述
      // lyric: "xxxxx", // 歌词文件路径地址(本地路径或网络路径),当前仅支持本地文件
      previousAssetId: `${this.sourceFiles[previousIndex].fd}`, // 上一首媒体ID
      nextAssetId: `${this.sourceFiles[nextIndex].fd}`, // 下一首媒体ID
    };
    return metadata;
  }

  async createAVSession() {
    let type: avSession.AVSessionType = this.isVideo ? 'video' : 'audio';
    await avSession.createAVSession(getContext(this), 'AVPlayerDemo', type).then((data: avSession.AVSession) => {
      console.info(`AVPlayerDemo CreateAVSession : SUCCESS : sessionId = ${data.sessionId}`);
      this.currentAVSession = data;
      this.curSessionId = data.sessionId;
      this.setAVSessionCallback(data);
    }).catch((err: BusinessError) => {
      console.error(`AVPlayerDemo CreateAVSession BusinessError: code: ${err.code}, message: ${err.message}`);
    });
  }

  setAVSessionCallback(curAvSession: avSession.AVSession) {
    // 播放
    curAvSession.on('play', () => {
      console.info(`AVPlayerDemo AVSession on play entry`);
      this.play();
    });
    // 暂停
    curAvSession.on('pause', () => {
      console.info(`AVPlayerDemo AVSession on pause entry`);
      this.isPlay = false;
      if (this.avPlayer) {
        this.avPlayer.pause();
      }
    });
    // 停止
    curAvSession.on('stop', () => {
      console.info(`AVPlayerDemo AVSession on stop entry`);
      this.stopPlay();
    });
    // 下一个
    curAvSession.on('playNext', () => {
      console.info(`AVPlayerDemo AVSession on playNext entry`);
      this.goToNextOrPre(1);
    });
    // 上一个
    curAvSession.on('playPrevious', () => {
      console.info(`AVPlayerDemo AVSession on playPrevious entry`);
      this.goToNextOrPre(-1);
    });
    // 快进
    curAvSession.on('fastForward', () => {
      console.info(`AVPlayerDemo AVSession on fastForward entry`);
      this.setSeek(15);
    });
    // 快退
    curAvSession.on('rewind', () => {
      console.info(`AVPlayerDemo AVSession on rewind entry`);
      this.setSeek(-15);
    });
    // 媒体id播放监听事件
    curAvSession.on('playFromAssetId', (assetId: number) => {
      console.info(`AVPlayerDemo AVSession on playFromAssetId entry assetId : ${assetId}`);
    });
    // 跳转节点监听事件
    curAvSession.on('seek', (time: number) => {
      console.info(`AVPlayerDemo AVSession on seek entry time : ${time}`);
      this.setSeek(time);
    });
    // 播放速率的监听事件
    curAvSession.on('setSpeed', (speed: number) => {
      console.info(`AVPlayerDemo AVSession on setSpeed entry speed : ${speed}`);
      this.setSpeed(speed);
    });
  }

  async saveRawFileToPixelMap(rawFilePath: string) {
    let value: Uint8Array = await getContext(this).resourceManager.getRawFileContent(rawFilePath);
    let imageBuffer: ArrayBuffer = value.buffer as ArrayBuffer;
    let imageSource: image.ImageSource = image.createImageSource(imageBuffer);
    await imageSource.createPixelMap({ desiredSize: { width: 900, height: 900 } }).then((pixelMap: image.PixelMap) => {
      console.info('AVPlayerDemo Succeeded in creating pixelMap object through image decoding parameters.');
      this.curPixelMap = pixelMap;
    }).catch((error: BusinessError) => {
      console.error('AVPlayerDemo Failed to create pixelMap object through image decoding parameters.');
    })
  }

  private addStepIndex(num: number): number {
    let result = this.currentIndex + num;
    if (result < 0) {
      result = this.sourceFiles.length - 1;
    } else if (result > this.sourceFiles.length - 1) {
      result = 0;
    }
    return result;
  }

  iconOnclick() {
    if (this.isPlay === true) {
      this.avPlayer && this.avPlayer.pause();
      this.isPlay = false;
      this.isOpacity = false;
      return;
    }
    if (this.flag === true) {
      this.avPlayer && this.avPlayer.play();
      this.isPlay = true;
      this.isOpacity = true;
    } else {
      // The scheduled task determines whether the video loading is complete.
      let intervalFlag = setInterval(() => {
        if (this.flag === true) {
          this.avPlayer && this.avPlayer.play();
          this.isPlay = true;
          this.isOpacity = true;
          clearInterval(intervalFlag);
        }
      }, 100);
    }
  }

  sliderOnchange(value: number, mode: SliderChangeMode) {
    console.info(`AVPlayerDemo sliderOnchange. value is ${value}`);
    if (!this.avPlayer) {
      return;
    }
    this.currentTime = value
    if (mode === SliderChangeMode.Begin || mode === SliderChangeMode.Moving) {
      this.isOpacity = false;
    }
    if (mode === SliderChangeMode.End && this.avPlayer) {
      let seekTime: number = value * this.avPlayer.duration / this.durationTime;
      this.currentStringTime = this.secondToTime(Math.floor(seekTime / 1000));
      console.info(`AVPlayerDemo sliderOnchange. time is ${seekTime}, currentTime is ${this.currentTime}`);
      this.avPlayer.seek(seekTime, media.SeekMode.SEEK_PREV_SYNC);
      this.isOpacity = true;
    }
  }

  /**
   * Seconds converted to HH:mm:ss.
   *
   * @param seconds Maximum video duration (seconds).
   * @return Time after conversion.
   */
  secondToTime(seconds: number): string {
    let hourUnit = 60 * 60;
    let hour: number = Math.floor(seconds / hourUnit);
    let minute: number = Math.floor((seconds - hour * hourUnit) / 60);
    let second: number = seconds - hour * hourUnit - minute * 60;
    let hourStr: string = hour < 10 ? `0${hour.toString()}` : `${hour.toString()}`
    let minuteStr: string = minute < 10 ? `0${minute.toString()}` : `${minute.toString()}`
    let secondStr: string = second < 10 ? `0${second.toString()}` : `${second.toString()}`
    if (hour > 0) {
      return `${hourStr}:${minuteStr}:${secondStr}`;
    }
    if (minute > 0) {
      return `${minuteStr}:${secondStr}`;
    } else {
      return `00:${secondStr}`;
    }
  }

  playOrPause() {
    if (this.avPlayer) {
      if (this.isPlay) {
        this.isPlay = false;
        this.avPlayer.pause();
      } else {
        this.play();
      }
    }
  }

  createAVPlayer() {
    media.createAVPlayer().then((video: media.AVPlayer) => {
      if (video != null) {
        this.avPlayer = video;
        this.setAVPlayerCallback(this.avPlayer);
        if (this.avPlayer && this.sourceFiles.length > this.currentIndex) {
          this.avPlayer.fdSrc = this.sourceFiles[this.currentIndex];
        }
        console.info('AVPlayerDemo createAVPlayer success');
      } else {
        console.error('AVPlayerDemo createAVPlayer fail');
      }
    }).catch((error: BusinessError) => {
      console.error(`AVPlayerDemo AVPlayer catchCallback, error message:${error.message}`);
    });
  }

  initFiles() {
    let fileList: string[] = getContext(this).resourceManager.getRawFileListSync('video');
    fileList.forEach((fileStr: string) => {
      // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
      // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
      let fileDescriptor = getContext().resourceManager.getRawFdSync(`video/${fileStr}`);
      let avFileDescriptor: media.AVFileDescriptor = {
        fd: fileDescriptor.fd,
        offset: fileDescriptor.offset,
        length: fileDescriptor.length
      };
      this.videoFiles.push(avFileDescriptor)
    })

    let fileList2: string[] = getContext(this).resourceManager.getRawFileListSync('audio');
    fileList2.forEach((fileStr: string) => {
      // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
      // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
      let fileDescriptor = getContext().resourceManager.getRawFdSync(`audio/${fileStr}`);
      let avFileDescriptor: media.AVFileDescriptor = {
        fd: fileDescriptor.fd,
        offset: fileDescriptor.offset,
        length: fileDescriptor.length
      };
      this.audioFiles.push(avFileDescriptor)
    })

    if (this.isVideo) {
      this.sourceFiles = this.videoFiles;
    } else {
      this.sourceFiles = this.audioFiles;
    }
  }

  setSeek(addSecond: number, curTime?: number) {
    if (!this.avPlayer || this.OPERATE_STATE.indexOf(this.avPlayer.state) === -1) {
      console.error('AVPlayerDemo setSeek failed. no avPlayer or state is not prepared/playing/paused/completed')
      return;
    }
    if (!curTime) {
      curTime = this.avPlayer.currentTime;
    }
    let curMillSeconds: number = curTime + addSecond * 1000;
    curMillSeconds = Math.min(Math.max(curMillSeconds, 0), this.avPlayer.duration);

    this.currentTime = curMillSeconds * this.durationTime / this.avPlayer.duration;
    this.currentStringTime = this.secondToTime(Math.floor(curMillSeconds / 1000));
    console.error(`AVPlayerDemo setSeek. curMillSeconds is ${curMillSeconds}`)
    this.avPlayer.seek(curMillSeconds, media.SeekMode.SEEK_PREV_SYNC);
  }

  goToNextOrPre(num: number) {
    this.currentIndex += num;
    if (this.currentIndex < 0) {
      this.currentIndex = this.sourceFiles.length - 1;
    } else if (this.currentIndex > this.sourceFiles.length - 1) {
      this.currentIndex = 0;
    }
    if (this.avPlayer) {
      this.avPlayer.reset();
    }
    console.info(`AVPlayerDemo goToNextOrPre success. currentIndex is ${this.currentIndex}`)
  }

  stopPlay() {
    if (this.avPlayer) {
      this.avPlayer.stop();
      this.reset();
      this.avPlayer.reset();
      this.avPlayer.seek(1, media.SeekMode.SEEK_NEXT_SYNC);
    }
  }
}

2、index.ets

import media from '@ohos.multimedia.media';
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
import { BusinessError } from '@ohos.base';
import buffer from '@ohos.buffer';


let context = getContext(this) as common.UIAbilityContext;
// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例
let pathDir = context.filesDir;
let path = pathDir + '/1234.mp4';

@Entry
@Component
struct AVPlayerDemo {
  xComponentController: XComponentController = new XComponentController();
  surfaceID: string = '';
  public xComponentContext: Record<string, () => void> = {}
  public player: media.AVPlayer | null = null;
  private fileSize: number = -1;
  private fd: number = 0;
  public isSeek: boolean = true;

  build() {
    Column() {
      XComponent({
        id: 'AVPlayer',
        type: XComponentType.SURFACE,
        controller: this.xComponentController
      })
        .onLoad(() => {
          this.surfaceID = this.xComponentController.getXComponentSurfaceId()
          this.xComponentContext = this.xComponentController.getXComponentContext() as Record<string, () => void>
          this.read();
        })

      Button('speed')
        .onClick(() => {
          this.player?.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X);
        })
        .margin({ right: 12 })
        .margin({ top: 10 })
        .width(150)

      Button('paused')
        .onClick(() => {
          this.player?.pause();
        })
        .margin({ right: 12 })
        .margin({ top: 20 })
        .width(150)

      Button('seek')
        .onClick(() => {
          this.player?.seek(9000, 0);
        })
        .margin({ right: 12 })
        .margin({ top: 20 })
        .width(150)

      Button('player')
        .onClick(() => {
          this.avPlayerDataSrcNOSeekDemo()
        })
        .margin({ right: 12 })
        .margin({ top: 20 })
        .width(150)

      Button('reset')
        .onClick(() => {
          this.player?.reset();
        })
        .margin({ right: 12 })
        .margin({ top: 20 })
        .width(150)
    }
    .margin({ top: 40 })
    .height(400)
  }

  async avPlayerDataSrcNOSeekDemo() {
    // 创建avPlayer实例对象
    this.player = await media.createAVPlayer();
    // 创建状态机变化回调函数
    this.setAVPlayerCallback(this.player);
    // dataSrc播放模式的的播放源地址,当播放为Seek模式时fileSize为播放文件的具体大小,下面会对fileSize赋值
    let src: media.AVDataSrcDescriptor = {
      fileSize: -1,
      callback: (buf: ArrayBuffer, length: number) => {
        let num = 0;
        if (buf == undefined || length == undefined) {
          return -1;
        }
        num = fs.readSync(this.fd, buf);
        if (num > 0) {
          return num;
        }
        return -1;
      }
    }
    let context = getContext(this) as common.UIAbilityContext;
    // 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例
    let pathDir = context.filesDir;
    let path = pathDir + '/1234.mp4';
    await fs.open(path).then((file: fs.File) => {
      this.fd = file.fd;
    })
    // 获取播放文件的大小
    this.fileSize = fs.statSync(path).size;
    src.fileSize = this.fileSize;
    this.isSeek = false; // 支持seek操作
    this.player.dataSrc = src;
  }

  async read() {
    let context = getContext(this) as common.UIAbilityContext;
    let filesDir = context.filesDir;
    console.info(filesDir)
    request.downloadFile(context, {
      url: 'xxx.mp4',
      filePath: filesDir + '/1234.mp4'
    }).then((downloadTask: request.DownloadTask) => {
      downloadTask.on('complete', () => {
        let file = fs.openSync(filesDir + '/1234.mp4', fs.OpenMode.READ_WRITE);
        let arrayBuffer = new ArrayBuffer(1024);
        let readLen = fs.readSync(file.fd, arrayBuffer);
        let buf = buffer.from(arrayBuffer, 0, readLen);
        console.info(`The content of file: ${buf.toString()}`);
        console.info(filesDir)
        fs.closeSync(file);
      })
    }).catch((err: BusinessError) => {
      console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
    });
  }

  // 注册avplayer回调函数
  private setAVPlayerCallback(avPlayer: media.AVPlayer) {
    if (this.player == null) {
      return;
    }
    // // 响应API调用,监听seek()请求完成情况。
    avPlayer.on('speedDone', (number: Number) => this.speedDone(number));
    avPlayer.on('seekDone', (number: Number) => this.seekDone(number));

    // 状态机变化回调函数
    avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info('AVPlayer state idle called.');
          this.avPlayerDataSrcNOSeekDemo()
        //avPlayer.release(); // 调用release接口销毁实例对象
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info('AVPlayer state initialized called.');
          avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置
          avPlayer.prepare();
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info('AVPlayer state prepared called.');
          avPlayer.play(); // 调用播放接口开始播放
          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info('AVPlayer state playing called.');
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info('AVPlayer state paused called.');
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info('AVPlayer state completed called.');
          avPlayer.stop(); //调用播放结束接口
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info('AVPlayer state stopped called.');
          avPlayer.release(); // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          console.info('AVPlayer state released called.');
          fs.unlink(path)
          break;
        default:
          console.info('AVPlayer state unknown called.');
          break;
      }
    })
  }

  private speedDone(number: Number) {
    console.info('AVPlayer

分享
微博
QQ
微信
回复
2天前
相关问题
HarmonyOS VideoController无法继承
74浏览 • 1回复 待解决
HarmonyOS @Reusable缺少API文档
377浏览 • 1回复 待解决
HarmonyOS 手机缺少方正字体
87浏览 • 1回复 待解决
缺少serialport包,终端重用
3674浏览 • 1回复 待解决
HarmonyOS base64解码内容缺少
9浏览 • 0回复 待解决
崩溃信息中缺少详细的系统信息
875浏览 • 1回复 待解决
HarmonyOS Push Kit 缺少服务端sdk的demo
338浏览 • 1回复 待解决
关于权限列表条目缺少问题
2021浏览 • 1回复 待解决
DevEco-studio提示缺少zipalign怎么回事?
2726浏览 • 1回复 待解决
没有找到相应的MD5算法实现
704浏览 • 1回复 待解决
提问
该提问已有0人参与 ,帮助了0人