1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the 'License'); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an 'AS IS' BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// Reverse camera_ Multiple workstations_ Take photos Video 17 18import DateTimeUtil from '../model/DateTimeUtil'; 19import Logger from '../model/Logger'; 20import cameraDemo from 'libentry.so'; 21import mediaLibrary from '@ohos.multimedia.mediaLibrary'; 22import image from '@ohos.multimedia.image'; 23import media from '@ohos.multimedia.media'; 24import MediaUtils from '../model/MediaUtils'; 25import deviceInfo from '@ohos.deviceInfo'; 26import fileio from '@ohos.fileio'; 27import { SettingDataObj } from '../common/Constants' 28import common from '@ohos.app.ability.common' 29 30let context = getContext(this) as common.UIAbilityContext; 31 32interface CameraSize { 33 WIDTH: number, 34 HEIGHT: number 35}; 36 37interface PhotoSettings { 38 quality: number, // Photo quality 39 rotation: number, // Photo direction 40 mirror: boolean, // Mirror Enable 41 latitude: number, // geographic location 42 longitude: number, // geographic location 43 altitude: number // geographic location 44}; 45 46interface PhotoRotationMap { 47 rotation0: number, 48 rotation90: number, 49 rotation180: number, 50 rotation270: number, 51}; 52 53@Component 54export struct modeSwitchPage { 55 private tag: string = 'sample modeSwitchPage:'; 56 private mediaUtil = MediaUtils.getInstance(); 57 private fileAsset?: mediaLibrary.FileAsset; 58 private fd: number = -1; 59 @State videoId: string = ''; 60 @State mSurfaceId: string = ''; 61 private cameraSize: CameraSize = { 62 WIDTH: 1280, 63 HEIGHT: 720 64 }; 65 private photoSettings: PhotoSettings = { 66 quality: 0, 67 rotation: 0, 68 mirror: false, 69 latitude: 12.9698, 70 longitude: 77.7500, 71 altitude: 1000 72 }; 73 private mReceiver?: image.ImageReceiver; 74 private videoRecorder?: media.AVRecorder; 75 private videoConfig: media.AVRecorderConfig = { 76 audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, 77 videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV, 78 profile: { 79 audioBitrate: 48000, 80 audioChannels: 2, 81 audioCodec: media.CodecMimeType.AUDIO_AAC, 82 audioSampleRate: 48000, 83 fileFormat: media.ContainerFormatType.CFT_MPEG_4, 84 videoBitrate: 512000, 85 videoCodec: media.CodecMimeType.VIDEO_AVC, 86 videoFrameWidth: 640, 87 videoFrameHeight: 480, 88 videoFrameRate: 30 89 }, 90 url: '', 91 rotation: 0 92 }; 93 private photoRotationMap: PhotoRotationMap = { 94 rotation0: 0, 95 rotation90: 90, 96 rotation180: 180, 97 rotation270: 270, 98 }; 99 // Front and rear cameras 100 @Link cameraDeviceIndex: number; 101 // SurfaceID 102 @Prop surfaceId: string; 103 // Countdown value 104 @Link countdownNum: number; 105 // Countdown timer 106 @State countTimerInt: number = -1; 107 @State countTimerOut: number = -1; 108 // Photo Thumbnails 109 @State imgThumbnail: string = ''; 110 // Recording time 111 @State videoRecodeTime: number = 0; 112 // Recording time timer 113 @State timer: number = -1; 114 // Time Manager 115 @State dateTimeUtil: DateTimeUtil = new DateTimeUtil(); 116 // Select mode 117 @State modelBagCol: string = 'photo'; 118 // Choose camera or capture 119 @State @Watch('onChangeIsModeBol') isModeBol: boolean = true; 120 // Video Thumbnails 121 // private videoThumbnail?: image.PixelMap; 122 @State videoThumbnail: image.PixelMap | undefined | null = undefined; 123 private settingDataObj: SettingDataObj = { 124 mirrorBol: false, 125 videoStabilizationMode: 0, 126 exposureMode: 1, 127 focusMode: 2, 128 photoQuality: 1, 129 locationBol: false, 130 photoFormat: 1, 131 photoOrientation: 0, 132 photoResolution: 0, 133 videoResolution: 0, 134 videoFrame: 0, 135 referenceLineBol: false 136 }; 137 138 // After pausing, click 'stop' to reset the pause to default 139 onChangeIsModeBol() { 140 } 141 142 // Countdown capture and video 143 countTakeVideoFn() { 144 if (this.countdownNum) { 145 // Clear Countdown 146 if (this.countTimerOut) { 147 clearTimeout(this.countTimerOut); 148 } 149 if (this.countTimerInt) { 150 clearInterval(this.countTimerInt); 151 } 152 // Turn on timer 153 this.countTimerOut = setTimeout(() => { 154 // Determine whether it is in video or photo mode 155 this.isVideoPhotoFn(); 156 }, this.countdownNum * 1000) 157 // Turn on timer 158 this.countTimerInt = setInterval(() => { 159 this.countdownNum--; 160 if (this.countdownNum === 0) { 161 clearInterval(this.countTimerInt); 162 } 163 }, 1000) 164 } else { 165 this.isVideoPhotoFn(); 166 } 167 } 168 169 async getVideoSurfaceID() { 170 Logger.info(this.tag, `getVideoSurfaceID`); 171 this.videoRecorder = await media.createAVRecorder(); 172 Logger.info(this.tag, `getVideoSurfaceID videoRecorder: ${this.videoRecorder}`); 173 174 this.fileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.VIDEO); 175 Logger.info(this.tag, `getVideoSurfaceID fileAsset: ${this.fileAsset}`); 176 177 this.fd = await this.mediaUtil.getFdPath(this.fileAsset); 178 Logger.info(this.tag, `getVideoSurfaceID fd: ${this.fd}`); 179 180 this.videoConfig.url = `fd://${this.fd}`; 181 Logger.info(this.tag, `getVideoSurfaceID videoConfig.url : ${this.videoConfig.url}`); 182 183 if (deviceInfo.deviceType == 'default') { 184 Logger.info(this.tag, `deviceType = default`); 185 this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_ES; 186 } 187 if (deviceInfo.deviceType == 'phone') { 188 Logger.info(this.tag, `deviceType = phone`) 189 this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV; 190 this.videoConfig.profile.videoCodec = media.CodecMimeType.VIDEO_MPEG4; 191 if (this.cameraDeviceIndex == 1) { 192 this.videoConfig.rotation = this.photoRotationMap.rotation270; 193 } else { 194 this.videoConfig.rotation = this.photoRotationMap.rotation90; 195 } 196 } 197 if (deviceInfo.deviceType == 'tablet') { 198 Logger.info(this.tag, `deviceType = tablet`); 199 this.videoConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV; 200 } 201 202 this.videoConfig.profile.videoFrameWidth = cameraDemo.getVideoFrameWidth(); 203 this.videoConfig.profile.videoFrameHeight = cameraDemo.getVideoFrameHeight(); 204 this.videoConfig.profile.videoFrameRate = cameraDemo.getVideoFrameRate(); 205 206 await this.videoRecorder.prepare(this.videoConfig); 207 this.videoId = await this.videoRecorder.getInputSurface(); 208 Logger.info(this.tag, `getVideoSurfaceID videoId: ${this.videoId}`); 209 } 210 211 createImageReceiver() { 212 try { 213 this.mReceiver = image.createImageReceiver(this.cameraSize.WIDTH, this.cameraSize.HEIGHT, 2000, 8); 214 Logger.info(this.tag, `createImageReceiver value: ${this.mReceiver} `); 215 this.mReceiver.on('imageArrival', () => { 216 Logger.info(this.tag, 'imageArrival start'); 217 if (this.mReceiver) { 218 this.mReceiver.readNextImage((err, image) => { 219 Logger.info(this.tag, 'readNextImage start'); 220 if (err || image === undefined) { 221 Logger.error(this.tag, 'readNextImage failed '); 222 return; 223 } 224 image.getComponent(4, (errMsg, img) => { 225 Logger.info(this.tag, 'getComponent start'); 226 if (errMsg || img === undefined) { 227 Logger.info(this.tag, 'getComponent failed '); 228 return; 229 } 230 let buffer = new ArrayBuffer(2048); 231 if (img.byteBuffer) { 232 buffer = img.byteBuffer; 233 } else { 234 Logger.error(this.tag, 'img.byteBuffer is undefined'); 235 } 236 this.savePicture(buffer, image); 237 }) 238 }) 239 } 240 }) 241 } catch { 242 Logger.info(this.tag, 'savePicture err'); 243 } 244 } 245 246 // Read Image 247 async savePicture(buffer: ArrayBuffer, img: image.Image) { 248 try { 249 Logger.info(this.tag, 'savePicture start'); 250 let imgFileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE); 251 let imgPhotoUri = imgFileAsset.uri; 252 Logger.info(this.tag, `photoUri = ${imgPhotoUri}`); 253 let imgFd = await this.mediaUtil.getFdPath(imgFileAsset); 254 Logger.info(this.tag, `fd = ${imgFd}`); 255 await fileio.write(imgFd, buffer); 256 await imgFileAsset.close(imgFd); 257 await img.release(); 258 Logger.info(this.tag, 'save image End'); 259 // problem 260 if (this.handleTakePicture) { 261 this.handleTakePicture(imgPhotoUri); 262 } 263 } catch (err) { 264 Logger.info(this.tag, 'savePicture err' + JSON.stringify(err.message)); 265 } 266 } 267 268 async getPhotoSurfaceID() { 269 if (this.mReceiver) { 270 Logger.info(this.tag, 'imageReceiver has been created'); 271 } else { 272 this.createImageReceiver(); 273 } 274 if (this.mReceiver) { 275 this.mSurfaceId = await this.mReceiver.getReceivingSurfaceId(); 276 } 277 if (this.mSurfaceId) { 278 Logger.info(this.tag, `createImageReceiver mSurfaceId: ${this.mSurfaceId} `); 279 } else { 280 Logger.info(this.tag, `Get mSurfaceId failed `); 281 } 282 } 283 284 // Determine the video or photo mode 285 async isVideoPhotoFn() { 286 await this.getPhotoSurfaceID(); 287 288 if (this.modelBagCol == 'photo') { 289 cameraDemo.startPhotoOrVideo(this.modelBagCol, this.videoId, this.mSurfaceId); 290 } else if (this.modelBagCol == 'video') { 291 this.isModeBol = false; 292 if (this.timer) { 293 clearInterval(this.timer); 294 } 295 // Start record 296 await this.getVideoSurfaceID(); 297 cameraDemo.startPhotoOrVideo(this.modelBagCol, this.videoId, this.mSurfaceId); 298 cameraDemo.videoOutputStart(); 299 if (this.videoRecorder) { 300 this.videoRecorder.start(); 301 } 302 } 303 } 304 305 aboutToAppear() { 306 } 307 308 handleTakePicture = (thumbnail: string) => { 309 this.imgThumbnail = thumbnail; 310 Logger.info(this.tag, `takePicture end , thumbnail: ${this.imgThumbnail}`); 311 } 312 313 build() { 314 if (this.isModeBol) { 315 Column() { 316 Text('拍照') 317 .size({ width: 64, height: 28 }) 318 .borderRadius(14) 319 .fontSize(15) 320 .fontColor(Color.White) 321 .onClick(() => { 322 cameraDemo.releaseSession() 323 cameraDemo.initCamera(this.surfaceId, this.settingDataObj.focusMode, this.cameraDeviceIndex) 324 this.modelBagCol = 'photo' 325 }) 326 }.position({ x: '45%', y: '77%' }) 327 328 Column() { 329 Text('录像') 330 .fontSize(15) 331 .fontColor(Color.White) 332 .borderRadius(14) 333 .size({ width: 64, height: 28 }) 334 .onClick(() => { 335 cameraDemo.releaseSession() 336 cameraDemo.initCamera(this.surfaceId, this.settingDataObj.focusMode, this.cameraDeviceIndex) 337 this.modelBagCol = 'video' 338 }) 339 }.position({ x: '60%', y: '77%' }) 340 341 // album 342 Column() { 343 Row() { 344 if (this.modelBagCol === 'photo') { 345 Text(this.imgThumbnail || 'app.media.camera_thumbnail_4x') 346 .width('1px') 347 .height('1px') 348 .borderRadius('1px') 349 .id('ThumbnailText') 350 Image(this.imgThumbnail || $r('app.media.camera_thumbnail_4x')) 351 .objectFit(ImageFit.Fill) 352 .width('200px') 353 .height('200px') 354 } else { 355 Image(this.videoThumbnail || $r('app.media.camera_thumbnail_4x')) 356 .objectFit(ImageFit.Fill) 357 .width('200px') 358 .height('200px') 359 } 360 }.onClick(() => { 361 if (deviceInfo.deviceType == 'default') { 362 context.startAbility({ 363 bundleName: 'com.ohos.photos', 364 abilityName: 'com.ohos.photos.MainAbility' 365 }) 366 } else if (deviceInfo.deviceType == 'phone') { 367 context.startAbility({ 368 bundleName: 'com.huawei.hmos.photos', 369 abilityName: 'com.huawei.hmos.photos.MainAbility' 370 }) 371 } 372 373 }) 374 }.position({ x: '10%', y: '85%' }) 375 .id('Thumbnail') 376 377 // capture video icon 378 Column() { 379 Row() { 380 if (this.modelBagCol === 'photo') { 381 Image($r('app.media.camera_take_photo_4x')) 382 .width('200px') 383 .height('200px') 384 .onClick(() => { 385 // Countdown camera recording - default camera recording 386 this.countTakeVideoFn(); 387 }) 388 } else { 389 Image($r('app.media.camera_take_video_4x')) 390 .width('200px') 391 .height('200px') 392 .onClick(() => { 393 // Countdown camera recording - default camera recording 394 this.countTakeVideoFn(); 395 }) 396 } 397 } 398 }.position({ x: '40%', y: '85%' }) 399 .id('CaptureOrVideoButton') 400 401 // Front and rear camera switching 402 Column() { 403 Row() { 404 Image($r('app.media.camera_switch_4x')) 405 .width('200px') 406 .height('200px') 407 .onClick(async () => { 408 // Switching Cameras 409 this.cameraDeviceIndex ? this.cameraDeviceIndex = 0 : this.cameraDeviceIndex = 1; 410 // Clear configuration 411 cameraDemo.releaseSession(); 412 // Start preview 413 cameraDemo.initCamera(this.surfaceId, this.settingDataObj.focusMode, this.cameraDeviceIndex); 414 }) 415 } 416 }.position({ x: '70%', y: '85%' }) 417 .id('SwitchCameraButton') 418 } else { 419 Column() { 420 Row() { 421 Text().size({ width: 12, height: 12 }).backgroundColor($r('app.color.theme_color')).borderRadius(6) 422 Text(this.dateTimeUtil.getVideoTime(this.videoRecodeTime)) 423 .fontSize(30) 424 .fontColor(Color.White) 425 .margin({ left: 8 }) 426 }.offset({ x: -580, y: -180 }) 427 }.position({ x: 120, y: 450 }) 428 429 Column() { 430 // Video capture button 431 Image($r('app.media.camera_take_photo_4x')) 432 .width('200px') 433 .height('200px') 434 .onClick(() => { 435 cameraDemo.takePictureWithSettings(this.photoSettings); 436 }) 437 }.position({ x: '10%', y: '85%' }) 438 .id('VideoCaptureButton') 439 440 Column() { 441 Row() { 442 Column() { 443 // video stop button 444 Image($r('app.media.camera_pause_video_4x')).size({ width: 25, height: 25 }) 445 .width('200px') 446 .height('200px') 447 .id('StopVideo') 448 .onClick(() => { 449 if (this.timer) { 450 clearInterval(this.timer); 451 } 452 // Stop video 453 this.stopVideo().then(async (fileAsset: mediaLibrary.FileAsset) => { 454 this.videoRecodeTime = 0; 455 this.isModeBol = true; 456 try { 457 // Get video thumbnail 458 this.videoThumbnail = await fileAsset.getThumbnail(); 459 } catch (err) { 460 Logger.info(this.tag, 'videoThumbnail err----------:' + JSON.stringify(err.message)); 461 } 462 }) 463 }) 464 } 465 .width('180px') 466 .height('180px') 467 } 468 .width('200px') 469 .height('200px') 470 }.position({ x: '40%', y: '85%' }) 471 } 472 } 473 474 async stopVideo() { 475 try { 476 if (this.videoRecorder) { 477 await this.videoRecorder.stop(); 478 await this.videoRecorder.release(); 479 } 480 cameraDemo.videoOutputStopAndRelease(); 481 if (this.fileAsset) { 482 await this.fileAsset.close(this.fd); 483 return this.fileAsset; 484 } 485 Logger.info(this.tag, 'stopVideo end'); 486 } catch (err) { 487 Logger.info(this.tag, 'stopVideo err: ' + JSON.stringify(err)); 488 } 489 return; 490 } 491}