1# Deferred Photo Delivery Practices (ArkTS) 2<!--Kit: Camera Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @qano--> 5<!--SE: @leo_ysl--> 6<!--TSE: @xchaosioda--> 7 8Before developing a camera application, request permissions by following the instructions provided in [Requesting Camera Development Permissions](camera-preparation.md). 9 10This topic provides sample code that covers the complete deferred photo delivery process to help you understand the complete API calling sequence. 11 12Before 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). 13 14## Development Process 15 16After obtaining the output stream capabilities supported by the camera, create a photo stream. The development process is as follows: 17 18 19 20## Sample Code 21 22For details about how to obtain the context, see [Obtaining the Context of UIAbility](../../application-models/uiability-usage.md#obtaining-the-context-of-uiability). 23 24```ts 25import { camera } from '@kit.CameraKit'; 26import { BusinessError } from '@kit.BasicServicesKit'; 27import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; 28import { photoAccessHelper } from '@kit.MediaLibraryKit'; 29 30let photoSession: camera.PhotoSession | undefined = undefined; 31let cameraInput: camera.CameraInput | undefined = undefined; 32let previewOutput: camera.PreviewOutput | undefined = undefined; 33let photoOutput: camera.PhotoOutput | undefined = undefined; 34 35function getPhotoAccessHelper(context: Context): photoAccessHelper.PhotoAccessHelper { 36 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 37 return phAccessHelper; 38} 39 40class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler<ArrayBuffer> { 41 onDataPrepared(data: ArrayBuffer) { 42 if (data === undefined) { 43 console.error('Error occurred when preparing data'); 44 return; 45 } 46 console.info('on image data prepared'); 47 // Release the session after the photo buffer is obtained. Releasing it beforehand can lead to photo capture failures. 48 releaseCamSession(); 49 } 50} 51 52async function mediaLibRequestBuffer(photoAsset: photoAccessHelper.PhotoAsset, context: Context) { 53 let requestOptions: photoAccessHelper.RequestOptions = { 54 deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE, 55 } 56 const handler = new MediaDataHandler(); 57 await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler); 58 console.info('requestImageData successfully'); 59} 60 61async function mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset, 62 phAccessHelper: photoAccessHelper.PhotoAccessHelper): Promise<void> { 63 try { 64 let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = 65 new photoAccessHelper.MediaAssetChangeRequest(photoAsset); 66 assetChangeRequest.saveCameraPhoto(); 67 await phAccessHelper.applyChanges(assetChangeRequest); 68 console.info('apply saveCameraPhoto successfully'); 69 } catch (err) { 70 console.error(`apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`); 71 } 72} 73 74function setPhotoOutputCb(photoOutput: camera.PhotoOutput, context: Context): void { 75 // After the callback is set, call capture() of photoOutput to trigger the callback upon the receiving of a low-quality image. 76 photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): void => { 77 console.info('getPhotoAsset start'); 78 console.info(`err: ${JSON.stringify(err)}`); 79 if ((err !== undefined && err.code !== 0) || photoAsset === undefined) { 80 console.error('getPhotoAsset failed'); 81 return; 82 } 83 // 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. 84 mediaLibSavePhoto(photoAsset, getPhotoAccessHelper(context)); 85 // Call the mediaLibrary API to register the buffer callback to receive low-quality or high-quality images for custom processing. 86 mediaLibRequestBuffer(photoAsset, context); 87 }); 88} 89 90async function deferredCaptureCase(context: Context, surfaceId: string): Promise<void> { 91 // Create a CameraManager object. 92 let cameraManager: camera.CameraManager = camera.getCameraManager(context); 93 if (!cameraManager) { 94 console.error('camera.getCameraManager error'); 95 return; 96 } 97 // Listen for camera status changes. 98 cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => { 99 if (err !== undefined && err.code !== 0) { 100 console.error('cameraStatus with errorCode = ' + err.code); 101 return; 102 } 103 console.info(`camera : ${cameraStatusInfo.camera.cameraId}`); 104 console.info(`status: ${cameraStatusInfo.status}`); 105 }); 106 107 // Obtain the camera list. 108 let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras(); 109 if (cameraArray.length <= 0) { 110 console.error('cameraManager.getSupportedCameras error'); 111 return; 112 } 113 114 for (let index = 0; index < cameraArray.length; index++) { 115 console.info('cameraId : ' + cameraArray[index].cameraId); // Obtain the camera ID. 116 console.info('cameraPosition : ' + cameraArray[index].cameraPosition); // Obtain the camera position. 117 console.info('cameraType : ' + cameraArray[index].cameraType); // Obtain the camera type. 118 console.info('connectionType : ' + cameraArray[index].connectionType); // Obtain the camera connection type. 119 } 120 121 // Create a camera input stream. 122 try { 123 cameraInput = cameraManager.createCameraInput(cameraArray[0]); 124 } catch (error) { 125 let err = error as BusinessError; 126 console.error('Failed to createCameraInput errorCode = ' + err.code); 127 } 128 if (cameraInput === undefined) { 129 return; 130 } 131 132 // Listen for camera input errors. 133 let cameraDevice: camera.CameraDevice = cameraArray[0]; 134 cameraInput.on('error', cameraDevice, (error: BusinessError) => { 135 console.error(`Camera input error code: ${error.code}`); 136 }) 137 138 // Open the camera. 139 await cameraInput.open(); 140 141 // Obtain the supported modes. 142 let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]); 143 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 144 if (!isSupportPhotoMode) { 145 console.error('photo mode not support'); 146 return; 147 } 148 // Obtain the output stream capability supported by the camera. 149 let cameraOutputCap: camera.CameraOutputCapability = 150 cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO); 151 if (!cameraOutputCap) { 152 console.error('cameraManager.getSupportedOutputCapability error'); 153 return; 154 } 155 console.info('outputCapability: ' + JSON.stringify(cameraOutputCap)); 156 157 let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles; 158 if (!previewProfilesArray) { 159 console.error('createOutput previewProfilesArray == null || undefined'); 160 } 161 162 let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles; 163 if (!photoProfilesArray) { 164 console.error('createOutput photoProfilesArray == null || undefined'); 165 } 166 167 // Create a preview output stream. For details about the surfaceId parameter, see the XComponent. The preview stream uses the surface provided by the XComponent. 168 try { 169 previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId); 170 } catch (error) { 171 let err = error as BusinessError; 172 console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`); 173 } 174 if (previewOutput === undefined) { 175 return; 176 } 177 178 // Listen for preview output errors. 179 previewOutput.on('error', (error: BusinessError) => { 180 console.error(`Preview output error code: ${error.code}`); 181 }); 182 183 // Create a photo output stream. 184 try { 185 photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]); 186 } catch (error) { 187 let err = error as BusinessError; 188 console.error('Failed to createPhotoOutput errorCode = ' + err.code); 189 } 190 if (photoOutput === undefined) { 191 return; 192 } 193 194 // Register the photoAssetAvailable callback. 195 setPhotoOutputCb(photoOutput, context); 196 197 // Create a session. 198 try { 199 photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 200 } catch (error) { 201 let err = error as BusinessError; 202 console.error('Failed to create the session instance. errorCode = ' + err.code); 203 } 204 if (photoSession === undefined) { 205 return; 206 } 207 // Listen for session errors. 208 photoSession.on('error', (error: BusinessError) => { 209 console.error(`Capture session error code: ${error.code}`); 210 }); 211 212 // Start configuration for the session. 213 try { 214 photoSession.beginConfig(); 215 } catch (error) { 216 let err = error as BusinessError; 217 console.error('Failed to beginConfig. errorCode = ' + err.code); 218 } 219 220 // Add the camera input stream to the session. 221 try { 222 photoSession.addInput(cameraInput); 223 } catch (error) { 224 let err = error as BusinessError; 225 console.error('Failed to addInput. errorCode = ' + err.code); 226 } 227 228 // Add the preview output stream to the session. 229 try { 230 photoSession.addOutput(previewOutput); 231 } catch (error) { 232 let err = error as BusinessError; 233 console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code); 234 } 235 236 // Add the photo output stream to the session. 237 try { 238 photoSession.addOutput(photoOutput); 239 } catch (error) { 240 let err = error as BusinessError; 241 console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code); 242 } 243 244 // Commit the session configuration. 245 await photoSession.commitConfig(); 246 247 // Start the session. 248 await photoSession.start().then(() => { 249 console.info('Promise returned to indicate the session start success.'); 250 }); 251 // Check whether the camera has flash. 252 let flashStatus: boolean = false; 253 try { 254 flashStatus = photoSession.hasFlash(); 255 } catch (error) { 256 let err = error as BusinessError; 257 console.error('Failed to hasFlash. errorCode = ' + err.code); 258 } 259 console.info('Returned with the flash light support status:' + flashStatus); 260 261 if (flashStatus) { 262 // Check whether the auto flash mode is supported. 263 let flashModeStatus: boolean = false; 264 try { 265 let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO); 266 flashModeStatus = status; 267 } catch (error) { 268 let err = error as BusinessError; 269 console.error('Failed to check whether the flash mode is supported. errorCode = ' + err.code); 270 } 271 if (flashModeStatus) { 272 // Set the flash mode to auto. 273 try { 274 photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO); 275 } catch (error) { 276 let err = error as BusinessError; 277 console.error('Failed to set the flash mode. errorCode = ' + err.code); 278 } 279 } 280 } 281 282 // Check whether the continuous auto focus is supported. 283 let focusModeStatus: boolean = false; 284 try { 285 let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO); 286 focusModeStatus = status; 287 } catch (error) { 288 let err = error as BusinessError; 289 console.error('Failed to check whether the focus mode is supported. errorCode = ' + err.code); 290 } 291 292 if (focusModeStatus) { 293 // Set the focus mode to continuous auto focus. 294 try { 295 photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO); 296 } catch (error) { 297 let err = error as BusinessError; 298 console.error('Failed to set the focus mode. errorCode = ' + err.code); 299 } 300 } 301 302 // Obtain the zoom ratio range supported by the camera. 303 let zoomRatioRange: Array<number> = []; 304 try { 305 zoomRatioRange = photoSession.getZoomRatioRange(); 306 } catch (error) { 307 let err = error as BusinessError; 308 console.error('Failed to get the zoom ratio range. errorCode = ' + err.code); 309 } 310 if (zoomRatioRange.length <= 0) { 311 return; 312 } 313 314 // Set a zoom ratio. 315 try { 316 photoSession.setZoomRatio(zoomRatioRange[0]); 317 } catch (error) { 318 let err = error as BusinessError; 319 console.error('Failed to set the zoom ratio value. errorCode = ' + err.code); 320 } 321 let photoCaptureSetting: camera.PhotoCaptureSetting = { 322 quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // Set the photo quality to high. 323 rotation: camera.ImageRotation.ROTATION_0 // Set the rotation angle of the photo to 0. 324 } 325 326 // Use the current photo capture settings to take a photo. 327 photoOutput.capture(photoCaptureSetting, (err: BusinessError) => { 328 if (err) { 329 console.error(`Failed to capture the photo ${err.message}`); 330 return; 331 } 332 console.info('Callback invoked to indicate the photo capture request success.'); 333 }); 334} 335 336async function releaseCamSession() { 337 // Stop the session. 338 await photoSession?.stop(); 339 340 // Release the camera input stream. 341 await cameraInput?.close(); 342 343 // Release the preview output stream. 344 await previewOutput?.release(); 345 346 // Release the photo output stream. 347 await photoOutput?.release(); 348 349 // Release the session. 350 await photoSession?.release(); 351 352 // Set the session to null. 353 photoSession = undefined; 354} 355 356@Entry 357@Component 358struct Index { 359 @State message: string = 'PhotoAssetDemo'; 360 @State isShow: boolean = false; 361 private mXComponentController: XComponentController = new XComponentController(); 362 private surfaceId = ''; 363 private uiContext: UIContext = this.getUIContext(); 364 private context: Context | undefined = this.uiContext.getHostContext(); 365 private cameraPermission: Permissions = 'ohos.permission.CAMERA'; // For details about how to request permissions, see the instructions provided at the beginning of this topic. 366 367 async requestPermissionsFn(): Promise<void> { 368 let atManager = abilityAccessCtrl.createAtManager(); 369 if (this.context) { 370 let res = await atManager.requestPermissionsFromUser(this.context, [this.cameraPermission]); 371 for (let i =0; i < res.permissions.length; i++) { 372 if (this.cameraPermission.toString() === res.permissions[i] && res.authResults[i] === 0) { 373 this.isShow = true; 374 } 375 } 376 } 377 } 378 379 aboutToAppear(): void { 380 this.requestPermissionsFn(); 381 } 382 383 build() { 384 Column() { 385 Column() { 386 if (this.isShow) { 387 XComponent({ 388 id: 'componentId', 389 type: XComponentType.SURFACE, 390 controller: this.mXComponentController 391 }) 392 .onLoad(async () => { 393 console.info('onLoad is called'); 394 if (this.context) { 395 this.surfaceId = this.mXComponentController.getXComponentSurfaceId(); 396 console.info(`onLoad surfaceId: ${this.surfaceId}`); 397 deferredCaptureCase(this.context, this.surfaceId); 398 } 399 })// The width and height of the surface are opposite to those of the XComponent. 400 .renderFit(RenderFit.RESIZE_CONTAIN) 401 } 402 } 403 .height('95%') 404 .justifyContent(FlexAlign.Center) 405 406 Text(this.message) 407 .id('PhotoAssetDemo') 408 .fontSize(38) 409 .fontWeight(FontWeight.Bold) 410 .alignRules({ 411 center: { anchor: '__container__', align: VerticalAlign.Center }, 412 middle: { anchor: '__container__', align: HorizontalAlign.Center } 413 }) 414 } 415 .height('100%') 416 .width('100%') 417 } 418} 419``` 420