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}