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 common from '@ohos.app.ability.common'; 16import fs from '@ohos.file.fs'; 17import audio from '@ohos.multimedia.audio'; 18import router from '@ohos.router'; 19import resourceManager from '@ohos.resourceManager'; 20import { BusinessError } from '@ohos.base'; 21 22const MUSIC_INDEX = 0; 23const RINGTONE_INDEX = 1; 24const TOTAL_SECOND = 30; 25const PLAYER_CONTAINER = [0, 1]; 26 27@Entry 28@Component 29struct Focus { 30 @State outSetValueOne: number = 50; 31 private audioRenderers: audio.AudioRenderer[] = []; 32 private audioRendererOptions: audio.AudioRendererOptions[] = [ 33 { 34 streamInfo: { 35 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 36 channels: audio.AudioChannel.CHANNEL_2, 37 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 38 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 39 }, 40 rendererInfo: { 41 content: audio.ContentType.CONTENT_TYPE_MUSIC, 42 usage: audio.StreamUsage.STREAM_USAGE_MEDIA, 43 rendererFlags: 0 44 } 45 }, 46 { 47 streamInfo: { 48 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 49 channels: audio.AudioChannel.CHANNEL_2, 50 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 51 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 52 }, 53 rendererInfo: { 54 content: audio.ContentType.CONTENT_TYPE_MUSIC, 55 usage: audio.StreamUsage.STREAM_USAGE_NOTIFICATION_RINGTONE, 56 rendererFlags: 0 57 } 58 } 59 ]; 60 private fileDescriptors: resourceManager.RawFileDescriptor[] = []; 61 private appContext?: common.Context; 62 private audioSources = ['test1.wav', 'test2.wav']; 63 @State stateImg: Array<Resource> = [$r('app.media.ic_pause_y'), $r('app.media.ic_pause_no')]; 64 @State stateText: Array<string> = ['ic_pause', 'ic_pause_no']; 65 @State starts: Array<number> = [0, 0]; 66 @State curTimeSecs: Array<number> = [0, 0]; 67 @State musicIsClicked: boolean = false; 68 69 aboutToAppear(): void { 70 this.init() 71 } 72 73 async init(): Promise<void> { 74 if (this.appContext) { 75 return 76 } 77 78 this.stateImg = [$r('app.media.ic_pause_y'), $r('app.media.ic_pause_no')]; 79 this.stateText = ['ic_pause', 'ic_pause_no']; 80 this.starts = [0, 0]; 81 this.curTimeSecs = [0, 0]; 82 this.musicIsClicked = false; 83 this.appContext = getContext(this); 84 await this.getStageFileDescriptor(this.audioSources[MUSIC_INDEX]); 85 await this.getStageFileDescriptor(this.audioSources[RINGTONE_INDEX]); 86 87 for (let index = 0; index < PLAYER_CONTAINER.length; index++) { 88 try { 89 let renderer = await audio.createAudioRenderer(this.audioRendererOptions[index]); 90 this.audioRenderers.push(renderer); 91 await this.audioRenderers[index].setInterruptMode(audio.InterruptMode.INDEPENDENT_MODE); 92 this.listenState(index); 93 this.listenFocus(index); 94 } catch (err) { 95 let error = err as BusinessError; 96 console.error(`audioRenderer_${index} create ,Error: ${JSON.stringify(error)}`); 97 return; 98 } 99 } 100 } 101 102 async over(): Promise<void> { 103 this.appContext = undefined; 104 for (let index = 0; index < this.audioRenderers.length; index++) { 105 await this.audioRenderers[index].release(); 106 } 107 this.audioRenderers = []; 108 109 for (let index = 0; index < this.fileDescriptors.length; index++) { 110 await this.closeResource(this.audioSources[index]); 111 } 112 this.fileDescriptors = []; 113 } 114 115 onBackPress(): void { 116 this.over(); 117 } 118 119 async onPageHide(): Promise<void> { 120 this.over(); 121 } 122 123 onPageShow(): void { 124 this.init(); 125 } 126 127 listenFocus(index: number): void { 128 this.audioRenderers[index].on('audioInterrupt', async audioInterrupt => { 129 let hintType = audioInterrupt.hintType; 130 if (hintType === audio.InterruptHint.INTERRUPT_HINT_PAUSE) { 131 this.stateImg[index] = $r('app.media.ic_pause_no'); 132 this.stateText[index] = 'ic_pause_no'; 133 } 134 if (hintType === audio.InterruptHint.INTERRUPT_HINT_RESUME) { 135 this.stateImg[index] = $r('app.media.ic_play_no'); 136 this.stateText[index] = 'ic_play_no'; 137 await this.play(index); 138 } 139 }); 140 } 141 142 listenState(index: number): void { 143 this.audioRenderers[index].on('stateChange', state => { 144 if (state === audio.AudioState.STATE_RUNNING) { 145 if (index === 0) { 146 this.stateImg[index] = $r('app.media.ic_play_no'); 147 this.stateText[index] = 'ic_play_no'; 148 } else { 149 this.stateImg[index] = $r('app.media.ic_play_y'); 150 this.stateText[index] = 'ic_play'; 151 } 152 } 153 if (state === audio.AudioState.STATE_PAUSED) { 154 this.stateImg[index] = $r('app.media.ic_pause_y'); 155 this.stateText[index] = 'ic_pause'; 156 } 157 if (state === audio.AudioState.STATE_STOPPED) { 158 this.stateImg[index] = $r('app.media.ic_pause_no'); 159 this.stateText[index] = 'ic_pause_no'; 160 } 161 }); 162 } 163 164 getCurTimeSec(totalSec: number, totalLen: number, PastLen: number): number { 165 return Number((totalSec / totalLen * PastLen).toFixed(0)); 166 } 167 168 async getStageFileDescriptor(fileName: string): Promise<void> { 169 if (this.appContext) { 170 let mgr = this.appContext.resourceManager; 171 await mgr.getRawFd(fileName).then(value => { 172 this.fileDescriptors.push(value) 173 console.log('case getRawFileDescriptor success fileName: ' + fileName) 174 }).catch((error: BusinessError) => { 175 console.log('case getRawFileDescriptor err: ' + error) 176 }); 177 } 178 } 179 180 async closeResource(fileName: string): Promise<void> { 181 if (this.appContext) { 182 let mgr = this.appContext.resourceManager; 183 await mgr.closeRawFd(fileName).then(() => { 184 console.log('case closeRawFd success fileName: ' + fileName) 185 }).catch((error: BusinessError) => { 186 console.log('case closeRawFd err: ' + error) 187 }); 188 } 189 } 190 191 async play(index: number): Promise<void> { 192 if (this.audioRenderers[index] === null) { 193 return; 194 } 195 let bufferSize: number = 0; 196 try { 197 bufferSize = await this.audioRenderers[index].getBufferSize(); 198 await this.audioRenderers[index].start(); 199 } catch (err) { 200 let error = err as BusinessError; 201 console.error(`audioRenderer start : Error: ${JSON.stringify(error)}`); 202 return; 203 } 204 try { 205 let buf = new ArrayBuffer(bufferSize); 206 let start = this.fileDescriptors[index].offset as number; 207 if (this.starts[index] === 0) { 208 this.starts[index] = start; 209 } 210 let cur = this.starts[index]; 211 while (cur < start + this.fileDescriptors[index].length) { 212 // when render released,state is changed to STATE_RELEASED 213 if (this.audioRenderers[index].state === audio.AudioState.STATE_RELEASED) { 214 break; 215 } 216 // when render paused,state is changed to STATE_PAUSED 217 if (this.audioRenderers[index].state === audio.AudioState.STATE_PAUSED) { 218 this.starts[index] = cur; 219 break; 220 } 221 // when render stopped,state is changed to STATE_STOPPED 222 if (this.audioRenderers[index].state === audio.AudioState.STATE_STOPPED) { 223 this.starts[index] = this.fileDescriptors[index].length; 224 this.curTimeSecs[index] = TOTAL_SECOND; 225 break; 226 } 227 class Options { 228 offset: number = 0; 229 length: number = 0; 230 } 231 let options: Options = { 232 offset: cur, 233 length: bufferSize 234 } 235 await fs.read(this.fileDescriptors[index].fd, buf, options); 236 await this.audioRenderers[index].write(buf); 237 // update progress 238 this.curTimeSecs[index] = this.getCurTimeSec(TOTAL_SECOND, this.fileDescriptors[index].length, cur - start); 239 cur += bufferSize; 240 } 241 // when audio play completed,update state to stopped 242 if (cur >= this.fileDescriptors[index].length) { 243 await this.audioRenderers[index].stop(); 244 this.curTimeSecs[index] = TOTAL_SECOND; 245 } 246 } catch (err) { 247 let error = err as BusinessError; 248 console.error(`audioRenderer write : Error: ${JSON.stringify(error)}`); 249 } 250 } 251 252 async stop(index: number): Promise<void> { 253 try { 254 await this.audioRenderers[index].stop(); 255 } 256 catch (err) { 257 let error = err as BusinessError; 258 console.error(`render_1 stop err:${JSON.stringify(error)}`); 259 } 260 } 261 262 build() { 263 Column() { 264 Row() { 265 Navigation() { 266 NavRouter() { 267 NavDestination() { 268 } 269 } 270 } 271 .height('100%') 272 .width('100%') 273 .hideBackButton(false) 274 .titleMode(NavigationTitleMode.Mini) 275 .title($r('app.string.AudioFocus')) 276 .mode(NavigationMode.Stack); 277 }.height(56).width('100%').id('back_btn_focus') 278 .onClick(async () => { 279 await router.pushUrl({ url: 'pages/Index' }); 280 }); 281 282 Column() { 283 Column() { 284 Row() { 285 Row() { 286 Image($r('app.media.ic_music')).width(48).height(48); 287 Text($r('app.string.MusicType')) 288 .fontSize(16) 289 .margin({ left: 12 }) 290 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 291 .fontColor('#182431') 292 .fontWeight(500); 293 } 294 295 Text(this.stateText[MUSIC_INDEX]).id('music_state_text').fontSize(10).fontColor(Color.White); 296 Image(this.stateImg[MUSIC_INDEX]).id('music_state_img').width(36).height(36); 297 }.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 12 }); 298 299 Row() { 300 Progress({ value: this.curTimeSecs[MUSIC_INDEX], total: TOTAL_SECOND, type: ProgressType.Linear }) 301 .color('#007DFF') 302 .value(this.curTimeSecs[MUSIC_INDEX]) 303 .width('100%') 304 .height(4); 305 }.margin({ top: 24, bottom: 3 }).width('100%'); 306 307 Row() { 308 Text(this.curTimeSecs[MUSIC_INDEX] + 's') 309 .fontSize(12) 310 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 311 .fontColor('#182431') 312 .opacity(0.6) 313 .fontWeight(400); 314 Text(TOTAL_SECOND + 's') 315 .fontSize(12) 316 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 317 .fontColor('#182431') 318 .opacity(0.6) 319 .fontWeight(400); 320 }.justifyContent(FlexAlign.SpaceBetween).width('100%'); 321 } 322 .id('music_player_item') 323 .height(126) 324 .width('100%') 325 .padding({ left: '3.35%', right: '3.35%' }) 326 .backgroundColor(Color.White) 327 .margin({ bottom: 20 }) 328 .borderRadius(24) 329 .onClick(() => { 330 if (this.audioRenderers[MUSIC_INDEX].state === audio.AudioState.STATE_PREPARED) { 331 this.play(MUSIC_INDEX); 332 this.musicIsClicked = true; 333 this.stateText[RINGTONE_INDEX] = 'ic_pause'; 334 this.stateImg[RINGTONE_INDEX] = $r('app.media.ic_pause_y'); 335 } 336 }); 337 338 Column() { 339 Row() { 340 Row() { 341 Image($r('app.media.ic_ring')).width(48).height(48); 342 Text($r('app.string.RingtoneType')) 343 .fontSize(16) 344 .margin({ left: 12 }) 345 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 346 .fontColor('#182431') 347 .fontWeight(500); 348 } 349 350 Text(this.stateText[RINGTONE_INDEX]).id('ringtone_state_text').fontSize(10).fontColor(Color.White); 351 Image(this.stateImg[RINGTONE_INDEX]).id('ringtone_state_img').width(36).height(36); 352 }.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 10 }); 353 354 Row() { 355 Progress({ value: this.curTimeSecs[RINGTONE_INDEX], total: TOTAL_SECOND, type: ProgressType.Linear }) 356 .color('#007DFF') 357 .value(this.curTimeSecs[RINGTONE_INDEX]) 358 .width('100%') 359 .height(4); 360 }.margin({ top: 24, bottom: 3 }); 361 362 Row() { 363 Text(this.curTimeSecs[RINGTONE_INDEX] + 's') 364 .fontSize(12) 365 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 366 .fontColor('#182431') 367 .opacity(0.6) 368 .fontWeight(400); 369 Text(TOTAL_SECOND + 's') 370 .fontSize(12) 371 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 372 .fontColor('#182431') 373 .opacity(0.6) 374 .fontWeight(400); 375 }.justifyContent(FlexAlign.SpaceBetween).width('100%'); 376 } 377 .id('ringtone_player_item') 378 .width('100%') 379 .padding({ left: '3.35%', right: '3.35%' }) 380 .height(126) 381 .backgroundColor(Color.White) 382 .borderRadius(24) 383 .onClick(() => { 384 if (this.audioRenderers[RINGTONE_INDEX].state === audio.AudioState.STATE_RUNNING) { 385 this.stop(RINGTONE_INDEX); 386 } else if (this.audioRenderers[RINGTONE_INDEX].state === audio.AudioState.STATE_PREPARED && this.musicIsClicked === true) { 387 this.play(RINGTONE_INDEX); 388 } 389 }); 390 }.width('100%').padding({ left: '3.35%', right: '3.35%' }); 391 }.height('100%').width('100%').backgroundColor('#f1f3f5'); 392 } 393}