RedPlayer 视频播放器在 HarmonyOS 应用中的实践

前言

在现代移动应用中,视频播放功能已经成为核心功能之一。尤其在教育类应用中,优质的视频播放体验直接影响用户学习效率和应用的用户留存率。在开发《鸿蒙轻松学》这款 HarmonyOS 教育应用时,我们选择了 RedPlayer 作为核心视频播放解决方案。RedPlayer 是小红书开源的跨平台视频播放器,基于 FFmpeg 构建,支持丰富的视频格式,并提供了稳定的播放性能。

RedPlayer 的核心优势在于跨平台支持、高性能播放、低延迟缓冲以及丰富的 API 接口。通过将 RedPlayer 集成到《鸿蒙轻松学》中,我们不仅能够实现高质量的视频播放,还能提供丰富的用户交互功能,例如进度控制、音量调节、播放速度设置和断点续播等功能,从而提升整体用户体验。本文将从环境配置、组件实现到性能优化,详细介绍 RedPlayer 在 HarmonyOS 应用中的实践经验。

REDPlayer 三方库地址:https://ohpm.openharmony.cn/#/cn/detail/@rte-xhs%2Fredplayer

鸿蒙轻松学 App 项目地址:https://gitcode.com/nutpi/Learn-HarmonyOS-Easily


RedPlayer简介

RedPlayer 是一款开源、跨平台的视频播放器,最初由小红书开发,专为移动端优化设计。它基于 FFmpeg 构建,能够支持主流的视频格式,同时提供了硬件加速能力,从而保证了播放的高性能和低延迟。RedPlayer 的 API 设计简洁,易于在 HarmonyOS 应用中集成,并支持自定义播放控制、状态监听以及进度管理等功能。对于教育类应用来说,稳定性和性能是核心考量,而 RedPlayer 在这方面表现出色,可以满足从入门到高级的视频播放需求。

在功能上,RedPlayer 支持自动播放、循环播放、音量调节、倍速播放、进度条控制和视频预加载等多种功能。同时,它提供了事件回调接口,开发者可以监听播放状态变化、播放完成、播放错误等事件,从而实现对播放器的精细化控制。通过这些功能,教育类应用能够为用户提供流畅、稳定且可交互的学习视频体验。


环境准备与依赖配置

安装 RedPlayer OHPM 包

在 HarmonyOS 项目中,RedPlayer 以 OHPM 包形式提供。首先在项目根目录下通过 OHPM 命令安装依赖包:

bash 复制代码
ohpm install @rte-xhs/redplayer

这条命令会将 RedPlayer 包下载并集成到项目中,使我们可以在应用中直接使用 RedPlayer 的 API。安装完成后,可以在项目的 entry/oh-package.json5 文件中添加依赖配置,以确保项目构建时正确引入 RedPlayer:

bash 复制代码
{
  "dependencies": {
    "@rte-xhs/redplayer": "^1.0.0"
  }
}

完成依赖安装后,还需要配置网络权限,因为播放器需要访问在线视频资源。在 module.json5 文件中添加如下权限配置:

bash 复制代码
{
  "requestPermissions": [
    {
      "name": "ohos.permission.INTERNET",
      "reason": "$string:internet_permission_reason",
      "usedScene": {
        "abilities": ["EntryAbility"],
        "when": "inuse"
      }
    }
  ]
}

这一步确保播放器在加载远程视频时不会因权限问题而出错。

完成上述配置后,RedPlayer 的环境依赖准备工作就算完成。


基础播放器组件实现

在 HarmonyOS 中,我们通常将视频播放器封装成一个组件,以便在不同页面中复用。下面是一个基础 RedPlayer 组件实现示例:

TypeScript 复制代码
import { RedPlayerController, RedPlayerXComponentController } from '@rte-xhs/redplayer'

@Component
export struct RedPlayerComponent {
  @Prop videoUrl: string = ''
  @Prop playerWidth: string | number = '100%'
  @Prop playerHeight: number = 200
  @Prop autoplay: boolean = false

  private redPlayerController: RedPlayerController = new RedPlayerController()
  private xComponentController: RedPlayerXComponentController = new RedPlayerXComponentController()

  aboutToAppear() {
    this.initializePlayer()
  }

  private initializePlayer() {
    const config = {
      url: this.videoUrl,
      autoPlay: this.autoplay,
      loop: false,
      muted: false,
      volume: 1.0
    }

    this.redPlayerController.init(config)

    this.redPlayerController.setOnPreparedListener(() => {
      console.info('RedPlayer: 播放器准备完成')
    })

    this.redPlayerController.setOnErrorListener((error) => {
      console.error('RedPlayer: 播放错误', error)
    })

    this.redPlayerController.setOnCompletionListener(() => {
      console.info('RedPlayer: 播放完成')
    })
  }

  build() {
    Column() {
      XComponent({
        id: 'redplayer_xcomponent',
        type: 'surface',
        controller: this.xComponentController
      })
      .width(this.playerWidth)
      .height(this.playerHeight)
      .onLoad(() => {
        this.redPlayerController.attachToXComponent(this.xComponentController)
      })

      this.buildControlButtons()
    }
  }

  @Builder
  buildControlButtons() {
    Row() {
      Button('播放')
        .onClick(() => {
          this.redPlayerController.start()
        })

      Button('暂停')
        .onClick(() => {
          this.redPlayerController.pause()
        })

      Button('停止')
        .onClick(() => {
          this.redPlayerController.stop()
        })
    }
    .justifyContent(FlexAlign.SpaceEvenly)
    .width('100%')
    .margin({ top: 10 })
  }
}

上述代码通过 RedPlayerController 初始化播放器,并提供播放、暂停、停止按钮以便用户控制视频播放。aboutToAppear 生命周期中调用 initializePlayer 方法配置播放器参数,并设置播放状态、错误和完成监听器。XComponent 用于渲染播放器画面,并与 RedPlayer 绑定。

通过这种组件化封装,我们可以在不同页面中复用 RedPlayer 播放器,同时保证代码结构清晰。


高级播放器功能实现

在基础播放器之上,我们实现了更高级的播放器功能,包括播放进度控制、音量调节、倍速播放等。组件通过 State 管理播放状态,包括当前播放时间、视频总时长、是否播放、音量和播放速度。组件生命周期方法 aboutToAppear 中初始化播放器并启动进度定时器,aboutToDisappear 中释放资源和停止定时器,避免内存泄漏。

TypeScript 复制代码
@Component
export struct AdvancedRedPlayerComponent {
  @State currentTime: number = 0
  @State duration: number = 0
  @State isPlaying: boolean = false
  @State volume: number = 1.0
  @State playbackSpeed: number = 1.0

  private redPlayerController: RedPlayerController = new RedPlayerController()
  private progressTimer: number = -1

  aboutToAppear() {
    this.initializeAdvancedPlayer()
    this.startProgressTimer()
  }

  aboutToDisappear() {
    this.stopProgressTimer()
    this.redPlayerController.release()
  }

  private initializeAdvancedPlayer() {
    this.redPlayerController.setOnProgressUpdateListener((current, total) => {
      this.currentTime = current
      this.duration = total
    })

    this.redPlayerController.setOnPlaybackStateChangeListener((isPlaying) => {
      this.isPlaying = isPlaying
    })
  }

  private startProgressTimer() {
    this.progressTimer = setInterval(() => {
      if (this.isPlaying) {
        this.currentTime = this.redPlayerController.getCurrentPosition()
      }
    }, 1000)
  }

  private stopProgressTimer() {
    if (this.progressTimer !== -1) {
      clearInterval(this.progressTimer)
      this.progressTimer = -1
    }
  }

  private seekTo(position: number) {
    this.redPlayerController.seekTo(position)
    this.currentTime = position
  }

  private setVolume(volume: number) {
    this.volume = volume
    this.redPlayerController.setVolume(volume)
  }

  private setPlaybackSpeed(speed: number) {
    this.playbackSpeed = speed
    this.redPlayerController.setPlaybackSpeed(speed)
  }

  @Builder
  buildAdvancedControls() {
    Column() {
      Row() {
        Text(this.formatTime(this.currentTime))
          .fontSize(12)
          .fontColor('#666')

        Slider({
          value: this.currentTime,
          min: 0,
          max: this.duration,
          step: 1
        })
        .layoutWeight(1)
        .margin({ left: 10, right: 10 })
        .onChange((value: number) => {
          this.seekTo(value)
        })

        Text(this.formatTime(this.duration))
          .fontSize(12)
          .fontColor('#666')
      }
      .width('100%')
      .margin({ top: 10 })

      Row() {
        Text('音量:')
        Slider({
          value: this.volume,
          min: 0,
          max: 1,
          step: 0.1
        })
        .width(100)
        .onChange((value: number) => {
          this.setVolume(value)
        })

        Text('倍速:')
        Button(`${this.playbackSpeed}x`)
          .onClick(() => {
            const speeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0]
            const currentIndex = speeds.indexOf(this.playbackSpeed)
            const nextIndex = (currentIndex + 1) % speeds.length
            this.setPlaybackSpeed(speeds[nextIndex])
          })
      }
      .justifyContent(FlexAlign.SpaceEvenly)
      .width('100%')
      .margin({ top: 10 })
    }
  }

  private formatTime(seconds: number): string {
    const minutes = Math.floor(seconds / 60)
    const remainingSeconds = Math.floor(seconds % 60)
    return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
  }
}

该组件实现了高级控制功能。Slider 用于控制播放进度和音量,Button 用于切换播放速度。组件通过定时器每秒更新播放进度,使 UI 与播放器状态同步。通过这种方式,用户可以灵活控制播放,提升学习体验。


真机效果演示

视频列表页面,展示了从入门到高级的完整学习路径:

当用户点击视频卡片时,应用会切换到视频播放页面,展示完整的播放界面:

相关推荐
云雾J视界6 小时前
Linux企业级解决方案架构:字节跳动短视频推荐系统全链路实践
linux·云原生·架构·kubernetes·音视频·glusterfs·elk stack
Likeadust8 小时前
新版视频直播点播平台EasyDSS用视频破局,获客转化双提升
大数据·音视频
猫林老师15 小时前
HarmonyOS分布式硬件共享:调用手机摄像头的手表应用
华为·交互·harmonyos
涛涛讲AI18 小时前
一段音频多段字幕,让音频能够流畅自然对应字幕 AI生成视频,扣子生成剪映视频草稿
人工智能·音视频·语音识别
前端世界19 小时前
HarmonyOS应用开发指南:Toast无法显示的完整排查流程与实战案例
华为·harmonyos
lzptouch21 小时前
数据预处理(音频/图像/视频/文字)及多模态统一大模型输入方案
人工智能·音视频
安卓开发者1 天前
鸿蒙NEXT Wear Engine穿戴侧应用开发完全指南
ubuntu·华为·harmonyos
安卓开发者1 天前
鸿蒙Next振动开发指南:打造沉浸式触觉反馈体验
华为·harmonyos
Devil枫1 天前
HarmonyOS屏幕方向适配指南
华为·harmonyos