介绍
在ArkTS中调用相机拍照和录像,以及如何使用媒体库接口进行媒体文件的增、删、改、查操作。本示例用到了
- 权限管理能力
- 相机模块能力接口
- 图片处理接口
- 音视频相关媒体业务能力接口
- 媒体库管理接口
- 设备信息能力接口
- 文件存储管理能力接口
- 弹窗能力接口
效果预览
首页 |
---|
![]() |
使用说明
1.启动应用,在权限弹窗中授权后返回应用,首页显示当前设备的相册信息,首页监听相册变化会刷新相册列表。
2.点击 + 按钮,弹出相机、录音、文本文件三个图标。
3.安装相机应用[Camera]应用后,点击相机 图标,进入相机界面,默认是拍照模式,点击底部拍照按钮可以拍照,拍照完成会在底部左侧显示照片预览图。点击录像 切换到录像模式,点击底部按钮开始录像,点击结束按钮结束录像,结束录像后底部左侧显示视频图标。点击系统Back 键或界面顶部返回按钮返回首页。
4.点击录音 图标进入录音界面,点击右侧开始 按钮开始录音,按钮变为暂停按钮,点击可以暂停和继续录音,点击左侧结束按钮结束录音返回首页。
5.点击文本 图标进入文本编辑界面,输入文本内容后点击Save按钮,会创建并写入文本文件,完成后返回首页。
6.点击相册进入文件列表界面,展示相册内的文件,列表中有删除 和重命名按钮,点击可以删除文件和重命名文件。
7.安装视频播放[VideoPlayer]应用后,点击视频文件可以调起视频播放界面播放该视频。
相关概念
媒体库管理:媒体库管理提供接口对公共媒体资源文件进行管理,包括文件的增、删、改、查等。 相机:相机模块支持相机相关基础功能的开发,主要包括预览、拍照、录像等。
工程目录
entry/src/main/ets/
|---MainAbility
| |---MainAbility.ts // 主程序入口,应用启动时获取相应权限
|---pages
| |---index.ets // 首页
| |---AlbumPage.ets // 相册页面
| |---CameraPage.ets // 相机页面
| |---RecordPage.ets // 录音页面
| |---DocumentPage.ets // 存储文件页面
|---model
| |---CameraService.ts // 相机模块(拍照录像模式)
| |---DateTimeUtil.ts // 日期工具包
| |---MediaUtils.ts // 媒体工具模块
| |---RecordModel.ts // 录音模块(底层能力实现)
| |---TimeUtils.ts // 时间工具包
|---view
| |---BasicDataSource.ets // 初始化媒体服务数组
| |---MediaItem.ets // 定义具体的某一媒体模块页面
| |---MediaView.ets // 媒体模块的前置模块(判断是否有展示的媒体内容)
| |---RenameDialog.ets // 重命名文件模块
| |---TitleBar.ets // 标题栏
具体实现
-
布局原理:定义@ObjectLink 装饰的数组变量album存放资源文件,使用list()组件中ListItem()循环数组展示,加号Button(),点击后触发 animateTo({ duration: 500, curve: Curve.Ease })控制动画展示,[源码参考]。
/*
-
Copyright (c) 2022-2023 Huawei Device Co., Ltd.
-
Licensed under the Apache License, Version 2.0 (the "License");
-
you may not use this file except in compliance with the License.
-
You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
-
distributed under the License is distributed on an "AS IS" BASIS,
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
See the License for the specific language governing permissions and
-
limitations under the License.
*/
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
import router from '@ohos.router';
import TitleBar from '../view/TitleBar';
import MediaUtils from '../model/MediaUtils';
import { MediaView } from '../view/MediaView';
import Logger from '../model/Logger';
@Observed
export default class Album {
constructor(public albumName: string, public count: number, public mediaType?: mediaLibrary.MediaType) { this.albumName = albumName; this.count = count; this.mediaType = mediaType; }
}
@Entry
@Component
struct Index {
private mediaUtils: MediaUtils = MediaUtils.getInstance(getContext(this)) @State albums: Array<Album> = [] @State selectIndex: number = 0 @State operateVisible: boolean = false async onPageShow() { this.albums = []; this.albums = await this.mediaUtils.getAlbums(); } @Builder OperateBtn(src, zIndex, translate, handleClick) { Button() { Image(src) .width('70%') .height('70%') } .type(ButtonType.Circle) .width('40%') .height('40%') .backgroundColor('#0D9FFB') .zIndex(zIndex) .translate({ x: translate.x, y: translate.y }) .transition({ type: TransitionType.Insert, translate: { x: 0, y: 0 } }) .transition({ type: TransitionType.Delete, opacity: 0 }) .onClick(handleClick) } build() { Stack({ alignContent: Alignment.BottomEnd }) { Column() { TitleBar() List() { ForEach(this.albums, (item: Album, index) => { ListItem() { MediaView({ album: item }) .id(`mediaType${index}`) } }, item => item.albumName) } .divider({ strokeWidth: 1, color: Color.Gray, startMargin: 16, endMargin: 16 }) .layoutWeight(1) } .width('100%') .height('100%') Stack({ alignContent: Alignment.Center }) { Button() { Image($r('app.media.add')) .width('100%') .height('100%') } .width(60) .height(60) .padding(10) .id('addBtn') .type(ButtonType.Circle) .backgroundColor('#0D9FFB') .onClick(() => { animateTo({ duration: 500, curve: Curve.Ease }, () => { this.operateVisible = !this.operateVisible }) }) Button() { Image($r('app.media.icon_camera')) .id('camera') .width('100%') .height('100%') } .width(60) .height(60) .padding(10) .type(ButtonType.Circle) .backgroundColor('#0D9FFB') .translate({ x: 0, y: -80 }) .visibility(this.operateVisible ? Visibility.Visible : Visibility.None) .onClick(() => { this.operateVisible = !this.operateVisible; let context: common.UIAbilityContext | undefined = AppStorage.Get('context'); let want: Want = { bundleName: "com.samples.camera_page", abilityName: "EntryAbility", }; context && context.startAbility(want, (err) => { if (err.code) { Logger.error('StartAbility', `Failed to startAbility. Code: ${err.code}, message: ${err.message}`); } }); }) Button() { Image($r('app.media.icon_record')) .id('record') .width('100%') .height('100%') } .width(60) .height(60) .padding(10) .type(ButtonType.Circle) .backgroundColor('#0D9FFB') .translate({ x: -80, y: 0 }) .visibility(this.operateVisible ? Visibility.Visible : Visibility.None) .onClick(() => { this.operateVisible = !this.operateVisible router.push({ url: 'pages/RecordPage' }) }) Button() { Image($r('app.media.icon_document')) .width('100%') .height('100%') } .width(60) .height(60) .padding(10) .id('document') .type(ButtonType.Circle) .backgroundColor('#0D9FFB') .translate({ x: 0, y: 80 }) .visibility(this.operateVisible ? Visibility.Visible : Visibility.None) .onClick(() => { this.operateVisible = !this.operateVisible router.pushUrl({ url: 'pages/DocumentPage' }) }) } .width(180) .height(220) .margin({ right: 40, bottom: 120 }) } .width('100%') .height('100%') } aboutToDisappear() { this.mediaUtils.offDateChange() }
}
-
-
获取资源文件:通过引入媒体库实例(入口)接口@ohos.multimedia.medialibrary,例如通过this.getFileAssetsFromType(mediaLibrary.MediaType.FILE)获取FILE类型的文件资源,并通过albums.push()添加至album数组中。
-
展示系统资源文件:当album内的值被修改时,只会让用 @ObjectLink 装饰的变量album所在的组件被刷新,当前组件不会刷新。
-
录音功能:通过引入音视频接口@ohos.multimedia.media,例如通过media.createAudioRecorder()创建音频录制的实例来控制音频的录制,通过this.audioRecorder.on('prepare', () => {this.audioRecorder.start()})异步方式开始音频录制,[源码参考]
/*
-
Copyright (c) 2022 Huawei Device Co., Ltd.
-
Licensed under the Apache License, Version 2.0 (the "License");
-
you may not use this file except in compliance with the License.
-
You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
-
distributed under the License is distributed on an "AS IS" BASIS,
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
See the License for the specific language governing permissions and
-
limitations under the License.
*/
import media from '@ohos.multimedia.media'
import Logger from '../model/Logger'
let audioConfig = {
audioSourceType: 1, audioEncoder: 3, audioEncodeBitRate: 22050, audioSampleRate: 22050, numberOfChannels: 2, format: 6, uri: ''
}
export default class RecordModel {
private tag: string = 'RecordModel' private audioRecorder: media.AudioRecorder = undefined initAudioRecorder(handleStateChange: () => void) { this.release(); this.audioRecorder = media.createAudioRecorder() Logger.info(this.tag, 'create audioRecorder success') this.audioRecorder.on('prepare', () => { Logger.info(this.tag, 'setCallback prepare case callback is called') this.audioRecorder.start() }) this.audioRecorder.on('start', () => { Logger.info(this.tag, 'setCallback start case callback is called') handleStateChange() }) this.audioRecorder.on('stop', () => { Logger.info(this.tag, 'audioRecorder stop called') this.audioRecorder.release() }) this.audioRecorder.on('pause', () => { Logger.info(this.tag, 'audioRecorder pause finish') handleStateChange() }) this.audioRecorder.on('resume', () => { Logger.info(this.tag, 'audioRecorder resume finish') handleStateChange() }) } release() { if (typeof (this.audioRecorder) !== `undefined`) { Logger.info(this.tag, 'audioRecorder release') this.audioRecorder.release() this.audioRecorder = undefined } } startRecorder(pathName: string) { Logger.info(this.tag, `startRecorder, pathName = ${pathName}`) if (typeof (this.audioRecorder) !== 'undefined') { Logger.info(this.tag, 'start prepare') audioConfig.uri = pathName this.audioRecorder.prepare(audioConfig) } else { Logger.error(this.tag, 'case failed, audioRecorder is null') } } pause() { Logger.info(this.tag, 'audioRecorder pause called') if (typeof (this.audioRecorder) !== `undefined`) { this.audioRecorder.pause() } } resume() { Logger.info(this.tag, 'audioRecorder resume called') if (typeof (this.audioRecorder) !== `undefined`) { this.audioRecorder.resume() } } finish() { if (typeof (this.audioRecorder) !== `undefined`) { this.audioRecorder.stop() } }
}
-
-
拍照录像功能:通过引入相机模块接口@ohos.multimedia.camera,例如通过this.cameraManager.createCaptureSession()创建相机入口的实例来控制拍照和录像,通过this.captureSession.start()开始会话工作,[源码参考]
/*
-
Copyright (c) 2022 Huawei Device Co., Ltd.
-
Licensed under the Apache License, Version 2.0 (the "License");
-
you may not use this file except in compliance with the License.
-
You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software
-
distributed under the License is distributed on an "AS IS" BASIS,
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
See the License for the specific language governing permissions and
-
limitations under the License.
*/
import camera from '@ohos.multimedia.camera'
import deviceInfo from '@ohos.deviceInfo'
import fileio from '@ohos.fileio'
import image from '@ohos.multimedia.image'
import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
import Logger from '../model/Logger'
import MediaUtils from '../model/MediaUtils'
const CameraMode = {
MODE_PHOTO: 0, // 拍照模式 MODE_VIDEO: 1 // 录像模式
}
const CameraSize = {
WIDTH: 1920, HEIGHT: 1080
}
export default class CameraService {
private tag: string = 'CameraService' private context: any = undefined private mediaUtil: MediaUtils = undefined private cameraManager: camera.CameraManager = undefined private cameras: Array<camera.CameraDevice> = undefined private cameraId: string = '' private cameraInput: camera.CameraInput = undefined private previewOutput: camera.PreviewOutput = undefined private photoOutPut: camera.PhotoOutput = undefined private captureSession: camera.CaptureSession = undefined private mReceiver: image.ImageReceiver = undefined private photoUri: string = '' private fileAsset: mediaLibrary.FileAsset = undefined private fd: number = -1 private curMode = CameraMode.MODE_PHOTO private videoRecorder: media.VideoRecorder = undefined private videoOutput: camera.VideoOutput = undefined private handleTakePicture: (photoUri: string) => void = undefined private cameraOutputCapability: camera.CameraOutputCapability = undefined private videoConfig: any = { audioSourceType: 1, videoSourceType: 0, profile: { audioBitrate: 48000, audioChannels: 2, audioCodec: 'audio/mp4v-es', audioSampleRate: 48000, durationTime: 1000, fileFormat: 'mp4', videoBitrate: 48000, videoCodec: 'video/mp4v-es', videoFrameWidth: 640, videoFrameHeight: 480, videoFrameRate: 30 }, url: '', orientationHint: 0, location: { latitude: 30, longitude: 130 }, maxSize: 10000, maxDuration: 10000 } constructor(context: any) { this.context = context this.mediaUtil = MediaUtils.getInstance(context) this.mReceiver = image.createImageReceiver(CameraSize.WIDTH, CameraSize.HEIGHT, 4, 8) Logger.info(this.tag, 'createImageReceiver') this.mReceiver.on('imageArrival', () => { Logger.info(this.tag, 'imageArrival') this.mReceiver.readNextImage((err, image) => { Logger.info(this.tag, 'readNextImage') if (err || image === undefined) { Logger.error(this.tag, 'failed to get valid image') return } image.getComponent(4, (errMsg, img) => { Logger.info(this.tag, 'getComponent') if (errMsg || img === undefined) { Logger.info(this.tag, 'failed to get valid buffer') return } let buffer = new ArrayBuffer(4096) if (img.byteBuffer) { buffer = img.byteBuffer } else { Logger.error(this.tag, 'img.byteBuffer is undefined') } this.savePicture(buffer, image) }) }) }) } async savePicture(buffer: ArrayBuffer, img: image.Image) { Logger.info(this.tag, 'savePicture') this.fileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE) this.photoUri = this.fileAsset.uri Logger.info(this.tag, `this.photoUri = ${this.photoUri}`) this.fd = await this.mediaUtil.getFdPath(this.fileAsset) Logger.info(this.tag, `this.fd = ${this.fd}`) await fileio.write(this.fd, buffer) await this.fileAsset.close(this.fd) await img.release() Logger.info(this.tag, 'save image done') if (this.handleTakePicture) { this.handleTakePicture(this.photoUri) } } async initCamera(surfaceId: string) { Logger.info(this.tag, 'initCamera') await this.releaseCamera() Logger.info(this.tag, `deviceInfo.deviceType = ${deviceInfo.deviceType}`) if (deviceInfo.deviceType === 'default') { this.videoConfig.videoSourceType = 1 } else { this.videoConfig.videoSourceType = 0 } this.cameraManager = await camera.getCameraManager(this.context) Logger.info(this.tag, 'getCameraManager') this.cameras = await this.cameraManager.getSupportedCameras() Logger.info(this.tag, `get cameras ${this.cameras.length}`) if (this.cameras.length === 0) { Logger.info(this.tag, 'cannot get cameras') return } let cameraDevice = this.cameras[0] this.cameraInput = await this.cameraManager.createCameraInput(cameraDevice) this.cameraInput.open() Logger.info(this.tag, 'createCameraInput') this.cameraOutputCapability = await this.cameraManager.getSupportedOutputCapability(cameraDevice) let previewProfile = this.cameraOutputCapability.previewProfiles[0] this.previewOutput = await this.cameraManager.createPreviewOutput(previewProfile, surfaceId) Logger.info(this.tag, 'createPreviewOutput') let mSurfaceId = await this.mReceiver.getReceivingSurfaceId() let photoProfile = this.cameraOutputCapability.photoProfiles[0] this.photoOutPut = await this.cameraManager.createPhotoOutput(photoProfile, mSurfaceId) this.captureSession = await this.cameraManager.createCaptureSession() Logger.info(this.tag, 'createCaptureSession') await this.captureSession.beginConfig() Logger.info(this.tag, 'beginConfig') await this.captureSession.addInput(this.cameraInput) await this.captureSession.addOutput(this.previewOutput) await this.captureSession.addOutput(this.photoOutPut) await this.captureSession.commitConfig() await this.captureSession.start() Logger.info(this.tag, 'captureSession start') } setTakePictureCallback(callback) { this.handleTakePicture = callback } async takePicture() { Logger.info(this.tag, 'takePicture') if (this.curMode === CameraMode.MODE_VIDEO) { this.curMode = CameraMode.MODE_PHOTO } let photoSettings = { rotation: camera.ImageRotation.ROTATION_0, quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM, location: { // 位置信息,经纬度 latitude: 12.9698, longitude: 77.7500, altitude: 1000 }, mirror: false } await this.photoOutPut.capture(photoSettings) Logger.info(this.tag, 'takePicture done') AppStorage.Set('isRefresh', true) } async startVideo() { Logger.info(this.tag, 'startVideo begin') await this.captureSession.stop() await this.captureSession.beginConfig() if (this.curMode === CameraMode.MODE_PHOTO) { this.curMode = CameraMode.MODE_VIDEO if (this.photoOutPut) { await this.captureSession.removeOutput(this.photoOutPut) this.photoOutPut.release() } } else { if (this.videoOutput) { await this.captureSession.removeOutput(this.videoOutput) } } if (this.videoOutput) { await this.captureSession.removeOutput(this.videoOutput) await this.videoOutput.release() } this.fileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.VIDEO) this.fd = await this.mediaUtil.getFdPath(this.fileAsset) this.videoRecorder = await media.createVideoRecorder() this.videoConfig.url = `fd://${this.fd}` await this.videoRecorder.prepare(this.videoConfig) let videoId = await this.videoRecorder.getInputSurface() let videoProfile = this.cameraOutputCapability.videoProfiles[0]; this.videoOutput = await this.cameraManager.createVideoOutput(videoProfile, videoId) await this.captureSession.addOutput(this.videoOutput) await this.captureSession.commitConfig() await this.captureSession.start() await this.videoOutput.start() await this.videoRecorder.start() Logger.info(this.tag, 'startVideo end') } async stopVideo() { Logger.info(this.tag, 'stopVideo called') await this.videoRecorder.stop() await this.videoOutput.stop() await this.videoRecorder.release() await this.fileAsset.close(this.fd) } async releaseCamera() { Logger.info(this.tag, 'releaseCamera') if (this.cameraInput) { await this.cameraInput.close() } if (this.previewOutput) { await this.previewOutput.release() } if (this.photoOutPut) { await this.photoOutPut.release() } if (this.videoOutput) { await this.videoOutput.release() } if (this.captureSession) { await this.captureSession.release() } }
}
-
鸿蒙NEXT开发知识已更新 gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
可参考学习更多。

约束与限制
1.rk3568底层录像功能有问题,暂不支持录像功能,当前拍照功能仅支持部分机型。
2.本示例仅支持标准系统上运行。
3.本示例为Stage模型,已适配API version 9版本SDK,版本号:3.2.11.9;
4.本示例需要使用DevEco Studio 3.1 Beta2 (Build Version: 3.1.0.400, built on April 7, 2023)及以上版本才可编译运行。