diff --git a/scenario/MusicPlayerOnline/README_zh.md b/scenario/MusicPlayerOnline/README_zh.md index 3115c6067ccc1017efb8a82ffa371c742f0756f4..acd79e0a68b9da4d282caebbe862ef83f7b53e07 100644 --- a/scenario/MusicPlayerOnline/README_zh.md +++ b/scenario/MusicPlayerOnline/README_zh.md @@ -85,28 +85,28 @@ 5.1 从歌曲详情页加入创建并加入歌单 (已完成) 5.2 从歌曲详情页加入已有歌单 (已完成) 5.3 删除歌单 - 5.4 从歌单删除歌曲 + 5.4 从歌单删除歌曲 (已完成) 5.5 歌单改名 - 5.6 从其他歌单选个加入歌单 + 5.6 从其他歌单选择歌曲加入当前歌单 (已完成) 6. 登录注册页面 (已完成) - 7. 每日推荐歌单 + 7. 每日推荐歌单 (已完成) 8. 播放模式 (已完成) 9. 进度条拖动 10. 边听边存、离线播放 - 11. 歌词 + 11. 歌词 (已完成) 12. 桌面播放卡片 (已完成) 13. 歌曲分类: 热门歌手 (已完成) 14. 其他平台歌曲推荐、播放 15. 音频焦点处理 16. 其他界面交互效果(切换动画、点击效果) - 17. 设置页 + 17. 设置页 17.1. 退出应用 (已完成) - 17.2. 其他设置:仅wifi、边听边存、保持登录 + 17.2. 其他设置:仅WiFi下载、自动登录、边听边存 (部分) 18. 分布式播放 19. 性能优化:加载大量歌单和歌曲的列表(1000以上容易appfreeze),应用优化+服务器控制数据量 20. 一多适配:平板、pc 21. 异常处理:检测网络状态、播放器异常等 - 22. 播放状态保存恢复:服务器记录,上次播放的歌曲和歌单,登录或重启后恢复 + 22. 播放状态保存恢复:服务器记录,上次播放的歌曲和歌单,登录或重启后恢复 (部分) #### 未计划功能: 1. 社交(用户信息,好友,分享,评论) @@ -242,9 +242,10 @@ ohos.permission.KEEP_BACKGROUND_RUNNING ### 约束与限制 -1. 本示例仅支持标准系统上运行,支持设备:RK3568; -2. 本示例仅支持API11版本SDK,版本号:4.1.7.5; -3. 本示例需要使用DevEco Studio 4.1 Release (Build Version: 4.0.0.400); +1. 本示例仅支持标准系统上运行,支持设备:RK3568等; +2. 本示例仅支持API12版本SDK,版本号:5.0.0.70; +3. 本示例需要使用DevEco Studio NEXT Release + Build Version: 5.0.3.900, built on October 8, 2024 ### 下载 diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/constants/AppConstants.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/AppConstants.ets index 5e579066fd5614cc901882ded219c18823629b05..44d63da51e6dad6ed0fd29ef498c47e9c59a15c6 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/constants/AppConstants.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/constants/AppConstants.ets @@ -178,6 +178,10 @@ export default class AppConstants { static readonly keepLoginDes = '重启自动登录'; static readonly KEEP_LOGIN_SETTING = 'keepLoginSetting'; static readonly NO_MORE_SINGER = '没有更多内容了'; + + static readonly PLAYLIST_RECORD = 'PLAYLIST_RECORD'; + static readonly PLAYLIST_RECORD_INDEX = 'PLAYLIST_RECORD_INDEX'; + //点击效果 static readonly CLICK_EFFECT: ClickEffect = { level: ClickEffectLevel.MIDDLE, scale: 0.8 diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets index 8e39f9b61ce231ebea807373ba3a442fc9797d2c..1528dadbc621828e2e9535f7e57150cefb9ec231 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/manager/PlayerManager.ets @@ -55,6 +55,32 @@ export default class PlayerManager { this.startContinuousTask(); } + initialPlayList() { + if (AppStorage.get(AppConstants.PLAYLIST_RECORD) as AudioData[]) { + this.list = AppStorage.get(AppConstants.PLAYLIST_RECORD) as AudioData[]; + let id = AppStorage.get(AppConstants.PLAYLIST_RECORD_INDEX) as string; + let index = this.list.findIndex((audioData) => audioData.id === id); + this.listPosition = (index !== -1) ? index : 0; + if (this.list.length > 0) { + this.item = this.list[this.listPosition]; + } + this.listTitle = AppConstants.CURRENT_LIST_TITLE; + this.shuffleIndex = []; + for (let i = 0; i < this.list.length; i++) { + this.shuffleIndex.push(i); + } + this.shuffleIndex.sort(() => Math.random() - 0.5); + setTimeout(() => { + let eventData: emitter.EventData = { + data: { + "state": AppConstants.PLAYER_STATE_IDLE, + } + }; + emitter.emit(AppConstants.MAIN_UPDATE_STATE, this.emitterOptions, eventData); + }, 500) + } + } + initialPlayMode() { if (AppStorage.get(AppConstants.KEEP_LOGIN_SETTING) != AppConstants.TOGGLE_SETTING_ON) { AppStorage.setOrCreate(AppConstants.LOGIN_COOKIE, AppConstants.LOGIN_COOKIE_NONE); @@ -173,6 +199,7 @@ export default class PlayerManager { return; } this.list = list; + AppStorage.setOrCreate(AppConstants.PLAYLIST_RECORD, this.list); this.listTitle = listTitle; this.initialPlayMode(); this.shuffleIndex = []; @@ -220,6 +247,7 @@ export default class PlayerManager { } Logger.info(this.tag, 'Play :' + this.listPosition.toString()); this.item = this.list[this.listPosition] + AppStorage.setOrCreate(AppConstants.PLAYLIST_RECORD_INDEX, this.item.id); let isFavor = false; for (let favorItem of this.favourList) { if (this.item.id === favorItem.id) { @@ -425,6 +453,7 @@ export default class PlayerManager { } let insertIndex = this.listPosition + 1; this.list.splice(insertIndex, 0, item); + AppStorage.setOrCreate(AppConstants.PLAYLIST_RECORD, this.list); let newShuffleIndex: number[] = [...this.shuffleIndex]; for (let i = 0; i < newShuffleIndex.length; i++) { //将原来大于等于插入位置的索引+1 @@ -456,6 +485,7 @@ export default class PlayerManager { let index = this.list.indexOf(item); if (index > -1) { this.list.splice(index, 1); + AppStorage.setOrCreate(AppConstants.PLAYLIST_RECORD, this.list); let newShuffleIndex: number[] = [...this.shuffleIndex]; let shuffleIndex = newShuffleIndex.indexOf(index); if (shuffleIndex > -1) { diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets index e3b5ba409d1c1f55dbfd223dbc13303a8c7faea1..587bd23d752ceb885445bba5ee6602864a8a1933 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/pages/Index.ets @@ -39,6 +39,8 @@ PersistentStorage.persistProp(AppConstants.PLAY_MODE_SETTING, AppConstants.TOGGL PersistentStorage.persistProp(AppConstants.PLAY_MODE_RECORD, PLAY_MODE.REPEAT); PersistentStorage.persistProp(AppConstants.KEEP_LOGIN_SETTING, AppConstants.TOGGLE_SETTING_ON); PersistentStorage.persistProp(AppConstants.SERVER_HOST_PROP, AppConstants.SERVER_HOST); +PersistentStorage.persistProp(AppConstants.PLAYLIST_RECORD, []); +PersistentStorage.persistProp(AppConstants.PLAYLIST_RECORD_INDEX, '0'); @Entry @Component @@ -106,6 +108,7 @@ struct Index { aboutToAppear(): void { let loginName: string | undefined = AppStorage.get(AppConstants.LOGIN_NAME); + this.PlayerManager.initialPlayList(); this.PlayerManager.initialPlayMode(); HttpManager.getInstance().getRecommendListFromServer(); if (loginName != AppConstants.LOGIN_INFO_NO) { @@ -220,16 +223,21 @@ struct Index { Search({ placeholder: $r('app.string.search_holder') }) - .width('80%') + .width('75%') .height('6%') .enableKeyboardOnFocus(false) .onSubmit((value: string) => { this.searchSongsFromServer(value); }) Text($r('app.string.all_singer_list')) - .margin(6) - .padding(4) - .fontSize(20) + .margin({ top: 6 }) + .padding({ + left: 8, + right: 8, + top: 4, + bottom: 4 + }) + .fontSize(18) .clickEffect(AppConstants.CLICK_EFFECT) .height('6%') .textAlign(TextAlign.Center) diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/AllSinger.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/AllSinger.ets index 85c75c01c421053259cd8b4bb68d3410ed075efb..6a592b6c125377dad498a866708d406c64d9fcd7 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/AllSinger.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/AllSinger.ets @@ -17,16 +17,16 @@ import AllSingerDataSource from "../model/AllSingerDataSource"; import PlayListData from "../model/PlayListData"; import emitter from '@ohos.events.emitter'; import HttpManager from "../manager/HttpManager"; -import PlayListItem from "./PlayListItem"; import prompt from '@ohos.promptAction'; +import SingerItem from "./SingerItem"; @Component export default struct AllSinger { @Link isShowSingerList: boolean; - @State count:number = 0 - private aAllSingerDataSource: AllSingerDataSource = new AllSingerDataSource(); + @State count: number = 0 + private aAllSingerDataSource: AllSingerDataSource = new AllSingerDataSource(); private currentIndex: number = 0; - private step: number = 50; + private step: number = 100; aboutToAppear() { HttpManager.getInstance().getAllSinger(0, this.step); @@ -53,45 +53,53 @@ export default struct AllSinger { build() { Column({ space: 2 }) { - Row() { - Image($r('app.media.xmark')) - .width(42) - .height(42) - .padding(6) + Stack({ alignContent: Alignment.Start }) { + Image($r('app.media.chevron_down')) + .width(48) + .height(60) + .padding({ bottom: 6, top: 6 }) + .rotate({ angle: 90 }) .clickEffect(AppConstants.CLICK_EFFECT) .onClick(() => { this.isShowSingerList = false; }) - Text($r('app.string.all_singer_list')) - .fontSize(24) - .maxLines(1) - Text(this.count.toString()) - .fontSize(18) - .maxLines(1) + .alignSelf(ItemAlign.Start) + Row() { + Text($r('app.string.all_singer_list')) + .fontSize(24) + .maxLines(1) + .alignSelf(ItemAlign.Center) + Text('(' + this.count.toString() + ')') + .fontSize(16) + .fontColor(Color.Gray) + .maxLines(1) + }.width('100%') + .justifyContent(FlexAlign.Center) } - .width('90%') + .width('100%') .margin(4) - .justifyContent(FlexAlign.SpaceBetween) WaterFlow() { LazyForEach(this.aAllSingerDataSource, (item: PlayListData, index) => { FlowItem() { - PlayListItem({ item }) + SingerItem({ item }) } .onAppear(() => { if (index + 10 == this.aAllSingerDataSource.totalCount()) { HttpManager.getInstance().getAllSinger(this.currentIndex, this.currentIndex + this.step); } }) - .width('100%') .backgroundColor(Color.White) .borderRadius(12) }, (item: string) => item) } - .columnsTemplate("1fr 1fr 1fr") - .columnsGap(10) - .rowsGap(5) - .margin({ bottom: 36 }) + .columnsTemplate("1fr 1fr 1fr 1fr") + .columnsGap(8) + .rowsGap(16) + .padding({ left: 8, right: 8 }) + .margin({ bottom: 64 }) + .scrollBar(BarState.On) + .scrollBarWidth(10) } .alignItems(HorizontalAlign.Center) .height('100%') diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayListItem.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayListItem.ets index 06e24ced8103191ebb927f82222482c29894b351..0fe72c2201bbdbe4defc816cc48051ac4ac622a9 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayListItem.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayListItem.ets @@ -48,7 +48,9 @@ export default struct PlayListItem { .width('100%') } .borderRadius(12) - .backgroundImage(this.item.list.length > 0 ? AppStorage.get(AppConstants.SERVER_HOST_PROP) + AppConstants.SONG_IMAGE_URL + this.item.list[0].id : this.item.img) + .backgroundImage(this.item.list.length > 0 ? + AppStorage.get(AppConstants.SERVER_HOST_PROP) + AppConstants.SONG_IMAGE_URL + this.item.list[0].id : + this.item.img) .backgroundImageSize(ImageSize.Cover) .shadow({ radius: 1, color: Color.Gray }) .width(120) diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets index 03b1293e4fe1401b64fef79e65a9bd6be1b4b2e2..a7e56024a4376c3aa7d8fafb120d4856f148d49e 100644 --- a/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/PlayerBar.ets @@ -148,7 +148,6 @@ export default struct PlayerBar { if (eventData !== undefined && eventData.data !== undefined) { Logger.info(this.tag, 'state:' + eventData.data.state); if (this.state !== AppConstants.PLAYER_STATE_PLAYING && eventData.data.state === AppConstants.PLAYER_STATE_PLAYING) { - this.item = this.PlayerManager.getItem(); if (this.rotateAngle === 0) { this.rotateAngle = 360; Logger.info(this.tag, 'rotateAngle:360'); @@ -159,6 +158,7 @@ export default struct PlayerBar { Logger.info(this.tag, 'rotateAngle:0'); } this.state = eventData.data.state; + this.item = this.PlayerManager.getItem(); } } } \ No newline at end of file diff --git a/scenario/MusicPlayerOnline/entry/src/main/ets/view/SingerItem.ets b/scenario/MusicPlayerOnline/entry/src/main/ets/view/SingerItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..c91a5c259215d50d12e8dc29cf858d5b62a33e7c --- /dev/null +++ b/scenario/MusicPlayerOnline/entry/src/main/ets/view/SingerItem.ets @@ -0,0 +1,58 @@ +/* + * 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. + */ + +import ItemData from '../model/PlayListData'; +import AppConstants from '../constants/AppConstants'; +import PlayerManager from '../manager/PlayerManager'; +import emitter from '@ohos.events.emitter'; + +/** + * List item information component. + */ +@Component +export default struct SingerItem { + private item: ItemData = new ItemData('', $r('app.media.icon'), '', [], ''); + private PlayerManager: PlayerManager = AppStorage.get('PlayerManager') as PlayerManager; + private colors: number[] = [0xFF2222, 0xFF22FF, 0x22AA22, 0x22AAAA, 0xAAAA00, 0x222299] + private emitterOptions: emitter.Options = { + priority: emitter.EventPriority.HIGH + }; + + aboutToAppear() { + } + + build() { + Column() { + Text(this.item.title) + .fontSize(20) + .fontColor(this.item.list.length > 70 ? this.colors[0] : this.item.list.length > 50 ? this.colors[1] : + this.item.list.length > 30 ? this.colors[2] : + this.item.list.length > 20 ? this.colors[3] : this.item.list.length > 10 ? this.colors[4] : this.colors[5]) + .width(90) + .textAlign(TextAlign.Center) + .margin(4) + .padding({ top: 4, bottom: 4 }) + } + .backgroundColor('#f8f8f8') + .shadow({ radius: 1, color: Color.Gray }) + .borderRadius(4) + .alignItems(HorizontalAlign.Center) + .clickEffect(AppConstants.CLICK_EFFECT) + .onClick(() => { + this.PlayerManager.setExploreList(this.item); + emitter.emit(AppConstants.MAIN_SHOW_PLAYLIST, this.emitterOptions); + }) + } +} \ No newline at end of file