Skip to content

多媒体 ​

HarmonyOS 提供丰富的多媒体能力,包括相机拍照/录像、音频播放/录制、视频播放、图片选择等功能,满足应用多媒体处理需求。

前置配置 ​

module.json5 权限声明 ​

根据使用场景声明相应权限:

json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:reason_camera",
        "usedScene": {}
      },
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:reason_microphone",
        "usedScene": {}
      },
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:reason_read_image",
        "usedScene": {}
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "reason": "$string:reason_write_image",
        "usedScene": {}
      }
    ]
  }
}
权限说明是否动态申请
ohos.permission.CAMERA相机权限是
ohos.permission.MICROPHONE麦克风权限是
ohos.permission.READ_IMAGEVIDEO读取图片/视频是
ohos.permission.WRITE_IMAGEVIDEO写入图片/视频是

相机 ​

API 列表 ​

API说明起始版本
camera.getCameraManager()获取相机管理器API 10
CameraManager.getSupportedCameras()获取支持的相机列表API 10
CameraManager.createCameraInput()创建相机输入API 10
CameraManager.createPreviewOutput()创建预览输出API 10
CameraManager.createPhotoOutput()创建拍照输出API 10
CameraManager.createVideoOutput()创建录像输出API 10
CameraInput.open()打开相机API 10
CameraSession.beginConfig()开始配置会话API 10
CameraSession.commitConfig()提交配置API 10
CameraSession.start()开始会话API 10
PhotoOutput.capture()拍照API 10

相机组件(推荐) ​

API 11+ 推荐使用系统相机组件,无需处理复杂的相机会话:

typescript
import { camera } from '@kit.MultimediaKit'

@Entry
@Component
struct CameraPage {
  @State photoUri: string = ''

  build() {
    Column() {
      Camera({
        camera: {
          cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
        }
      })
        .width('100%')
        .height('80%')

      Row({ space: 20 }) {
        Button('拍照')
          .onClick(async () => {
            // 调用系统相机拍照
            const picker = new camera.CameraPicker()
            const result = await picker.pick(getContext(), {
              cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
              saveType: camera.CameraPickerMediaType.PHOTO
            })
            this.photoUri = result.resultUri
          })

        Button('录像')
          .onClick(async () => {
            const picker = new camera.CameraPicker()
            const result = await picker.pick(getContext(), {
              cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
              saveType: camera.CameraPickerMediaType.VIDEO
            })
            this.photoUri = result.resultUri
          })
      }
      .padding(20)

      if (this.photoUri) {
        Image(this.photoUri)
          .width(200)
          .height(200)
          .objectFit(ImageFit.Cover)
      }
    }
    .width('100%')
    .height('100%')
  }
}

使用系统相机选择器 ​

最简单的拍照方式,无需处理权限和相机会话:

typescript
import { camera } from '@kit.MultimediaKit'

async function takePhoto(): Promise<string> {
  try {
    const picker = new camera.CameraPicker()
    const result = await picker.pick(getContext(), {
      cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
      saveType: camera.CameraPickerMediaType.PHOTO
    })
    return result.resultUri
  } catch (error) {
    console.error('拍照失败:', JSON.stringify(error))
    return ''
  }
}

图片选择 ​

API 列表 ​

API说明起始版本
photoAccessHelper.getPhotoAccessHelper()获取图片访问助手API 10
PhotoViewPicker.select()选择图片/视频API 10
PhotoViewPicker.save()保存图片/视频API 10

选择图片 ​

typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'

async function pickImage(): Promise<string> {
  try {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
    photoSelectOptions.maxSelectNumber = 1

    const photoPicker = new photoAccessHelper.PhotoViewPicker()
    const result = await photoPicker.select(photoSelectOptions)

    if (result.photoUris.length > 0) {
      return result.photoUris[0]
    }
    return ''
  } catch (error) {
    console.error('选择图片失败:', JSON.stringify(error))
    return ''
  }
}

// 选择多张图片
async function pickMultipleImages(maxCount: number): Promise<string[]> {
  try {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
    photoSelectOptions.maxSelectNumber = maxCount

    const photoPicker = new photoAccessHelper.PhotoViewPicker()
    const result = await photoPicker.select(photoSelectOptions)

    return result.photoUris
  } catch (error) {
    console.error('选择图片失败:', JSON.stringify(error))
    return []
  }
}

选择视频 ​

typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'

async function pickVideo(): Promise<string> {
  try {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
    photoSelectOptions.maxSelectNumber = 1

    const photoPicker = new photoAccessHelper.PhotoViewPicker()
    const result = await photoPicker.select(photoSelectOptions)

    if (result.photoUris.length > 0) {
      return result.photoUris[0]
    }
    return ''
  } catch (error) {
    console.error('选择视频失败:', JSON.stringify(error))
    return ''
  }
}

保存图片到相册 ​

typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'

async function saveImageToGallery(imageUri: string): Promise<void> {
  try {
    const photoSaveOptions = new photoAccessHelper.PhotoSaveOptions()
    photoSaveOptions.newFileNames = ['saved_image.jpg']

    const photoPicker = new photoAccessHelper.PhotoViewPicker()
    await photoPicker.save(photoSaveOptions)
    console.info('图片保存成功')
  } catch (error) {
    console.error('保存图片失败:', JSON.stringify(error))
  }
}

音频播放 ​

API 列表 ​

API说明起始版本
media.createAVPlayer()创建音视频播放器API 10
AVPlayer.prepare()准备播放API 10
AVPlayer.play()开始播放API 10
AVPlayer.pause()暂停播放API 10
AVPlayer.stop()停止播放API 10
AVPlayer.seek()跳转指定位置API 10
AVPlayer.setVolume()设置音量API 10
AVPlayer.release()释放资源API 10

AVPlayerState 枚举 ​

枚举值说明
idle初始状态
initialized已设置数据源
prepared准备完成
playing播放中
paused已暂停
completed播放完成
stopped已停止
released已释放
error错误状态

基础音频播放 ​

typescript
import { media } from '@kit.MultimediaKit'

class AudioPlayer {
  private player: media.AVPlayer | null = null
  private isPlaying: boolean = false

  async init(url: string): Promise<void> {
    this.player = await media.createAVPlayer()

    // 监听状态变化
    this.player.on('stateChange', (state: media.AVPlayerState) => {
      console.info('播放器状态:', state)
      this.isPlaying = state === 'playing'
    })

    // 监听播放错误
    this.player.on('error', (error) => {
      console.error('播放错误:', JSON.stringify(error))
    })

    // 监听播放完成
    this.player.on('seekDone', (seekDoneTime) => {
      console.info('跳转完成:', seekDoneTime)
    })

    // 设置数据源
    this.player.url = url
    await this.player.prepare()
  }

  async play(): Promise<void> {
    if (this.player) {
      await this.player.play()
    }
  }

  async pause(): Promise<void> {
    if (this.player) {
      await this.player.pause()
    }
  }

  async stop(): Promise<void> {
    if (this.player) {
      await this.player.stop()
    }
  }

  async seek(timeMs: number): Promise<void> {
    if (this.player) {
      await this.player.seek(timeMs)
    }
  }

  setVolume(volume: number): void {
    if (this.player) {
      this.player.setVolume(volume)
    }
  }

  getDuration(): number {
    return this.player?.duration || 0
  }

  getCurrentTime(): number {
    return this.player?.currentTime || 0
  }

  async release(): Promise<void> {
    if (this.player) {
      await this.player.release()
      this.player = null
    }
  }
}

// 使用示例
const audioPlayer = new AudioPlayer()

async function playMusic() {
  await audioPlayer.init('https://example.com/music.mp3')
  await audioPlayer.play()

  // 5 秒后暂停
  setTimeout(() => {
    audioPlayer.pause()
  }, 5000)
}

本地音频播放 ​

typescript
import { media } from '@kit.MultimediaKit'
import { fileIo } from '@kit.CoreFileKit'

async function playLocalAudio(filePath: string): Promise<void> {
  const player = await media.createAVPlayer()

  try {
    // 使用 fdSrc 播放本地文件
    const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY)
    player.fdSrc = {
      fd: file.fd,
      offset: 0,
      length: fileIo.statSync(file.fd).size
    }

    await player.prepare()
    await player.play()

    // 播放完成后关闭文件
    player.on('stateChange', async (state) => {
      if (state === 'completed' || state === 'stopped') {
        fileIo.closeSync(file)
        await player.release()
      }
    })
  } catch (error) {
    fileIo.closeSync(file)
    await player.release()
    throw error
  }
}

音频录制 ​

API 列表 ​

API说明起始版本
media.createAVRecorder()创建音视频录制器API 10
AVRecorder.prepare()准备录制API 10
AVRecorder.start()开始录制API 10
AVRecorder.pause()暂停录制API 10
AVRecorder.resume()恢复录制API 10
AVRecorder.stop()停止录制API 10
AVRecorder.release()释放资源API 10

基础音频录制 ​

typescript
import { media } from '@kit.MultimediaKit'
import { fileIo } from '@kit.CoreFileKit'

class AudioRecorder {
  private recorder: media.AVRecorder | null = null
  private outputPath: string = ''

  async init(outputPath: string): Promise<void> {
    this.outputPath = outputPath
    this.recorder = await media.createAVRecorder()

    // 监听状态变化
    this.recorder.on('stateChange', (state: media.AVRecorderState) => {
      console.info('录制器状态:', state)
    })

    // 监听错误
    this.recorder.on('error', (error) => {
      console.error('录制错误:', JSON.stringify(error))
    })

    // 配置录制参数
    const config: media.AVRecorderConfig = {
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
      profile: media.AVRecorderProfileType.RECORDER_PROFILE_H264_AAC_MP4,
      url: `fd://${outputPath}`,
      rotation: 0
    }

    await this.recorder.prepare(config)
  }

  async start(): Promise<void> {
    if (this.recorder) {
      await this.recorder.start()
    }
  }

  async pause(): Promise<void> {
    if (this.recorder) {
      await this.recorder.pause()
    }
  }

  async resume(): Promise<void> {
    if (this.recorder) {
      await this.recorder.resume()
    }
  }

  async stop(): Promise<void> {
    if (this.recorder) {
      await this.recorder.stop()
    }
  }

  async release(): Promise<void> {
    if (this.recorder) {
      await this.recorder.release()
      this.recorder = null
    }
  }
}

// 使用示例
const recorder = new AudioRecorder()

async function recordAudio() {
  const outputPath = getContext().cacheDir + '/recording.mp4'
  await recorder.init(outputPath)
  await recorder.start()

  // 录制 5 秒
  setTimeout(async () => {
    await recorder.stop()
    await recorder.release()
    console.info('录制完成:', outputPath)
  }, 5000)
}

视频播放 ​

基础视频播放 ​

typescript
import { media } from '@kit.MultimediaKit'

@Entry
@Component
struct VideoPlayerPage {
  private player: media.AVPlayer | null = null
  @State isPlaying: boolean = false
  @State currentTime: number = 0
  @State duration: number = 0

  aboutToAppear() {
    this.initPlayer()
  }

  aboutToDisappear() {
    this.releasePlayer()
  }

  async initPlayer() {
    this.player = await media.createAVPlayer()
    this.player.url = 'https://example.com/video.mp4'

    this.player.on('stateChange', (state) => {
      if (state === 'playing') {
        this.isPlaying = true
      } else if (state === 'paused' || state === 'stopped') {
        this.isPlaying = false
      }
    })

    this.player.on('timeUpdate', (time) => {
      this.currentTime = time
    })

    await this.player.prepare()
    this.duration = this.player.duration
  }

  async togglePlay() {
    if (this.isPlaying) {
      await this.player?.pause()
    } else {
      await this.player?.play()
    }
  }

  async seekTo(time: number) {
    await this.player?.seek(time)
  }

  async releasePlayer() {
    if (this.player) {
      await this.player.release()
      this.player = null
    }
  }

  build() {
    Column() {
      // 视频组件
      Video({
        src: 'https://example.com/video.mp4',
        controller: new VideoController()
      })
        .width('100%')
        .height(300)
        .autoPlay(false)

      Row({ space: 20 }) {
        Button(this.isPlaying ? '暂停' : '播放')
          .onClick(() => this.togglePlay())

        Button('停止')
          .onClick(() => {
            this.player?.stop()
          })
      }
      .padding(20)

      // 进度条
      Slider({
        value: this.currentTime,
        min: 0,
        max: this.duration
      })
        .width('90%')
        .onChange((value) => {
          this.seekTo(value)
        })

      Text(`${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}`)
        .fontSize(14)
        .fontColor('#999')
    }
    .width('100%')
    .height('100%')
  }

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

完整封装示例 ​

typescript
import { media } from '@kit.MultimediaKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { camera } from '@kit.MultimediaKit'

class MultimediaManager {
  private static instance: MultimediaManager
  private player: media.AVPlayer | null = null
  private recorder: media.AVRecorder | null = null

  static getInstance(): MultimediaManager {
    if (!MultimediaManager.instance) {
      MultimediaManager.instance = new MultimediaManager()
    }
    return MultimediaManager.instance
  }

  // ========== 图片选择 ==========
  async pickImage(maxCount: number = 1): Promise<string[]> {
    try {
      const options = new photoAccessHelper.PhotoSelectOptions()
      options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
      options.maxSelectNumber = maxCount

      const picker = new photoAccessHelper.PhotoViewPicker()
      const result = await picker.select(options)
      return result.photoUris
    } catch (error) {
      console.error('选择图片失败:', JSON.stringify(error))
      return []
    }
  }

  async pickVideo(): Promise<string> {
    try {
      const options = new photoAccessHelper.PhotoSelectOptions()
      options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
      options.maxSelectNumber = 1

      const picker = new photoAccessHelper.PhotoViewPicker()
      const result = await picker.select(options)
      return result.photoUris[0] || ''
    } catch (error) {
      console.error('选择视频失败:', JSON.stringify(error))
      return ''
    }
  }

  // ========== 拍照 ==========
  async takePhoto(): Promise<string> {
    try {
      const picker = new camera.CameraPicker()
      const result = await picker.pick(getContext(), {
        cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
        saveType: camera.CameraPickerMediaType.PHOTO
      })
      return result.resultUri
    } catch (error) {
      console.error('拍照失败:', JSON.stringify(error))
      return ''
    }
  }

  // ========== 音频播放 ==========
  async initPlayer(url: string): Promise<void> {
    if (this.player) {
      await this.releasePlayer()
    }

    this.player = await media.createAVPlayer()
    this.player.url = url
    await this.player.prepare()
  }

  async play(): Promise<void> {
    await this.player?.play()
  }

  async pause(): Promise<void> {
    await this.player?.pause()
  }

  async stop(): Promise<void> {
    await this.player?.stop()
  }

  async seek(timeMs: number): Promise<void> {
    await this.player?.seek(timeMs)
  }

  setVolume(volume: number): void {
    this.player?.setVolume(volume)
  }

  getDuration(): number {
    return this.player?.duration || 0
  }

  getCurrentTime(): number {
    return this.player?.currentTime || 0
  }

  async releasePlayer(): Promise<void> {
    if (this.player) {
      await this.player.release()
      this.player = null
    }
  }

  // ========== 音频录制 ==========
  async initRecorder(outputPath: string): Promise<void> {
    if (this.recorder) {
      await this.releaseRecorder()
    }

    this.recorder = await media.createAVRecorder()
    const config: media.AVRecorderConfig = {
      audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
      profile: media.AVRecorderProfileType.RECORDER_PROFILE_H264_AAC_MP4,
      url: `fd://${outputPath}`,
      rotation: 0
    }
    await this.recorder.prepare(config)
  }

  async startRecording(): Promise<void> {
    await this.recorder?.start()
  }

  async stopRecording(): Promise<void> {
    await this.recorder?.stop()
  }

  async releaseRecorder(): Promise<void> {
    if (this.recorder) {
      await this.recorder.release()
      this.recorder = null
    }
  }
}

export const multimediaManager = MultimediaManager.getInstance()

最佳实践 ​

  1. 权限申请:相机、麦克风等敏感权限需动态申请,使用前先检查权限状态
  2. 资源释放:播放器、录制器使用完毕后及时调用 release() 释放资源
  3. 错误处理:多媒体操作可能失败,使用 try/catch 处理异常
  4. 后台限制:应用在后台时音频播放可能受限,需申请后台播放权限
  5. 格式兼容:选择设备普遍支持的音视频格式(如 MP4、MP3)
  6. 性能优化:大图片加载前进行压缩,避免内存溢出
  7. 用户体验:录制/播放时提供明确的开始/停止/暂停控制

参考链接 ​