1# Practices for Automatic Camera Switching (ArkTS) 2<!--Kit: Camera Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @qano--> 5<!--SE: @leo_ysl--> 6<!--TSE: @xchaosioda--> 7 8This document describes the scenario of automatic front-camera switching on foldable devices. The system selects the appropriate front camera depending on the device's fold state. 9 10For example, foldable device A has three cameras: rear camera B, front camera C, and front camera D. In the unfolded state, [CameraManager.getSupportedCameras](../../reference/apis-camera-kit/arkts-apis-camera-CameraManager.md#getsupportedcameras) returns rear camera B and front camera C. In the folded state, it returns rear camera B and front camera D. 11 12Enable the front camera in the current fold state, and call [enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitch.md#enableautodeviceswitch13) to enable automatic camera switching. This ensures that when the fold state changes, the front camera corresponding to that state is automatically selected. 13 14Read [Module Description](../../reference/apis-camera-kit/arkts-apis-camera.md) for the API reference. 15 16For details about how to obtain the context, see [Obtaining the Context of UIAbility](../../application-models/uiability-usage.md#obtaining-the-context-of-uiability). 17 18Before developing a camera application, request permissions by following the instructions provided in [Requesting Camera Development Permissions](camera-preparation.md). 19 20## Importing Dependencies 21```ts 22import { camera } from '@kit.CameraKit'; 23import { BusinessError } from '@kit.BasicServicesKit'; 24import { abilityAccessCtrl } from '@kit.AbilityKit'; 25``` 26## Creating an XComponent 27Use [XComponent](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md) to display the camera preview. 28```ts 29@Entry 30@Component 31struct Index { 32 private mXComponentController: XComponentController = new XComponentController(); 33 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 34 private mXComponentOptions: XComponentOptions = { 35 type: XComponentType.SURFACE, 36 controller: this.mXComponentController 37 } 38 39 async loadXComponent() { 40 // Initialize the XComponent. 41 } 42 43 build() { 44 Stack() { 45 XComponent(this.mXComponentOptions) 46 .onLoad(async () => { 47 await this.loadXComponent(); 48 }) 49 .width(this.getUIContext().px2vp(1080)) 50 .height(this.getUIContext().px2vp(1920)) 51 Text('Switch camera') 52 .size({ width: 80, height: 48 }) 53 .position({ x: 1, y: 1 }) 54 .backgroundColor(Color.White) 55 .textAlign(TextAlign.Center) 56 .borderRadius(24) 57 .onClick(async () => { 58 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 59 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 60 await this.loadXComponent(); 61 }) 62 } 63 .size({ width: '100%', height: '100%' }) 64 .backgroundColor(Color.Black) 65 } 66} 67``` 68## Enabling Automatic Camera Switching 69Before calling [enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitch.md#enableautodeviceswitch13), use [isAutoDeviceSwitchSupported](../../reference/apis-camera-kit/arkts-apis-camera-AutoDeviceSwitchQuery.md#isautodeviceswitchsupported13) to check whether the device supports automatic camera switching. 70```ts 71function enableAutoDeviceSwitch(session: camera.PhotoSession) { 72 if (session.isAutoDeviceSwitchSupported()) { 73 session.enableAutoDeviceSwitch(true); 74 } 75} 76``` 77## Listening for Automatic Camera Switching 78You can use [enableAutoDeviceSwitch](../../reference/apis-camera-kit/arkts-apis-camera-PhotoSession.md#onautodeviceswitchstatuschange13) to listen for automatic camera switching. A callback is invoked once the switch is complete. 79 80Do not call any session related APIs during the switching process. 81 82```ts 83function callback(err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus): void { 84 if (err !== undefined && err.code !== 0) { 85 console.error(`Callback Error, errorCode: ${err.code}`); 86 return; 87 } 88 console.info(`isDeviceSwitched: ${autoDeviceSwitchStatus.isDeviceSwitched}, isDeviceCapabilityChanged: ${autoDeviceSwitchStatus.isDeviceCapabilityChanged}`); 89} 90 91function registerAutoDeviceSwitchStatus(photoSession: camera.PhotoSession): void { 92 photoSession.on('autoDeviceSwitchStatusChange', callback); 93} 94function unregisterAutoDeviceSwitchStatus(photoSession: camera.PhotoSession): void { 95 photoSession.off('autoDeviceSwitchStatusChange', callback); 96} 97``` 98 99## Sample Code 100```ts 101import { camera } from '@kit.CameraKit'; 102import { BusinessError } from '@kit.BasicServicesKit'; 103import { abilityAccessCtrl } from '@kit.AbilityKit'; 104 105const TAG = 'AutoSwitchCameraDemo '; 106 107@Entry 108@Component 109struct Index { 110 @State isShow: boolean = false; 111 @State reloadXComponentFlag: boolean = false; 112 private mXComponentController: XComponentController = new XComponentController(); 113 private mXComponentOptions: XComponentOptions = { 114 type: XComponentType.SURFACE, 115 controller: this.mXComponentController 116 } 117 private mSurfaceId: string = ''; 118 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 119 private mCameraManager: camera.CameraManager | undefined = undefined; 120 // Select the surface width and height as required. 121 private surfaceRect: SurfaceRect = { 122 surfaceWidth: 1080, 123 surfaceHeight: 1920 124 }; 125 private curCameraDevice: camera.CameraDevice | undefined = undefined; 126 private mCameraInput: camera.CameraInput | undefined = undefined; 127 private mPreviewOutput: camera.PreviewOutput | undefined = undefined; 128 private mPhotoSession: camera.PhotoSession | undefined = undefined; 129 // One of the recommended preview resolutions. 130 private previewProfileObj: camera.Profile = { 131 format: 1003, 132 size: { 133 width: 1920, 134 height: 1080 135 } 136 }; 137 private mContext: Context | undefined = undefined; 138 autoDeviceSwitchCallback: (err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus) => void = 139 (err: BusinessError, autoDeviceSwitchStatus: camera.AutoDeviceSwitchStatus) => { 140 if (err !== undefined && err.code !== 0) { 141 console.error(`${TAG} Callback Error, errorCode: ${err.code}`); 142 return; 143 } 144 console.info(`${TAG} isDeviceSwitched: ${autoDeviceSwitchStatus.isDeviceSwitched}, isDeviceCapabilityChanged: ${autoDeviceSwitchStatus.isDeviceCapabilityChanged}`); 145 } 146 147 requestPermissionsFn(): void { 148 let atManager = abilityAccessCtrl.createAtManager(); 149 atManager.requestPermissionsFromUser(this.mContext, [ 150 'ohos.permission.CAMERA' 151 ]).then((): void => { 152 this.isShow = true; 153 }).catch((error: BusinessError): void => { 154 console.error(TAG + 'ohos.permission.CAMERA no permission.'); 155 }); 156 } 157 158 initContext(): void { 159 let uiContext = this.getUIContext(); 160 this.mContext = uiContext.getHostContext(); 161 } 162 163 initCameraManager(): void { 164 this.mCameraManager = camera.getCameraManager(this.mContext); 165 } 166 167 aboutToAppear(): void { 168 console.log(TAG + 'aboutToAppear is called'); 169 this.initContext(); 170 this.requestPermissionsFn(); 171 this.initCameraManager(); 172 } 173 174 async aboutToDisappear(): Promise<void> { 175 await this.releaseCamera(); 176 } 177 178 async onPageShow(): Promise<void> { 179 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 180 } 181 182 async releaseCamera(): Promise<void> { 183 // Stop the session. 184 try { 185 await this.mPhotoSession?.stop(); 186 } catch (error) { 187 let err = error as BusinessError; 188 console.error(TAG + 'Failed to stop session, errorCode = ' + err.code); 189 } 190 191 // Release the camera input stream. 192 try { 193 await this.mCameraInput?.close(); 194 } catch (error) { 195 let err = error as BusinessError; 196 console.error(TAG + 'Failed to close device, errorCode = ' + err.code); 197 } 198 199 // Release the preview output stream. 200 try { 201 await this.mPreviewOutput?.release(); 202 } catch (error) { 203 let err = error as BusinessError; 204 console.error(TAG + 'Failed to release previewOutput, errorCode = ' + err.code); 205 } 206 207 this.mPreviewOutput = undefined; 208 209 // Release the session. 210 try { 211 await this.mPhotoSession?.release(); 212 } catch (error) { 213 let err = error as BusinessError; 214 console.error(TAG + 'Failed to release photoSession, errorCode = ' + err.code); 215 } 216 217 // Set the session to null. 218 this.mPhotoSession = undefined; 219 } 220 221 async loadXComponent(): Promise<void> { 222 this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId(); 223 console.info(TAG + `mCameraPosition: ${this.mCameraPosition}`) 224 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 225 } 226 227 getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined { 228 let previewProfiles = cameraOutputCapability.previewProfiles; 229 if (previewProfiles.length < 1) { 230 return undefined; 231 } 232 let index = previewProfiles.findIndex((previewProfile: camera.Profile) => { 233 return previewProfile.size.width === this.previewProfileObj.size.width && 234 previewProfile.size.height === this.previewProfileObj.size.height && 235 previewProfile.format === this.previewProfileObj.format; 236 }) 237 if (index === -1) { 238 return undefined; 239 } 240 return previewProfiles[index]; 241 } 242 243 async initCamera(surfaceId: string, cameraPosition: camera.CameraPosition, 244 connectionType: camera.ConnectionType = camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN): Promise<void> { 245 await this.releaseCamera(); 246 // Create a CameraManager object. 247 if (!this.mCameraManager) { 248 console.error(TAG + 'camera.getCameraManager error'); 249 return; 250 } 251 252 // Obtain the camera list. 253 let cameraArray: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras(); 254 if (cameraArray.length <= 0) { 255 console.error(TAG + 'cameraManager.getSupportedCameras error'); 256 return; 257 } 258 259 for (let index = 0; index < cameraArray.length; index++) { 260 console.info(TAG + 'cameraId : ' + cameraArray[index].cameraId); // Obtain the camera ID. 261 console.info(TAG + 'cameraPosition : ' + cameraArray[index].cameraPosition); // Obtain the camera position. 262 console.info(TAG + 'cameraType : ' + cameraArray[index].cameraType); // Obtain the camera type. 263 console.info(TAG + 'connectionType : ' + cameraArray[index].connectionType); // Obtain the camera connection type. 264 } 265 266 let deviceIndex = cameraArray.findIndex((cameraDevice: camera.CameraDevice) => { 267 return cameraDevice.cameraPosition === cameraPosition && cameraDevice.connectionType === connectionType; 268 }) 269 // If no camera is found at the specified position, you can select another camera. Handle the situation based on the specific scenario. 270 if (deviceIndex === -1) { 271 deviceIndex = 0; 272 console.error(TAG + 'not found camera'); 273 } 274 this.curCameraDevice = cameraArray[deviceIndex]; 275 276 // Create a camera input stream. 277 try { 278 this.mCameraInput = this.mCameraManager.createCameraInput(this.curCameraDevice); 279 } catch (error) { 280 let err = error as BusinessError; 281 console.error(TAG + 'Failed to createCameraInput errorCode = ' + err.code); 282 } 283 if (this.mCameraInput === undefined) { 284 return; 285 } 286 287 // Open the camera. 288 try { 289 await this.mCameraInput.open(); 290 } catch (error) { 291 let err = error as BusinessError; 292 console.error(TAG + 'Failed to open device, errorCode = ' + err.code); 293 } 294 295 // Obtain the supported modes. 296 let sceneModes: Array<camera.SceneMode> = this.mCameraManager.getSupportedSceneModes(this.curCameraDevice); 297 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 298 if (!isSupportPhotoMode) { 299 console.error(TAG + 'photo mode not support'); 300 return; 301 } 302 303 // Obtain the output stream capability supported by the camera. 304 let cameraOutputCapability: camera.CameraOutputCapability = 305 this.mCameraManager.getSupportedOutputCapability(this.curCameraDevice, camera.SceneMode.NORMAL_PHOTO); 306 if (!cameraOutputCapability) { 307 console.error(TAG + 'cameraManager.getSupportedOutputCapability error'); 308 return; 309 } 310 console.info(TAG + 'outputCapability: ' + JSON.stringify(cameraOutputCapability)); 311 let previewProfile = this.getPreviewProfile(cameraOutputCapability); 312 if (previewProfile === undefined) { 313 console.error(TAG + 'The resolution of the current preview stream is not supported.'); 314 return; 315 } 316 this.previewProfileObj = previewProfile; 317 318 // Create a preview output stream. For details about the surfaceId parameter, see the XComponent. The preview stream uses the surface provided by the XComponent. 319 try { 320 this.mPreviewOutput = this.mCameraManager.createPreviewOutput(this.previewProfileObj, surfaceId); 321 } catch (error) { 322 let err = error as BusinessError; 323 console.error(TAG + `Failed to create the PreviewOutput instance. error code: ${err.code}`); 324 } 325 if (this.mPreviewOutput === undefined) { 326 return; 327 } 328 329 // Create a session. 330 try { 331 this.mPhotoSession = this.mCameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 332 } catch (error) { 333 let err = error as BusinessError; 334 console.error(TAG + 'Failed to create the session instance. errorCode = ' + err.code); 335 } 336 if (this.mPhotoSession === undefined) { 337 return; 338 } 339 if (this.mPhotoSession.isAutoDeviceSwitchSupported()) { 340 this.mPhotoSession.enableAutoDeviceSwitch(true); 341 this.mPhotoSession.on('autoDeviceSwitchStatusChange', this.autoDeviceSwitchCallback); 342 } 343 // Start configuration for the session. 344 try { 345 this.mPhotoSession.beginConfig(); 346 } catch (error) { 347 let err = error as BusinessError; 348 console.error(TAG + 'Failed to beginConfig. errorCode = ' + err.code); 349 } 350 351 // Add the camera input stream to the session. 352 try { 353 this.mPhotoSession.addInput(this.mCameraInput); 354 } catch (error) { 355 let err = error as BusinessError; 356 console.error(TAG + 'Failed to addInput. errorCode = ' + err.code); 357 } 358 359 // Add the preview output stream to the session. 360 try { 361 this.mPhotoSession.addOutput(this.mPreviewOutput); 362 } catch (error) { 363 let err = error as BusinessError; 364 console.error(TAG + 'Failed to addOutput(previewOutput). errorCode = ' + err.code); 365 } 366 367 // Commit the session configuration. 368 try { 369 await this.mPhotoSession.commitConfig(); 370 } catch (error) { 371 let err = error as BusinessError; 372 console.error(TAG + 'Failed to commit session configuration, errorCode = ' + err.code); 373 } 374 375 // Start the session. 376 try { 377 await this.mPhotoSession.start() 378 } catch (error) { 379 let err = error as BusinessError; 380 console.error(TAG + 'Failed to start session. errorCode = ' + err.code); 381 } 382 } 383 384 build() { 385 if (this.isShow) { 386 Stack() { 387 XComponent(this.mXComponentOptions) 388 .onLoad(async () => { 389 await this.loadXComponent(); 390 }) 391 .width(this.getUIContext().px2vp(1080)) 392 .height(this.getUIContext().px2vp(1920)) 393 Text('Switch camera') 394 .size({ width: 80, height: 48 }) 395 .position({ x: 1, y: 1 }) 396 .backgroundColor(Color.White) 397 .textAlign(TextAlign.Center) 398 .borderRadius(24) 399 .onClick(async () => { 400 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 401 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 402 await this.loadXComponent(); 403 }) 404 } 405 .size({ width: '100%', height: '100%' }) 406 .backgroundColor(Color.Black) 407 } 408 } 409} 410``` 411