• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 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 display from '@ohos.display'
16import hardware_deviceManager from '@ohos.distributedHardware.deviceManager'
17import KvStoreModel from '../model/KvStoreModel'
18import Logger from '../model/Logger'
19import mediaQuery from '@ohos.mediaquery'
20import PlayerModel from '../model/PlayerModel'
21import { RemoteDeviceModel } from '../model/RemoteDeviceModel'
22import { DeviceDialog } from '../common/DeviceDialog'
23
24const TAG: string = 'Index'
25const DESIGN_WIDTH: number = 720.0
26const SYSTEM_UI_HEIGHT: number = 134
27const DESIGN_RATIO: number = 16 / 9
28const ONE_HUNDRED: number = 100
29const ONE_THOUSAND: number = 1000
30const SIXTY: number = 60
31const REMOTE_ABILITY_STARTED: string = 'remoteAbilityStarted'
32
33@Entry
34@Component
35struct Index {
36  private listener = mediaQuery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)')
37  @State isLand: boolean = false
38  @State currentTimeText: string = ''
39  @State currentProgress: number = 0
40  @State totalMs: number = 0
41  @State riscale: number = 1
42  @State risw: number = 720
43  @State rish: number = 1280
44  @State isSwitching: boolean = false
45  @State deviceLists: Array<hardware_deviceManager.DeviceInfo> = []
46  @State isDialogShowing: boolean = false
47  @State isDistributed: boolean = false
48  @State title: string = ''
49  @State totalTimeText: string = '00:00'
50  @State albumSrc: Resource = $r('app.media.album')
51  @State selectedIndex: number = 0
52  @State imageArrays: Array<Resource> = [$r('app.media.ic_hop'), $r('app.media.ic_play_previous'), $r('app.media.ic_play'), $r('app.media.ic_play_next')]
53  private dialogController: CustomDialogController = null
54  private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel()
55  private context
56  onLand = (mediaQueryResult) => {
57    Logger.info(TAG, `onLand: mediaQueryResult.matches= ${mediaQueryResult.matches}`)
58    if (mediaQueryResult.matches) {
59      this.isLand = true
60    } else {
61      this.isLand = false
62    }
63  }
64
65  showDialog() {
66    this.remoteDeviceModel.registerDeviceListCallback(() => {
67      Logger.info(TAG, 'registerDeviceListCallback, callback entered')
68      this.deviceLists = []
69      this.deviceLists.push({
70        deviceId: '0',
71        deviceName: '本机',
72        deviceType: 0,
73        networkId: ''
74      })
75      let deviceTempList = this.remoteDeviceModel.discoverLists.length > 0 ? this.remoteDeviceModel.discoverLists : this.remoteDeviceModel.deviceLists
76      for (let i = 0; i < deviceTempList.length; i++) {
77        Logger.info(TAG, `device ${i}/${deviceTempList.length} deviceId= ${deviceTempList[i].deviceId},
78         deviceName= ${deviceTempList[i].deviceName}, deviceType= ${deviceTempList[i].deviceType}`)
79        this.deviceLists.push(deviceTempList[i])
80        Logger.info(TAG, 'deviceLists push end')
81      }
82      Logger.info(TAG, 'CustomDialogController start')
83      if (this.dialogController !== null) {
84        this.dialogController.close()
85        this.dialogController = null
86      }
87      this.dialogController = new CustomDialogController({
88        builder: DeviceDialog({
89          deviceLists: this.deviceLists,
90          selectedIndex: this.selectedIndex,
91          selectedIndexChange: this.selectedIndexChange
92        }),
93        autoCancel: true
94      })
95      this.dialogController.open()
96      Logger.info(TAG, 'CustomDialogController end')
97    })
98  }
99
100  onBackPress() {
101    if (this.isDialogShowing === true) {
102      this.dismissDialog()
103      return true
104    }
105    return false
106  }
107
108  dismissDialog() {
109    this.dialogController.close()
110    this.remoteDeviceModel.unregisterDeviceListCallback()
111    this.isDialogShowing = false
112  }
113
114  startAbilityContinuation(deviceId) {
115    let params
116    Logger.info(TAG, `startAbilityContinuation PlayerModel.index= ${PlayerModel.index}/${PlayerModel.playlist.audioFiles.length}`)
117    if (PlayerModel.index >= 0 && PlayerModel.index <= PlayerModel.playlist.audioFiles.length) {
118      params = {
119        uri: PlayerModel.playlist.audioFiles[PlayerModel.index].fileUri,
120        seekTo: PlayerModel.getCurrentMs(),
121        isPlaying: PlayerModel.isPlaying
122      }
123    } else {
124      params = {
125        uri: '',
126        seekTo: 0,
127        isPlaying: false
128      }
129    }
130    Logger.info(TAG, `context.startAbility deviceId= ${deviceId}`)
131    let wantValue = {
132      bundleName: 'ohos.samples.etsdistributedmusicplayer',
133      abilityName: 'MainAbility',
134      deviceId: deviceId,
135      parameters: params
136    }
137    let timerId = setTimeout(() => {
138      Logger.info(TAG, 'onMessageReceiveTimeout, terminateSelf')
139      this.context.terminateSelf((error) => {
140        Logger.info(TAG, `terminateSelf finished, error= ${error}`)
141      })
142    }, 3000)
143
144    KvStoreModel.setOnMessageReceivedListener(this.context, REMOTE_ABILITY_STARTED, () => {
145      Logger.info(TAG, 'OnMessageReceived, terminateSelf')
146      clearTimeout(timerId)
147      this.context.terminateSelf((error) => {
148        Logger.info(TAG, `terminateSelf finished, error= ${error}`)
149      })
150    })
151    Logger.info(TAG, `context.startAbility start`)
152    this.context.startAbility(wantValue).then((data) => {
153      Logger.info(TAG, `context.startAbility finished ${JSON.stringify(data)}`)
154    })
155    this.clearSelectState()
156    Logger.info(TAG, `context.startAbility want= ${JSON.stringify(wantValue)}`)
157    Logger.info(TAG, 'context.startAbility end')
158  }
159
160  selectedIndexChange = (selectedIndex) => {
161    if (selectedIndex === 0) {
162      this.context.startAbility({ bundleName: 'ohos.samples.etsdistributedmusicplayer',
163        abilityName: 'MainAbility',
164        deviceId: this.deviceLists[selectedIndex].deviceId,
165        parameters: {
166          isFA: 'EXIT'
167        }
168      }).then((data) => {
169        Logger.info(TAG, `startAbility finished ${JSON.stringify(data)}`)
170      })
171      this.isDistributed = false
172      this.selectedIndex = 0
173      this.dialogController.close()
174      this.deviceLists = []
175      return
176    }
177    this.selectedIndex = selectedIndex
178    this.selectDevice()
179  }
180
181  selectDevice() {
182    Logger.info(TAG, 'start ability ......')
183    if (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverLists.length <= 0) {
184      Logger.info(TAG, `start ability device:${JSON.stringify(this.deviceLists)}`)
185      this.startAbilityContinuation(this.deviceLists[this.selectedIndex].deviceId)
186      this.clearSelectState()
187      return
188    }
189    Logger.info(TAG, 'start ability, needAuth')
190    this.remoteDeviceModel.authDevice(this.deviceLists[this.selectedIndex], (device) => {
191      Logger.info(TAG, 'auth and online finished')
192      this.startAbilityContinuation(device.deviceId)
193    })
194    Logger.info(TAG, 'start ability2 ......')
195    this.clearSelectState()
196  }
197
198  clearSelectState() {
199    this.deviceLists = []
200    if(this.dialogController){
201      this.dialogController.close()
202      this.dialogController = null
203    }
204  }
205
206  getShownTimer(ms) {
207    let minStr: string
208    let secStr: string
209    let seconds = Math.floor(ms / ONE_THOUSAND)
210    let sec = seconds % SIXTY
211    Logger.info(TAG, `getShownTimer sec = ${sec}`)
212    let min = (seconds - sec) / SIXTY
213    Logger.info(TAG, `getShownTimer min = ${min}`)
214    if (sec < 10) {
215      secStr = '0' + sec
216    } else {
217      secStr = sec.toString(10)
218    }
219    if (min < 10) {
220      minStr = '0' + min
221    } else {
222      minStr = min.toString(10)
223    }
224    Logger.warn(TAG, `getShownTimer = ${minStr}:${secStr}`)
225    return minStr + ':' + secStr
226  }
227
228  refreshSongInfo(index) {
229    Logger.info(TAG, `refreshSongInfo ${index}/${PlayerModel.playlist.audioFiles.length}`)
230    if (index >= PlayerModel.playlist.audioFiles.length) {
231      Logger.warn(TAG, 'refreshSongInfo ignored')
232      return
233    }
234    // update song title
235    this.title = PlayerModel.playlist.audioFiles[index].name
236    this.albumSrc = (index % 2 === 0) ? $r('app.media.album') : $r('app.media.album2')
237
238    // update duration
239    this.totalMs = PlayerModel.getDuration()
240    this.totalTimeText = this.getShownTimer(this.totalMs)
241    this.currentTimeText = this.getShownTimer(PlayerModel.getCurrentMs())
242    Logger.info(TAG, `refreshSongInfo this.title= ${this.title}, this.totalMs= ${this.totalMs}, this.totalTimeText= ${this.totalTimeText},this.currentTimeText= ${this.currentTimeText}`)
243  }
244
245  onPreviousClick() {
246    if (this.isSwitching) {
247      Logger.info(TAG, 'onPreviousClick ignored, isSwitching')
248      return
249    }
250    Logger.info(TAG, 'onPreviousClick')
251    PlayerModel.index--
252    if (PlayerModel.index < 0 && PlayerModel.playlist.audioFiles.length >= 1) {
253      PlayerModel.index = PlayerModel.playlist.audioFiles.length - 1
254    }
255    this.currentProgress = 0
256    this.isSwitching = true
257
258    PlayerModel.preLoad(PlayerModel.index, () => {
259      this.refreshSongInfo(PlayerModel.index)
260      PlayerModel.play(0, true)
261      if (PlayerModel.isPlaying) {
262        this.imageArrays[2] = $r('app.media.ic_pause')
263      }
264      this.isSwitching = false
265    })
266  }
267
268  onNextClick() {
269    if (this.isSwitching) {
270      Logger.info(TAG, 'onNextClick ignored, isSwitching')
271      return
272    }
273    Logger.info(TAG, 'onNextClick')
274    PlayerModel.index++
275    if (PlayerModel.index >= PlayerModel.playlist.audioFiles.length) {
276      PlayerModel.index = 0
277    }
278    this.currentProgress = 0
279    this.isSwitching = true
280    PlayerModel.preLoad(PlayerModel.index, () => {
281      this.refreshSongInfo(PlayerModel.index)
282      PlayerModel.play(0, true)
283      if (PlayerModel.isPlaying) {
284        this.imageArrays[2] = $r('app.media.ic_pause')
285      }
286      this.isSwitching = false
287    })
288  }
289
290  onPlayClick() {
291    if (this.isSwitching) {
292      Logger.info(TAG, 'onPlayClick ignored, isSwitching')
293      return
294    }
295    Logger.info(TAG, `onPlayClick isPlaying= ${PlayerModel.isPlaying}`)
296    if (PlayerModel.isPlaying) {
297      PlayerModel.pause()
298      this.imageArrays[2] = $r('app.media.ic_play')
299    } else {
300      PlayerModel.preLoad(PlayerModel.index, () => {
301        PlayerModel.play(-1, true)
302        this.imageArrays[2] = $r('app.media.ic_pause')
303      })
304    }
305  }
306
307  restoreFromWant() {
308    Logger.info(TAG, 'restoreFromWant')
309    let status:any = AppStorage.Get('status')
310    if (status !== null && status.uri !== null) {
311      KvStoreModel.broadcastMessage(this.context, REMOTE_ABILITY_STARTED)
312      Logger.info(TAG, 'restorePlayingStatus')
313      PlayerModel.restorePlayingStatus(status, (index) => {
314        Logger.info(TAG, `restorePlayingStatus finished, index= ${index}`)
315        if (index >= 0) {
316          this.refreshSongInfo(index)
317        } else {
318          PlayerModel.preLoad(0, () => {
319            this.refreshSongInfo(0)
320          })
321        }
322        Logger.info(TAG, `Index PlayerModel.restorePlayingStatus this.totalMs = ${this.totalMs}, status.seekTo = ${status.seekTo}`)
323        this.currentProgress = Math.floor(status.seekTo / this.totalMs * ONE_HUNDRED)
324      })
325    } else {
326      PlayerModel.preLoad(0, () => {
327        this.refreshSongInfo(0)
328      })
329    }
330  }
331
332  aboutToAppear() {
333    Logger.info(TAG, `begin`)
334    Logger.info(TAG, 'grantPermission')
335    this.context = getContext(this) as any
336    let requestCode = 666
337    this.context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], requestCode, function (result) {
338      Logger.info(TAG, `grantPermission,requestPermissionsFromUser,result= ${result}`)
339    })
340    display.getDefaultDisplay().then((dis) => {
341      Logger.info(TAG, `getDefaultDisplay dis= ${JSON.stringify(dis)}`)
342      let proportion = DESIGN_WIDTH / dis.width
343      let screenWidth = DESIGN_WIDTH
344      let screenHeight = (dis.height - SYSTEM_UI_HEIGHT) * proportion
345      this.riscale = (screenHeight / screenWidth) / DESIGN_RATIO
346      if (this.riscale < 1) {
347        // The screen ratio is shorter than design ratio
348        this.risw = screenWidth * this.riscale
349        this.rish = screenHeight
350      } else {
351        // The screen ratio is longer than design ratio
352        this.risw = screenWidth
353        this.rish = screenHeight / this.riscale
354      }
355      Logger.info(TAG, `proportion=${proportion} , screenWidth= ${screenWidth},
356      screenHeight= ${screenHeight} , riscale= ${this.riscale} , risw= ${this.risw} , rish= ${this.rish}`)
357    })
358    Logger.info(TAG, 'getDefaultDisplay end')
359    this.currentTimeText = this.getShownTimer(0)
360    PlayerModel.setOnStatusChangedListener((isPlaying) => {
361      Logger.info(TAG, `on player status changed, isPlaying= ${isPlaying} refresh ui`)
362      PlayerModel.setOnPlayingProgressListener((currentTimeMs) => {
363        this.currentTimeText = this.getShownTimer(currentTimeMs)
364        this.currentProgress = Math.floor(currentTimeMs / this.totalMs * ONE_HUNDRED)
365      })
366      if (isPlaying) {
367        this.imageArrays[2] = $r('app.media.ic_pause')
368      } else {
369        this.imageArrays[2] = $r('app.media.ic_play')
370      }
371    })
372
373    PlayerModel.getPlaylist(() => {
374      Logger.info(TAG, 'on playlist generated, refresh ui')
375      this.restoreFromWant()
376    })
377  }
378
379  build() {
380    Column() {
381      Text(this.title)
382        .width('100%')
383        .fontSize(30)
384        .margin({ top: '10%' })
385        .fontColor(Color.White)
386        .textAlign(TextAlign.Center)
387      Image(this.albumSrc)
388        .width(this.isLand ? '60%' : '80%')
389        .height(this.isLand ? '50%' : '35%')
390        .objectFit(ImageFit.Contain)
391        .margin({ top: 20, left: 40, right: 40 })
392      Row() {
393        Text(this.currentTimeText)
394          .fontSize(20)
395          .fontColor(Color.White)
396        Blank()
397        Text(this.totalTimeText)
398          .fontSize(20)
399          .fontColor(Color.White)
400      }
401      .width('90%')
402      .margin({ top: '10%' })
403
404      Slider({ value: this.currentProgress })
405        .width('80%')
406        .selectedColor('#ff0c4ae7')
407        .onChange((value: number, mode: SliderChangeMode) => {
408          this.currentProgress = value
409          if (isNaN(this.totalMs)) {
410            this.currentProgress = 0
411            Logger.info(TAG, `setProgress ignored, totalMs= ${this.totalMs}`)
412            return
413          }
414          let currentMs = this.currentProgress / ONE_HUNDRED * this.totalMs
415          this.currentTimeText = this.getShownTimer(currentMs)
416          if (mode === SliderChangeMode.End || mode === 3) {
417            Logger.info(TAG, `player.seek= ${currentMs}`)
418            PlayerModel.seek(currentMs)
419          }
420        })
421
422      Row() {
423        ForEach(this.imageArrays, (item, index) => {
424          Column() {
425            Image(item)
426              .size({ width: 72, height: 72 })
427              .objectFit(ImageFit.Contain)
428              .onClick(() => {
429                switch (index) {
430                  case 0:
431                    this.showDialog()
432                    break
433                  case 1:
434                    this.onPreviousClick()
435                    break
436                  case 2:
437                    this.onPlayClick()
438                    break
439                  case 3:
440                    this.onNextClick()
441                    break
442                  default:
443                    break
444                }
445              })
446          }
447          .key('image' + (index + 1))
448          .width(100)
449          .height(100)
450          .alignItems(HorizontalAlign.Center)
451          .justifyContent(FlexAlign.Center)
452        })
453
454      }
455      .width('100%')
456      .margin({ top: '10%' })
457      .justifyContent(FlexAlign.SpaceEvenly)
458    }
459    .width('100%')
460    .height('100%')
461    .backgroundImage($r('app.media.bg_blurry'))
462    .backgroundImageSize({ width: '100%', height: '100%' })
463  }
464}