多媒体
HarmonyOS 提供丰富的多媒体能力,包括相机拍照/录像、音频播放/录制、视频播放、图片选择等功能,满足应用多媒体处理需求。
前置配置
module.json5 权限声明
根据使用场景声明相应权限:
json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:reason_camera",
"usedScene": {}
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:reason_microphone",
"usedScene": {}
},
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:reason_read_image",
"usedScene": {}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:reason_write_image",
"usedScene": {}
}
]
}
}| 权限 | 说明 | 是否动态申请 |
|---|---|---|
ohos.permission.CAMERA | 相机权限 | 是 |
ohos.permission.MICROPHONE | 麦克风权限 | 是 |
ohos.permission.READ_IMAGEVIDEO | 读取图片/视频 | 是 |
ohos.permission.WRITE_IMAGEVIDEO | 写入图片/视频 | 是 |
相机
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
camera.getCameraManager() | 获取相机管理器 | API 10 |
CameraManager.getSupportedCameras() | 获取支持的相机列表 | API 10 |
CameraManager.createCameraInput() | 创建相机输入 | API 10 |
CameraManager.createPreviewOutput() | 创建预览输出 | API 10 |
CameraManager.createPhotoOutput() | 创建拍照输出 | API 10 |
CameraManager.createVideoOutput() | 创建录像输出 | API 10 |
CameraInput.open() | 打开相机 | API 10 |
CameraSession.beginConfig() | 开始配置会话 | API 10 |
CameraSession.commitConfig() | 提交配置 | API 10 |
CameraSession.start() | 开始会话 | API 10 |
PhotoOutput.capture() | 拍照 | API 10 |
相机组件(推荐)
API 11+ 推荐使用系统相机组件,无需处理复杂的相机会话:
typescript
import { camera } from '@kit.MultimediaKit'
@Entry
@Component
struct CameraPage {
@State photoUri: string = ''
build() {
Column() {
Camera({
camera: {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
}
})
.width('100%')
.height('80%')
Row({ space: 20 }) {
Button('拍照')
.onClick(async () => {
// 调用系统相机拍照
const picker = new camera.CameraPicker()
const result = await picker.pick(getContext(), {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveType: camera.CameraPickerMediaType.PHOTO
})
this.photoUri = result.resultUri
})
Button('录像')
.onClick(async () => {
const picker = new camera.CameraPicker()
const result = await picker.pick(getContext(), {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveType: camera.CameraPickerMediaType.VIDEO
})
this.photoUri = result.resultUri
})
}
.padding(20)
if (this.photoUri) {
Image(this.photoUri)
.width(200)
.height(200)
.objectFit(ImageFit.Cover)
}
}
.width('100%')
.height('100%')
}
}使用系统相机选择器
最简单的拍照方式,无需处理权限和相机会话:
typescript
import { camera } from '@kit.MultimediaKit'
async function takePhoto(): Promise<string> {
try {
const picker = new camera.CameraPicker()
const result = await picker.pick(getContext(), {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveType: camera.CameraPickerMediaType.PHOTO
})
return result.resultUri
} catch (error) {
console.error('拍照失败:', JSON.stringify(error))
return ''
}
}图片选择
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
photoAccessHelper.getPhotoAccessHelper() | 获取图片访问助手 | API 10 |
PhotoViewPicker.select() | 选择图片/视频 | API 10 |
PhotoViewPicker.save() | 保存图片/视频 | API 10 |
选择图片
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function pickImage(): Promise<string> {
try {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
photoSelectOptions.maxSelectNumber = 1
const photoPicker = new photoAccessHelper.PhotoViewPicker()
const result = await photoPicker.select(photoSelectOptions)
if (result.photoUris.length > 0) {
return result.photoUris[0]
}
return ''
} catch (error) {
console.error('选择图片失败:', JSON.stringify(error))
return ''
}
}
// 选择多张图片
async function pickMultipleImages(maxCount: number): Promise<string[]> {
try {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
photoSelectOptions.maxSelectNumber = maxCount
const photoPicker = new photoAccessHelper.PhotoViewPicker()
const result = await photoPicker.select(photoSelectOptions)
return result.photoUris
} catch (error) {
console.error('选择图片失败:', JSON.stringify(error))
return []
}
}选择视频
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function pickVideo(): Promise<string> {
try {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
photoSelectOptions.maxSelectNumber = 1
const photoPicker = new photoAccessHelper.PhotoViewPicker()
const result = await photoPicker.select(photoSelectOptions)
if (result.photoUris.length > 0) {
return result.photoUris[0]
}
return ''
} catch (error) {
console.error('选择视频失败:', JSON.stringify(error))
return ''
}
}保存图片到相册
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function saveImageToGallery(imageUri: string): Promise<void> {
try {
const photoSaveOptions = new photoAccessHelper.PhotoSaveOptions()
photoSaveOptions.newFileNames = ['saved_image.jpg']
const photoPicker = new photoAccessHelper.PhotoViewPicker()
await photoPicker.save(photoSaveOptions)
console.info('图片保存成功')
} catch (error) {
console.error('保存图片失败:', JSON.stringify(error))
}
}音频播放
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
media.createAVPlayer() | 创建音视频播放器 | API 10 |
AVPlayer.prepare() | 准备播放 | API 10 |
AVPlayer.play() | 开始播放 | API 10 |
AVPlayer.pause() | 暂停播放 | API 10 |
AVPlayer.stop() | 停止播放 | API 10 |
AVPlayer.seek() | 跳转指定位置 | API 10 |
AVPlayer.setVolume() | 设置音量 | API 10 |
AVPlayer.release() | 释放资源 | API 10 |
AVPlayerState 枚举
| 枚举值 | 说明 |
|---|---|
idle | 初始状态 |
initialized | 已设置数据源 |
prepared | 准备完成 |
playing | 播放中 |
paused | 已暂停 |
completed | 播放完成 |
stopped | 已停止 |
released | 已释放 |
error | 错误状态 |
基础音频播放
typescript
import { media } from '@kit.MultimediaKit'
class AudioPlayer {
private player: media.AVPlayer | null = null
private isPlaying: boolean = false
async init(url: string): Promise<void> {
this.player = await media.createAVPlayer()
// 监听状态变化
this.player.on('stateChange', (state: media.AVPlayerState) => {
console.info('播放器状态:', state)
this.isPlaying = state === 'playing'
})
// 监听播放错误
this.player.on('error', (error) => {
console.error('播放错误:', JSON.stringify(error))
})
// 监听播放完成
this.player.on('seekDone', (seekDoneTime) => {
console.info('跳转完成:', seekDoneTime)
})
// 设置数据源
this.player.url = url
await this.player.prepare()
}
async play(): Promise<void> {
if (this.player) {
await this.player.play()
}
}
async pause(): Promise<void> {
if (this.player) {
await this.player.pause()
}
}
async stop(): Promise<void> {
if (this.player) {
await this.player.stop()
}
}
async seek(timeMs: number): Promise<void> {
if (this.player) {
await this.player.seek(timeMs)
}
}
setVolume(volume: number): void {
if (this.player) {
this.player.setVolume(volume)
}
}
getDuration(): number {
return this.player?.duration || 0
}
getCurrentTime(): number {
return this.player?.currentTime || 0
}
async release(): Promise<void> {
if (this.player) {
await this.player.release()
this.player = null
}
}
}
// 使用示例
const audioPlayer = new AudioPlayer()
async function playMusic() {
await audioPlayer.init('https://example.com/music.mp3')
await audioPlayer.play()
// 5 秒后暂停
setTimeout(() => {
audioPlayer.pause()
}, 5000)
}本地音频播放
typescript
import { media } from '@kit.MultimediaKit'
import { fileIo } from '@kit.CoreFileKit'
async function playLocalAudio(filePath: string): Promise<void> {
const player = await media.createAVPlayer()
try {
// 使用 fdSrc 播放本地文件
const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY)
player.fdSrc = {
fd: file.fd,
offset: 0,
length: fileIo.statSync(file.fd).size
}
await player.prepare()
await player.play()
// 播放完成后关闭文件
player.on('stateChange', async (state) => {
if (state === 'completed' || state === 'stopped') {
fileIo.closeSync(file)
await player.release()
}
})
} catch (error) {
fileIo.closeSync(file)
await player.release()
throw error
}
}音频录制
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
media.createAVRecorder() | 创建音视频录制器 | API 10 |
AVRecorder.prepare() | 准备录制 | API 10 |
AVRecorder.start() | 开始录制 | API 10 |
AVRecorder.pause() | 暂停录制 | API 10 |
AVRecorder.resume() | 恢复录制 | API 10 |
AVRecorder.stop() | 停止录制 | API 10 |
AVRecorder.release() | 释放资源 | API 10 |
基础音频录制
typescript
import { media } from '@kit.MultimediaKit'
import { fileIo } from '@kit.CoreFileKit'
class AudioRecorder {
private recorder: media.AVRecorder | null = null
private outputPath: string = ''
async init(outputPath: string): Promise<void> {
this.outputPath = outputPath
this.recorder = await media.createAVRecorder()
// 监听状态变化
this.recorder.on('stateChange', (state: media.AVRecorderState) => {
console.info('录制器状态:', state)
})
// 监听错误
this.recorder.on('error', (error) => {
console.error('录制错误:', JSON.stringify(error))
})
// 配置录制参数
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: media.AVRecorderProfileType.RECORDER_PROFILE_H264_AAC_MP4,
url: `fd://${outputPath}`,
rotation: 0
}
await this.recorder.prepare(config)
}
async start(): Promise<void> {
if (this.recorder) {
await this.recorder.start()
}
}
async pause(): Promise<void> {
if (this.recorder) {
await this.recorder.pause()
}
}
async resume(): Promise<void> {
if (this.recorder) {
await this.recorder.resume()
}
}
async stop(): Promise<void> {
if (this.recorder) {
await this.recorder.stop()
}
}
async release(): Promise<void> {
if (this.recorder) {
await this.recorder.release()
this.recorder = null
}
}
}
// 使用示例
const recorder = new AudioRecorder()
async function recordAudio() {
const outputPath = getContext().cacheDir + '/recording.mp4'
await recorder.init(outputPath)
await recorder.start()
// 录制 5 秒
setTimeout(async () => {
await recorder.stop()
await recorder.release()
console.info('录制完成:', outputPath)
}, 5000)
}视频播放
基础视频播放
typescript
import { media } from '@kit.MultimediaKit'
@Entry
@Component
struct VideoPlayerPage {
private player: media.AVPlayer | null = null
@State isPlaying: boolean = false
@State currentTime: number = 0
@State duration: number = 0
aboutToAppear() {
this.initPlayer()
}
aboutToDisappear() {
this.releasePlayer()
}
async initPlayer() {
this.player = await media.createAVPlayer()
this.player.url = 'https://example.com/video.mp4'
this.player.on('stateChange', (state) => {
if (state === 'playing') {
this.isPlaying = true
} else if (state === 'paused' || state === 'stopped') {
this.isPlaying = false
}
})
this.player.on('timeUpdate', (time) => {
this.currentTime = time
})
await this.player.prepare()
this.duration = this.player.duration
}
async togglePlay() {
if (this.isPlaying) {
await this.player?.pause()
} else {
await this.player?.play()
}
}
async seekTo(time: number) {
await this.player?.seek(time)
}
async releasePlayer() {
if (this.player) {
await this.player.release()
this.player = null
}
}
build() {
Column() {
// 视频组件
Video({
src: 'https://example.com/video.mp4',
controller: new VideoController()
})
.width('100%')
.height(300)
.autoPlay(false)
Row({ space: 20 }) {
Button(this.isPlaying ? '暂停' : '播放')
.onClick(() => this.togglePlay())
Button('停止')
.onClick(() => {
this.player?.stop()
})
}
.padding(20)
// 进度条
Slider({
value: this.currentTime,
min: 0,
max: this.duration
})
.width('90%')
.onChange((value) => {
this.seekTo(value)
})
Text(`${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}`)
.fontSize(14)
.fontColor('#999')
}
.width('100%')
.height('100%')
}
private formatTime(ms: number): string {
const seconds = Math.floor(ms / 1000)
const minutes = Math.floor(seconds / 60)
const secs = seconds % 60
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
}完整封装示例
typescript
import { media } from '@kit.MultimediaKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { camera } from '@kit.MultimediaKit'
class MultimediaManager {
private static instance: MultimediaManager
private player: media.AVPlayer | null = null
private recorder: media.AVRecorder | null = null
static getInstance(): MultimediaManager {
if (!MultimediaManager.instance) {
MultimediaManager.instance = new MultimediaManager()
}
return MultimediaManager.instance
}
// ========== 图片选择 ==========
async pickImage(maxCount: number = 1): Promise<string[]> {
try {
const options = new photoAccessHelper.PhotoSelectOptions()
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
options.maxSelectNumber = maxCount
const picker = new photoAccessHelper.PhotoViewPicker()
const result = await picker.select(options)
return result.photoUris
} catch (error) {
console.error('选择图片失败:', JSON.stringify(error))
return []
}
}
async pickVideo(): Promise<string> {
try {
const options = new photoAccessHelper.PhotoSelectOptions()
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
options.maxSelectNumber = 1
const picker = new photoAccessHelper.PhotoViewPicker()
const result = await picker.select(options)
return result.photoUris[0] || ''
} catch (error) {
console.error('选择视频失败:', JSON.stringify(error))
return ''
}
}
// ========== 拍照 ==========
async takePhoto(): Promise<string> {
try {
const picker = new camera.CameraPicker()
const result = await picker.pick(getContext(), {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveType: camera.CameraPickerMediaType.PHOTO
})
return result.resultUri
} catch (error) {
console.error('拍照失败:', JSON.stringify(error))
return ''
}
}
// ========== 音频播放 ==========
async initPlayer(url: string): Promise<void> {
if (this.player) {
await this.releasePlayer()
}
this.player = await media.createAVPlayer()
this.player.url = url
await this.player.prepare()
}
async play(): Promise<void> {
await this.player?.play()
}
async pause(): Promise<void> {
await this.player?.pause()
}
async stop(): Promise<void> {
await this.player?.stop()
}
async seek(timeMs: number): Promise<void> {
await this.player?.seek(timeMs)
}
setVolume(volume: number): void {
this.player?.setVolume(volume)
}
getDuration(): number {
return this.player?.duration || 0
}
getCurrentTime(): number {
return this.player?.currentTime || 0
}
async releasePlayer(): Promise<void> {
if (this.player) {
await this.player.release()
this.player = null
}
}
// ========== 音频录制 ==========
async initRecorder(outputPath: string): Promise<void> {
if (this.recorder) {
await this.releaseRecorder()
}
this.recorder = await media.createAVRecorder()
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: media.AVRecorderProfileType.RECORDER_PROFILE_H264_AAC_MP4,
url: `fd://${outputPath}`,
rotation: 0
}
await this.recorder.prepare(config)
}
async startRecording(): Promise<void> {
await this.recorder?.start()
}
async stopRecording(): Promise<void> {
await this.recorder?.stop()
}
async releaseRecorder(): Promise<void> {
if (this.recorder) {
await this.recorder.release()
this.recorder = null
}
}
}
export const multimediaManager = MultimediaManager.getInstance()最佳实践
- 权限申请:相机、麦克风等敏感权限需动态申请,使用前先检查权限状态
- 资源释放:播放器、录制器使用完毕后及时调用
release()释放资源 - 错误处理:多媒体操作可能失败,使用 try/catch 处理异常
- 后台限制:应用在后台时音频播放可能受限,需申请后台播放权限
- 格式兼容:选择设备普遍支持的音视频格式(如 MP4、MP3)
- 性能优化:大图片加载前进行压缩,避免内存溢出
- 用户体验:录制/播放时提供明确的开始/停止/暂停控制