• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}