HarmonyOS 鸿蒙Next视频播放类应用键鼠控制实践

HarmonyOS 鸿蒙Next视频播放类应用键鼠控制实践

一、控制视频播放/暂停最佳实践

场景介绍

在使用PC/2in1设备播放视频时,通常需要响应鼠标/键盘操作控制视频播放/暂停,常见的操作类型有:鼠标/触控板/触控屏单击、空格键/Enter键按下,可以分别监听点击和按键的输入事件实现对应功能,本篇文档以AVPlayer播放视频场景为例提供适配指导。

开发步骤

  1. 定义变量维护视频播放/暂停状态。
  2. 使用emitter.on()注册播放状态变更回调。
  3. 在avplayer状态机变化回调中调用状态变更。
  4. 处理ClickEvent/KeyEvent事件,判断当前视频播放状态,执行播放/暂停操作。
// 1. 定义变量维护视频播放/暂停状态
isPlaying: boolean = true;

// 2. 使用emitter注册播放状态变更回调
emitter.on(innerEventTrue, (res) => {
  if (res.data) {
    console.log(`emitter innerEventTrue.`, res.data.flag);
    this.isPlaying = res.data.flag;
}
});
emitter.on(innerEventFalse, (res) => {
  if (res.data) {
    console.log(`emitter innerEventFalse.`, res.data.flag);
    this.isPlaying = res.data.flag;
  }
});

// 3. 在avplayer状态机变化回调中调用状态变更
this.avPlayer.on('stateChange', async (state, reason) => {
  switch (state) {
    case 'playing': // After the play interface is successfully invoked, the state machine is reported.
      Logger.info(TAG, 'setAVPlayerCallback AVPlayer state playing called.');
      let eventDataTrue: emitter.EventData = {
        data: {
          'flag': true
        }
      };
      emitter.emit(innerEventTrue, eventDataTrue);
      break;
    case 'paused': {
      Logger.info(TAG, 'setAVPlayerCallback AVPlayer state paused called.');
      let eventDataFalse: emitter.EventData = {
        data: {
          'flag': false
        }
      };
      emitter.emit(innerEventFalse, eventDataFalse);
      break;
    }
  }
});

// 4. 处理ClickEvent/KeyEvent事件,判断当前视频播放状态,执行播放/暂停操作
.onClick((event: ClickEvent) => {
  console.info('enter click event source:', event.source, 'sourceTool:', event.sourceTool);
  if (event.sourceTool !== SourceTool.Unknown) {
    this.toggleStartState();
  }
})
.onKeyEvent((event: KeyEvent) => {
  console.info('enter key event, keyType: ', event.type);
  if (event && event.type === KeyType.Down) {
    switch (event.keyCode) { // 此处以空格键为例,应用可以根据自己的业务场景响应不同的按键
      case KeyCode.KEYCODE_SPACE:
        this.toggleStartState();
        break;
      default:
        break;
    }
  }
})

toggleStartState() {
  if (this.isPlaying) {
    this.avPlayManager.videoPause();
    console.info('toggleStartState pause the video.')
    this.isShowProcessBar = true;
  } else {
    this.avPlayManager.videoPlay();
    console.info('toggleStartState start the video.')
  }
}

二、调节视频播放进度最佳实践

场景介绍

在使用PC应用播放视频时,通常需要响应用户操作控制视频播放进度,常见的操作类型有:触控板双指水平滑动、触控屏单指水平滑动、键盘左右方向键按下,可以分别监听滑动手势和按键的输入事件实现对应功能,本篇文档以AVPlayer播放视频场景为例提供适配指导

开发步骤

  1. 定义变量维护视频播放当前时间/总时间。
  2. 在avlayer初始化完成的回调事件中获取视频播放当前时间/总时间。
  3. 监听滑动手势,判断是水平方向滑动,则调整视频当前播放事件,在滑动结束时通过seek函数调整视频播放进度。
  4. 监听keyEvent事件,根据输入增减播放当前时间,并通过seek函数调整视频播放进度。
// 1. 定义变量维护视频播放当前时间/总时间
@State durationTime: number = 0;
@State currentTime: number = 0;

// 2. 在avplayer初始化完成的回调事件中获取视频播放当前时间/总时间
this.avPlayManager.initPlayer(this.surfaceId, (avPlayer: media.AVPlayer) => {
  this.durationTime = this.avPlayManager.getDurationTime();
  setInterval(() => { // Update the current time.
    if (!this.isShowProcessMask) {
      this.currentTime = this.avPlayManager.getCurrentTime();
    }
  }, SET_INTERVAL);
  this.setTimer();
});

// 3. 监听滑动手势,判断是水平方向滑动,则调整视频当前播放事件,在滑动结束时通过seek函数调整视频播放进度
.gesture(
  PanGesture()
    .onActionStart((event: GestureEvent) => {
      if (event) {
        if (Math.abs(event.offsetX) > Math.abs(event.offsetY)) {
          this.positionX = this.currentTime
          this.isShowProcessMask = true;
        }
      }
    })
    .onActionUpdate((event: GestureEvent) => {
      if (event) {
        if (this.isShowProcessMask) {
          this.currentTime = this.positionX + event.offsetX * (this.durationTime / 2000);
          console.info('enter PanGesture update, event.offsetX:', event.offsetX, 'currentTime:', this.currentTime);
        }
      }
    })
    .onActionEnd((event: GestureEvent) => {
      if (this.isShowProcessMask) {
        this.avPlayManager.videoSeek(this.currentTime);
        this.isShowProcessMask = false;
      }
    })
)

// 4. 监听keyEvent事件,根据输入增减播放当前时间,并通过seek函数调整视频播放进度
.onKeyEvent((event: KeyEvent) => {
  console.info('enter key event, keyType: ', event.type);
  if (event && event.type === KeyType.Down) {
    switch (event.keyCode) {
      case KeyCode.KEYCODE_DPAD_LEFT:
        if (this.currentTime - 1000 < 0) {
          this.currentTime = 0;
        } else {
          this.currentTime -= 1000;
        }
        this.avPlayManager.videoSeek(this.currentTime);
        break;
      case KeyCode.KEYCODE_DPAD_RIGHT:
        if (this.currentTime + 1000 > this.durationTime ) {
          this.currentTime = this.durationTime;
        } else {
          this.currentTime += 1000;
        }
        this.avPlayManager.videoSeek(this.currentTime);
        break;
      default:
        break;
    }
  }
})

三、调节视频音量/亮度最佳实践

场景介绍

在使用PC应用播放视频时,通常需要响应用户操作调节视频音量/亮度,常见的操作类型有:在视频左侧区域通过触控板双指竖直滑动、触控屏单指竖直滑动、鼠标滚轮滚动调节亮度在视频右侧区域通过触控板双指竖直滑动、触控屏单指竖直滑动、鼠标滚轮滚动调节音量

开发步骤

  1. 定义变量维护视频音量/亮度。
  2. 在avplayer初始化完成的回调事件中获取视频播放当前时间/总时间。
  3. 监听滑动手势,判断是水平方向滑动,则调整视频当前播放事件,在滑动结束时通过seek函数调整视频播放进度。
  4. 监听KeyEvent事件,判断当前视频播放状态,执行播放/暂停操作。
// 1. 定义变量维护视频音量/亮度
@State volume: number = 50;
@State bright: number = 50;

// 2. 在页面加载完成时初始化屏幕亮度
aboutToAppear(): void {
  this.mainWin.setWindowBrightness(this.bright / 100);
}

// 3. 在avplayer状态变化到prepared时初始化播放音量
this.avPlayer.on('stateChange', async (state, reason) => {
  ...
  switch (state) {
    ...
    case 'prepared': // This state machine is reported after the prepare interface is successfully invoked.
      ...
      this.avPlayer.setVolume(this.volume);
    ...
  }
});

// 4. 监听滑动手势,判断是竖直方向滑动,如果滑动区域是左半屏则调整屏幕亮度;如果滑动区域是右半屏则调整视频音量
.gesture(
  PanGesture()
    .onActionStart((event: GestureEvent) => {
      if (event) {
        if (Math.abs(event.offsetX) > Math.abs(event.offsetY)) {
          this.positionX = this.currentTime
          this.isShowProcessMask = true;
        } else {
          if (vp2px(event.fingerList[0].globalX) >= this.surfaceW / 2) {
            // 右半屏纵向滑动,调节音量
            this.isShowVolumeMask = true;
            this.positionY = this.volume;
          } else {
            // 左半屏纵向滑动,调节亮度
            this.isShowBrightnessMask = true;
            this.positionY = this.bright;
          }
        }
      }
    })
    .onActionUpdate((event: GestureEvent) => {
      if (event) {
        if (this.isShowProcessMask) {
          this.currentTime = this.positionX + event.offsetX * (this.durationTime / 2000);
          console.info('enter PanGesture update, event.offsetX:', event.offsetX, 'currentTime:', this.currentTime);
        }
        // 区分输入设备,鼠标和触控板触控屏正负逻辑相反
        console.info('enter PanGesture update source:', event.source, 'sourceTool:', event.sourceTool);

        if (this.isShowVolumeMask) {
          this.volume = this.valueConvert(event);
          this.avPlayManager.setVolume(this.volume);
        }
        if (this.isShowBrightnessMask) {
          this.bright = this.valueConvert(event);
          this.mainWin.setWindowBrightness(this.bright / 100);
        }
      }
    })
    .onActionEnd((event: GestureEvent) => {
      if (this.isShowProcessMask) {
        this.avPlayManager.videoSeek(this.currentTime);
        this.isShowProcessMask = false;
      }
      if (this.isShowVolumeMask) {
        this.isShowVolumeMask = false;
      }
      if (this.isShowBrightnessMask) {
        this.isShowBrightnessMask = false;
      }
    })
)

// 5. 监听KeyEvent事件,如果是上下方向键,则控制视频音量放大/减小
.onKeyEvent((event: KeyEvent) => {
  if (event && event.type === KeyType.Down) {
    switch (event.keyCode) {
      ...
      case KeyCode.KEYCODE_DPAD_UP: {
        let value = this.volume + 10;
        if (value < 0) {
          value = 0;
        } else if (value > 100) {
          value = 100;
        }
        this.volume = value;
        this.avPlayManager.setVolume(this.volume);
        break;
      }
      case KeyCode.KEYCODE_DPAD_DOWN: {
        let value = this.volume - 10;
        if (value < 0) {
          value = 0;
        } else if (value > 100) {
          value = 100;
        }
        this.volume = value;
        this.avPlayManager.setVolume(this.volume);
        break;
      }
      default:
        break;
    }
  }
})

四、示例代码

参考链接:


更多关于HarmonyOS 鸿蒙Next视频播放类应用键鼠控制实践的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

标题

这是段落内容。

这是另一段落内容。

更多关于HarmonyOS 鸿蒙Next视频播放类应用键鼠控制实践的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next视频播放应用支持键鼠控制实现方式如下:

  1. 使用InputSubscriber监听键鼠事件
  2. 注册KeyCode.BACK等键值处理返回操作
  3. 通过MouseEvent处理滚轮控制音量
  4. 监听KeyEvent处理空格键暂停/播放
  5. 方向键实现快进/后退功能

关键代码示例:

inputMonitor.on(KeyEvent.KEY_DOWN, (event: KeyEvent) => {
  if(event.keyCode === KeyCode.SPACE) {
    playerCtrl.togglePlay()
  }
})

需在config.json声明ohos.permission.INPUT_MONITORING权限。

在HarmonyOS Next中实现视频播放器的键鼠控制功能,您提供的代码示例已经很好地展示了关键实现点。这里补充几点技术细节:

  1. 事件优先级处理: 建议在onKeyEventonClick事件处理中增加事件消费标记(event.stopPropagation),避免事件冒泡导致多次触发。

  2. 手势识别优化: 对于进度调节的PanGesture,可以增加最小移动阈值判断,避免微小误触:

.onActionStart((event) => {
  if(Math.abs(event.offsetX) < 10) return // 忽略小于10px的移动
})
  1. 音量/亮度调节: 建议增加动画效果平滑过渡,可以使用animateTo实现数值变化的平滑过渡:
animateTo({
  duration: 100
}, () => {
  this.volume = newValue
})
  1. 性能优化: 频繁的seek操作可能会影响性能,可以添加操作间隔限制:
lastSeekTime: number = 0

seekVideo(time: number) {
  const now = Date.now()
  if(now - this.lastSeekTime < 200) return // 200ms内不重复seek
  this.lastSeekTime = now
  // ...执行seek操作
}

这些优化点可以使您的视频播放控制更加流畅和稳定。

回到顶部