鸿蒙Media Kit媒体服务开发快速指南

80 阅读7分钟

一、Media Kit概述

Media Kit是HarmonyOS提供的媒体服务框架,提供音视频播放、录制、编解码等全方位的媒体处理能力。

1.1 核心能力

能力类型说明主要API
音频播放播放各种格式音频文件AVPlayer, SoundPool, AudioRenderer
视频播放播放各种格式视频文件AVPlayer, Video组件
音频录制录制音频AVRecorder, AudioCapturer
视频录制录制视频AVRecorder
音视频解码解码音视频数据AVDemuxer, AVDecoder
音视频编码编码音视频数据AVMuxer, AVEncoder

1.2 选择合适的API

音频播放场景:

graph TB
    A[选择音频播放API] --> B{场景判断}
    B -->|简短音效<5s| C[SoundPool]
    B -->|完整音频文件| D[AVPlayer]
    B -->|PCM数据流| E[AudioRenderer]
    B -->|音振协同| F[AudioHaptic]

    C --> G[低时延播放]
    D --> H[支持多种格式]
    E --> I[需要预处理]
    F --> J[铃声/反馈]

视频播放场景:

  • AVPlayer: 功能完善,支持多种格式,适合专业视频播放
  • Video组件: UI组件封装,简单易用,适合快速开发

二、音频播放开发

2.1 使用AVPlayer播放音频

AVPlayer适用于播放完整的音频文件,支持mp3、m4a、flac等格式。

2.1.1 AVPlayer状态机

stateDiagram-v2
    [*] --> idle: createAVPlayer()
    idle --> initialized: url设置
    initialized --> prepared: prepare()
    prepared --> playing: play()
    playing --> paused: pause()
    paused --> playing: play()
    playing --> stopped: stop()
    stopped --> prepared: prepare()
    prepared --> released: release()
    playing --> released: release()
    released --> [*]

2.1.2 完整示例

import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

class AudioPlayer {
  private avPlayer: media.AVPlayer | undefined = undefined;

  async init() {
    // 1. 创建AVPlayer实例
    this.avPlayer = await media.createAVPlayer();

    // 2. 设置状态变化监听
    this.avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => {
      console.info(`AVPlayer state changed to: ${state}, reason: ${reason}`);
    });

    // 3. 设置错误监听
    this.avPlayer.on('error', (error: BusinessError) => {
      console.error(`AVPlayer error: ${error.code}, ${error.message}`);
    });

    // 4. 监听播放进度
    this.avPlayer.on('timeUpdate', (time: number) => {
      console.info(`Current time: ${time}ms`);
    });

    // 5. 监听播放完成
    this.avPlayer.on('durationUpdate', (duration: number) => {
      console.info(`Total duration: ${duration}ms`);
    });
  }

  async playAudio(url: string) {
    if (!this.avPlayer) {
      return;
    }

    try {
      // 设置音频资源
      this.avPlayer.url = url;
      // 准备播放
      await this.avPlayer.prepare();
      // 开始播放
      await this.avPlayer.play();
    } catch (err) {
      let error = err as BusinessError;
      console.error(`Play failed: ${error.code}, ${error.message}`);
    }
  }

  async pause() {
    if (this.avPlayer && this.avPlayer.state === 'playing') {
      await this.avPlayer.pause();
    }
  }

  async resume() {
    if (this.avPlayer && this.avPlayer.state === 'paused') {
      await this.avPlayer.play();
    }
  }

  async seek(time: number) {
    if (this.avPlayer) {
      await this.avPlayer.seek(time, media.SeekMode.SEEK_PREV_SYNC);
    }
  }

  async stop() {
    if (this.avPlayer) {
      await this.avPlayer.stop();
    }
  }

  async release() {
    if (this.avPlayer) {
      await this.avPlayer.release();
      this.avPlayer = undefined;
    }
  }
}

// 使用示例
let player = new AudioPlayer();
await player.init();
await player.playAudio('https://example.com/audio.mp3');

2.2 使用SoundPool播放短音

SoundPool适用于播放低时延的短音效,如按键音、提示音等。

2.2.1 工作流程

sequenceDiagram
    participant App as 应用
    participant SP as SoundPool
    participant Decoder as 解码器
    participant Audio as 音频系统

    App->>SP: createSoundPool()
    App->>SP: load(fd)
    SP->>Decoder: 解码音频
    Decoder-->>SP: 解码完成
    SP->>App: on('loadComplete')
    App->>SP: play(soundId)
    SP->>Audio: 播放音频
    Audio-->>SP: 播放完成
    SP->>App: on('playFinished')

2.2.2 代码示例

import { media } from '@kit.MediaKit';
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

class SoundEffectPlayer {
  private soundPool: media.SoundPool | undefined = undefined;
  private soundId: number = 0;
  private streamId: number = 0;

  async init() {
    // 1. 配置音频渲染信息
    let audioRendererInfo: audio.AudioRendererInfo = {
      usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
      rendererFlags: 1
    };

    // 2. 创建SoundPool实例,最大支持14个并发流
    this.soundPool = await media.createSoundPool(14, audioRendererInfo);

    // 3. 监听资源加载完成
    this.soundPool.on('loadComplete', (soundId: number) => {
      this.soundId = soundId;
      console.info(`Sound loaded: ${soundId}`);
    });

    // 4. 监听播放完成
    this.soundPool.on('playFinishedWithStreamId', (streamId: number) => {
      console.info(`Play finished: ${streamId}`);
    });

    // 5. 监听错误
    this.soundPool.on('error', (error: BusinessError) => {
      console.error(`SoundPool error: ${error.code}`);
    });
  }

  async loadSound(fd: number, offset: number, length: number) {
    if (this.soundPool) {
      this.soundId = await this.soundPool.load(fd, offset, length);
    }
  }

  async playSound() {
    if (!this.soundPool) {
      return;
    }

    let playParameters: media.PlayParameters = {
      loop: 0,      // 不循环
      rate: 1,      // 正常速度
      leftVolume: 1.0,
      rightVolume: 1.0,
      priority: 0
    };

    this.soundPool.play(this.soundId, playParameters, (error, streamId: number) => {
      if (error) {
        console.error(`Play error: ${error.code}`);
      } else {
        this.streamId = streamId;
        console.info(`Playing stream: ${streamId}`);
      }
    });
  }

  async release() {
    if (this.soundPool) {
      await this.soundPool.off('loadComplete');
      await this.soundPool.off('playFinishedWithStreamId');
      await this.soundPool.off('error');
      await this.soundPool.release();
      this.soundPool = undefined;
    }
  }
}

// 使用示例
let soundPlayer = new SoundEffectPlayer();
await soundPlayer.init();

// 从rawfile加载音频
let context = getContext(this);
let fileDescriptor = await context.resourceManager.getRawFd('click.ogg');
await soundPlayer.loadSound(fileDescriptor.fd, fileDescriptor.offset, fileDescriptor.length);
await soundPlayer.playSound();

三、视频播放开发

3.1 AVPlayer视频播放

视频播放在音频播放基础上增加了视窗显示功能。

3.1.1 关键步骤

sequenceDiagram
    participant App as 应用
    participant AVP as AVPlayer
    participant XComp as XComponent
    participant Video as 视频系统

    App->>AVP: createAVPlayer()
    App->>AVP: 设置监听
    App->>AVP: url = videoUrl
    App->>XComp: 获取surfaceId
    XComp-->>App: surfaceId
    App->>AVP: surfaceId = id
    App->>AVP: prepare()
    AVP->>Video: 初始化视频管道
    App->>AVP: play()
    AVP->>Video: 渲染视频帧
    Video-->>XComp: 显示画面

3.1.2 完整代码示例

import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct VideoPlayer {
  private avPlayer: media.AVPlayer | undefined = undefined;
  private surfaceId: string = '';
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;

  async aboutToAppear() {
    await this.initPlayer();
  }

  async initPlayer() {
    // 1. 创建AVPlayer
    this.avPlayer = await media.createAVPlayer();

    // 2. 设置状态监听
    this.avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => {
      console.info(`State: ${state}`);
      if (state === 'playing') {
        this.isPlaying = true;
      } else if (state === 'paused' || state === 'stopped') {
        this.isPlaying = false;
      }
    });

    // 3. 设置错误监听
    this.avPlayer.on('error', (error: BusinessError) => {
      console.error(`Error: ${error.code}`);
    });

    // 4. 监听时长
    this.avPlayer.on('durationUpdate', (duration: number) => {
      this.duration = duration;
    });

    // 5. 监听进度
    this.avPlayer.on('timeUpdate', (time: number) => {
      this.currentTime = time;
    });

    // 6. 监听视频尺寸变化
    this.avPlayer.on('videoSizeChange', (width: number, height: number) => {
      console.info(`Video size: ${width}x${height}`);
    });

    // 7. 监听首帧渲染
    this.avPlayer.on('startRenderFrame', () => {
      console.info('First frame rendered');
    });
  }

  async playVideo(url: string) {
    if (!this.avPlayer || !this.surfaceId) {
      return;
    }

    try {
      // 设置视频URL
      this.avPlayer.url = url;
      // 设置窗口
      this.avPlayer.surfaceId = this.surfaceId;
      // 准备播放
      await this.avPlayer.prepare();
      // 开始播放
      await this.avPlayer.play();
    } catch (err) {
      let error = err as BusinessError;
      console.error(`Play failed: ${error.message}`);
    }
  }

  async pauseVideo() {
    if (this.avPlayer && this.isPlaying) {
      await this.avPlayer.pause();
    }
  }

  async resumeVideo() {
    if (this.avPlayer && !this.isPlaying) {
      await this.avPlayer.play();
    }
  }

  async seekTo(time: number) {
    if (this.avPlayer) {
      await this.avPlayer.seek(time);
    }
  }

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

  build() {
    Column() {
      // 视频显示区域
      XComponent({
        id: 'video_player',
        type: XComponentType.SURFACE,
        controller: new XComponentController()
      })
        .onLoad((context) => {
          // 获取surfaceId
          this.surfaceId = context.getXComponentSurfaceId();
          // 播放视频
          this.playVideo('https://example.com/video.mp4');
        })
        .width('100%')
        .height(300)

      // 播放控制区域
      Row({ space: 20 }) {
        // 播放/暂停按钮
        Button(this.isPlaying ? '暂停' : '播放')
          .onClick(() => {
            if (this.isPlaying) {
              this.pauseVideo();
            } else {
              this.resumeVideo();
            }
          })

        // 进度显示
        Text(`${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}`)
          .fontSize(14)
      }
      .width('90%')
      .margin({ top: 20 })

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

  aboutToDisappear() {
    if (this.avPlayer) {
      this.avPlayer.release();
    }
  }
}

四、音频录制开发

4.1 使用AudioCapturer录制音频

AudioCapturer用于采集PCM音频数据。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

class AudioRecorder {
  private audioCapturer: audio.AudioCapturer | undefined = undefined;
  private audioFile: fs.File | undefined = undefined;

  async init() {
    // 1. 配置音频采集参数
    let audioStreamInfo: audio.AudioStreamInfo = {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
      channels: audio.AudioChannel.CHANNEL_2,
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
    };

    let audioCapturerInfo: audio.AudioCapturerInfo = {
      source: audio.SourceType.SOURCE_TYPE_MIC,
      capturerFlags: 0
    };

    let audioCapturerOptions: audio.AudioCapturerOptions = {
      streamInfo: audioStreamInfo,
      capturerInfo: audioCapturerInfo
    };

    // 2. 创建AudioCapturer
    this.audioCapturer = await audio.createAudioCapturer(audioCapturerOptions);

    // 3. 监听状态变化
    this.audioCapturer.on('stateChange', (state: audio.AudioState) => {
      console.info(`AudioCapturer state: ${state}`);
    });
  }

  async startRecording(filePath: string) {
    if (!this.audioCapturer) {
      return;
    }

    try {
      // 创建文件
      this.audioFile = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);

      // 启动录制
      await this.audioCapturer.start();

      // 读取音频数据并写入文件
      let bufferSize = await this.audioCapturer.getBufferSize();
      let buffer = await this.audioCapturer.read(bufferSize, true);
      fs.writeSync(this.audioFile.fd, buffer);
    } catch (err) {
      let error = err as BusinessError;
      console.error(`Recording failed: ${error.message}`);
    }
  }

  async stopRecording() {
    if (this.audioCapturer) {
      await this.audioCapturer.stop();
      await this.audioCapturer.release();
    }
    if (this.audioFile) {
      fs.closeSync(this.audioFile);
    }
  }
}

五、视频录制开发

5.1 使用AVRecorder录制视频

AVRecorder集成了音视频采集、编码、封装功能。

5.1.1 录制状态机

stateDiagram-v2
    [*] --> idle: createAVRecorder()
    idle --> prepared: prepare()
    prepared --> started: start()
    started --> paused: pause()
    paused --> started: resume()
    started --> stopped: stop()
    stopped --> prepared: prepare()
    prepared --> released: release()
    released --> [*]

5.1.2 代码示例

import { media } from '@kit.MediaKit';
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

class VideoRecorder {
  private avRecorder: media.AVRecorder | undefined = undefined;
  private videoFile: fs.File | undefined = undefined;

  async init() {
    // 1. 创建AVRecorder
    this.avRecorder = await media.createAVRecorder();

    // 2. 设置状态监听
    this.avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
      console.info(`AVRecorder state: ${state}, reason: ${reason}`);
    });

    // 3. 设置错误监听
    this.avRecorder.on('error', (error: BusinessError) => {
      console.error(`AVRecorder error: ${error.code}`);
    });
  }

  async startRecording(context: Context) {
    if (!this.avRecorder) {
      return;
    }

    try {
      // 1. 创建录制文件
      let filePath = context.filesDir + '/video.mp4';
      this.videoFile = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);

      // 2. 配置录制参数
      let avProfile: media.AVRecorderProfile = {
        fileFormat: media.ContainerFormatType.CFT_MPEG_4,
        videoBitrate: 2000000,
        videoCodec: media.CodecMimeType.VIDEO_AVC,
        videoFrameWidth: 1920,
        videoFrameHeight: 1080,
        videoFrameRate: 30
      };

      let avConfig: media.AVRecorderConfig = {
        videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
        profile: avProfile,
        url: 'fd://' + this.videoFile.fd,
        metadata: {
          videoOrientation: '0'
        }
      };

      // 3. 准备录制
      await this.avRecorder.prepare(avConfig);

      // 4. 获取Surface用于相机输入
      let surfaceId = await this.avRecorder.getInputSurface();

      // 5. 初始化相机(省略相机初始化代码)
      // await this.initCamera(surfaceId);

      // 6. 开始录制
      await this.avRecorder.start();

    } catch (err) {
      let error = err as BusinessError;
      console.error(`Start recording failed: ${error.message}`);
    }
  }

  async pauseRecording() {
    if (this.avRecorder && this.avRecorder.state === 'started') {
      await this.avRecorder.pause();
    }
  }

  async resumeRecording() {
    if (this.avRecorder && this.avRecorder.state === 'paused') {
      await this.avRecorder.resume();
    }
  }

  async stopRecording() {
    if (this.avRecorder) {
      await this.avRecorder.stop();
      await this.avRecorder.release();
    }
    if (this.videoFile) {
      fs.closeSync(this.videoFile);
    }
  }
}

六、实战示例:多媒体播放器应用

6.1 应用架构

MediaPlayerApp/
├── entry/
│   └── src/main/
│       ├── ets/
│       │   ├── entryability/
│       │   │   └── EntryAbility.ets
│       │   ├── pages/
│       │   │   ├── Index.ets          (首页)
│       │   │   ├── AudioPlayer.ets    (音频播放器)
│       │   │   └── VideoPlayer.ets    (视频播放器)
│       │   └── model/
│       │       ├── MediaManager.ets   (媒体管理器)
│       │       └── PlaylistModel.ets  (播放列表)
│       └── resources/
│           └── rawfile/
│               ├── audio/
│               └── video/

6.2 媒体管理器实现

import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';

export interface MediaItem {
  id: string;
  title: string;
  url: string;
  duration: number;
  type: 'audio' | 'video';
}

export class MediaManager {
  private avPlayer: media.AVPlayer | undefined = undefined;
  private currentMedia: MediaItem | null = null;
  private onStateChange?: (state: string) => void;
  private onTimeUpdate?: (time: number) => void;
  private onDurationUpdate?: (duration: number) => void;

  async init() {
    this.avPlayer = await media.createAVPlayer();

    // 状态变化监听
    this.avPlayer.on('stateChange', (state: string, reason: media.StateChangeReason) => {
      console.info(`State changed to: ${state}`);
      if (this.onStateChange) {
        this.onStateChange(state);
      }
    });

    // 错误监听
    this.avPlayer.on('error', (error: BusinessError) => {
      console.error(`Player error: ${error.code}, ${error.message}`);
    });

    // 时长更新
    this.avPlayer.on('durationUpdate', (duration: number) => {
      if (this.onDurationUpdate) {
        this.onDurationUpdate(duration);
      }
    });

    // 进度更新
    this.avPlayer.on('timeUpdate', (time: number) => {
      if (this.onTimeUpdate) {
        this.onTimeUpdate(time);
      }
    });
  }

  setStateChangeListener(callback: (state: string) => void) {
    this.onStateChange = callback;
  }

  setTimeUpdateListener(callback: (time: number) => void) {
    this.onTimeUpdate = callback;
  }

  setDurationUpdateListener(callback: (duration: number) => void) {
    this.onDurationUpdate = callback;
  }

  async playMedia(media: MediaItem, surfaceId?: string) {
    if (!this.avPlayer) {
      return;
    }

    try {
      this.currentMedia = media;
      this.avPlayer.url = media.url;

      if (media.type === 'video' && surfaceId) {
        this.avPlayer.surfaceId = surfaceId;
      }

      await this.avPlayer.prepare();
      await this.avPlayer.play();
    } catch (err) {
      let error = err as BusinessError;
      console.error(`Play media failed: ${error.message}`);
    }
  }

  async pause() {
    if (this.avPlayer && this.avPlayer.state === 'playing') {
      await this.avPlayer.pause();
    }
  }

  async resume() {
    if (this.avPlayer && this.avPlayer.state === 'paused') {
      await this.avPlayer.play();
    }
  }

  async seek(time: number) {
    if (this.avPlayer) {
      await this.avPlayer.seek(time, media.SeekMode.SEEK_PREV_SYNC);
    }
  }

  async stop() {
    if (this.avPlayer) {
      await this.avPlayer.stop();
      this.currentMedia = null;
    }
  }

  async release() {
    if (this.avPlayer) {
      await this.avPlayer.release();
      this.avPlayer = undefined;
    }
  }

  getCurrentMedia(): MediaItem | null {
    return this.currentMedia;
  }

  getState(): string {
    return this.avPlayer?.state || 'idle';
  }
}

6.3 音频播放器页面

import { MediaManager, MediaItem } from '../model/MediaManager';

@Entry
@Component
struct AudioPlayerPage {
  @State playlist: MediaItem[] = [];
  @State currentIndex: number = 0;
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;
  private mediaManager: MediaManager = new MediaManager();

  async aboutToAppear() {
    // 初始化媒体管理器
    await this.mediaManager.init();

    // 设置监听回调
    this.mediaManager.setStateChangeListener((state: string) => {
      this.isPlaying = (state === 'playing');
    });

    this.mediaManager.setTimeUpdateListener((time: number) => {
      this.currentTime = time;
    });

    this.mediaManager.setDurationUpdateListener((duration: number) => {
      this.duration = duration;
    });

    // 加载播放列表
    this.loadPlaylist();
  }

  loadPlaylist() {
    this.playlist = [
      {
        id: '1',
        title: '音乐1',
        url: 'https://example.com/music1.mp3',
        duration: 0,
        type: 'audio'
      },
      {
        id: '2',
        title: '音乐2',
        url: 'https://example.com/music2.mp3',
        duration: 0,
        type: 'audio'
      }
    ];
  }

  async playAudio(index: number) {
    if (index >= 0 && index < this.playlist.length) {
      this.currentIndex = index;
      await this.mediaManager.playMedia(this.playlist[index]);
    }
  }

  async togglePlayPause() {
    if (this.isPlaying) {
      await this.mediaManager.pause();
    } else {
      await this.mediaManager.resume();
    }
  }

  async playNext() {
    let nextIndex = (this.currentIndex + 1) % this.playlist.length;
    await this.playAudio(nextIndex);
  }

  async playPrevious() {
    let prevIndex = (this.currentIndex - 1 + this.playlist.length) % this.playlist.length;
    await this.playAudio(prevIndex);
  }

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

  build() {
    Column() {
      // 顶部导航
      Row() {
        Text('音频播放器')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#f0f0f0')

      // 播放列表
      List({ space: 10 }) {
        ForEach(this.playlist, (item: MediaItem, index: number) => {
          ListItem() {
            Row() {
              Text(item.title)
                .fontSize(16)
                .layoutWeight(1)
                .fontColor(index === this.currentIndex ? '#007DFF' : '#000')

              if (index === this.currentIndex) {
                Text(this.isPlaying ? '播放中' : '已暂停')
                  .fontSize(12)
                  .fontColor('#666')
              }
            }
            .width('100%')
            .padding(15)
            .backgroundColor(index === this.currentIndex ? '#e6f2ff' : '#fff')
            .borderRadius(8)
          }
          .onClick(() => {
            this.playAudio(index);
          })
        }, (item: MediaItem) => item.id)
      }
      .width('90%')
      .layoutWeight(1)
      .margin({ top: 20 })

      // 播放控制区域
      Column({ space: 20 }) {
        // 当前播放信息
        Text(this.currentIndex >= 0 ? this.playlist[this.currentIndex].title : '未选择')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)

        // 进度条
        Column({ space: 10 }) {
          Slider({
            value: this.currentTime,
            min: 0,
            max: this.duration,
            step: 1000
          })
            .width('100%')
            .onChange((value: number) => {
              this.mediaManager.seek(value);
            })

          Row() {
            Text(this.formatTime(this.currentTime))
              .fontSize(12)
            Blank()
            Text(this.formatTime(this.duration))
              .fontSize(12)
          }
          .width('100%')
        }
        .width('100%')

        // 控制按钮
        Row({ space: 30 }) {
          Button('上一首')
            .onClick(() => this.playPrevious())

          Button(this.isPlaying ? '暂停' : '播放')
            .onClick(() => this.togglePlayPause())
            .width(80)

          Button('下一首')
            .onClick(() => this.playNext())
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)
      }
      .width('90%')
      .padding(20)
      .backgroundColor('#f8f8f8')
      .borderRadius(10)
      .margin({ bottom: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#fff')
  }

  aboutToDisappear() {
    this.mediaManager.release();
  }
}

七、后台播放与媒体会话

7.1 实现后台播放

要实现后台播放或熄屏播放,需要:

  1. 注册AVSession(媒体会话)
  2. 申请长时任务
import { AVSessionManager } from '@kit.AVSessionKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';

class BackgroundAudioPlayer {
  private session: AVSessionManager.AVSession | undefined = undefined;

  async setupBackgroundPlayback() {
    try {
      // 1. 创建媒体会话
      this.session = await AVSessionManager.createAVSession(
        getContext(this),
        'audio',
        'com.example.mediaplayer'
      );

      // 2. 激活会话
      await this.session.activate();

      // 3. 设置会话元数据
      let metadata: AVSessionManager.AVMetadata = {
        assetId: '001',
        title: '歌曲标题',
        artist: '艺术家',
        album: '专辑名称',
        duration: 240000
      };
      await this.session.setAVMetadata(metadata);

      // 4. 设置播放状态
      let playbackState: AVSessionManager.AVPlaybackState = {
        state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY,
        speed: 1.0,
        position: { elapsedTime: 0, updateTime: Date.now() }
      };
      await this.session.setAVPlaybackState(playbackState);

      // 5. 申请长时任务
      let bgMode = backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK;
      backgroundTaskManager.startBackgroundRunning(getContext(this), bgMode);

    } catch (err) {
      let error = err as BusinessError;
      console.error(`Setup background playback failed: ${error.message}`);
    }
  }

  async stopBackgroundPlayback() {
    // 停止长时任务
    backgroundTaskManager.stopBackgroundRunning(getContext(this));

    // 销毁会话
    if (this.session) {
      await this.session.deactivate();
      await this.session.destroy();
    }
  }
}

八、最佳实践

8.1 性能优化

1. 资源预加载

class MediaResourceManager {
  private soundPool: media.SoundPool | undefined = undefined;
  private soundCache: Map<string, number> = new Map();

  async preloadSounds(sounds: string[]) {
    for (let sound of sounds) {
      let fd = await this.getSoundFd(sound);
      let soundId = await this.soundPool!.load(fd.fd, fd.offset, fd.length);
      this.soundCache.set(sound, soundId);
    }
  }

  async playPreloadedSound(soundName: string) {
    let soundId = this.soundCache.get(soundName);
    if (soundId) {
      await this.soundPool!.play(soundId, {} as media.PlayParameters, () => {});
    }
  }

  private async getSoundFd(name: string) {
    let context = getContext(this);
    return await context.resourceManager.getRawFd(name);
  }
}

2. 内存管理

class PlayerLifecycleManager {
  private player: media.AVPlayer | undefined = undefined;

  async onPageShow() {
    // 页面显示时创建播放器
    if (!this.player) {
      this.player = await media.createAVPlayer();
    }
  }

  async onPageHide() {
    // 页面隐藏时释放播放器资源
    if (this.player) {
      await this.player.stop();
      await this.player.release();
      this.player = undefined;
    }
  }
}

8.2 错误处理

class RobustMediaPlayer {
  private avPlayer: media.AVPlayer | undefined = undefined;
  private retryCount: number = 0;
  private maxRetries: number = 3;

  async playWithRetry(url: string) {
    try {
      await this.play(url);
      this.retryCount = 0;
    } catch (err) {
      let error = err as BusinessError;
      console.error(`Play failed: ${error.code}`);

      if (this.retryCount < this.maxRetries) {
        this.retryCount++;
        console.info(`Retry ${this.retryCount}/${this.maxRetries}`);
        setTimeout(() => {
          this.playWithRetry(url);
        }, 1000 * this.retryCount);
      } else {
        console.error('Max retries reached');
        // 通知用户播放失败
      }
    }
  }

  private async play(url: string) {
    if (!this.avPlayer) {
      this.avPlayer = await media.createAVPlayer();
    }
    this.avPlayer.url = url;
    await this.avPlayer.prepare();
    await this.avPlayer.play();
  }
}

8.3 音频焦点管理

import { audio } from '@kit.AudioKit';

class AudioFocusManager {
  private avPlayer: media.AVPlayer | undefined = undefined;

  async setupAudioFocus() {
    if (!this.avPlayer) {
      return;
    }

    // 设置音频中断模式
    this.avPlayer.audioInterruptMode = audio.InterruptMode.INDEPENDENT_MODE;

    // 监听音频焦点变化
    this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
      console.info(`Audio interrupt: ${info.hintType}`);

      switch (info.hintType) {
        case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
          // 暂停播放
          this.avPlayer?.pause();
          break;
        case audio.InterruptHint.INTERRUPT_HINT_RESUME:
          // 恢复播放
          this.avPlayer?.play();
          break;
        case audio.InterruptHint.INTERRUPT_HINT_STOP:
          // 停止播放
          this.avPlayer?.stop();
          break;
        case audio.InterruptHint.INTERRUPT_HINT_DUCK:
          // 降低音量
          this.avPlayer?.setVolume(0.3);
          break;
        case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
          // 恢复音量
          this.avPlayer?.setVolume(1.0);
          break;
      }
    });
  }
}

九、支持的格式

9.1 音频格式

格式编解码器封装格式
AACAAC-LC, HE-AACMP4, M4A, AAC
MP3MP3MP3
FLACFLACFLAC
VorbisVorbisOGG
OpusOpusOGG

9.2 视频格式

格式视频编解码器音频编解码器封装格式
H.264AVCAAC, MP3MP4, MKV
H.265HEVCAAC, MP3MP4, MKV

十、总结

本文全面介绍了HarmonyOS Media Kit的核心能力:

功能模块主要API适用场景
音频播放AVPlayer播放完整音频文件
短音播放SoundPool低时延音效播放
视频播放AVPlayer播放视频文件
音频录制AudioCapturer, AVRecorder录制音频
视频录制AVRecorder + Camera录制视频
后台播放AVSession + 长时任务后台音乐播放

开发要点:

  1. ✅ 根据场景选择合适的API
  2. ✅ 正确处理播放器状态转换
  3. ✅ 做好错误处理和重试机制
  4. ✅ 及时释放资源避免内存泄漏
  5. ✅ 后台播放需注册AVSession
  6. ✅ 正确处理音频焦点事件

通过本文学习,您应该能够:

  • 熟练使用AVPlayer播放音视频
  • 使用SoundPool实现低时延音效
  • 实现音视频录制功能
  • 开发完整的多媒体播放器应用
  • 实现后台播放和媒体会话管理