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. 用户体验:录制/播放时提供明确的开始/停止/暂停控制

参考链接