多媒体权限声明
所有多媒体功能都涉及用户隐私,必须在 module.json5 中声明对应权限,并在运行时向用户申请。HarmonyOS NEXT 的权限分为两类:
- 普通权限(Normal):在 module.json5 声明即自动授权,无需运行时弹框(如 INTERNET)
- 用户授权权限(User_grant):必须在运行时通过弹框向用户申请(如相机、麦克风、位置)
// module.json5
{
"requestPermissions": [
{ "name": "ohos.permission.CAMERA" },
{ "name": "ohos.permission.MICROPHONE" },
{ "name": "ohos.permission.READ_MEDIA" },
{ "name": "ohos.permission.WRITE_MEDIA" },
{ "name": "ohos.permission.LOCATION" },
{
"name": "ohos.permission.LOCATION_IN_BACKGROUND",
"reason": "$string:location_reason", // 必须提供使用理由
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
运行时权限申请
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
import bundleManager from '@ohos.bundle.bundleManager'
// 通用权限申请函数
async function requestPermission(
context: Context,
permissions: Permissions[]
): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
// 先检查是否已授权
const grantResults = await atManager.requestPermissionsFromUser(context, permissions)
// 检查所有权限是否都已授权
return grantResults.authResults.every(
result => result === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
)
}
// 使用示例:申请相机权限
async function openCamera(context: Context) {
const granted = await requestPermission(context, [
'ohos.permission.CAMERA',
'ohos.permission.MICROPHONE'
])
if (!granted) {
promptAction.showToast({ message: '需要相机和麦克风权限' })
return
}
// 权限获取成功,继续相机操作
}
相机开发
HarmonyOS NEXT 提供了 @ohos.multimedia.camera 模块用于相机操控,以及 XComponent 组件作为相机预览界面:
import camera from '@ohos.multimedia.camera'
import image from '@ohos.multimedia.image'
@Entry
@Component
struct CameraPage {
private cameraManager?: camera.CameraManager
private captureSession?: camera.PhotoSession
private photoOutput?: camera.PhotoOutput
@State capturedImage: string = ''
aboutToAppear() {
this.initCamera()
}
aboutToDisappear() {
this.releaseCamera() // 必须释放,否则相机资源泄漏
}
async initCamera() {
// 获取 CameraManager
this.cameraManager = camera.getCameraManager(getContext(this))
// 获取可用摄像头列表(前后摄)
const cameras = this.cameraManager.getSupportedCameras()
const backCamera = cameras.find(
c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
)
if (!backCamera) return
// 创建相机输入
const cameraInput = this.cameraManager.createCameraInput(backCamera)
await cameraInput.open()
// 创建拍照输出
const photoProfiles = this.cameraManager
.getSupportedOutputCapability(backCamera, camera.SceneMode.NORMAL_PHOTO)
.photoProfiles
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfiles[0])
// 监听拍照完成事件
this.photoOutput.on('photoAvailable', (photo: camera.Photo) => {
this.handlePhoto(photo)
})
}
async takePhoto() {
if (!this.photoOutput) return
const settings: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: camera.ImageRotation.ROTATION_0
}
await this.photoOutput.capture(settings)
}
async handlePhoto(photo: camera.Photo) {
// 将相机原始数据转为 PixelMap
const buffer = new ArrayBuffer(photo.main.capacity)
await photo.main.readBuffer(buffer)
const imageSource = image.createImageSource(buffer)
const pixelMap = await imageSource.createPixelMap()
// 保存到沙箱目录
const path = `${getContext(this).filesDir}/photo_${Date.now()}.jpg`
const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 90 }
const imagePacker = image.createImagePacker()
const data = await imagePacker.packing(pixelMap, packOpts)
// 写入文件...
this.capturedImage = path
}
async releaseCamera() {
await this.captureSession?.stop()
await this.captureSession?.release()
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
// XComponent 是相机预览的宿主(系统级别,性能最优)
XComponent({
id: 'cameraPreview',
type: XComponentType.SURFACE,
controller: new XComponentController()
})
.width('100%')
.height('100%')
// 底部拍照按钮
Button('📷 拍照')
.width(80)
.height(80)
.borderRadius(40)
.backgroundColor('rgba(255,255,255,0.2)')
.border({ width: 3, color: '#ffffff' })
.margin({ bottom: 40 })
.onClick(() => this.takePhoto())
}
.width('100%')
.height('100%')
}
}
音视频播放
HarmonyOS NEXT 使用 AVPlayer(Audio/Video Player)统一处理音频和视频播放:
import media from '@ohos.multimedia.media'
@Entry
@Component
struct VideoPlayer {
private avPlayer?: media.AVPlayer
@State isPlaying: boolean = false
@State duration: number = 0
@State currentTime: number = 0
aboutToAppear() {
this.initPlayer()
}
async initPlayer() {
// 创建 AVPlayer 实例
this.avPlayer = await media.createAVPlayer()
// 监听状态变化
this.avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'prepared':
this.duration = this.avPlayer!.duration
console.log('播放器就绪,时长:', this.duration)
break
case 'playing':
this.isPlaying = true
break
case 'paused':
case 'stopped':
this.isPlaying = false
break
case 'completed':
this.isPlaying = false
this.currentTime = 0
break
}
})
// 监听播放进度
this.avPlayer.on('timeUpdate', (time: number) => {
this.currentTime = time
})
// 设置视频源(支持网络 URL 或本地路径)
this.avPlayer.url = 'https://example.com/video.mp4'
}
build() {
Column({ space: 16 }) {
// 视频渲染到 XComponent
XComponent({
id: 'videoSurface',
type: XComponentType.SURFACE,
controller: new XComponentController()
})
.width('100%')
.aspectRatio(16 / 9)
// 进度条
Slider({
value: this.currentTime,
min: 0,
max: this.duration,
style: SliderStyle.OutSet
})
.trackColor('#21262d')
.selectedColor('#CF0A2C')
.onChange((value: number) => {
this.avPlayer?.seek(value) // 跳转到指定时间(ms)
})
// 播放控制
Row({ space: 24 }) {
Button('⏮').onClick(() => this.avPlayer?.seek(0))
Button(this.isPlaying ? '⏸' : '▶')
.onClick(() => {
if (this.isPlaying) {
this.avPlayer?.pause()
} else {
this.avPlayer?.play()
}
})
Button('⏹').onClick(() => this.avPlayer?.stop())
}
}
.padding(20)
}
}
图片处理与 PixelMap
PixelMap 是 HarmonyOS NEXT 的图像数据容器,所有图片处理操作(缩放、裁剪、旋转、滤镜)都基于它。理解 PixelMap 是图像处理的基础:
import image from '@ohos.multimedia.image'
class ImageProcessor {
// 从网络 URL 加载图片为 PixelMap
static async loadFromUrl(url: string): Promise<image.PixelMap> {
// 先下载图片字节数据
const request = http.createHttp()
const response = await request.request(url, {
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER
})
request.destroy()
// 用 ImageSource 解码为 PixelMap
const source = image.createImageSource(response.result as ArrayBuffer)
return await source.createPixelMap({
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
})
}
// 裁剪 PixelMap
static async crop(
pixelMap: image.PixelMap,
region: { x: number; y: number; width: number; height: number }
): Promise<image.PixelMap> {
await pixelMap.crop(region)
return pixelMap
}
// 缩放图片
static async resize(
pixelMap: image.PixelMap,
targetWidth: number,
targetHeight: number
): Promise<image.PixelMap> {
await pixelMap.scale(
targetWidth / pixelMap.getImageInfo().size.width,
targetHeight / pixelMap.getImageInfo().size.height
)
return pixelMap
}
// 旋转图片
static async rotate(pixelMap: image.PixelMap, degrees: number) {
await pixelMap.rotate(degrees)
return pixelMap
}
// 将 PixelMap 保存为 JPEG 文件
static async saveAsJpeg(
pixelMap: image.PixelMap,
filePath: string,
quality: number = 85
) {
const packer = image.createImagePacker()
const data = await packer.packing(pixelMap, {
format: 'image/jpeg',
quality: quality
})
// 写入文件(使用 fs 模块)
const file = await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY)
await fs.write(file.fd, data)
await fs.close(file.fd)
packer.release()
}
}
传感器 API
HarmonyOS NEXT 统一了各类传感器的访问接口:
import sensor from '@ohos.sensor'
@Entry
@Component
struct SensorDemo {
@State accelerometerData: string = '等待数据...'
@State stepCount: number = 0
aboutToAppear() {
this.startSensors()
}
aboutToDisappear() {
this.stopSensors()
}
startSensors() {
// 加速度计
sensor.on(sensor.SensorId.ACCELEROMETER, (data) => {
this.accelerometerData =
`X: ${data.x.toFixed(2)}, Y: ${data.y.toFixed(2)}, Z: ${data.z.toFixed(2)}`
}, { interval: 100 }) // 每 100ms 更新一次
// 计步器
sensor.on(sensor.SensorId.PEDOMETER, (data) => {
this.stepCount = data.steps
})
}
stopSensors() {
sensor.off(sensor.SensorId.ACCELEROMETER)
sensor.off(sensor.SensorId.PEDOMETER)
}
build() {
Column({ space: 20 }) {
Text('加速度计').fontSize(18).fontWeight(FontWeight.Bold)
Text(this.accelerometerData).fontColor('#adbac7')
Text('今日步数').fontSize(18).fontWeight(FontWeight.Bold)
Text(this.stepCount.toString())
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#CF0A2C')
}
.padding(24)
}
}
地理位置
import geoLocationManager from '@ohos.geoLocationManager'
async function getCurrentLocation(): Promise<{ lat: number; lng: number }> {
const request: geoLocationManager.CurrentLocationRequest = {
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
maxAccuracy: 100, // 最大精度误差 100 米
timeoutMs: 10000 // 10 秒超时
}
const location = await geoLocationManager.getCurrentLocation(request)
return {
lat: location.latitude,
lng: location.longitude
}
}
// 持续监听位置变化(如导航应用)
const subscriptionId = geoLocationManager.on('locationChange', {
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
timeInterval: 1, // 最少 1 秒更新一次
distanceInterval: 5 // 移动超过 5 米更新
}, (location) => {
console.log(`位置更新: ${location.latitude}, ${location.longitude}`)
})
传感器使用注意
传感器是高耗电设备,一旦不再需要必须立即关闭订阅(在 aboutToDisappear 或 onBackground 中调用 sensor.off())。在后台运行位置监听需要申请 LOCATION_IN_BACKGROUND 权限,且华为对此类行为审核严格,需提供合理的使用理由。