diff --git a/scenario/MusicPlayerOnline/README_zh.md b/scenario/MusicPlayerOnline/README_zh.md index 5ca0c07ae12e77507b09e15dc269a0e9685d5904..3e72aa91b841cf1c361337d76b147f66ee87117e 100644 --- a/scenario/MusicPlayerOnline/README_zh.md +++ b/scenario/MusicPlayerOnline/README_zh.md @@ -85,7 +85,7 @@ 5.5 歌单改名 6. 登录注册页面 7. 每日推荐歌单 - 8. 播放模式 + 8. 播放模式 (已完成) 9. 进度条拖动 10. 其他设置 11. 歌词 @@ -95,6 +95,7 @@ 15. 音频焦点处理 16. 其他界面交互效果(切换动画、点击效果) 17. 设置页(关于、登录、登出、退出) + 18. 分布式播放 #### 未计划功能: 1. 社交(用户信息,好友,分享,评论) diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/constants/PlayMode.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/PlayMode.ets new file mode 100644 index 0000000000000000000000000000000000000000..b2463ec1ba7e940285b8341c1ebfc89dd80f3cdd --- /dev/null +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/PlayMode.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common constants for Player. + */ + +export enum PLAY_MODE { + 'REPEAT' = 0, + 'REPEAT1' = 1, + 'SHUFFLE' = 2 +} \ No newline at end of file diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/constants/ServerConstants.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/ServerConstants.ets index df01abc157a2d9ab4c1c47fd4e9a8a97c802afe4..535042e2132f5ef4e513035a4d2f24810676671a 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/constants/ServerConstants.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/ServerConstants.ets @@ -78,6 +78,7 @@ export default class ServerConstants { static readonly PLAYER_STATE_STOPPED = 'stopped'; static readonly PLAYER_STATE_RELEASED = 'released'; static readonly PLAYER_STATE_UNKNOWN = 'unknown'; + /** * PLAYER UPDATE */ @@ -96,7 +97,7 @@ export default class ServerConstants { static readonly LOGIN_BUTTON_IN = '登录'; static readonly LOGIN_BUTTON_OUT = '登出'; static readonly LOGIN_INFO_NO = '未登录'; - static readonly LOGIN_INFO_ING = '正在登录...'; + static readonly LOGIN_INFO_ING = '···'; static readonly LOGIN_INFO_FAILED = '登录失败'; static readonly LOGIN_INFO_DONE = ' 你好!'; static readonly TEST_ACCOUNT_USER = 'test'; @@ -109,4 +110,3 @@ export default class ServerConstants { static readonly SEARCHING = '搜索中'; static readonly EMPTY_SEARCH_WORD = '不能搜索空字符'; } - diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets index e109641d71419efc5544086bdb144cc3be0ccbbf..3f6d4788185fd583edc8332079e7b11404822f1c 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets @@ -19,11 +19,13 @@ import AudioItem from '../model/AudioItem'; import Logger from '../utils/Logger'; import emitter from '@ohos.events.emitter'; import ServerConstants from '../constants/ServerConstants'; +import { PLAY_MODE } from '../constants/PlayMode'; import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager'; import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent'; import common from '@ohos.app.ability.common'; import http from '@ohos.net.http' + export default class PlayerManager { private tag: string = 'PlayerManager'; private isSeek: boolean = false; @@ -35,12 +37,18 @@ export default class PlayerManager { private item: AudioItem = new AudioItem('', '', ''); private listPosition: number = 0; private state: string = ServerConstants.PLAYER_STATE_UNKNOWN; - private listTitle: string = ''; + private listTitle: string = ServerConstants.LIST_SONG_NO_PLAYING; private httpRequest = http.createHttp(); + private playMode: number | undefined = PLAY_MODE.REPEAT; + private shuffleIndex: number[] = []; private emitterOptions: emitter.Options = { priority: emitter.EventPriority.HIGH }; + constructor() { + this.startContinuousTask(); + } + // 注册avplayer回调函数 setAVPlayerCallback(avPlayer: media.AVPlayer) { // seek操作结果回调函数 @@ -68,6 +76,9 @@ export default class PlayerManager { console.info('PlayerManager state durationUpdate:' + time); this.currentDuration = time; }) + avPlayer.on('seekDone', (seekDoneTime:number) => { + console.info('seekDone success,and seek time is:' + seekDoneTime) + }) avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => { this.state = state; let eventData: emitter.EventData = { @@ -79,7 +90,7 @@ export default class PlayerManager { switch (state) { case ServerConstants.PLAYER_STATE_IDLE: // 成功调用reset接口后触发该状态机上报 console.info('PlayerManager state idle called.'); - avPlayer.release(); // 调用release接口销毁实例对象 + //avPlayer.release(); // 调用release接口销毁实例对象 break; case ServerConstants.PLAYER_STATE_INITIALIZED: // avplayer 设置播放源后触发该状态上报 console.info('PlayerManager state initialized called.'); @@ -93,19 +104,18 @@ export default class PlayerManager { console.info('PlayerManager state playing called.'); this.list[this.listPosition].isPlaying = true; this.record_recent(); - this.startContinuousTask(); break; case ServerConstants.PLAYER_STATE_PAUSED: // pause成功调用后触发该状态机上报 console.info('PlayerManager state paused called.'); break; case ServerConstants.PLAYER_STATE_COMPLETED: // 播放结束后触发该状态机上报 console.info('PlayerManager state completed called.'); - avPlayer.stop(); //调用播放结束接口 + //avPlayer.stop(); //调用播放结束接口 this.next(); break; case ServerConstants.PLAYER_STATE_STOPPED: // stop接口成功调用后触发该状态机上报 console.info('PlayerManager state stopped called.'); - this.stopContinuousTask(); + //this.stopContinuousTask(); this.currentTime = 0; Logger.info(this.tag, 'Stop:' + this.item.title); avPlayer.reset(); // 调用reset接口初始化avplayer状态 @@ -131,6 +141,11 @@ export default class PlayerManager { } this.list = list; this.listTitle = listTitle; + this.playMode = PLAY_MODE.REPEAT; + this.shuffleIndex = []; + for (let i = 0; i < list.length; i++) { + this.shuffleIndex.push(i); + } this.play(item); } @@ -153,12 +168,15 @@ export default class PlayerManager { * 播放 */ play(item: AudioItem): void { - this.stop(); Logger.info(this.tag, 'Play finish:' + this.listPosition.toString()); + if (this.item) { + this.item.isPlaying = false; + } let index = -1 - if (item !== undefined) { - index = this.list.indexOf(item) + if (item === undefined) { + return } + index = this.list.indexOf(item) if (-1 === index) { this.listPosition = 0; } else { @@ -212,10 +230,25 @@ export default class PlayerManager { */ next(): void { let newPosition = 0; - if (this.listPosition + 1 === this.list.length) { - newPosition = 0; - } else { - newPosition = this.listPosition + 1; + switch (this.playMode) { + case PLAY_MODE.REPEAT1: + newPosition = this.listPosition; + break; + case PLAY_MODE.SHUFFLE: + let currentIndex: number = this.shuffleIndex.indexOf(this.listPosition); + let nextIndex: number = 0; + if (currentIndex + 1 !== this.shuffleIndex.length) { + nextIndex = currentIndex + 1; + } + newPosition = this.shuffleIndex[nextIndex]; + break; + default: + if (this.listPosition + 1 === this.list.length) { + newPosition = 0; + } else { + newPosition = this.listPosition + 1; + } + break; } Logger.info(this.tag, 'Play next:' + newPosition.toString()); this.play(this.list[newPosition]); @@ -226,18 +259,50 @@ export default class PlayerManager { */ previous() { let newPosition = 0; - if (this.listPosition === 0) { - newPosition = 0; - } else { - newPosition = this.listPosition - 1; + switch (this.playMode) { + case PLAY_MODE.REPEAT1: + newPosition = this.listPosition; + break; + case PLAY_MODE.SHUFFLE: + let currentIndex: number = this.shuffleIndex.indexOf(this.listPosition); + let nextIndex: number = 0; + if (currentIndex !== 0) { + nextIndex = currentIndex - 1; + } else { + nextIndex = this.shuffleIndex.length - 1; + } + newPosition = this.shuffleIndex[nextIndex]; + break; + default: + if (this.listPosition === 0) { + newPosition = this.list.length - 1; + } else { + newPosition = this.listPosition - 1; + } + break; } Logger.info(this.tag, 'Play previous:' + newPosition.toString()); this.play(this.list[newPosition]); } //播放顺序 - setPlayMode() { - + setPlayMode(): number { + switch (this.playMode) { + //to repeat1 + case PLAY_MODE.REPEAT: + this.playMode = PLAY_MODE.REPEAT1; + break; + //to shuffle + case PLAY_MODE.REPEAT1: + this.playMode = PLAY_MODE.SHUFFLE; + this.shuffleIndex.sort(() => Math.random() - 0.5); + break; + //to repeat + default: + this.playMode = PLAY_MODE.REPEAT; + break; + } + return this.playMode; } getItem(): AudioItem { @@ -252,15 +317,23 @@ export default class PlayerManager { return this.state; } + getPlayMode(): number { + if (this.playMode === undefined) { + return PLAY_MODE.REPEAT; + } + return this.playMode; + } + async avPlayerLive(url: string) { // 创建avPlayer实例对象 if (this.avPlayer === undefined) { this.avPlayer = await media.createAVPlayer(); this.setAVPlayerCallback(this.avPlayer); } else { - this.avPlayer.release(); - this.avPlayer = await media.createAVPlayer(); - this.setAVPlayerCallback(this.avPlayer); + await this.avPlayer.reset(); + //this.avPlayer.release(); + //this.avPlayer = await media.createAVPlayer(); + //this.setAVPlayerCallback(this.avPlayer); } console.info('PlayerManager state url:' + url); this.avPlayer.url = url; diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets index 5a3a077e4e9ec2f37f98f26120beb39afca31fd2..aef9135daaf1177c4976af81e7fb8513fcfb8fbc 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets @@ -43,10 +43,9 @@ struct Index { @State isRefreshing: boolean = false; @State mOffset: number = 0; @State mFriction: number = 48; - @State maskOpacity: number = 0.5; + @State maskOpacity: number = 0.3; @State loginInfo: string = ServerConstants.LOGIN_INFO_NO; @State loginButton: string = ServerConstants.LOGIN_BUTTON_IN; - @State loginButtonClickable: boolean = true; @State rowAngle: number = 0; @State colAngle: number = 0; private httpRequest = http.createHttp(); @@ -95,49 +94,51 @@ struct Index { build() { Column() { Row() { - Image($r('app.media.open_sidebar')) - .objectFit(ImageFit.Fill) - .width(32) - .height(42) - .margin({ right: 8, left: 8 }) - .clickEffect(UIConstants.CLICK_EFFECT) - .onClick(() => { - animateTo({ duration: 1000 }, () => { + Row() { + Image($r('app.media.open_sidebar')) + .width(32) + .height(42) + .objectFit(ImageFit.Fill) + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { //this.isShowSetting = true; }) - }) - .id('setting') + .id('setting') + } + .width(96) + .height('100%') + .alignSelf(ItemAlign.Start) + if (this.loginInfo === ServerConstants.LOGIN_INFO_NO) { Text(this.loginInfo) .fontSize(24) .margin({ right: 4, left: 4 }) - .transition(TransitionEffect.OPACITY) + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }) + .combine(TransitionEffect.translate({ y: 24 }))) } else { Text(this.loginInfo) .fontSize(24) .margin({ right: 4, left: 4 }) - .transition(TransitionEffect.OPACITY) + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }) + .combine(TransitionEffect.translate({ y: -24 }))) } - Button(this.loginButton) - .type(ButtonType.Normal) - .stateEffect(this.loginButtonClickable) + Text(this.loginButton) .backgroundColor(Color.White) .fontColor(Color.Red) .fontWeight(FontWeight.Lighter) - .borderWidth(1) .borderColor(Color.Gray) .borderRadius(4) .height(42) - .width(108) - .margin({ right: 4, left: 4 }) + .width(96) + .textAlign(TextAlign.End) .fontSize(28) + .maxLines(1) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { if (this.loginButton === ServerConstants.LOGIN_BUTTON_OUT) { this.logout(); - } - else { + } else if (this.loginButton === ServerConstants.LOGIN_BUTTON_IN) { this.login() } }) @@ -365,7 +366,7 @@ struct Index { .width('100%') .height('100%') .transition(TransitionEffect.OPACITY - .animation({ curve: curves.springMotion() }) + .animation({ duration: 1000, curve: Curve.FastOutSlowIn }) .combine(TransitionEffect.move(TransitionEdge.BOTTOM)) ) } @@ -373,22 +374,17 @@ struct Index { Rect() .width('100%') .height('100%') - .fill('#e0e0e0') + .fill('#f0f0f0') .fillOpacity(this.maskOpacity) - .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayList = false; - }) + this.isShowPlayList = false; }) - .transition(TransitionEffect.OPACITY) - } - if (this.isShowPlayList) { + .transition(TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Ease })) PlayList() .width('100%') .height('80%') .transition(TransitionEffect.OPACITY - .animation({ curve: curves.springMotion() }) + .animation({ duration: 1000, curve: Curve.FastOutSlowIn }) .combine(TransitionEffect.move(TransitionEdge.BOTTOM))) } } @@ -400,14 +396,10 @@ struct Index { onBackPress() { if (this.isShowPlayList) { - animateTo({ duration: 1000 }, () => { - this.isShowPlayList = false; - }) + this.isShowPlayList = false; return true; } else if (this.isShowPlayerDetail) { - animateTo({ duration: 1000 }, () => { - this.isShowPlayerDetail = false; - }) + this.isShowPlayerDetail = false; return true; } else { return false; @@ -417,9 +409,9 @@ struct Index { logout() { AppStorage.setOrCreate('loginName', ServerConstants.LOGIN_INFO_NO); AppStorage.setOrCreate('loginCookie', ''); + this.loginButton = ServerConstants.LOGIN_BUTTON_IN; animateTo({ duration: 2000 }, () => { this.customPlayLists = []; - this.loginButton = ServerConstants.LOGIN_BUTTON_IN; this.loginInfo = ServerConstants.LOGIN_INFO_NO; }) this.getRecommendListFromServer(); @@ -428,10 +420,7 @@ struct Index { login() { try { - animateTo({ duration: 2000 }, () => { - this.loginButton = ServerConstants.LOGIN_INFO_ING; - }) - this.loginButtonClickable = false; + this.loginButton = ServerConstants.LOGIN_INFO_ING; this.httpRequest.request(ServerConstants.CSRF_URL, (err: Error, data: http.HttpResponse) => { if (!err) { @@ -454,9 +443,7 @@ struct Index { console.info('HttpResponse Result:' + data.result); console.info('HttpResponse code:' + data.responseCode); if (200 === data.responseCode) { - animateTo({ duration: 2000 }, () => { - this.loginInfo = ServerConstants.TEST_ACCOUNT_USER + ServerConstants.LOGIN_INFO_DONE - }) + this.loginInfo = ServerConstants.TEST_ACCOUNT_USER + ServerConstants.LOGIN_INFO_DONE; AppStorage.setOrCreate('loginName', ServerConstants.TEST_ACCOUNT_USER); let loginCookie: string[] = data.header['set-cookie']; let cookie: string = loginCookie.map(cookie => { @@ -467,9 +454,7 @@ struct Index { } console.info('HttpResponse type:' + JSON.stringify(data.resultType)); console.info('HttpResponse header:' + JSON.stringify(data.header)); - animateTo({ duration: 2000 }, () => { - this.loginButton = ServerConstants.LOGIN_BUTTON_OUT; - }) + this.loginButton = ServerConstants.LOGIN_BUTTON_OUT; this.getRecommendListFromServer(); this.getCustomListFromServer(); } else { @@ -499,7 +484,6 @@ struct Index { message: ServerConstants.LOGIN_INFO_FAILED }) } - this.loginButtonClickable = true; } getRecommendListFromServer() { @@ -612,8 +596,8 @@ struct Index { searchSongsFromServer(search_word: string) { search_word = search_word.trim(); - if(!search_word){ - prompt.showToast({message:ServerConstants.EMPTY_SEARCH_WORD}); + if (!search_word) { + prompt.showToast({ message: ServerConstants.EMPTY_SEARCH_WORD }); return; } animateTo( diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayList.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayList.ets index ac2b73334f403b644c3bd18512b990ee42ed9523..27c824469987c0c86f63a7e371127da1d0bf8589 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayList.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayList.ets @@ -22,16 +22,27 @@ import UIConstants from '../constants/UIConstants'; @Component export default struct PlayList { + private playerManager: PlayerManager = AppStorage.get('PlayerManager') as PlayerManager; @State playingList: AudioItem[] = Array(); @State listTitle: string = ServerConstants.LIST_SONG_NO_PLAYING; - private playerManager: PlayerManager = AppStorage.get('PlayerManager') as PlayerManager; + private scrollerForList: Scroller = new Scroller(); aboutToAppear() { - this.playingList = this.playerManager.getCurrentPlayList(); this.listTitle = this.playerManager.getListTitle(); + animateTo({ duration: 2000 }, () => { + this.playingList = this.playerManager.getCurrentPlayList(); + }) + setTimeout(() => { + this.scrollerForList.scrollToIndex(this.playingList.indexOf(this.playerManager.getItem()), true, ScrollAlign.CENTER); + }, 3000) emitter.on(ServerConstants.UPDATE_STATE_EVENT_ID, (eventData: emitter.EventData) => { - if (eventData !== undefined && eventData.data !== undefined) { - this.playingList = this.playerManager.getCurrentPlayList(); + if (eventData !== undefined && eventData.data !== undefined && eventData.data.state === ServerConstants.PLAYER_STATE_PLAYING) { + animateTo({ duration: 2000 }, () => { + this.playingList = this.playerManager.getCurrentPlayList(); + }) + setTimeout(() => { + this.scrollerForList.scrollToIndex(this.playingList.indexOf(this.playerManager.getItem()), true, ScrollAlign.CENTER); + }, 2000) } }); } @@ -43,30 +54,27 @@ export default struct PlayList { Column() { Text(this.listTitle) .fontSize(24) - .margin(8) - Scroll() { - Column({ space: 10 }) { - List() { - ForEach(this.playingList, (item: AudioItem) => { - ListItem() { - SongCell({ item }) - } - .width('100%') - .backgroundColor(item.isPlaying ? '#f0f0f0' : Color.White) - .clickEffect(UIConstants.CLICK_EFFECT) - .onClick(() => { - this.playerManager.playList(this.listTitle, this.playingList, item) - }) - .height('84vp') - }) + .margin(12) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.MARQUEE }) + List({ scroller: this.scrollerForList }) { + ForEach(this.playingList, (item: AudioItem) => { + ListItem() { + SongCell({ item }) } .width('100%') - .backgroundColor(Color.White) - } - .width('100%') + .height('84vp') + .backgroundColor(item.isPlaying ? '#f0f0f0' : Color.White) + .transition({ type: TransitionType.All, opacity: 0 }) + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + this.playerManager.play(item) + }) + }) } - .margin({ bottom: 36 }) .width('100%') + .backgroundColor(Color.White) + .margin({ bottom: 36 }) } .height('100%') .backgroundColor(Color.White) diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets index 40dd2a83d7dc03179f50f0500f61f894e071f12b..fa2ddf8a2c681759b892270f4ef232fccf9ec9b1 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets @@ -34,7 +34,7 @@ export default struct PlayerBar { @Link isShowPlayList: boolean; @Link isShowPlayerDetail: boolean; private PlayerManager: PlayerManager = AppStorage.get('PlayerManager') as PlayerManager; - private spaces:string = ' ' + private spaces: string = ' ' aboutToAppear() { Logger.info(this.tag, 'aboutToAppear'); @@ -80,7 +80,7 @@ export default struct PlayerBar { .borderRadius(48) .rotate({ angle: this.rotateAngle }) .animation({ - duration: 3600, + duration: 7200, curve: Curve.Linear, delay: 500, iterations: -1, @@ -89,9 +89,7 @@ export default struct PlayerBar { .id('songImage') .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayerDetail = true; - }) + this.isShowPlayerDetail = true; }) Text(this.item.id ? (this.state === ServerConstants.PLAYER_STATE_PLAYING ? this.spaces + this.item.title + ' - ' + this.item.artist + this.spaces : this.item.title + ' - ' + this.item.artist) : ServerConstants.LIST_SONG_NO_PLAYING) @@ -105,15 +103,13 @@ export default struct PlayerBar { .id('songName') .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayerDetail = true; - }) + this.isShowPlayerDetail = true; }) Blank() Stack() { Row({ space: 40 }) { Progress({ value: 0, total: this.durationTime, type: ProgressType.Ring }) - .color(Color.Blue) + .color(Color.Gray) .value(this.currentTime) .width('100%') .height('100%') @@ -123,9 +119,6 @@ export default struct PlayerBar { if (this.state === ServerConstants.PLAYER_STATE_PLAYING) { Image($r('app.media.pause')) - .objectFit(ImageFit.Contain) - .width('90%') - .height('90%') .padding('4vp') .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { @@ -134,9 +127,6 @@ export default struct PlayerBar { .id('paused') } else { Image($r('app.media.media_center')) - .objectFit(ImageFit.Contain) - .width('90%') - .height('90%') .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { this.PlayerManager.resume(); @@ -144,20 +134,17 @@ export default struct PlayerBar { .id('playing') } }.alignContent(Alignment.Center) - .width(48) - .height(48) + .width(46) + .height(46) .margin({ right: 8 }) Image($r('app.media.music_note_list')) - .objectFit(ImageFit.Contain) .width(42) .height(42) .margin({ right: 8, left: 8 }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayList = true; - }) + this.isShowPlayList = true; }) .id('playlist') } diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerDetail.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerDetail.ets index 863ea9fbd727634c6f950e610dbfc871739cb15c..c2c75b676362f3b0d20b2873b661aafcca152407 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerDetail.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerDetail.ets @@ -21,6 +21,7 @@ import CommonUtils from '../utils/CommonUtils'; import ServerConstants from '../constants/ServerConstants'; import UIConstants from '../constants/UIConstants'; import AudioItem from '../model/AudioItem'; +import { PLAY_MODE } from '../constants/PlayMode'; /** * Setting tab content @@ -32,13 +33,15 @@ export default struct PlayerDetail { @State item: AudioItem = this.PlayerManager.getItem(); @State listTitle: string = this.PlayerManager.getListTitle(); @State durationTime: number = 0; - @State currentTime: number = 0; // 当前时间 - @State state: string = this.PlayerManager.getState(); // 播放状态 + @State currentTime: number = 0; + @State state: string = this.PlayerManager.getState(); @State rotateAngle: number = 0; @State subCurrentIndex: number = 0; @State lrcIndex: number = 0 @State lrc: LrcLine[] = []; @State contentSwitch: boolean = true; + @State playMode: number = this.PlayerManager.getPlayMode(); + @State noItem: boolean = false; @Link isShowPlayList: boolean; @Link isShowPlayerDetail: boolean; private scroller: Scroller = new Scroller() @@ -73,6 +76,7 @@ export default struct PlayerDetail { emitter.on(ServerConstants.UPDATE_FAVOR_EVENT_ID, (eventData: emitter.EventData) => { if (eventData !== undefined && eventData.data !== undefined) { this.item = this.PlayerManager.getItem(); + this.noItem = false; } }); emitter.on(ServerConstants.UPDATE_TIME_EVENT_ID, (eventData: emitter.EventData) => { @@ -90,15 +94,21 @@ export default struct PlayerDetail { emitter.on(ServerConstants.UPDATE_STATE_EVENT_ID, (eventData: emitter.EventData) => { if (eventData !== undefined && eventData.data !== undefined) { Logger.info(this.tag, 'state:' + eventData.data.state); + if (eventData.data.state === ServerConstants.PLAYER_STATE_IDLE) { + this.noItem = true; + } if (this.state !== ServerConstants.PLAYER_STATE_PLAYING && eventData.data.state === ServerConstants.PLAYER_STATE_PLAYING) { this.item = this.PlayerManager.getItem(); + this.noItem = false; this.lrc = CommonUtils.getLrc(this.item.title); this.listTitle = this.PlayerManager.getListTitle(); this.scroller.scrollToIndex(0, true) - if (this.rotateAngle === 0) { - this.rotateAngle = 360; - Logger.info(this.tag, 'rotateAngle:360'); - } + setTimeout(() => { + if (this.rotateAngle === 0) { + this.rotateAngle = 360; + Logger.info(this.tag, 'rotateAngle:360'); + } + }, 1000); } if (eventData.data.state !== ServerConstants.PLAYER_STATE_PLAYING) { this.rotateAngle = 0; @@ -124,160 +134,183 @@ export default struct PlayerDetail { Column() { Row() { Image($r('app.media.chevron_down_circle')) - .height('48vp') - .width('48vp') - .padding('8vp') + .height('36vp') + .width('36vp') .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayerDetail = false; - }) + this.isShowPlayerDetail = false; }) .id('detail_dismiss') + Column() { if (this.contentSwitch) { - Text(this.listTitle).fontSize('18fp') + Text(this.listTitle) + .fontSize(20) .maxLines(2) .id('detail_title1') + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }) + .combine(TransitionEffect.translate({ y: 24 }))) } else { - Text(this.item.title).fontSize('18fp') - .maxLines(1) - .id('detail_title2') - Text(this.item.artist).fontSize('14fp') - .fontColor('#a0a0a0') - .id('detail_title3') + Column() { + Text(this.item.title) + .fontSize(18) + .maxLines(1) + .id('detail_title2') + Text(this.item.artist) + .fontSize(14) + .maxLines(1) + .fontColor('#a0a0a0') + .id('detail_title3') + } + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }).combine( + TransitionEffect.translate({ y: -24 })) + ) } } + .width('60%') .justifyContent(FlexAlign.Center) .height('60vp') - .width('70%') Image($r('app.media.folder_badge_plus')) - .height('32vp') - .width('32vp') + .height('36vp') + .width('36vp') + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + }) } - .width('100%') - .justifyContent(FlexAlign.SpaceEvenly) + .width('90%') + .justifyContent(FlexAlign.SpaceBetween) + Column() { - if (this.contentSwitch) { - Column() { - Image(this.item.id ? ServerConstants.SONG_IMAGE_URL + this.item.id : $r('app.media.startIcon')) - .width('300vp') - .height('300vp') - .borderRadius('200vp') - .margin('50vp') - .rotate({ angle: this.rotateAngle }) - .animation({ - duration: 3600, - curve: Curve.Linear, - delay: 500, - iterations: -1, // 设置-1表示动画无限循环 - playMode: PlayMode.Normal - }) - .id('detail_songImg') - .clickEffect(UIConstants.CLICK_EFFECT) - .onClick(() => { - this.contentSwitch = false; - }) - Row() { - Column() { - Text(this.item.title).fontSize('18fp') - .id('detail_songName') - .fontSize(20) - Text(this.item.artist).fontSize('16fp') - .fontColor('#a0a0a0') - .id('detail_artist') - }.alignItems(HorizontalAlign.Start) + if (!this.noItem) { + if (this.contentSwitch) { + Column() { + Image(this.item.id ? ServerConstants.SONG_IMAGE_URL + this.item.id : $r('app.media.startIcon')) + .width('300vp') + .height('300vp') + .borderRadius('200vp') + .margin('50vp') + .rotate({ angle: this.rotateAngle }) + .animation({ + duration: 36000, + curve: Curve.Linear, + delay: 500, + iterations: -1, // 设置-1表示动画无限循环 + playMode: PlayMode.Normal + }) + .id('detail_songImg') + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + this.contentSwitch = false; + }) - Blank() - Stack() { - if (this.item.isFavor) { - Image($r('app.media.heart_fill')) - .width('36vp') - .height('36vp') - } else { - Image($r('app.media.heart')) - .width('36vp') - .height('36vp') + Row() { + Column() { + Text(this.item.title).fontSize('18fp') + .id('detail_songName') + .fontSize(20) + Text(this.item.artist).fontSize('16fp') + .fontColor('#a0a0a0') + .id('detail_artist') + }.alignItems(HorizontalAlign.Start) + + Blank() + Stack() { + if (this.item.isFavor) { + Image($r('app.media.heart_fill')) + .width('36vp') + .height('36vp') + } else { + Image($r('app.media.heart')) + .width('36vp') + .height('36vp') + } } + .alignContent(Alignment.Top) + .margin({ right: '8vp' }) + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + this.PlayerManager.set_favourite(); + }) } - .alignContent(Alignment.Top) - .margin({ right: '8vp' }) - .clickEffect(UIConstants.CLICK_EFFECT) - .onClick(() => { - this.PlayerManager.set_favourite(); - }) - } - .width('80%') - .justifyContent(FlexAlign.SpaceBetween) - }.width('100%') - .height('100%') - .justifyContent(FlexAlign.SpaceAround) - } else { - Column() { - Tabs({ - barPosition: BarPosition.End, - index: 0, - controller: this.tabsController - }) { - TabContent() { - Column() { - List({ space: 10, scroller: this.scroller }) { - ForEach(this.lrc, (item: LrcLine, index: number) => { - ListItem() { - if (index === this.lrcIndex) { - Text(item.title) - .width('100%') - .textAlign(TextAlign.Center) - .fontSize('24fp') - .fontColor(Color.Black) - } else { - Text(item.title) - .width('100%') - .textAlign(TextAlign.Center) - .fontSize('18fp') - .fontColor(Color.Gray) + .width('80%') + .justifyContent(FlexAlign.SpaceBetween) + }.width('100%') + .height('100%') + .justifyContent(FlexAlign.SpaceAround) + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }).combine( + TransitionEffect.translate({ z: 24 }))) + } else { + Column() { + Tabs({ + barPosition: BarPosition.End, + index: 0, + controller: this.tabsController + }) { + TabContent() { + Column() { + List({ space: 10, scroller: this.scroller }) { + ForEach(this.lrc, (item: LrcLine, index: number) => { + ListItem() { + if (index === this.lrcIndex) { + Text(item.title) + .width('100%') + .textAlign(TextAlign.Center) + .fontSize('24fp') + .fontColor(Color.Black) + } else { + Text(item.title) + .width('100%') + .textAlign(TextAlign.Center) + .fontSize('18fp') + .fontColor(Color.Gray) + } } - } - }) - } - .width('100%') - }.height('100%') + }) + } + .width('100%') + }.height('100%') + } + .tabBar(this.TabBuilder($r('app.string.lyrics'), 0, + $r('app.media.ic_screenshot_line_select'), $r('app.media.ic_screenshot_line'))) } - .tabBar(this.TabBuilder($r('app.string.lyrics'), 0, - $r('app.media.ic_screenshot_line_select'), $r('app.media.ic_screenshot_line'))) + .width('100%') + .barHeight(60) + .barWidth('70%') + .barMode(BarMode.Scrollable) + .barPosition(BarPosition.Start) + .onChange((index: number) => { + this.subCurrentIndex = index; + }) } .width('100%') - .barHeight(60) - .barWidth('70%') - .barMode(BarMode.Scrollable) - .barPosition(BarPosition.Start) - .onChange((index: number) => { - this.subCurrentIndex = index; + .height('100%') + .transition(TransitionEffect.OPACITY.animation({ duration: 2000, curve: Curve.Ease }).combine( + TransitionEffect.translate({ y: -24 }))) + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + this.contentSwitch = true; + if (this.state === ServerConstants.PLAYER_STATE_PLAYING) { + this.rotateAngle = 0; + animateTo({ duration: 36000 }, () => { + this.rotateAngle = 360; + }) + } }) } - .width('100%') - .height('100%') - .clickEffect(UIConstants.CLICK_EFFECT) - .onClick(() => { - this.contentSwitch = true; - if (this.state === ServerConstants.PLAYER_STATE_PLAYING) { - this.rotateAngle = 0; - animateTo({ duration: 3600 }, () => { - this.rotateAngle = 360; - }) - } - }) } - }.width('100%') + } + .width('100%') .height('70%') Row() { Progress({ value: this.currentTime, total: this.durationTime, type: ProgressType.Linear }) + .color(Color.Gray) .width('80%') .height(30) .id('detail_progress') + }.width('100%') .justifyContent(FlexAlign.Center) @@ -290,17 +323,17 @@ export default struct PlayerDetail { .justifyContent(FlexAlign.SpaceBetween) Row() { - Image($r('app.media.repeat')) - .objectFit(ImageFit.Contain) + Image(this.playMode === PLAY_MODE.REPEAT ? $r('app.media.repeat') : (this.playMode === PLAY_MODE.REPEAT1 ? $r('app.media.repeat_1') : $r('app.media.shuffle'))) .width(42) .height(42) - .margin({ right: 12, left: 8 }) + .clickEffect(UIConstants.CLICK_EFFECT) + .onClick(() => { + this.playMode = this.PlayerManager.setPlayMode(); + }) .id('detail_mode') Image($r('app.media.backward_end_fill')) - .objectFit(ImageFit.Contain) .width(42) .height(42) - .margin({ right: 12, left: 8 }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { this.PlayerManager.previous(); @@ -308,10 +341,8 @@ export default struct PlayerDetail { .id('detail_previous') if (this.state === ServerConstants.PLAYER_STATE_PLAYING) { Image($r('app.media.pause')) - .objectFit(ImageFit.Contain) .width(48) .height(48) - .margin({ right: 12, left: 8 }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { this.PlayerManager.pause(); @@ -319,10 +350,8 @@ export default struct PlayerDetail { .id('detail_paused') } else { Image($r('app.media.play_fill')) - .objectFit(ImageFit.Contain) .width(48) .height(48) - .margin({ right: 12, left: '8vp' }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { this.PlayerManager.resume(); @@ -330,25 +359,19 @@ export default struct PlayerDetail { .id('detail_playing') } Image($r('app.media.forward_end_fill')) - .objectFit(ImageFit.Contain) .width(42) .height(42) - .margin({ right: 12, left: 8 }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { this.PlayerManager.next(); }) .id('detail_next') Image($r('app.media.music_note_list')) - .objectFit(ImageFit.Contain) .width(42) .height(42) - .margin({ right: 12, left: 8 }) .clickEffect(UIConstants.CLICK_EFFECT) .onClick(() => { - animateTo({ duration: 1000 }, () => { - this.isShowPlayList = true; - }) + this.isShowPlayList = true; }) .id('detail_playlist') }.width('100%') diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/SongCell.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/SongCell.ets index 629ae3602b9f418170b748cdf5e293b53a3d69b5..476f6ebf38a3be0a54047e369a683f93711ee5c6 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/SongCell.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/SongCell.ets @@ -26,6 +26,7 @@ export default struct SongCell { Image(ServerConstants.SONG_IMAGE_URL + this.item.id) .width(64) .height(64) + .borderRadius(4) Blank() Text() { if(this.item.isPlaying){