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