1# Adapting to Camera Changes in Different Folding States (ArkTS) 2 3Before developing a camera application, request permissions by following the instructions provided in [Camera Development Preparations](camera-preparation.md). 4 5Cameras that a foldable device can use vary according to its folding states. To deliver a smooth user experience during transitions between folded and unfolded states, an application can call [CameraManager.on('foldStatusChange')](../../reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12) or [display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10) to listen for folding state changes of the device, call [CameraManager.getSupportedCameras](../../reference/apis-camera-kit/js-apis-camera.md#getsupportedcameras) to obtain the available cameras in the current state, and make adaptations accordingly. 6 7Read [Camera](../../reference/apis-camera-kit/js-apis-camera.md) for the API reference. 8 9## Creating an XComponent 10Use two [XComponents](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md) to present the folded and unfolded states, respectively. This prevents the previous camera feed from lingering on the screen if the camera is not properly closed during folding state transition. 11 12 ```ts 13 @Entry 14 @Component 15 struct Index { 16 @State reloadXComponentFlag: boolean = false; 17 @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0; 18 private mXComponentController: XComponentController = new XComponentController(); 19 private mXComponentOptions: XComponentOptions = { 20 type: XComponentType.SURFACE, 21 controller: this.mXComponentController 22 } 23 24 reloadXComponent() { 25 this.reloadXComponentFlag = !this.reloadXComponentFlag; 26 } 27 28 async loadXComponent() { 29 // Initialize the XComponent. 30 } 31 32 build() { 33 Stack() { 34 if (this.reloadXComponentFlag) { 35 XComponent(this.mXComponentOptions) 36 .onLoad(async () => { 37 await this.loadXComponent(); 38 }) 39 .width(px2vp(1080)) 40 .height(px2vp(1920)) 41 } else { 42 XComponent(this.mXComponentOptions) 43 .onLoad(async () => { 44 await this.loadXComponent(); 45 }) 46 .width(px2vp(1080)) 47 .height(px2vp(1920)) 48 } 49 } 50 .size({ width: '100%', height: '100%' }) 51 .backgroundColor(Color.Black) 52 } 53 } 54 ``` 55## Obtaining the Device Folding State 56 57You can use either of the following solutions. 58 59- Solution 1: Call [CameraManager.on('foldStatusChange')](../../../application-dev/reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12) provided by the camera framework to listen for device folding state changes. 60 ```ts 61 import { camera } from '@kit.CameraKit'; 62 import { BusinessError } from '@kit.BasicServicesKit'; 63 64 let cameraManager = camera.getCameraManager(getContext()) 65 66 function registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) { 67 // The foldStatus variable is used to control the display of the XComponent. 68 AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus); 69 } 70 71 cameraManager.on('foldStatusChange', registerFoldStatusChanged); 72 //cameraManager.off('foldStatusChange', registerFoldStatusChanged); 73 ``` 74- Solution 2: Call [display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10) to listen for device folding state changes. 75 ```ts 76 import { display } from '@kit.ArkUI'; 77 let preFoldStatus: display.FoldStatus = display.getFoldStatus(); 78 display.on('foldStatusChange', (foldStatus: display.FoldStatus) => { 79 // The supported cameras returned by the camera framework are the same when the device is in the FOLD_STATUS_HALF_FOLDED or FOLD_STATUS_EXPANDED state. Therefore, you do not need to reconfigure streams during the transition between these two states. 80 if ((preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED && 81 foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) || 82 (preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED && 83 foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) { 84 preFoldStatus = foldStatus; 85 return; 86 } 87 preFoldStatus = foldStatus; 88 // The foldStatus variable is used to control the display of the XComponent. 89 AppStorage.setOrCreate<number>('foldStatus', foldStatus); 90 }) 91 ``` 92 93## Example 94```ts 95import { camera } from '@kit.CameraKit'; 96import { BusinessError } from '@kit.BasicServicesKit'; 97import { abilityAccessCtrl } from '@kit.AbilityKit'; 98import { display } from '@kit.ArkUI'; 99 100let context = getContext(this); 101 102const TAG = 'FoldScreenCameraAdaptationDemo '; 103 104@Entry 105@Component 106struct Index { 107 @State isShow: boolean = false; 108 @State reloadXComponentFlag: boolean = false; 109 @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0; 110 private mXComponentController: XComponentController = new XComponentController(); 111 private mXComponentOptions: XComponentOptions = { 112 type: XComponentType.SURFACE, 113 controller: this.mXComponentController 114 } 115 private mSurfaceId: string = ''; 116 private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; 117 private mCameraManager: camera.CameraManager = camera.getCameraManager(context); 118 // Select the surface width and height as required. 119 private surfaceRect: SurfaceRect = { 120 surfaceWidth: 1080, 121 surfaceHeight: 1920 122 }; 123 private curCameraDevice: camera.CameraDevice | undefined = undefined; 124 private mCameraInput: camera.CameraInput | undefined = undefined; 125 private mPreviewOutput: camera.PreviewOutput | undefined = undefined; 126 private mPhotoSession: camera.PhotoSession | undefined = undefined; 127 // One of the recommended preview resolutions 128 private previewProfileObj: camera.Profile = { 129 format: 1003, 130 size: { 131 width: 1920, 132 height: 1080 133 } 134 }; 135 136 private preFoldStatus: display.FoldStatus = display.getFoldStatus(); 137 // Listen for the foldable screen status. You can use cameraManager.on(type: 'foldStatusChange', callback: AsyncCallback<FoldStatusInfo>): void; 138 // or display.on(type: 'foldStatusChange', callback: Callback<FoldStatus>): void;. 139 private foldStatusCallback = 140 (err: BusinessError, info: camera.FoldStatusInfo): void => this.registerFoldStatusChanged(err, info); 141 private displayFoldStatusCallback = 142 (foldStatus: display.FoldStatus): void => this.onDisplayFoldStatusChange(foldStatus); 143 144 145 registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) { 146 console.info(TAG + 'foldStatusChanged foldStatus: ' + foldStatusInfo.foldStatus); 147 for (let i = 0; i < foldStatusInfo.supportedCameras.length; i++) { 148 console.info(TAG + 149 `foldStatusChanged camera[${i}]: ${foldStatusInfo.supportedCameras[i].cameraId},cameraPosition: ${foldStatusInfo.supportedCameras[i].cameraPosition}`); 150 } 151 AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus); 152 } 153 154 onDisplayFoldStatusChange(foldStatus: display.FoldStatus): void { 155 console.error(TAG + `onDisplayFoldStatusChange foldStatus: ${foldStatus}`); 156 if ((this.preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED && 157 foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) || 158 (this.preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED && 159 foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) { 160 this.preFoldStatus = foldStatus; 161 return; 162 } 163 this.preFoldStatus = foldStatus; 164 // Obtain the currently opened camera. If the rear camera is opened, its use is not affected when the device is folded. 165 if (!this.curCameraDevice) { 166 return; 167 } 168 // The foldStatus variable is used to control the display of the XComponent. 169 AppStorage.setOrCreate<number>('foldStatus', foldStatus); 170 } 171 172 requestPermissionsFn(): void { 173 let atManager = abilityAccessCtrl.createAtManager(); 174 atManager.requestPermissionsFromUser(context, [ 175 'ohos.permission.CAMERA' 176 ]).then((): void => { 177 this.isShow = true; 178 }).catch((error: BusinessError): void => { 179 console.error(TAG + 'ohos.permission.CAMERA no permission.'); 180 }); 181 } 182 183 aboutToAppear(): void { 184 console.log(TAG + 'aboutToAppear is called'); 185 this.requestPermissionsFn(); 186 this.onFoldStatusChange(); 187 } 188 189 async aboutToDisappear(): Promise<void> { 190 await this.releaseCamera(); 191 // Stop the listening. 192 this.offFoldStatusChange(); 193 } 194 195 async onPageShow(): Promise<void> { 196 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 197 } 198 199 async releaseCamera(): Promise<void> { 200 // Stop the session. 201 try { 202 await this.mPhotoSession?.stop(); 203 } catch (error) { 204 let err = error as BusinessError; 205 console.error(TAG + 'Failed to stop session, errorCode = ' + err.code); 206 } 207 208 // Release the camera input stream. 209 try { 210 await this.mCameraInput?.close(); 211 } catch (error) { 212 let err = error as BusinessError; 213 console.error(TAG + 'Failed to close device, errorCode = ' + err.code); 214 } 215 216 // Release the preview output stream. 217 try { 218 await this.mPreviewOutput?.release(); 219 } catch (error) { 220 let err = error as BusinessError; 221 console.error(TAG + 'Failed to release previewOutput, errorCode = ' + err.code); 222 } 223 224 this.mPreviewOutput = undefined; 225 226 // Release the session. 227 try { 228 await this.mPhotoSession?.release(); 229 } catch (error) { 230 let err = error as BusinessError; 231 console.error(TAG + 'Failed to release photoSession, errorCode = ' + err.code); 232 } 233 234 // Set the session to null. 235 this.mPhotoSession = undefined; 236 } 237 238 onFoldStatusChange(): void { 239 this.mCameraManager.on('foldStatusChange', this.foldStatusCallback); 240 // display.on('foldStatusChange', this.displayFoldStatusCallback); 241 } 242 243 offFoldStatusChange(): void { 244 this.mCameraManager.off('foldStatusChange', this.foldStatusCallback); 245 // display.off('foldStatusChange', this.displayFoldStatusCallback); 246 } 247 248 reloadXComponent(): void { 249 this.reloadXComponentFlag = !this.reloadXComponentFlag; 250 } 251 252 async loadXComponent(): Promise<void> { 253 this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId(); 254 this.mXComponentController.setXComponentSurfaceRect(this.surfaceRect); 255 console.info(TAG + `mCameraPosition: ${this.mCameraPosition}`) 256 await this.initCamera(this.mSurfaceId, this.mCameraPosition); 257 } 258 259 getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined { 260 let previewProfiles = cameraOutputCapability.previewProfiles; 261 if (previewProfiles.length < 1) { 262 return undefined; 263 } 264 let index = previewProfiles.findIndex((previewProfile: camera.Profile) => { 265 return previewProfile.size.width === this.previewProfileObj.size.width && 266 previewProfile.size.height === this.previewProfileObj.size.height && 267 previewProfile.format === this.previewProfileObj.format; 268 }) 269 if (index === -1) { 270 return undefined; 271 } 272 return previewProfiles[index]; 273 } 274 275 async initCamera(surfaceId: string, cameraPosition: camera.CameraPosition): Promise<void> { 276 await this.releaseCamera(); 277 // Create a CameraManager object. 278 if (!this.mCameraManager) { 279 console.error(TAG + 'camera.getCameraManager error'); 280 return; 281 } 282 283 // Obtain the camera list. 284 let cameraArray: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras(); 285 if (cameraArray.length <= 0) { 286 console.error(TAG + 'cameraManager.getSupportedCameras error'); 287 return; 288 } 289 290 for (let index = 0; index < cameraArray.length; index++) { 291 console.info(TAG + 'cameraId : ' + cameraArray[index].cameraId); // Obtain the camera ID. 292 console.info(TAG + 'cameraPosition : ' + cameraArray[index].cameraPosition); // Obtain the camera position. 293 console.info(TAG + 'cameraType : ' + cameraArray[index].cameraType); // Obtain the camera type. 294 console.info(TAG + 'connectionType : ' + cameraArray[index].connectionType); // Obtain the camera connection type. 295 } 296 297 let deviceIndex = cameraArray.findIndex((cameraDevice: camera.CameraDevice) => { 298 return cameraDevice.cameraPosition === cameraPosition; 299 }) 300 if (deviceIndex === -1) { 301 deviceIndex = 0; 302 console.error(TAG + 'not found camera'); 303 } 304 this.curCameraDevice = cameraArray[deviceIndex]; 305 306 // Create a camera input stream. 307 try { 308 this.mCameraInput = this.mCameraManager.createCameraInput(this.curCameraDevice); 309 } catch (error) { 310 let err = error as BusinessError; 311 console.error(TAG + 'Failed to createCameraInput errorCode = ' + err.code); 312 } 313 if (this.mCameraInput === undefined) { 314 return; 315 } 316 317 // Open a camera. 318 try { 319 await this.mCameraInput.open(); 320 } catch (error) { 321 let err = error as BusinessError; 322 console.error(TAG + 'Failed to open device, errorCode = ' + err.code); 323 } 324 325 // Obtain the supported scene modes. 326 let sceneModes: Array<camera.SceneMode> = this.mCameraManager.getSupportedSceneModes(this.curCameraDevice); 327 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 328 if (!isSupportPhotoMode) { 329 console.error(TAG + 'photo mode not support'); 330 return; 331 } 332 333 // Obtain the output streams supported by the camera device. 334 let cameraOutputCapability: camera.CameraOutputCapability = 335 this.mCameraManager.getSupportedOutputCapability(this.curCameraDevice, camera.SceneMode.NORMAL_PHOTO); 336 if (!cameraOutputCapability) { 337 console.error(TAG + 'cameraManager.getSupportedOutputCapability error'); 338 return; 339 } 340 console.info(TAG + 'outputCapability: ' + JSON.stringify(cameraOutputCapability)); 341 let previewProfile = this.getPreviewProfile(cameraOutputCapability); 342 if (previewProfile === undefined) { 343 console.error(TAG + 'The resolution of the current preview stream is not supported.'); 344 return; 345 } 346 this.previewProfileObj = previewProfile; 347 348 // Create a preview output stream. For details about the surfaceId parameter, see the XComponent. The preview stream uses the surface provided by the XComponent. 349 try { 350 this.mPreviewOutput = this.mCameraManager.createPreviewOutput(this.previewProfileObj, surfaceId); 351 } catch (error) { 352 let err = error as BusinessError; 353 console.error(TAG + `Failed to create the PreviewOutput instance. error code: ${err.code}`); 354 } 355 if (this.mPreviewOutput === undefined) { 356 return; 357 } 358 359 // Create a session. 360 try { 361 this.mPhotoSession = this.mCameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 362 } catch (error) { 363 let err = error as BusinessError; 364 console.error(TAG + 'Failed to create the session instance. errorCode = ' + err.code); 365 } 366 if (this.mPhotoSession === undefined) { 367 return; 368 } 369 370 // Start configuration for the session. 371 try { 372 this.mPhotoSession.beginConfig(); 373 } catch (error) { 374 let err = error as BusinessError; 375 console.error(TAG + 'Failed to beginConfig. errorCode = ' + err.code); 376 } 377 378 // Add the camera input stream to the session. 379 try { 380 this.mPhotoSession.addInput(this.mCameraInput); 381 } catch (error) { 382 let err = error as BusinessError; 383 console.error(TAG + 'Failed to addInput. errorCode = ' + err.code); 384 } 385 386 // Add the preview output stream to the session. 387 try { 388 this.mPhotoSession.addOutput(this.mPreviewOutput); 389 } catch (error) { 390 let err = error as BusinessError; 391 console.error(TAG + 'Failed to addOutput(previewOutput). errorCode = ' + err.code); 392 } 393 394 // Commit the session configuration. 395 try { 396 await this.mPhotoSession.commitConfig(); 397 } catch (error) { 398 let err = error as BusinessError; 399 console.error(TAG + 'Failed to commit session configuration, errorCode = ' + err.code); 400 } 401 402 // Start the session. 403 try { 404 await this.mPhotoSession.start() 405 } catch (error) { 406 let err = error as BusinessError; 407 console.error(TAG + 'Failed to start session. errorCode = ' + err.code); 408 } 409 } 410 411 build() { 412 if (this.isShow) { 413 Stack() { 414 if (this.reloadXComponentFlag) { 415 XComponent(this.mXComponentOptions) 416 .onLoad(async () => { 417 await this.loadXComponent(); 418 }) 419 .width(px2vp(1080)) 420 .height(px2vp(1920)) 421 } else { 422 XComponent(this.mXComponentOptions) 423 .onLoad(async () => { 424 await this.loadXComponent(); 425 }) 426 .width(px2vp(1080)) 427 .height(px2vp(1920)) 428 } 429 Text('Switch camera') 430 .size({ width: 80, height: 48 }) 431 .position({ x: 1, y: 1 }) 432 .backgroundColor(Color.White) 433 .textAlign(TextAlign.Center) 434 .borderRadius(24) 435 .onClick(async () => { 436 this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? 437 camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; 438 this.reloadXComponentFlag = !this.reloadXComponentFlag; 439 }) 440 } 441 .size({ width: '100%', height: '100%' }) 442 .backgroundColor(Color.Black) 443 } 444 } 445} 446``` 447