Răsfoiți Sursa

feat(playlist): 实现歌单详情页面跳转与播放功能

- 实现从歌单列表跳转到详情页面的功能
- 完善歌单详情页面的数据加载逻辑
- 添加 PlaylistSong 到 VideoItem 的转换方法
- 实现歌单播放请求的处理逻辑-优化页面间的参数传递和错误处理- 清理无用代码和注释
chendeben 10 luni în urmă
părinte
comite
5b0e9af211

+ 15 - 2
entry/src/main/ets/pages/NewIndex.ets

@@ -1140,8 +1140,21 @@ struct NewIndex {
    * 打开歌单详情
    */
   openPlaylist(playlist: Playlist) {
-    // TODO: 跳转到歌单详情页面
-    console.info('打开歌单:', playlist.name)
+    try {
+      // 跳转到歌单详情页面
+      router.pushUrl({
+        url: 'pages/PlaylistDetailPage',
+        params: {
+          playlist: playlist
+        }
+      }).catch((err: Error) => {
+        console.error('跳转到歌单详情页面失败:', err.message);
+        ToastUtil.showToast('打开歌单失败');
+      });
+    } catch (error) {
+      console.error('打开歌单失败:', error);
+      ToastUtil.showToast('打开歌单失败');
+    }
   }
 
   /**

+ 40 - 12
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -22,10 +22,11 @@ export struct PlaylistDetailPage {
   private playlistId: string = ''
 
   aboutToAppear() {
-    // 获取传入的歌单ID
+    // 获取传入的歌单对象
     const params = router.getParams() as Record<string, Object>
-    if (params && params['playlistId']) {
-      this.playlistId = params['playlistId'] as string
+    if (params && params['playlist']) {
+      this.playlist = params['playlist'] as Playlist
+      this.playlistId = this.playlist.id
       this.loadPlaylistDetail()
     }
   }
@@ -37,18 +38,17 @@ export struct PlaylistDetailPage {
     try {
       this.isLoading = true
       
-      // 加载歌单信息
-      const playlist = await this.playlistTable.queryPlaylistById(this.playlistId)
-      if (playlist) {
-        this.playlist = playlist
-        
-        // 加载歌单歌曲
-        const songs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
-        this.songList = songs
-      } else {
+      if (!this.playlist) {
         ToastUtil.showToast('歌单不存在')
         router.back()
+        return
       }
+      
+      // 加载歌单歌曲
+      const playlistSongs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
+      
+      // 将 PlaylistSong 转换为 VideoItem
+      this.songList = await this.convertPlaylistSongsToVideoItems(playlistSongs)
     } catch (error) {
       console.error('加载歌单详情失败:', error)
       ToastUtil.showToast('加载歌单详情失败')
@@ -57,6 +57,34 @@ export struct PlaylistDetailPage {
     }
   }
 
+  /**
+   * 将 PlaylistSong 转换为 VideoItem
+   */
+  async convertPlaylistSongsToVideoItems(playlistSongs: PlaylistSong[]): Promise<VideoItem[]> {
+    const videoItems: VideoItem[] = []
+    
+    for (const playlistSong of playlistSongs) {
+      try {
+        // 这里需要根据文件路径查询媒体库获取完整的 VideoItem 信息
+        // 暂时创建一个基本的 VideoItem 对象
+        const videoItem: VideoItem = {
+          filePath: playlistSong.songFilePath,
+          fileName: playlistSong.songFilePath.split('/').pop() || '',
+          fileSize: 0,
+          duration: 0,
+          type: 0, // 音乐类型
+          isSelected: false,
+          isPlaying: false
+        }
+        videoItems.push(videoItem)
+      } catch (error) {
+        console.error('转换歌曲失败:', error)
+      }
+    }
+    
+    return videoItems
+  }
+
   /**
    * 播放歌单
    */

+ 40 - 70
entry/src/main/ets/view/LocalMusic.ets

@@ -1,6 +1,7 @@
 import TitleBar from './TitleBar'
 import { curves, display, PiPWindow, promptAction, router, SymbolGlyphModifier, window } from '@kit.ArkUI';
 import { VideoItem } from '../viewmodel/VideoItem';
+import { Playlist } from '../viewmodel/Playlist';
 import {  LengthMetrics, SegmentButton,borderRadiuses, SegmentButtonOptions } from '@kit.ArkUI';
 import {
   AppUtil,
@@ -81,7 +82,7 @@ import { TextNodeController } from './PipLyricTextBuilder';
 import { IndexerView } from './IndexerView';
 import { KeyCode } from '@kit.InputKit';
 import { KnockController } from '../controller/KnockController';
-import { DeleteComptent } from '../view/DeleteComptent'
+import { DeleteComptent } from '../view/DeleteComptent';
 import { ABLoopComptent } from '../view/ABLoopComptent';
 import { FixMessyView } from '../view/FixMessyView';
 import { parseCueFile,CueTrack,CueInfo } from '../common/util/CueUtils';
@@ -549,6 +550,15 @@ export struct LocalMusic {
       this.doUpdateData()
     });
 
+    // 监听歌单播放请求事件
+    let eventPlaylistPlay: emitter.InnerEvent = { eventId: 2002 }
+    emitter.on(eventPlaylistPlay, (eventData: emitter.EventData) => {
+      const data = eventData.data as Record<string, Object>
+      if (data.playlist && data.songs && data.startIndex !== undefined) {
+        this.handlePlaylistPlayRequest(data.playlist as Playlist, data.songs as VideoItem[], data.startIndex as number)
+      }
+    });
+
     let eventSetting: emitter.InnerEvent = { eventId: 333 }
     // 监听广播事件(通用设置配置更新)
     emitter.on(eventSetting, (eventData: emitter.EventData) => {
@@ -789,7 +799,6 @@ export struct LocalMusic {
       this.setBarHeightNormal()
     }
   }
-
   onPageShow() {
     this.knockController?.immersiveListening();
     app.setImageCacheCount(100);
@@ -797,7 +806,6 @@ export struct LocalMusic {
     app.setImageRawDataCacheSize(104857600);
     Logger.info('onecold onPageShow currentBreakpoint= ' + this.currentBreakpoint)
   }
-
   //开启线程查看各个数据库
   makeWorker() {
     setTimeout(() => {
@@ -1585,7 +1593,6 @@ export struct LocalMusic {
       }
     })
   }
-
   //拉起音频
   goSelectMusic() {
     if (DeviceUtil.getDeviceType() == resourceManager.DeviceType.DEVICE_TYPE_TABLET ||
@@ -1920,7 +1927,6 @@ export struct LocalMusic {
         if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库
           this.table.insert(newItem, (id: number) => {
             //加入数据库
-
           });
         }
 
@@ -2373,7 +2379,6 @@ export struct LocalMusic {
     }
 
   }
-
   @Builder
   TagsContentCoverBuilder() {
     Scroll() {
@@ -3076,7 +3081,6 @@ export struct LocalMusic {
     }
     return (this.modeType == 2 || this.modeType == 3) && this.isCanBack
   }
-
   @Builder
   coverHeader() {
     if (this.isShowCoverHeader()) {
@@ -3696,7 +3700,7 @@ export struct LocalMusic {
               })
             .transition(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 }))
             .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 0.8, y: 0.8 }).animation({ duration: 500 }),
-              TransitionEffect.scale({ x: 0, y: 0 })  ))
+              TransitionEffect.scale({ x: 0, y: 0 })))
             .gesture(
               GestureGroup(GestureMode.Exclusive,
                 SwipeGesture({ direction: SwipeDirection.Horizontal })
@@ -3868,7 +3872,6 @@ export struct LocalMusic {
     .scrollBar(BarState.Off)
 
   }
-
   //瀑布流卡片布局
   @Builder
   private MusicWaterCardItem(item: VideoItem, index: number) {
@@ -4055,16 +4058,6 @@ export struct LocalMusic {
     }
   }
 
-  // [Start itemMove_start]
-  // itemMoveGrid(index: number, newIndex: number): void {
-  //   if (!this.isDraggable(newIndex)) {
-  //     return;
-  //   }
-  //   let tmp = this.videoLocalList.splice(index, 1);
-  //   this.videoLocalList.splice(newIndex, 0, tmp[0]);
-  //   // this.bigItemIndex = this.videoLocalList.findIndex((item) => item === 0);
-  // }
-
   isInLeft(index: number) {
     return index % 2 == 0;
   }
@@ -4161,7 +4154,6 @@ export struct LocalMusic {
         .scale({ x: this.scaleItem === index ? 1.02 : 1, y: this.scaleItem === index ? 1.02 : 1 })
         .zIndex(this.dragItem === index ? 1 : 0)
         .translate(this.dragItem === index ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
-        // .hitTestBehavior(this.isDraggable(this.videoLocalList.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
 
 
       }, (item: VideoItem) => item.filePath)
@@ -4182,7 +4174,6 @@ export struct LocalMusic {
     .scrollBar(BarState.Off)
     .supportAnimation(true)
     .cachedCount(this.twoFingerType==1?5:this.twoFingerType==2?4:3)
-    // .columnsTemplate('1fr '.repeat(this.currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM ? 2 : 5))
     .columnsTemplate(
       this.twoFingerType == 3 ? 'repeat(auto-fit, 160)' :
         this.twoFingerType == 2 ? 'repeat(auto-fit, 110)' : 'repeat(auto-fit, 80)'
@@ -4201,16 +4192,11 @@ export struct LocalMusic {
 
       }))
     .enableScrollInteraction(true)
-    // 滚轴滑动,记录下滑动时的起始位置和终点位置
     .onScrollIndex((start: number, end: number) => {
       this.startIndex = start
       this.endIndex = end
     })
     .onScrollFrameBegin((offset: number) => {
-      //滚动小屏幕 比如puraX外屏和手机横屏可以隐藏bottomBarHeight topBarHeight
-      // if (this.isPhoneLan()) {
-      //   this.setBarHeightHide(offset)
-      // } else
       if (this.currentWidthBreakpoint !== WidthBreakpoint.WIDTH_SM ||
         (this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_MD &&
           this.currentHeightBreakpoint !== HeightBreakpoint.HEIGHT_SM)) {
@@ -4225,7 +4211,6 @@ export struct LocalMusic {
 
       return { offsetRemain: offset };
     })
-    //允许拖拽音乐和视频到List或Grid上自动导入视频
     .allowDrop([uniformTypeDescriptor.UniformDataType.AUDIO,uniformTypeDescriptor.UniformDataType.VIDEO])
     .onDrop((event?: DragEvent) => {
       try {
@@ -4323,8 +4308,6 @@ export struct LocalMusic {
             })
             .draggable(false)
             .opacity(this.opacityItem)// 绑定透明度
-            // .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
-            //   .animation({ duration: 500, curve: Curve.Ease }))
             .animation({
               duration: 666,
               curve: 'ease-in-out' // 可选动画曲线
@@ -4502,8 +4485,6 @@ export struct LocalMusic {
     return wightG
   }
   @State isShowDetail:boolean = false
-  // @State isShowDetailGrid:boolean = false
-  // @State isShowEditGrid:boolean = false
   @State longItemFilePathDetail:string =''
   @Builder
   MenuBuilder(item: VideoItem, index: number, filePath: string) {
@@ -4558,7 +4539,6 @@ export struct LocalMusic {
               .onClick(async() => {
                 this.setEditStrEmpty()
                 this.tempLyricContent = await this.getLyricContent(item);
-                // console.info('onecold 长按this.tempLyricContent' +this.tempLyricContent)
                 if(this.isGridMusic||this.twoFingerType==4){
                   this.longItemFilePath = item.filePath
                 }else{
@@ -4643,7 +4623,6 @@ export struct LocalMusic {
     }
 
   }
-
   private listMaxScrollOffsetY: number = 0
   @State selectedIndex: number = -1
 
@@ -4671,7 +4650,6 @@ export struct LocalMusic {
     this.pinchValue = 1;
     this.scaleValue = 1;
   }
-
   @Builder
   getListView() {
 
@@ -5444,7 +5422,6 @@ export struct LocalMusic {
       this.showPlayerView()
     })
   }
-
   //打开播放页
   showPlayerView() {
 
@@ -5459,8 +5436,6 @@ export struct LocalMusic {
       ToastUtil.showToast('当前播放列表为空,请先导入音乐。');
     }
   }
-
-
   @Builder
   playConRigth() {
     Row() {
@@ -5626,15 +5601,6 @@ export struct LocalMusic {
       return
     }
 
-    // let doChangeLyric = false
-    // if(item.filePath==this.currentSong?.filePath){
-    //   if(this.lyricConStr !== this.lyricContent){
-    //     doChangeLyric = true
-    //   }
-    // }else if(this.lyricConStr !== this.tempLyricContent){
-    //   doChangeLyric = true
-    // }
-
     //用户更改了封面,那就内嵌下封面
     if(StrUtil.isNotEmpty(this.imagePathStr)){
       const resultCover:boolean = await changeMusicCover(this.context,
@@ -5650,8 +5616,6 @@ export struct LocalMusic {
       title: this.titleStr,
       artist: this.artistStr,
       album: this.ablumStr,
-      // LYRICS:this.lyricConStr,
-      // USLT:this.lyricConStr,
       TYER:this.yearStr,
       genre:this.genreStr,
       track:this.trackStr,
@@ -5661,7 +5625,6 @@ export struct LocalMusic {
       lyricist:this.lyricistStr,
       TEXT:this.lyricistStr,//ID3v2 使用 TEXT 字段来表示作词者
       comment:this.commentStr,
-      // comm:this.commentStr,//ID3v2:使用 COMM(Comment)字段。
       disc:this.discStr,
     };
 
@@ -6274,7 +6237,6 @@ export struct LocalMusic {
     }
     .margin({ bottom: 20 })
   }
-
   setEditStrEmpty(){
     this.imagePathStr = ''
     this.lyricConStr = ''
@@ -6301,7 +6263,6 @@ export struct LocalMusic {
     .width('100%')
     .height('100%')
   }
-
   @Builder
   songDetail(currentItem:VideoItem) {
     Column() {
@@ -7044,7 +7005,6 @@ export struct LocalMusic {
     this.sonDataSource.pushArrayData(this.songList)
     this.curIndex = Utility.getIndexFromList(this.songList, this.videoUrl)
   }
-
   @Builder
   PlayList() {
     List({ scroller: this.playListScroller }) {
@@ -7763,7 +7723,6 @@ export struct LocalMusic {
   private castSeek: boolean = false;
   private castItem: avSession.AVQueueItem | undefined = undefined;
   // @State isBgPlayOpen:boolean = true   //是否启用后台播放
-
   @State imageLabel: PixelMap | Resource = CommonConstants.musicBgList[0];
   @State imageLabelBg: PixelMap | Resource = CommonConstants.musicBgList[0];
   @State rotateAngle2: number = -9
@@ -7782,7 +7741,6 @@ export struct LocalMusic {
   @State lyricContent: string = ''
   @State isDebug: boolean = false
   @State isHightLightCenter: boolean = true
-
   /**
    * 初始化歌词加载与展示逻辑
    * @param lyricPath 歌词文件路径(支持普通.lrc和加密.lrcc)
@@ -8502,7 +8460,6 @@ export struct LocalMusic {
     .position({ bottom: this.isHiCarSmall()?50:(this.isCoverOpacity() ? 55 : 70) }) // 将  固定在底部
 
   }
-
   //播放控制上一首 下一首 暂停和播放
   @Builder
   playCenterView(){
@@ -9290,10 +9247,8 @@ export struct LocalMusic {
     }
     .width('66%')
   }
-
   @State isOpenPip: boolean = false
   @State pipBgIndex: number = 0
-
   // 构建字号控制器
   @Builder
   BuildFontControls(isPip: boolean) {
@@ -10085,7 +10040,6 @@ export struct LocalMusic {
       ? '歌词时间已重置'
       : `歌词已${this.timeOffset < 0 ? '延后' : '提前'} ${absValue.toFixed(1)} 秒`
   }
-
   // 导入  本地歌词 搜索歌词
   @Builder
   pushLyricButton(icon: Resource, step: number, label: string) {
@@ -10693,8 +10647,8 @@ export struct LocalMusic {
         this.isShowMoreView = false
         break;
       case 20://AB循环
-        this.isABSheet = !this.isABSheet;
-        this.isShowMoreView = false
+        this.isOpenAB = !this.isOpenAB;
+        this.isABSheet = false
         break;
     }
 
@@ -10858,7 +10812,6 @@ export struct LocalMusic {
     }
     Logger.info(`[${TAG}] onecold onStateChange: ${this.curState}, reason: ${reason}`);
   }
-
   onActionEvent(event: PiPWindow.PiPActionEventType, status: number | undefined) {
     LogUtil.info('onecold onActionEvent = ' + event + '  status=' + status)
     switch (event) {
@@ -10889,7 +10842,6 @@ export struct LocalMusic {
     this.buttonAction = event + `-status:${status}`;
     Logger.info(`[${TAG}] onActionEvent: ${this.buttonAction} status:${status}}`);
   }
-
   /**
    * 画中画功能(悬浮歌词功能)结束
    *
@@ -11391,7 +11343,6 @@ export struct LocalMusic {
     this.loadingVisible = Visibility.None;
     this.replayVisible = Visibility.Visible;
   }
-
   private async play(url: string,startOffset?:number) {
     let that = this;
     that.showLoadIng();
@@ -12164,7 +12115,6 @@ export struct LocalMusic {
 
 
   };
-
   /**
    * Gesture method onActionUpdate.
    *
@@ -12194,7 +12144,6 @@ export struct LocalMusic {
     this.currentTime = this.stringForTime(position);
     this.isCurrentTime = false
   }
-
   private sessionRewindCallback = (time?: number) => {
     if (!time) {
       return;
@@ -12938,9 +12887,33 @@ export struct LocalMusic {
    * 穿山甲广告代码结束
    */
 
+  /**
+   * 处理歌单播放请求
+   */
+  private  handlePlaylistPlayRequest(playlist: Playlist, songs: VideoItem[], startIndex: number) {
+  try {
+    // 1. 替换当前播放列表
+    this.songList = songs
+    this.sonDataSource.pushArrayData(songs)
 
-}
+    // 2. 设置当前播放索引
+    this.curIndex = startIndex
+
+    // 3. 播放指定歌曲
+    if (songs[startIndex]) {
+      this.doPlay(songs[startIndex], startIndex)
+    }
+
+    // 4. 保存播放列表
+    PreferencesUtil.putSync('LastMusicList', this.songList)
 
+    console.info(`开始播放歌单: ${playlist.name}, 歌曲数量: ${songs.length}, 开始索引: ${startIndex}`)
+  } catch (error) {
+    console.error('处理歌单播放请求失败:', error)
+    ToastUtil.showToast('播放失败')
+  }
+}
+}
 //视频气泡窗口的布局
 @Builder
 function customPopupBuilder(dataBu: BubbleBean) {
@@ -12990,7 +12963,6 @@ function customPopupBuilder(dataBu: BubbleBean) {
     color: "#22FFFFFF"
   })
 }
-
 // Function to calculate a hash for the file list
 function simpleHash(fileList: string[]): string {
   let hash = 0;
@@ -13091,6 +13063,4 @@ function getFileDirName(filePath: string,rootPath:string): string{
   if(result.startsWith('.'))
     result = result.replace(/\./g, '')
   return result
-}
-
-
+}