1# 自动切换摄像头实践(ArkTS) 2<!--Kit: Camera Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @qano--> 5<!--Designer: @leo_ysl--> 6<!--Tester: @xchaosioda--> 7<!--Adviser: @zengyawen--> 8 9本文档仅针对折叠屏设备自动切换前置摄像头的场景。在不同折叠状态下,自动切换到当前状态支持的摄像头。 10 11例如:折叠设备A拥有三颗摄像头:后置摄像头B、前置摄像头C和前置摄像头D。在展开状态下,通过[CameraManager.getSupportedCameras](../../reference/apis-camera-kit/arkts-apis-camera-CameraManager.md#getsupportedcameras)接口可获取到后置摄像头B和前置摄像头C。在折叠状态下,可获取到后置摄像头B和前置摄像头D。在当前折叠状态下启用前置摄像头,并调用[enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitch.md#enableautodeviceswitch13)开启自动切换镜头。这样,在下次折叠屏状态变化时,会自动切换到对应折叠状态下的前置摄像头。 12 13详细的API说明请参考[Camera API参考](../../reference/apis-camera-kit/arkts-apis-camera.md)。 14 15Context获取方式请参考:[获取UIAbility的上下文信息](../../application-models/uiability-usage.md#获取uiability的上下文信息)。 16 17在开发相机应用时,需要先[申请相关权限](camera-preparation.md)。 18 19## 导入相关依赖 20```ts 21import { camera } from '@kit.CameraKit'; 22import { BusinessError } from '@kit.BasicServicesKit'; 23import { abilityAccessCtrl } from '@kit.AbilityKit'; 24``` 25## 创建XComponent 26使用[XComponent](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)展示摄像头的预览画面。 27```ts 28@Entry 29@Component 30struct Index { 31 private mXComponentController: XComponentController = new XComponentController(); 32 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 33 private mXComponentOptions: XComponentOptions = { 34 type: XComponentType.SURFACE, 35 controller: this.mXComponentController 36 } 37 38 async loadXComponent() { 39 //初始化XComponent。 40 } 41 42 build() { 43 Stack() { 44 XComponent(this.mXComponentOptions) 45 .onLoad(async () => { 46 await this.loadXComponent(); 47 }) 48 .width(this.getUIContext().px2vp(1080)) 49 .height(this.getUIContext().px2vp(1920)) 50 Text('切换相机') 51 .size({ width: 80, height: 48 }) 52 .position({ x: 1, y: 1 }) 53 .backgroundColor(Color.White) 54 .textAlign(TextAlign.Center) 55 .borderRadius(24) 56 .onClick(async () => { 57 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 58 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 59 await this.loadXComponent(); 60 }) 61 } 62 .size({ width: '100%', height: '100%' }) 63 .backgroundColor(Color.Black) 64 } 65} 66``` 67## 开启自动切换摄像头 68调用[enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitch.md#enableautodeviceswitch13)接口前需要通过[isAutoDeviceSwitchSupported](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitchQuery.md#isautodeviceswitchsupported13)接口查询当前设备是否支持自动切换摄像头能力。 69```ts 70function enableAutoDeviceSwitch(session: camera.PhotoSession) { 71 if (session.isAutoDeviceSwitchSupported()) { 72 session.enableAutoDeviceSwitch(true); 73 } 74} 75``` 76## 监听或解监听自动切换摄像头状态 77可以通过[enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-PhotoSession.md#onautodeviceswitchstatuschange13)监听自动切换摄像头的结果。系统自动切换镜头结束后会触发该回调。 78自动切换摄像头期间,禁止调用任何session相关接口。 79```ts 80function callback(err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus): void { 81 if (err !== undefined && err.code !== 0) { 82 console.error(`Callback Error, errorCode: ${err.code}`); 83 return; 84 } 85 console.info(`isDeviceSwitched: ${autoDeviceSwitchStatus.isDeviceSwitched}, isDeviceCapabilityChanged: ${autoDeviceSwitchStatus.isDeviceCapabilityChanged}`); 86} 87 88function registerAutoDeviceSwitchStatus(photoSession: camera.PhotoSession): void { 89 photoSession.on('autoDeviceSwitchStatusChange', callback); 90} 91function unregisterAutoDeviceSwitchStatus(photoSession: camera.PhotoSession): void { 92 photoSession.off('autoDeviceSwitchStatusChange', callback); 93} 94``` 95 96## 完整示例代码 97```ts 98import { camera } from '@kit.CameraKit'; 99import { BusinessError } from '@kit.BasicServicesKit'; 100import { abilityAccessCtrl } from '@kit.AbilityKit'; 101 102const TAG = 'AutoSwitchCameraDemo '; 103 104@Entry 105@Component 106struct Index { 107 @State isShow: boolean = false; 108 @State reloadXComponentFlag: boolean = false; 109 private mXComponentController: XComponentController = new XComponentController(); 110 private mXComponentOptions: XComponentOptions = { 111 type: XComponentType.SURFACE, 112 controller: this.mXComponentController 113 } 114 private mSurfaceId: string = ''; 115 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 116 private mCameraManager: camera.CameraManager | undefined = undefined; 117 // surface宽高根据需要自行选择。 118 private surfaceRect: SurfaceRect = { 119 surfaceWidth: 1080, 120 surfaceHeight: 1920 121 }; 122 private curCameraDevice: camera.CameraDevice | undefined = undefined; 123 private mCameraInput: camera.CameraInput | undefined = undefined; 124 private mPreviewOutput: camera.PreviewOutput | undefined = undefined; 125 private mPhotoSession: camera.PhotoSession | undefined = undefined; 126 // One of the recommended preview resolutions. 127 private previewProfileObj: camera.Profile = { 128 format: 1003, 129 size: { 130 width: 1920, 131 height: 1080 132 } 133 }; 134 private mContext: Context | undefined = undefined; 135 autoDeviceSwitchCallback: (err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus) => void = 136 (err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus) => { 137 if (err !== undefined && err.code !== 0) { 138 console.error(`${TAG} Callback Error, errorCode: ${err.code}`); 139 return; 140 } 141 console.info(`${TAG} isDeviceSwitched: ${autoDeviceSwitchStatus.isDeviceSwitched}, isDeviceCapabilityChanged: ${autoDeviceSwitchStatus.isDeviceCapabilityChanged}`); 142 } 143 144 requestPermissionsFn(): void { 145 let atManager = abilityAccessCtrl.createAtManager(); 146 atManager.requestPermissionsFromUser(this.mContext, [ 147 'ohos.permission.CAMERA' 148 ]).then((): void => { 149 this.isShow = true; 150 }).catch((error: BusinessError): void => { 151 console.error(TAG + 'ohos.permission.CAMERA no permission.'); 152 }); 153 } 154 155 initContext(): void { 156 let uiContext = this.getUIContext(); 157 this.mContext = uiContext.getHostContext(); 158 } 159 160 initCameraManager(): void { 161 this.mCameraManager = camera.getCameraManager(this.mContext); 162 } 163 164 aboutToAppear(): void { 165 console.info(TAG + 'aboutToAppear is called'); 166 this.initContext(); 167 this.requestPermissionsFn(); 168 this.initCameraManager(); 169 } 170 171 async aboutToDisappear(): Promise<void> { 172 await this.releaseCamera(); 173 } 174 175 async onPageShow(): Promise<void> { 176 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 177 } 178 179 async releaseCamera(): Promise<void> { 180 // 停止当前会话。 181 try { 182 await this.mPhotoSession?.stop(); 183 } catch (error) { 184 let err = error as BusinessError; 185 console.error(TAG + 'Failed to stop session, errorCode = ' + err.code); 186 } 187 188 // 释放相机输入流。 189 try { 190 await this.mCameraInput?.close(); 191 } catch (error) { 192 let err = error as BusinessError; 193 console.error(TAG + 'Failed to close device, errorCode = ' + err.code); 194 } 195 196 // 释放预览输出流。 197 try { 198 await this.mPreviewOutput?.release(); 199 } catch (error) { 200 let err = error as BusinessError; 201 console.error(TAG + 'Failed to release previewOutput, errorCode = ' + err.code); 202 } 203 204 this.mPreviewOutput = undefined; 205 206 // 释放会话。 207 try { 208 await this.mPhotoSession?.release(); 209 } catch (error) { 210 let err = error as BusinessError; 211 console.error(TAG + 'Failed to release photoSession, errorCode = ' + err.code); 212 } 213 214 // 会话置空。 215 this.mPhotoSession = undefined; 216 } 217 218 async loadXComponent(): Promise<void> { 219 this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId(); 220 console.info(TAG + `mCameraPosition: ${this.mCameraPosition}`) 221 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 222 } 223 224 getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined { 225 let previewProfiles = cameraOutputCapability.previewProfiles; 226 if (previewProfiles.length < 1) { 227 return undefined; 228 } 229 let index = previewProfiles.findIndex((previewProfile: camera.Profile) => { 230 return previewProfile.size.width === this.previewProfileObj.size.width && 231 previewProfile.size.height === this.previewProfileObj.size.height && 232 previewProfile.format === this.previewProfileObj.format; 233 }) 234 if (index === -1) { 235 return undefined; 236 } 237 return previewProfiles[index]; 238 } 239 240 async initCamera(surfaceId: string, cameraPosition: camera.CameraPosition, 241 connectionType: camera.ConnectionType = camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN): Promise<void> { 242 await this.releaseCamera(); 243 // 创建CameraManager对象。 244 if (!this.mCameraManager) { 245 console.error(TAG + 'camera.getCameraManager error'); 246 return; 247 } 248 249 // 获取相机列表。 250 let cameraArray: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras(); 251 if (cameraArray.length <= 0) { 252 console.error(TAG + 'cameraManager.getSupportedCameras error'); 253 return; 254 } 255 256 for (let index = 0; index < cameraArray.length; index++) { 257 console.info(TAG + 'cameraId : ' + cameraArray[index].cameraId); // 获取相机ID。 258 console.info(TAG + 'cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置。 259 console.info(TAG + 'cameraType : ' + cameraArray[index].cameraType); // 获取相机类型。 260 console.info(TAG + 'connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型。 261 } 262 263 let deviceIndex = cameraArray.findIndex((cameraDevice: camera.CameraDevice) => { 264 return cameraDevice.cameraPosition === cameraPosition && cameraDevice.connectionType === connectionType; 265 }) 266 // 没有找到对应位置的摄像头,可选择其他摄像头,具体场景具体对待。 267 if (deviceIndex === -1) { 268 deviceIndex = 0; 269 console.error(TAG + 'not found camera'); 270 } 271 this.curCameraDevice = cameraArray[deviceIndex]; 272 273 // 创建相机输入流。 274 try { 275 this.mCameraInput = this.mCameraManager.createCameraInput(this.curCameraDevice); 276 } catch (error) { 277 let err = error as BusinessError; 278 console.error(TAG + 'Failed to createCameraInput errorCode = ' + err.code); 279 } 280 if (this.mCameraInput === undefined) { 281 return; 282 } 283 284 // 打开相机。 285 try { 286 await this.mCameraInput.open(); 287 } catch (error) { 288 let err = error as BusinessError; 289 console.error(TAG + 'Failed to open device, errorCode = ' + err.code); 290 } 291 292 // 获取支持的模式类型。 293 let sceneModes: Array<camera.SceneMode> = this.mCameraManager.getSupportedSceneModes(this.curCameraDevice); 294 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 295 if (!isSupportPhotoMode) { 296 console.error(TAG + 'photo mode not support'); 297 return; 298 } 299 300 // 获取相机设备支持的输出流能力。 301 let cameraOutputCapability: camera.CameraOutputCapability = 302 this.mCameraManager.getSupportedOutputCapability(this.curCameraDevice, camera.SceneMode.NORMAL_PHOTO); 303 if (!cameraOutputCapability) { 304 console.error(TAG + 'cameraManager.getSupportedOutputCapability error'); 305 return; 306 } 307 console.info(TAG + 'outputCapability: ' + JSON.stringify(cameraOutputCapability)); 308 let previewProfile = this.getPreviewProfile(cameraOutputCapability); 309 if (previewProfile === undefined) { 310 console.error(TAG + 'The resolution of the current preview stream is not supported.'); 311 return; 312 } 313 this.previewProfileObj = previewProfile; 314 315 // 创建预览输出流,其中参数 surfaceId 参考上文 XComponent 组件,预览流为XComponent组件提供的surface。 316 try { 317 this.mPreviewOutput = this.mCameraManager.createPreviewOutput(this.previewProfileObj, surfaceId); 318 } catch (error) { 319 let err = error as BusinessError; 320 console.error(TAG + `Failed to create the PreviewOutput instance. error code: ${err.code}`); 321 } 322 if (this.mPreviewOutput === undefined) { 323 return; 324 } 325 326 //创建会话。 327 try { 328 this.mPhotoSession = this.mCameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 329 } catch (error) { 330 let err = error as BusinessError; 331 console.error(TAG + 'Failed to create the session instance. errorCode = ' + err.code); 332 } 333 if (this.mPhotoSession === undefined) { 334 return; 335 } 336 if (this.mPhotoSession.isAutoDeviceSwitchSupported()) { 337 this.mPhotoSession.enableAutoDeviceSwitch(true); 338 this.mPhotoSession.on('autoDeviceSwitchStatusChange', this.autoDeviceSwitchCallback); 339 } 340 // 开始配置会话。 341 try { 342 this.mPhotoSession.beginConfig(); 343 } catch (error) { 344 let err = error as BusinessError; 345 console.error(TAG + 'Failed to beginConfig. errorCode = ' + err.code); 346 } 347 348 // 向会话中添加相机输入流。 349 try { 350 this.mPhotoSession.addInput(this.mCameraInput); 351 } catch (error) { 352 let err = error as BusinessError; 353 console.error(TAG + 'Failed to addInput. errorCode = ' + err.code); 354 } 355 356 // 向会话中添加预览输出流。 357 try { 358 this.mPhotoSession.addOutput(this.mPreviewOutput); 359 } catch (error) { 360 let err = error as BusinessError; 361 console.error(TAG + 'Failed to addOutput(previewOutput). errorCode = ' + err.code); 362 } 363 364 // 提交会话配置。 365 try { 366 await this.mPhotoSession.commitConfig(); 367 } catch (error) { 368 let err = error as BusinessError; 369 console.error(TAG + 'Failed to commit session configuration, errorCode = ' + err.code); 370 } 371 372 // 启动会话。 373 try { 374 await this.mPhotoSession.start() 375 } catch (error) { 376 let err = error as BusinessError; 377 console.error(TAG + 'Failed to start session. errorCode = ' + err.code); 378 } 379 } 380 381 build() { 382 if (this.isShow) { 383 Stack() { 384 XComponent(this.mXComponentOptions) 385 .onLoad(async () => { 386 await this.loadXComponent(); 387 }) 388 .width(this.getUIContext().px2vp(1080)) 389 .height(this.getUIContext().px2vp(1920)) 390 Text('切换相机') 391 .size({ width: 80, height: 48 }) 392 .position({ x: 1, y: 1 }) 393 .backgroundColor(Color.White) 394 .textAlign(TextAlign.Center) 395 .borderRadius(24) 396 .onClick(async () => { 397 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 398 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 399 await this.loadXComponent(); 400 }) 401 } 402 .size({ width: '100%', height: '100%' }) 403 .backgroundColor(Color.Black) 404 } 405 } 406} 407```