一、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 实现后台播放
要实现后台播放或熄屏播放,需要:
- 注册AVSession(媒体会话)
- 申请长时任务
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 + 长时任务 | 后台音乐播放 |
开发要点:
- ✅ 根据场景选择合适的API
- ✅ 正确处理播放器状态转换
- ✅ 做好错误处理和重试机制
- ✅ 及时释放资源避免内存泄漏
- ✅ 后台播放需注册AVSession
- ✅ 正确处理音频焦点事件
通过本文学习,您应该能够:
- 熟练使用AVPlayer播放音视频
- 使用SoundPool实现低时延音效
- 实现音视频录制功能
- 开发完整的多媒体播放器应用
- 实现后台播放和媒体会话管理