1# Deferred Photo Delivery Sample (ArkTS)
2
3Before developing a camera application, request permissions by following the instructions provided in [Camera Development Preparations](camera-preparation.md).
4
5This topic provides sample code that covers the complete deferred photo delivery process to help you understand the complete API calling sequence.
6
7Before referring to the sample code, you are advised to read [Deferred Photo Delivery (ArkTS)](camera-deferred-capture.md), [Device Input Management](camera-device-input.md), [Camera Session Management](camera-session-management.md), and [Photo Capture](camera-shooting.md).
8
9## Development Process
10
11After obtaining the output stream capabilities supported by the camera, create a photo stream. The development process is as follows:
12
13![deferred-capture-development-process](figures/deferred-capture-development-process.png)
14
15## Sample Code
16
17For details about how to obtain the context, see [Obtaining the Context of UIAbility](../../application-models/uiability-usage.md#obtaining-the-context-of-uiability).
18
19```ts
20import { camera } from '@kit.CameraKit';
21import { BusinessError } from '@kit.BasicServicesKit';
22import { common } from '@kit.AbilityKit';
23import { photoAccessHelper } from '@kit.MediaLibraryKit';
24
25let context = getContext(this);
26let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
27
28class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler<ArrayBuffer> {
29  onDataPrepared(data: ArrayBuffer) {
30    if (data === undefined) {
31      console.error('Error occurred when preparing data');
32      return;
33    }
34    console.info('on image data prepared');
35  }
36}
37
38async function mediaLibRequestBuffer(photoAsset: photoAccessHelper.PhotoAsset) {
39  let requestOptions: photoAccessHelper.RequestOptions = {
40    deliveryMode: photoAccessHelper.DeliveryMode.HIGH_QUALITY_MODE,
41  }
42  const handler = new MediaDataHandler();
43  await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler);
44  console.info('requestImageData successfully');
45}
46
47async function mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset): Promise<void> {
48  try {
49    let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset);
50    assetChangeRequest.saveCameraPhoto();
51    await phAccessHelper.applyChanges(assetChangeRequest);
52    console.info('apply saveCameraPhoto successfully');
53  } catch (err) {
54    console.error(`apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`);
55  }
56}
57
58function setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {
59  // After the callback is set, call capture() of photoOutput to trigger the callback upon the receiving of a low-quality image.
60  photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): void => {
61    console.info('getPhotoAsset start');
62    console.info(`err: ${JSON.stringify(err)}`);
63    if ((err !== undefined && err.code !== 0) || photoAsset === undefined) {
64      console.error('getPhotoAsset failed');
65      return;
66    }
67    // Call the mediaLibrary flush API to save the low-quality image in the first phase. After the real image in the second phase is ready, the mediaLibrary proactively replaces the image flushed.
68    mediaLibSavePhoto(photoAsset);
69    // Call the mediaLibrary API to register the buffer callback to receive low-quality or high-quality images for custom processing.
70    mediaLibRequestBuffer(photoAsset);
71  });
72}
73
74async function deferredCaptureCase(baseContext: common.BaseContext, surfaceId: string): Promise<void> {
75  // Create a CameraManager object.
76  let cameraManager: camera.CameraManager = camera.getCameraManager(baseContext);
77  if (!cameraManager) {
78    console.error("camera.getCameraManager error");
79    return;
80  }
81  // Listen for camera status changes.
82  cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {
83    if (err !== undefined && err.code !== 0) {
84      console.error('cameraStatus with errorCode = ' + err.code);
85      return;
86    }
87    console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);
88    console.info(`status: ${cameraStatusInfo.status}`);
89  });
90
91  // Obtain the camera list.
92  let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
93  if (cameraArray.length <= 0) {
94    console.error("cameraManager.getSupportedCameras error");
95    return;
96  }
97
98  for (let index = 0; index < cameraArray.length; index++) {
99    console.info('cameraId : ' + cameraArray[index].cameraId);                          // Obtain the camera ID.
100    console.info('cameraPosition : ' + cameraArray[index].cameraPosition);              // Obtain the camera position.
101    console.info('cameraType : ' + cameraArray[index].cameraType);                      // Obtain the camera type.
102    console.info('connectionType : ' + cameraArray[index].connectionType);              // Obtain the camera connection type.
103  }
104
105  // Create a camera input stream.
106  let cameraInput: camera.CameraInput | undefined = undefined;
107  try {
108    cameraInput = cameraManager.createCameraInput(cameraArray[0]);
109  } catch (error) {
110    let err = error as BusinessError;
111    console.error('Failed to createCameraInput errorCode = ' + err.code);
112  }
113  if (cameraInput === undefined) {
114    return;
115  }
116
117  // Listen for camera input errors.
118  let cameraDevice: camera.CameraDevice = cameraArray[0];
119  cameraInput.on('error', cameraDevice, (error: BusinessError) => {
120    console.error(`Camera input error code: ${error.code}`);
121  })
122
123  // Open a camera.
124  await cameraInput.open();
125
126  // Obtain the supported modes.
127  let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
128  let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
129  if (!isSupportPhotoMode) {
130    console.error('photo mode not support');
131    return;
132  }
133  // Obtain the output streams supported by the camera.
134  let cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO);
135  if (!cameraOutputCap) {
136    console.error("cameraManager.getSupportedOutputCapability error");
137    return;
138  }
139  console.info("outputCapability: " + JSON.stringify(cameraOutputCap));
140
141  let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;
142  if (!previewProfilesArray) {
143    console.error("createOutput previewProfilesArray == null || undefined");
144  }
145
146  let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;
147  if (!photoProfilesArray) {
148    console.error("createOutput photoProfilesArray == null || undefined");
149  }
150
151  // Create a preview output stream. For details about the surfaceId parameter, see the XComponent. The preview stream uses the surface provided by the XComponent.
152  let previewOutput: camera.PreviewOutput | undefined = undefined;
153  try {
154    previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
155  } catch (error) {
156    let err = error as BusinessError;
157    console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`);
158  }
159  if (previewOutput === undefined) {
160    return;
161  }
162  // Listen for preview output errors.
163  previewOutput.on('error', (error: BusinessError) => {
164    console.error(`Preview output error code: ${error.code}`);
165  });
166
167  // Create a photo output stream.
168  let photoOutput: camera.PhotoOutput | undefined = undefined;
169  try {
170    photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]);
171  } catch (error) {
172    let err = error as BusinessError;
173    console.error('Failed to createPhotoOutput errorCode = ' + err.code);
174  }
175  if (photoOutput === undefined) {
176    return;
177  }
178
179  // Register the photoAssetAvailable callback.
180  setPhotoOutputCb(photoOutput);
181
182  // Create a session.
183  let photoSession: camera.PhotoSession | undefined = undefined;
184  try {
185    photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
186  } catch (error) {
187    let err = error as BusinessError;
188    console.error('Failed to create the session instance. errorCode = ' + err.code);
189  }
190  if (photoSession === undefined) {
191    return;
192  }
193  // Listen for session errors.
194  photoSession.on('error', (error: BusinessError) => {
195    console.error(`Capture session error code: ${error.code}`);
196  });
197
198  // Start configuration for the session.
199  try {
200    photoSession.beginConfig();
201  } catch (error) {
202    let err = error as BusinessError;
203    console.error('Failed to beginConfig. errorCode = ' + err.code);
204  }
205
206  // Add the camera input stream to the session.
207  try {
208    photoSession.addInput(cameraInput);
209  } catch (error) {
210    let err = error as BusinessError;
211    console.error('Failed to addInput. errorCode = ' + err.code);
212  }
213
214  // Add the preview output stream to the session.
215  try {
216    photoSession.addOutput(previewOutput);
217  } catch (error) {
218    let err = error as BusinessError;
219    console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code);
220  }
221
222  // Add the photo output stream to the session.
223  try {
224    photoSession.addOutput(photoOutput);
225  } catch (error) {
226    let err = error as BusinessError;
227    console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code);
228  }
229
230  // Commit the session configuration.
231  await photoSession.commitConfig();
232
233  // Start the session.
234  await photoSession.start().then(() => {
235    console.info('Promise returned to indicate the session start success.');
236  });
237  // Check whether the camera has flash.
238  let flashStatus: boolean = false;
239  try {
240    flashStatus = photoSession.hasFlash();
241  } catch (error) {
242    let err = error as BusinessError;
243    console.error('Failed to hasFlash. errorCode = ' + err.code);
244  }
245  console.info('Returned with the flash light support status:' + flashStatus);
246
247  if (flashStatus) {
248    // Check whether the auto flash mode is supported.
249    let flashModeStatus: boolean = false;
250    try {
251      let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO);
252      flashModeStatus = status;
253    } catch (error) {
254      let err = error as BusinessError;
255      console.error('Failed to check whether the flash mode is supported. errorCode = ' + err.code);
256    }
257    if(flashModeStatus) {
258      // Set the flash mode to auto.
259      try {
260        photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
261      } catch (error) {
262        let err = error as BusinessError;
263        console.error('Failed to set the flash mode. errorCode = ' + err.code);
264      }
265    }
266  }
267
268  // Check whether the continuous auto focus is supported.
269  let focusModeStatus: boolean = false;
270  try {
271    let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
272    focusModeStatus = status;
273  } catch (error) {
274    let err = error as BusinessError;
275    console.error('Failed to check whether the focus mode is supported. errorCode = ' + err.code);
276  }
277
278  if (focusModeStatus) {
279    // Set the focus mode to continuous auto focus.
280    try {
281      photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);
282    } catch (error) {
283      let err = error as BusinessError;
284      console.error('Failed to set the focus mode. errorCode = ' + err.code);
285    }
286  }
287
288  // Obtain the zoom ratio range supported by the camera.
289  let zoomRatioRange: Array<number> = [];
290  try {
291    zoomRatioRange = photoSession.getZoomRatioRange();
292  } catch (error) {
293    let err = error as BusinessError;
294    console.error('Failed to get the zoom ratio range. errorCode = ' + err.code);
295  }
296  if (zoomRatioRange.length <= 0) {
297    return;
298  }
299  // Set a zoom ratio.
300  try {
301    photoSession.setZoomRatio(zoomRatioRange[0]);
302  } catch (error) {
303    let err = error as BusinessError;
304    console.error('Failed to set the zoom ratio value. errorCode = ' + err.code);
305  }
306  let photoCaptureSetting: camera.PhotoCaptureSetting = {
307    quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // Set the photo quality to high.
308    rotation: camera.ImageRotation.ROTATION_0 // Set the rotation angle of the photo to 0.
309  }
310  // Use the current photo capture settings to take photos.
311  photoOutput.capture(photoCaptureSetting, (err: BusinessError) => {
312    if (err) {
313      console.error(`Failed to capture the photo ${err.message}`);
314      return;
315    }
316    console.info('Callback invoked to indicate the photo capture request success.');
317  });
318
319  // After the photo capture is complete, call the following APIs to close the camera and release the session. Do not release the session before the photo capture is complete.
320  // Stop the session.
321  await photoSession.stop();
322
323  // Release the camera input stream.
324  await cameraInput.close();
325
326  // Release the preview output stream.
327  await previewOutput.release();
328
329  // Release the photo output stream.
330  await photoOutput.release();
331
332  // Release the session.
333  await photoSession.release();
334
335  // Set the session to null.
336  photoSession = undefined;
337}
338```
339