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

一、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 完整示例

typescript 复制代码
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 代码示例

typescript 复制代码
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 完整代码示例

typescript 复制代码
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音频数据。

typescript 复制代码
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 代码示例

typescript 复制代码
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 应用架构

scss 复制代码
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 媒体管理器实现

typescript 复制代码
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 音频播放器页面

typescript 复制代码
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. 申请长时任务
typescript 复制代码
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. 资源预加载

typescript 复制代码
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. 内存管理

typescript 复制代码
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 错误处理

typescript 复制代码
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 音频焦点管理

typescript 复制代码
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 音频格式

格式 编解码器 封装格式
AAC AAC-LC, HE-AAC MP4, M4A, AAC
MP3 MP3 MP3
FLAC FLAC FLAC
Vorbis Vorbis OGG
Opus Opus OGG

9.2 视频格式

格式 视频编解码器 音频编解码器 封装格式
H.264 AVC AAC, MP3 MP4, MKV
H.265 HEVC AAC, MP3 MP4, MKV

十、总结

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

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

开发要点:

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

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

  • 熟练使用AVPlayer播放音视频
  • 使用SoundPool实现低时延音效
  • 实现音视频录制功能
  • 开发完整的多媒体播放器应用
  • 实现后台播放和媒体会话管理
相关推荐
马 孔 多 在下雨3 小时前
Android动画集大成之宗-MotionLayout基础指南
android
国霄3 小时前
(3)Kotlin/Js For Harmony——解决官方库序列化卡顿
harmonyos
用户413079810613 小时前
Android动画集大成之宗-MotionLayout
android
金鸿客3 小时前
在Compose中使用camerax进行拍照和录视频
android
光芒Shine3 小时前
【HarmonyOS-北向开发(软件)】
harmonyos
伟大的大威5 小时前
Android 端离线语音控制设备管理系统:完整技术方案与实践
android·macos·xcode
猫林老师5 小时前
Flutter for HarmonyOS开发指南(四):国际化与本地化深度实践
flutter·华为·harmonyos
骑驴看星星a8 小时前
【Three.js--manual script】4.光照
android·开发语言·javascript
猫林老师12 小时前
Flutter for HarmonyOS 开发指南(一):环境搭建与项目创建
flutter·华为·harmonyos