前言
在移动应用开发中,媒体能力是核心功能之一。HarmonyOS 提供了完整的媒体框架,覆盖图片选择、音频播放、视频录制等场景。本文将从实战角度出发,带你掌握 HarmonyOS 的媒体能力。
一、图片选择与处理
1.1 使用 Picker 选择图片
HarmonyOS 提供了统一的 Picker API,支持从相册选择图片:
import {
picker } from '@kit.CoreFileKit';
import {
BusinessError } from '@kit.BasicServicesKit';
async function pickImage(): Promise<string> {
try {
const photoSelectOptions = new picker.PhotoSelectOptions();
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 1;
const photoViewPicker = new picker.PhotoViewPicker();
const result = await photoViewPicker.select(photoSelectOptions);
if (result && result.photoUris.length > 0) {
return result.photoUris[0];
}
return '';
} catch (err) {
console.error('选择图片失败:', JSON.stringify(err));
return '';
}
}
1.2 图片解码与显示
获取图片 URI 后,使用 Image 组件显示:
import {
image } from '@kit.ImageKit';
@Entry
@Component
struct ImageDemo {
@State imageUri: string = '';
build() {
Column() {
Button('选择图片')
.onClick(async () => {
this.imageUri = await pickImage();
})
if (this.imageUri) {
Image(this.imageUri)
.width('100%')
.height(300)
.objectFit(ImageFit.Contain)
}
}
.padding(20)
}
}
1.3 图片压缩与保存
处理大图时需要压缩:
import {
image } from '@kit.ImageKit';
import {
fileIo } from '@kit.CoreFileKit';
async function compressImage(sourceUri: string, targetPath: string): Promise<void> {
try {
const imageSource = image.createImageSource(sourceUri);
const imageInfo = await imageSource.getImageInfo();
console.info(`原始尺寸: ${
imageInfo.size.width}x${
imageInfo.size.height}`);
const decodingOptions: image.DecodingOptions = {
desiredSize: {
width: 800, height: 800 },
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
const imagePacker = image.createImagePacker();
const packOpts: image.PackingOption = {
format: 'image/jpeg',
quality: 80
};
const buffer = await imagePacker.packing(pixelMap, packOpts);
const file = fileIo.openSync(targetPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
fileIo.writeSync(file.fd, buffer);
fileIo.closeSync(file);
console.info('图片压缩完成');
} catch (err) {
console.error('压缩失败:', JSON.stringify(err));
}
}
二、音频播放与录制
2.1 音频播放
使用 AVPlayer 播放音频:
import {
media } from '@kit.MediaKit';
@Component
export struct AudioPlayer {
private avPlayer?: media.AVPlayer;
@State isPlaying: boolean = false;
@State currentTime: number = 0;
@State duration: number = 0;
async initPlayer(audioUri: string) {
try {
this.avPlayer = await media.createAVPlayer();
this.avPlayer.on('stateChange', (state: string) => {
console.info(`播放器状态: ${
state}`);
});
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time;
});
this.avPlayer.on('durationUpdate', (duration: number) => {
this.duration = duration;
});
this.avPlayer.url = audioUri;
} catch (err) {
console.error('初始化播放器失败:', JSON.stringify(err));
}
}
async play() {
await this.avPlayer?.play();
this.isPlaying = true;
}
async pause() {
await this.avPlayer?.pause();
this.isPlaying = false;
}
build() {
Column() {
Text(`${
this.formatTime(this.currentTime)} / ${
this.formatTime(this.duration)}`)
Row() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => {
if (this.isPlaying) {
this.pause();
} else {
this.play();
}
})
}
}
}
formatTime(ms: number): string {
const seconds = Math.floor(ms / 1000);
const min = Math.floor(seconds / 60);
const sec = seconds % 60;
return `${
min}:${
sec.toString().padStart(2, '0')}`;
}
aboutToDisappear() {
this.avPlayer?.release();
}
}
2.2 音频录制
使用 AVRecorder 录制音频:
import {
media } from '@kit.MediaKit';
import {
fileIo } from '@kit.CoreFileKit';
@Component
export struct AudioRecorder {
private avRecorder?: media.AVRecorder;
@State isRecording: boolean = false;
private outputPath: string = '';
async initRecorder() {
try {
this.avRecorder = await media.createAVRecorder();
this.avRecorder.on('stateChange', (state: string) => {
console.info(`录制器状态: ${
state}`);
});
const context = getContext(this);
this.outputPath = `${
context.cacheDir}/audio_${
Date.now()}.m4a`;
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: {
audioBitrate: 128000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4A
},
url: `fd://${
fileIo.openSync(this.outputPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY).fd}`
};
await this.avRecorder.prepare(config);
} catch (err) {
console.error('初始化录制器失败:', JSON.stringify(err));
}
}
async startRecord() {
await this.avRecorder?.start();
this.isRecording = true;
}
async stopRecord() {
await this.avRecorder?.stop();
this.isRecording = false;
console.info('录音已保存:', this.outputPath);
}
build() {
Column() {
Button(this.isRecording ? '停止录音' : '开始录音')
.onClick(() => {
if (this.isRecording) {
this.stopRecord();
} else {
this.startRecord();
}
})
}
}
aboutToDisappear() {
this.avRecorder?.release();
}
}
三、视频播放与录制
3.1 视频播放
使用 AVPlayer + XComponent 播放视频:
import {
media } from '@kit.MediaKit';
@Entry
@Component
struct VideoPlayer {
private avPlayer?: media.AVPlayer;
private surfaceId: string = '';
@State isPlaying: boolean = false;
async initPlayer(videoUri: string) {
try {
this.avPlayer = await media.createAVPlayer();
this.avPlayer.on('stateChange', (state: string) => {
console.info(`播放器状态: ${
state}`);
});
this.avPlayer.url = videoUri;
this.avPlayer.surfaceId = this.surfaceId;
} catch (err) {
console.error('初始化播放器失败:', JSON.stringify(err));
}
}
build() {
Column() {
XComponent({
id: 'video_surface',
type: XComponentType.SURFACE,
controller: new XComponentController()
})
.onLoad((context?: object) => {
this.surfaceId = (context as {
surfaceId: string }).surfaceId;
this.initPlayer('file://...');
})
.width('100%')
.height(300)
Button(this.isPlaying ? '暂停' : '播放')
.onClick(async () => {
if (this.isPlaying) {
await this.avPlayer?.pause();
} else {
await this.avPlayer?.play();
}
this.isPlaying = !this.isPlaying;
})
}
}
}
3.2 视频录制
使用相机 API 录制视频:
import {
camera } from '@kit.CameraKit';
@Component
export struct VideoRecorder {
private cameraManager?: camera.CameraManager;
private videoOutput?: camera.VideoOutput;
@State isRecording: boolean = false;
async initCamera() {
try {
this.cameraManager = camera.getCameraManager(getContext(this));
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error('没有可用相机');
return;
}
const cameraInput = this.cameraManager.createCameraInput(cameras[0]);
await cameraInput.open();
const profile: camera.VideoProfile = {
format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP,
size: {
width: 1920, height: 1080 },
frameRateRange: {
min: 30, max: 30 }
};
this.videoOutput = this.cameraManager.createVideoOutput(profile, 'fd://...');
const session = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO);
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(this.videoOutput);
await session.commitConfig();
await session.start();
console.info('相机初始化完成');
} catch (err) {
console.error('初始化相机失败:', JSON.stringify(err));
}
}
async startRecord() {
await this.videoOutput?.start();
this.isRecording = true;
}
async stopRecord() {
await this.videoOutput?.stop();
this.isRecording = false;
}
build() {
Column() {
Button(this.isRecording ? '停止录制' : '开始录制')
.onClick(() => {
if (this.isRecording) {
this.stopRecord();
} else {
this.startRecord();
}
})
}
}
}
四、权限申请
媒体功能需要申请相应权限:
4.1 module.json5 配置
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:permission_read_media",
"usedScene": { "when": "inuse" }
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:permission_write_media",
"usedScene": { "when": "inuse" }
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:permission_microphone",
"usedScene": { "when": "inuse" }
},
{
"name": "ohos.permission.CAMERA",
"reason": "$string:permission_camera",
"usedScene": { "when": "inuse" }
}
]
}
}
4.2 运行时申请
import {
abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
async function requestPermissions(): Promise<boolean> {
const permissions: Permissions[] = [
'ohos.permission.READ_IMAGEVIDEO',
'ohos.permission.MICROPHONE',
'ohos.permission.CAMERA'
];
const context = getContext(this);
const atManager = abilityAccessCtrl.createAtManager();
try {
const result = await atManager.requestPermissionsFromUser(context, permissions);
return result.authResults.every(r => r === 0);
} catch (err) {
console.error('权限申请失败:', JSON.stringify(err));
return false;
}
}
五、实战案例:完整的媒体播放器
import {
media } from '@kit.MediaKit';
import {
picker } from '@kit.CoreFileKit';
@Entry
@Component
struct MediaPlayerDemo {
private avPlayer?: media.AVPlayer;
@State mediaUri: string = '';
@State isPlaying: boolean = false;
@State currentTime: number = 0;
@State duration: number = 0;
async selectMedia() {
try {
const options = new picker.PhotoSelectOptions();
options.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;
options.maxSelectNumber = 1;
const photoPicker = new picker.PhotoViewPicker();
const result = await photoPicker.select(options);
if (result.photoUris.length > 0) {
this.mediaUri = result.photoUris[0];
await this.initPlayer();
}
} catch (err) {
console.error('选择媒体失败:', JSON.stringify(err));
}
}
async initPlayer() {
this.avPlayer = await media.createAVPlayer();
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time;
});
this.avPlayer.on('durationUpdate', (duration: number) => {
this.duration = duration;
});
this.avPlayer.url = this.mediaUri;
}
build() {
Column() {
Text('HarmonyOS 媒体播放器')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('选择视频')
.onClick(() => this.selectMedia())
.margin({
top: 20 })
if (this.mediaUri) {
Text(`播放中: ${
this.mediaUri.split('/').pop()}`)
.margin({
top: 10 })
Row() {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(async () => {
if (this.isPlaying) {
await this.avPlayer?.pause();
} else {
await this.avPlayer?.play();
}
this.isPlaying = !this.isPlaying;
})
Button('停止')
.onClick(async () => {
await this.avPlayer?.stop();
this.isPlaying = false;
})
}
.margin({
top: 20 })
}
}
.width('100%')
.height('100%')
.padding(20)
}
aboutToDisappear() {
this.avPlayer?.release();
}
}
六、性能优化建议
6.1 图片加载优化
- 使用缩略图预览大图
- 异步解码避免主线程阻塞
- 实现图片缓存机制
6.2 音视频播放优化
- 预加载下一个媒体文件
- 实现播放进度保存与恢复
- 监听系统中断事件(来电等)
6.3 内存管理
- 及时释放 PixelMap 和 AVPlayer
- 使用弱引用避免内存泄漏
- 监控内存占用并主动回收
总结
本文系统介绍了 HarmonyOS 的媒体能力,涵盖:
- 图片处理 — Picker 选择、解码显示、压缩保存
- 音频能力 — AVPlayer 播放、AVRecorder 录制
- 视频能力 — 视频播放、相机录制
- 权限管理 — 静态声明与动态申请
- 实战案例 — 完整媒体播放器实现
- 性能优化 — 内存管理与加载优化
掌握这些能力后,你就可以开发功能完整的媒体类应用了。
本文基于 HarmonyOS NEXT(API 12+)编写,部分 API 可能随版本更新而变化。