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*/ 15import audio from '@ohos.multimedia.audio'; 16import fs from '@ohos.file.fs'; 17import common from '@ohos.app.ability.common'; 18import router from '@ohos.router'; 19import resourceManager from '@ohos.resourceManager'; 20import { BusinessError } from '@ohos.base'; 21 22const TOTAL_SECOND = 30; 23const NORMAL_INDEX = 0; 24const SCREEN_INDEX = 1; 25const MIN_RECORD_SECOND = 5; 26const RANDOM_NUM = 10000; 27const INTERVAL_TIME = 1000; 28const READ_TIME_OUT_SCREEN = 0; 29const READ_TIME_OUT_NORMAL = 0; 30class Options { 31 offset: number = 0; 32 length: number = 0; 33} 34 35@Entry 36@Component 37struct ParallelCapturer { 38 @State fontColor: string = '#182431'; 39 @State selectedFontColor: string = '#007DFF'; 40 @State currentIndex: number = 1; 41 // Capturer 42 private audioCapturerNormal?: audio.AudioCapturer; 43 private audioCapturerScreen?: audio.AudioCapturer; 44 @State recordState: string = 'init'; // [init,started,stoped] 45 @State recordSec: number = 0; 46 private interval: number = 0; 47 @State showTime: string = '00:00:00'; 48 private audioCapturerOptionNormal: audio.AudioCapturerOptions = { 49 streamInfo: { 50 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 51 channels: audio.AudioChannel.CHANNEL_2, 52 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 53 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 54 }, 55 capturerInfo: { 56 source: audio.SourceType.SOURCE_TYPE_MIC, 57 capturerFlags: 0 58 } 59 }; 60 private audioCapturerOptionScreen: audio.AudioCapturerOptions = { 61 streamInfo: { 62 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 63 channels: audio.AudioChannel.CHANNEL_2, 64 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 65 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 66 }, 67 capturerInfo: { 68 source: 2, 69 capturerFlags: 0 70 }, 71 playbackCaptureConfig: { 72 filterOptions: { 73 usages: [audio.StreamUsage.STREAM_USAGE_MEDIA] 74 } 75 } 76 }; 77 // recorder data 78 private audioRendererOptions: audio.AudioRendererOptions = { 79 streamInfo: { 80 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 81 channels: audio.AudioChannel.CHANNEL_2, 82 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 83 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 84 }, 85 rendererInfo: { 86 content: audio.ContentType.CONTENT_TYPE_MUSIC, 87 usage: audio.StreamUsage.STREAM_USAGE_MEDIA, 88 rendererFlags: 0 89 } 90 }; 91 private bufferSizeNormal = 0; 92 private bufferSizeScreen = 0; 93 @State date: string = ''; 94 private audioRenderers: audio.AudioRenderer[] = []; 95 @State titleList: string[] = ['', '']; 96 @State pathList: string[] = ['', '']; 97 @State fdList: number[] = [0, 0]; 98 @State playSecList: number[] = [0, 0]; 99 @State renderStateList: number[] = [0, 0]; 100 @State renderStartOffsetList: number[] = [0, 0]; 101 @State isRecordOver: boolean = false; 102 103 // Music Player 104 private audioRendererOptionsMusic: audio.AudioRendererOptions = { 105 streamInfo: { 106 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 107 channels: audio.AudioChannel.CHANNEL_2, 108 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 109 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 110 }, 111 rendererInfo: { 112 content: audio.ContentType.CONTENT_TYPE_MUSIC, 113 usage: audio.StreamUsage.STREAM_USAGE_MEDIA, 114 rendererFlags: 0 115 }, 116 privacyType: 0 117 }; 118 private audioRendererMusic?: audio.AudioRenderer; 119 @State renderMusicState: number = 0; 120 @State curTimeSec: number = 0; 121 @State startMusicOffset: number = 0; 122 private appContext?: common.Context; 123 private fileDescriptor?: resourceManager.RawFileDescriptor; 124 private audioSource = 'test1.wav'; 125 126 @Builder TabBuilder(index: number, btnId: string) { 127 Column() { 128 Text(index === 0 ? $r('app.string.NORMAL_CAPTURER') : 129 index === 1 ? $r('app.string.PARALLEL_CAPTURER') : 130 $r('app.string.LIVE_CAPTURER')) 131 .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor) 132 .opacity(this.currentIndex === index ? 1 : 0.6) 133 .fontSize(16) 134 .fontWeight(this.currentIndex === index ? 500 : 400) 135 .lineHeight(22) 136 .margin({ top: 17, bottom: 7 }); 137 Divider() 138 .strokeWidth(2) 139 .color('#007DFF') 140 .opacity(this.currentIndex === index ? 1 : 0); 141 }.width(78).id('btn_' + btnId); 142 } 143 144 async aboutToAppear(): Promise<void> { 145 console.log('ParallelCapturer aboutToAppear'); 146 await this.initResource(); 147 } 148 149 async initResource(): Promise<void> { 150 this.appContext = getContext(this); 151 this.fileDescriptor = await this.getStageFileDescriptor(this.audioSource); 152 await this.CreateMusicRenderer(this.audioRendererOptionsMusic); 153 try { 154 this.audioCapturerNormal = await audio.createAudioCapturer(this.audioCapturerOptionNormal); 155 console.log('ParallelCapturer,Normal capturer successs'); 156 this.audioCapturerScreen = await audio.createAudioCapturer(this.audioCapturerOptionScreen); 157 console.log('ParallelCapturer,Screen capturer successs'); 158 159 this.bufferSizeNormal = await this.audioCapturerNormal.getBufferSize(); 160 this.bufferSizeScreen = await this.audioCapturerScreen.getBufferSize(); 161 // recorder init 162 this.recordState = 'init'; 163 } catch (err) { 164 let error = err as BusinessError; 165 console.log(`ParallelCapturer:createAudioCapturer err=${JSON.stringify(error)}`); 166 } 167 } 168 169 async releseResource(): Promise<void> { 170 if (this.interval !== 0) { 171 clearInterval(this.interval); 172 } 173 if (this.fdList[NORMAL_INDEX] > 0) { 174 this.closeFile(this.fdList[NORMAL_INDEX]); 175 this.fdList[NORMAL_INDEX] = 0; 176 } 177 178 if (this.fdList[SCREEN_INDEX] > 0) { 179 this.closeFile(this.fdList[SCREEN_INDEX]); 180 this.fdList[SCREEN_INDEX] = 0; 181 } 182 183 if (this.audioCapturerNormal) { 184 await this.audioCapturerNormal.release(); 185 console.log('ParallelCapturer,audioCapturerNormal.release success'); 186 this.audioCapturerNormal = undefined; 187 } 188 189 if (this.audioCapturerScreen) { 190 await this.audioCapturerScreen.release(); 191 console.log('ParallelCapturer,audioCapturerScreen.release success'); 192 this.audioCapturerScreen = undefined; 193 } 194 195 if (this.fileDescriptor) { 196 await this.closeResource('test1.wav'); 197 this.fileDescriptor = undefined; 198 } 199 200 if (this.audioRendererMusic) { 201 await this.audioRendererMusic.release(); 202 this.audioRendererMusic = undefined; 203 } 204 for (let index = 0; index < this.audioRenderers.length; index++) { 205 await this.audioRenderers[index].release(); 206 } 207 this.audioRenderers = []; 208 } 209 210 async aboutToDisappear(): Promise<void> { 211 console.log('ParallelCapturer aboutToDisappear'); 212 await this.releseResource(); 213 } 214 215 async openFile(path: string): Promise<number | undefined> { 216 try { 217 await fs.open(path, 0o100); 218 console.log('ParallelCapturer,file created success'); 219 } catch (err) { 220 let error = err as BusinessError; 221 console.log('ParallelCapturer,file created err:' + JSON.stringify(error)); 222 return; 223 } 224 225 let file: fs.File; 226 try { 227 file = await fs.open(path, 0o2); 228 console.log(`ParallelCapturer,file open success for read and write mode,fd=${file.fd}`); 229 return file.fd; 230 } catch (err) { 231 let error = err as BusinessError; 232 console.log('ParallelCapturer,file open err:' + JSON.stringify(error)); 233 return 0; 234 } 235 } 236 237 async closeFile(fd: number): Promise<void> { 238 try { 239 await fs.close(fd); 240 console.log('ParallelCapturer,file close success'); 241 } catch (err) { 242 let error = err as BusinessError; 243 console.log('ParallelCapturer,file close err:' + JSON.stringify(error)); 244 return; 245 } 246 } 247 248 sleep(ms: number): Promise<number> { 249 return new Promise<number>((resolve) => setTimeout(resolve, ms)); 250 } 251 252 async capturerStart(): Promise<void> { 253 if (!this.audioCapturerNormal) { 254 console.log(`ParallelCapturer:audioCapturerNormal is null`); 255 return; 256 } 257 if (!this.audioCapturerScreen) { 258 console.log(`ParallelCapturer:audioCapturerScreen is null`); 259 return; 260 } 261 if (this.renderMusicState === audio.AudioState.STATE_PREPARED) { 262 this.startMusicRenderer(); 263 } 264 await this.sleep(200); 265 266 try { 267 await this.audioCapturerNormal.start(); 268 console.log('ParallelCapturer,audioCapturerNormal start success'); 269 await this.audioCapturerScreen.start(); 270 console.log('ParallelCapturer,audioCapturerScreen start success'); 271 272 // when start,init recordSec 273 this.recordState = 'started'; 274 console.log('audioCapturer start ok'); 275 276 clearInterval(this.interval); 277 this.interval = setInterval(async () => { 278 if (this.recordSec >= TOTAL_SECOND) { 279 // over TOTAL_SECOND,need to stop automatically 280 this.capturerStop(); 281 return; 282 } 283 this.recordSec++; 284 this.showTime = this.getTimesBySecond(this.recordSec); 285 }, INTERVAL_TIME); 286 287 let titleNormal = `${this.getDate(2)}_${Math.floor(Math.random() * RANDOM_NUM)}_Normal`; 288 let pathNormal = `/data/storage/el2/base/haps/entry/files/capturer_${titleNormal}.pcm`; 289 let mode = 1; 290 this.date = this.getDate(mode); 291 let titleScreen = `${this.getDate(2)}_${Math.floor(Math.random() * RANDOM_NUM)}_Screen`; 292 let pathScreen = `/data/storage/el2/base/haps/entry/files/capturer_${titleScreen}.pcm`; 293 this.titleList[NORMAL_INDEX] = titleNormal; 294 this.titleList[SCREEN_INDEX] = titleScreen; 295 this.pathList[NORMAL_INDEX] = pathNormal; 296 this.pathList[SCREEN_INDEX] = pathScreen; 297 let fdNormal = await this.openFile(pathNormal) as number; 298 let fdScreen = await this.openFile(pathScreen) as number; 299 this.fdList[NORMAL_INDEX] = fdNormal; 300 this.fdList[SCREEN_INDEX] = fdScreen; 301 setTimeout(async () => { 302 if (this.audioCapturerNormal) { 303 await this.readCapturer(this.audioCapturerNormal, this.bufferSizeNormal, fdNormal); 304 console.log('ParallelCapturer,audioCapturerNormal readCapturer success'); 305 } 306 }, READ_TIME_OUT_NORMAL); 307 setTimeout(async () => { 308 if (this.audioCapturerScreen) { 309 await this.readCapturer(this.audioCapturerScreen, this.bufferSizeScreen, fdScreen); 310 console.log('ParallelCapturer,audioCapturerScreen readCapturer success'); 311 } 312 }, READ_TIME_OUT_SCREEN); 313 } catch (err) { 314 let error = err as BusinessError; 315 console.log(`ParallelCapturer,:audioCapturer start err=${JSON.stringify(error)}`); 316 } 317 } 318 319 async capturerStop(): Promise<void> { 320 if (this.recordSec < MIN_RECORD_SECOND || !this.audioCapturerNormal || !this.audioCapturerScreen) { 321 return; 322 } 323 try { 324 325 await this.audioCapturerNormal.stop(); 326 await this.audioCapturerScreen.stop(); 327 // when recordState is stopped 328 this.recordState = 'stopped'; 329 clearInterval(this.interval); 330 if (this.renderMusicState === audio.AudioState.STATE_RUNNING) { 331 this.pauseMusicRenderer(); 332 } 333 } catch (err) { 334 let error = err as BusinessError; 335 console.log(`ParallelCapturer,:audioCapturer stop err=${JSON.stringify(error)}`); 336 } 337 this.isRecordOver = true; 338 339 console.log(JSON.stringify(this.titleList)); 340 await this.renderCreate(); 341 } 342 343 async readCapturer(audioCapturer: audio.AudioCapturer, bufferSize: number, fd: number): Promise<void> { 344 console.log('ParallelCapturer,start readCapturer'); 345 try { 346 let startOffset = 0; 347 while (true) { 348 if (audioCapturer.state === audio.AudioState.STATE_STOPPED) { 349 console.log('ParallelCapturer,state is changed to be stopped'); 350 break; 351 } 352 let buffer = await audioCapturer.read(bufferSize, true); 353 if (fd === this.fdList[NORMAL_INDEX]) { 354 console.log('NormalCapturer:readCapturer Normal read success'); 355 } else if (fd === this.fdList[SCREEN_INDEX]) { 356 console.log('NormalCapturer:readCapturer Screen read success'); 357 } 358 359 let options: Options = { 360 offset: startOffset, 361 length: bufferSize 362 }; 363 let writenSize = await fs.write(fd, buffer, options); 364 if (fd === this.fdList[NORMAL_INDEX]) { 365 console.log('ParallelCapturer--Normal,fd===' + fd + ',writenSize=' + writenSize); 366 } else if (fd === this.fdList[SCREEN_INDEX]) { 367 console.log('ParallelCapturer--Screen,fd===' + fd + ',writenSize=' + writenSize); 368 } 369 startOffset += bufferSize; 370 } 371 } 372 catch (err) { 373 let error = err as BusinessError; 374 console.log(`ParallelCapturer,readCapturer err=${JSON.stringify(error)}`); 375 } 376 } 377 378 async renderCreate(): Promise<void> { 379 try { 380 this.audioRenderers[NORMAL_INDEX] = await audio.createAudioRenderer(this.audioRendererOptions); 381 this.audioRenderers[SCREEN_INDEX] = await audio.createAudioRenderer(this.audioRendererOptions); 382 383 this.renderStateList[NORMAL_INDEX] = this.audioRenderers[NORMAL_INDEX].state; 384 this.renderStateList[SCREEN_INDEX] = this.audioRenderers[SCREEN_INDEX].state; 385 386 this.audioRenderers[NORMAL_INDEX].on('stateChange', (state) => { 387 console.log('ParallelCapturer,renderStateList[0] is changed to ' + state); 388 this.renderStateList[NORMAL_INDEX] = state; 389 }); 390 this.audioRenderers[SCREEN_INDEX].on('stateChange', (state) => { 391 console.log('ParallelCapturer,renderStateList[1] is changed to ' + state); 392 this.renderStateList[SCREEN_INDEX] = state; 393 }); 394 } catch (err) { 395 let error = err as BusinessError; 396 console.log(`ParallelCapturer,createAudioRenderer err=${JSON.stringify(error)}`); 397 } 398 } 399 400 async renderStart(index: number): Promise<void> { 401 let bufferSize = 0; 402 try { 403 bufferSize = await this.audioRenderers[index].getBufferSize(); 404 await this.audioRenderers[index].start(); 405 } catch (err) { 406 let error = err as BusinessError; 407 console.log(`ParallelCapturer,audioRenderers start err:${JSON.stringify(error)}`); 408 } 409 410 try { 411 let stat = await fs.stat(this.pathList[index]); 412 console.log(`ParallelCapturer,index:${index} stat=${JSON.stringify(stat)}`); 413 let buf = new ArrayBuffer(bufferSize); 414 console.log(`ParallelCapturer,audioRenderer write start..........`); 415 let startOffset = this.renderStartOffsetList[index]; 416 while (startOffset <= stat.size) { 417 if (this.audioRenderers[index].state === audio.AudioState.STATE_PAUSED) { 418 break; 419 } 420 // change tag,to stop 421 if (this.audioRenderers[index].state === audio.AudioState.STATE_STOPPED) { 422 break; 423 } 424 if (this.audioRenderers[index].state === audio.AudioState.STATE_RELEASED) { 425 return 426 } 427 let options: Options = { 428 offset: startOffset, 429 length: bufferSize 430 }; 431 await fs.read(this.fdList[index], buf, options); 432 await this.audioRenderers[index].write(buf); 433 this.playSecList[index] = Math.round(startOffset / stat.size * this.recordSec); //changed 434 startOffset = startOffset + bufferSize; 435 this.renderStartOffsetList[index] = startOffset; 436 } 437 console.log(`ParallelCapturer,audioRenderer write end..........`); 438 if (this.audioRenderers[index].state === audio.AudioState.STATE_RUNNING) { 439 this.renderStartOffsetList[index] = 0; 440 await this.renderStop(index); 441 } 442 } catch (err) { 443 let error = err as BusinessError; 444 console.log(`ParallelCapturer,write err:${JSON.stringify(error)}`); 445 } 446 } 447 448 async renderPause(index: number): Promise<void> { 449 try { 450 await this.audioRenderers[index].pause(); 451 } catch (err) { 452 let error = err as BusinessError; 453 console.log(`ParallelCapturer,pause err:${JSON.stringify(error)}`); 454 } 455 } 456 457 async renderStop(index: number): Promise<void> { 458 try { 459 await this.audioRenderers[index].stop(); 460 this.renderStartOffsetList[index] = 0; 461 } catch (err) { 462 let error = err as BusinessError; 463 console.log(`ParallelCapturer,stop err:${JSON.stringify(error)}`); 464 } 465 } 466 467 async renderRelease(index: number): Promise<void> { 468 try { 469 await this.audioRenderers[index].release(); 470 } catch (err) { 471 let error = err as BusinessError; 472 console.log(`ParallelCapturer,release err:${JSON.stringify(error)}`); 473 } 474 } 475 476 formatNumber(num: number): string { 477 if (num <= 9) { 478 return '0' + num; 479 } else { 480 return '' + num; 481 } 482 } 483 484 getDate(mode: number): string { 485 let date = new Date() 486 if (mode === 1) { 487 return `${date.getFullYear()}/${this.formatNumber(date.getMonth() + 1)}/${this.formatNumber(date.getDate())}`; 488 } else { 489 return `${date.getFullYear()}${this.formatNumber(date.getMonth() + 1)}${this.formatNumber(date.getDate())}`; 490 } 491 } 492 493 getTimesBySecond(t: number): string { 494 let h = Math.floor(t / 60 / 60 % 24); 495 let m = Math.floor(t / 60 % 60); 496 let s = Math.floor(t % 60); 497 let hs = h < 10 ? '0' + h : h; 498 let ms = m < 10 ? '0' + m : m; 499 let ss = s < 10 ? '0' + s : s; 500 return `${hs}:${ms}:${ss}`; 501 } 502 503 // start music player 504 async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> { 505 let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined; 506 if (this.appContext) { 507 let mgr = this.appContext.resourceManager; 508 await mgr.getRawFd(fileName).then(value => { 509 fileDescriptor = value; 510 console.log('ParallelCapturer,case getRawFileDescriptor success fileName: ' + fileName); 511 }).catch((error: BusinessError) => { 512 console.log('ParallelCapturer,case getRawFileDescriptor err: ' + error); 513 }); 514 } 515 return fileDescriptor; 516 } 517 518 async closeResource(fileName: string): Promise<void> { 519 if (this.appContext) { 520 let mgr = this.appContext.resourceManager; 521 await mgr.closeRawFd(fileName).then(() => { 522 console.log('ParallelCapturer,case closeRawFd success fileName: ' + fileName); 523 }).catch((error: BusinessError) => { 524 console.log('ParallelCapturer,case closeRawFd err: ' + error); 525 }); 526 } 527 } 528 529 async CreateMusicRenderer(options: audio.AudioRendererOptions): Promise<void> { 530 try { 531 this.audioRendererMusic = await audio.createAudioRenderer(options); 532 this.renderMusicState = this.audioRendererMusic.state; 533 this.audioRendererMusic.on('stateChange', (state) => { 534 console.log('ParallelCapturer,renderMusicState is changed to ' + state); 535 this.renderMusicState = state; 536 }); 537 } catch (err) { 538 let error = err as BusinessError; 539 console.log(`ParallelCapturer,createAudioRenderer err=${JSON.stringify(error)}`); 540 } 541 } 542 543 async startMusicRenderer(): Promise<void> { 544 if (!this.audioRendererMusic || !this.fileDescriptor) { 545 return; 546 } 547 let bufferSize: number = 0; 548 try { 549 bufferSize = await this.audioRendererMusic.getBufferSize(); 550 await this.audioRendererMusic.start(); 551 } catch (err) { 552 let error = err as BusinessError; 553 console.error(`ParallelCapturer,audioRenderer start : Error: ${JSON.stringify(error)}`); 554 return; 555 } 556 try { 557 let buf = new ArrayBuffer(bufferSize); 558 let start = this.fileDescriptor.offset; 559 if (this.startMusicOffset === 0) { 560 this.startMusicOffset = start; 561 } 562 let cur = this.startMusicOffset; 563 // start + this.fileDescriptor.length is end offset 564 while (cur <= start + this.fileDescriptor.length) { 565 // when render released,state is changed to STATE_RELEASED 566 if (this.audioRendererMusic.state === audio.AudioState.STATE_RELEASED) { 567 break; 568 } 569 // when render paused,state is changed to STATE_PAUSED 570 if (this.audioRendererMusic.state === audio.AudioState.STATE_PAUSED) { 571 this.startMusicOffset = cur; 572 break; 573 } 574 575 // change tag,to stop 576 if (this.audioRendererMusic.state === audio.AudioState.STATE_STOPPED) { 577 this.startMusicOffset = cur; 578 break; 579 } 580 581 let options: Options = { 582 offset: cur, 583 length: bufferSize 584 }; 585 console.log('startMusicRenderer,options=' + JSON.stringify(options)); 586 await fs.read(this.fileDescriptor.fd, buf, options); 587 await this.audioRendererMusic.write(buf); 588 // update progress 589 this.curTimeSec = this.getCurTimeSec(TOTAL_SECOND, this.fileDescriptor.length, cur - start); 590 cur += bufferSize; 591 } 592 // when audio play completed,update state to stopped 593 if (this.audioRendererMusic.state === audio.AudioState.STATE_RUNNING) { 594 await this.audioRendererMusic.stop(); 595 this.startMusicOffset = 0; 596 this.curTimeSec = TOTAL_SECOND; 597 } 598 } catch (err) { 599 let error = err as BusinessError; 600 console.error(`ParallelCapturer,audioRenderer write : Error: ${JSON.stringify(error)}`); 601 } 602 } 603 604 async pauseMusicRenderer(): Promise<void> { 605 try { 606 if (this.audioRendererMusic) { 607 await this.audioRendererMusic.pause(); 608 } 609 } catch (err) { 610 let error = err as BusinessError; 611 console.error(`ParallelCapturer,pauseMusicRenderer pause : Error: ${JSON.stringify(error)}`); 612 return; 613 } 614 } 615 616 async stopMusicRenderer(): Promise<void> { 617 try { 618 if (this.audioRendererMusic) { 619 await this.audioRendererMusic.stop(); 620 } 621 } catch (err) { 622 let error = err as BusinessError; 623 console.error(`ParallelCapturer,pauseMusicRenderer stop : Error: ${JSON.stringify(error)}`); 624 return; 625 } 626 } 627 628 getCurTimeSec(totalSec: number, totalLen: number, PastLen: number): number { 629 return Number((totalSec / totalLen * PastLen).toFixed(0)); 630 } 631 632 @Builder InitRecord() { 633 Column() { 634 Image($r('app.media.ic_record')).width(56).height(56); 635 } 636 .width('100%') 637 .height(56) 638 .position({ y: 60 }) 639 .id('parallel_start_btn') 640 .onClick(() => { 641 this.capturerStart(); 642 }); 643 } 644 645 @Builder StartedRecord() { 646 Column() { 647 Text(this.showTime).fontSize(21).fontWeight(500).margin({ bottom: 8 }); 648 }.width('100%').height(66).position({ y: 30 }); 649 650 Column() { 651 Image($r('app.media.ic_recording')).width(56).height(56); 652 } 653 .width('100%') 654 .height(56) 655 .position({ y: 60 }) 656 .id('parallel_stop_btn') 657 .onClick(() => { 658 this.capturerStop(); 659 }); 660 } 661 662 @Builder FinishedRecord() { 663 Column() { 664 Image($r('app.media.ic_record')).width(56).height(56); 665 }.width('100%').height(56).position({ y: 60 }).opacity(0.4); 666 } 667 668 build() { 669 Column() { 670 Column() { 671 Navigation() { 672 } 673 .width('100%') 674 .height('100%') 675 .hideBackButton(false) 676 .titleMode(NavigationTitleMode.Mini) 677 .title($r('app.string.AUDIO_CAPTURER')) 678 .mode(NavigationMode.Stack) 679 .backgroundColor('#F1F3F5'); 680 } 681 .id('parallel_capturer_back_btn') 682 .width('100%') 683 .height(56) 684 .onClick(async () => { 685 await router.replaceUrl({ url: 'pages/Index' }); 686 }); 687 688 Column() { 689 Tabs({ barPosition: BarPosition.Start, index: 1 }) { 690 TabContent().tabBar(this.TabBuilder(0, 'normal_capturer')); 691 TabContent().tabBar(this.TabBuilder(1, 'parallel_capturer')); 692 TabContent().tabBar(this.TabBuilder(2, 'live_capturer')); 693 } 694 .vertical(false) 695 .barMode(BarMode.Fixed) 696 .barWidth(360) 697 .barHeight(56) 698 .animationDuration(400) 699 .onChange((index: number) => { 700 this.currentIndex = index 701 console.log(`${index}`) 702 if (this.currentIndex === 0) { 703 router.replaceUrl({ url: 'pages/NormalCapturer' }); 704 } else if(this.currentIndex === 2) { 705 router.replaceUrl({ url: 'pages/LiveCapturer' }); 706 } 707 }) 708 .width('100%') 709 .height(56) 710 }.padding({ left: 12, right: 12 }); 711 712 Column() { 713 Row() { 714 Row() { 715 Image($r('app.media.ic_music')).width(48).height(48); 716 Text($r('app.string.MusicType')) 717 .fontSize(16) 718 .margin({ left: 12 }) 719 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 720 .fontColor('#182431') 721 .fontWeight(500); 722 } 723 724 if (this.renderMusicState === audio.AudioState.STATE_RUNNING) { 725 Image($r('app.media.ic_play_y')).width(36).height(36); 726 } else { 727 Image($r('app.media.ic_pause_y')).width(36).height(36); 728 } 729 730 }.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 12 }); 731 732 Row() { 733 Progress({ value: this.curTimeSec, total: TOTAL_SECOND, type: ProgressType.Linear }) 734 .color('#007DFF') 735 .value(this.curTimeSec) 736 .width('100%') 737 .height(4) 738 }.margin({ top: 24, bottom: 3 }).width('100%'); 739 740 Row() { 741 Text(this.getTimesBySecond(this.curTimeSec)) 742 .fontSize(12) 743 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 744 .fontColor('#182431') 745 .opacity(0.6) 746 .fontWeight(400); 747 Text(this.getTimesBySecond(TOTAL_SECOND)) 748 .fontSize(12) 749 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 750 .fontColor('#182431') 751 .opacity(0.6) 752 .fontWeight(400); 753 }.justifyContent(FlexAlign.SpaceBetween).width('100%'); 754 } 755 .id('music_player_card') 756 .height(126) 757 .width('100%') 758 .padding({ left: 12, right: 12 }) 759 .backgroundColor(Color.White) 760 .margin({ bottom: 20, top: 12 }) 761 .borderRadius(24) 762 .margin({ top: 12 }) 763 .onClick(() => { 764 if (this.renderMusicState === audio.AudioState.STATE_PREPARED) { 765 this.startMusicRenderer(); 766 } 767 if (this.renderMusicState === audio.AudioState.STATE_RUNNING) { 768 this.pauseMusicRenderer(); 769 } 770 if (this.renderMusicState === audio.AudioState.STATE_PAUSED) { 771 this.startMusicRenderer(); 772 } 773 if (this.renderMusicState === audio.AudioState.STATE_STOPPED) { 774 this.startMusicRenderer(); 775 } 776 }); 777 778 if (this.isRecordOver === true) { 779 Column() { 780 Row() { 781 Text($r('app.string.RECORD_RESULT')) 782 .fontSize(14) 783 .fontWeight(400) 784 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 785 .opacity(0.6) 786 .id('record_result') 787 .textAlign(TextAlign.Start); 788 }.padding({ left: 12, right: 12 }).width('100%').margin({ top: 16, bottom: 8 }); 789 790 ForEach(this.titleList, (item: string, index) => { 791 Column() { 792 Row() { 793 Text(this.titleList[index as number]) 794 .fontSize(16) 795 .fontWeight(500) 796 .fontColor('#182431') 797 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')); 798 if (this.renderStateList[index as number] === audio.AudioState.STATE_RUNNING) { 799 Image($r('app.media.ic_record_playing')).width(24).height(24).id('playing_state' + (index as number)); 800 } else { 801 Image($r('app.media.ic_record_paused')).width(24).height(24).id('paused_state' + (index as number)); 802 } 803 804 }.width('100%').height(24).justifyContent(FlexAlign.SpaceBetween).margin({ top: 16 }); 805 806 Row() { 807 Text(this.date) 808 .fontSize(14) 809 .fontWeight(400) 810 .fontColor('#182431') 811 .opacity(0.6) 812 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')); 813 Text(this.getTimesBySecond(this.recordSec) + '') 814 .fontSize(14) 815 .fontWeight(400) 816 .fontColor('#182431') 817 .opacity(0.6) 818 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')); 819 }.width('100%').height(24).justifyContent(FlexAlign.SpaceBetween).margin({ top: 4 }); 820 821 Row() { 822 Progress({ value: this.playSecList[index as number], total: this.recordSec, type: ProgressType.Linear }) 823 .color('#007DFF') 824 .value(this.playSecList[index as number]) 825 .width('100%') 826 .height(4); 827 }.margin({ top: 23, bottom: 3 }); 828 829 Row() { 830 Text(this.getTimesBySecond(this.playSecList[index as number]) + '') 831 .fontSize(12) 832 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 833 .fontColor('#182431') 834 .opacity(0.6) 835 .fontWeight(400); 836 Text(this.getTimesBySecond(this.recordSec) + '') 837 .fontSize(12) 838 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 839 .fontColor('#182431') 840 .opacity(0.6) 841 .fontWeight(400); 842 }.justifyContent(FlexAlign.SpaceBetween).width('100%'); 843 } 844 .width('100%') 845 .height(126) 846 .backgroundColor(Color.White) 847 .padding({ left: 12, right: 12 }) 848 .borderRadius(24) 849 .margin({ bottom: 12 }) 850 .id('record_player' + (index as number)) 851 .onClick(() => { 852 if (this.renderStateList[index as number] === audio.AudioState.STATE_PREPARED) { 853 this.renderStart(index as number); 854 } 855 if (this.renderStateList[index as number] === audio.AudioState.STATE_RUNNING) { 856 this.renderPause(index as number); 857 } 858 if (this.renderStateList[index as number] === audio.AudioState.STATE_PAUSED) { 859 this.renderStart(index as number); 860 } 861 if (this.renderStateList[index as number] === audio.AudioState.STATE_STOPPED) { 862 this.renderStart(index as number); 863 } 864 }); 865 }); 866 } 867 } 868 Row() { 869 if (this.recordState === 'init') { // init 870 this.InitRecord(); 871 } else if (this.recordState === 'started') { // started 872 this.StartedRecord(); 873 } else if (this.recordState === 'stopped') { // finished 874 this.FinishedRecord(); 875 } 876 } 877 .width('100%') 878 .position({ y: '82%' }) 879 .alignItems(VerticalAlign.Top) 880 .height(116) 881 .id('record_btn'); 882 } 883 .width('100%') 884 .height('100%') 885 .justifyContent(FlexAlign.Start) 886 .backgroundColor('#F1F3F5') 887 .padding({ left: 12, right: 12 }); 888 } 889} 890