• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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