Răsfoiți Sursa

歌单在播放列表可以排序,实时刷新 歌词双语修复bug

onecold 9 luni în urmă
părinte
comite
41f69bd756

+ 3 - 0
entry/src/main/ets/common/constants/EventConstants.ets

@@ -47,4 +47,7 @@ export class EventConstants {
 
   // 播放状态变化事件
   static readonly EVENT_PLAYBACK_STATUS: number = 2003;
+
+  // 刷新播放列表的排序
+  static readonly EVENT_PLAYLIST_REFRESH_SORT: number = 2004;
 }

+ 1 - 0
entry/src/main/ets/common/util/PlaylistTable.ets

@@ -593,6 +593,7 @@ export default class PlaylistTable {
    */
   async updatePlaylistSongSortOrder(playlistId: string, songFilePath: string, sortOrder: number): Promise<boolean> {
     if (!this.rdbStore) {
+      Logger.info('heanup PlaylistTable', `rdbStore未初始化: ${songFilePath}`);
       return false;
     }
 

+ 30 - 6
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -10,7 +10,7 @@ import { curves, router, SymbolGlyphModifier } from '@kit.ArkUI';
 import { GlobalContext } from '../common/util/GlobalContext';
 import { CommonConstants } from '../common/constants/CommonConstants';
 import { EventConstants } from '../common/constants/EventConstants';
-import { common } from '@kit.AbilityKit';
+import { common, Context } from '@kit.AbilityKit';
 import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
 import { LazyDataSource } from '../common/util/LazyDataSource';
 import { ConfigurationConstant } from '@kit.AbilityKit';
@@ -1402,7 +1402,8 @@ export struct PlaylistDetailPage {
           LogUtil.error(`heanup 更新歌曲[${i}] ${song.name} sortOrder失败`)
         }
       }
-
+      // 发送刷新事件
+      emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH_SORT }, {})
       LogUtil.info('heanup 所有歌曲sortOrder更新完成')
     } catch (error) {
       LogUtil.error('heanup 更新歌曲sortOrder失败: ' + error)
@@ -1478,10 +1479,6 @@ export function emptyView(themeColor: string, isDarkMode: boolean = false) {
       // 背景圆圈
       Circle({ width: 160, height: 160 })
         .fill(themeColorWithAlpha(themeColor, 0.08, isDarkMode))
-        .border({
-          width: 2,
-          color: themeColorWithAlpha(themeColor, 0.1, isDarkMode)
-        })
 
       // 中间圆圈
       Circle({ width: 120, height: 120 })
@@ -1520,5 +1517,32 @@ export function emptyView(themeColor: string, isDarkMode: boolean = false) {
 }
 
 
+export  async function  updateAllSongsSortOrder(playlistTable:PlaylistTable,songList:VideoItem[], playlistId:string) {
+  try {
+    LogUtil.info('heanup 开始更新所有歌曲的sortOrder playlistId='+playlistId)
+    // const playlistTable: PlaylistTable = new PlaylistTable(context)
+    for (let i = 0; i < songList.length; i++) {
+      const song = songList[i]
+      const success = await playlistTable.updatePlaylistSongSortOrder(
+        playlistId,
+        song.filePath,
+        i
+      )
+
+      if (success) {
+        LogUtil.info(`heanup 更新歌曲[${i}] ${song.name} sortOrder成功`)
+      } else {
+        LogUtil.error(`heanup 更新歌曲[${i}] ${song.name} sortOrder失败`)
+      }
+    }
+    // 发送刷新事件
+    emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_REFRESH_SORT }, {})
+    LogUtil.info('heanup 所有歌曲sortOrder更新完成')
+  } catch (error) {
+    LogUtil.error('heanup 更新歌曲sortOrder失败: ' + error)
+  }
+}
+
+
 
 

+ 1 - 1
entry/src/main/ets/view/DeleteComptent.ets

@@ -8,6 +8,7 @@ import { EventConstants } from '../common/constants/EventConstants';
 import { emitter } from '@kit.BasicServicesKit';
 import { taskpool } from '@kit.ArkTS';
 
+
 // 批量删除
 @Component
 export struct DeleteComptent {
@@ -172,7 +173,6 @@ export struct DeleteComptent {
       }else {
         this.onDeleteResult(false)
       }
-
     }).catch((error: Error) => {
       console.error('Subtitle sync failed:', error);
     });

+ 22 - 6
entry/src/main/ets/view/LocalMusic.ets

@@ -92,6 +92,7 @@ import { showAddToPlaylistDialog } from '../dialog/AddToPlaylistDialog';
 import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog';
 import { convertPlaylistSongsToVideoItems, emptyView } from '../pages/PlaylistDetailPage';
 import { Playlist, PlaylistSong } from '../viewmodel/Playlist';
+import { updateAllSongsSortOrder } from '../pages/PlaylistDetailPage';
 const TAG = 'LocalMusic';
 
 /**
@@ -685,6 +686,15 @@ export struct LocalMusic {
       Logger.error('heanup eventPlaylistPlay: 接收到无效的数据结构')
     });
 
+    let eventRefreshSort: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH_SORT }
+    // 监听广播事件(通用设置配置更新)
+    emitter.on(eventRefreshSort, (eventData: emitter.EventData) => {
+      if(this.modeType ==4){
+        this.doSongListTask()//获取歌单信息
+      }
+
+    });
+
     let eventSetting: emitter.InnerEvent = { eventId: EventConstants.EVENT_SETTING_UPDATE }
     // 监听广播事件(通用设置配置更新)
     emitter.on(eventSetting, (eventData: emitter.EventData) => {
@@ -3818,13 +3828,16 @@ export struct LocalMusic {
     }
   }
 
-  itemMove(index: number, newIndex: number): void {
+   itemMove(index: number, newIndex: number): void {
     if (newIndex < 0 || newIndex >= this.videoLocalList.length) {
       return;
     }
     let tmp = this.videoLocalList.splice(index, 1);
     this.videoLocalList.splice(newIndex, 0, tmp[0]);
     this.dataSource.pushArrayData(this.videoLocalList)
+    if(this.modeType==4&&this.playlistTable){
+       updateAllSongsSortOrder(this.playlistTable, this.videoLocalList,this.currentSongListID)
+    }
   }
 
 
@@ -7259,7 +7272,7 @@ export struct LocalMusic {
 
   }
 
-  itemMoveSon(index: number, newIndex: number): void {
+   itemMoveSon(index: number, newIndex: number): void {
     if (newIndex < 0 || newIndex >= this.songList.length) {
       return;
     }
@@ -7267,6 +7280,9 @@ export struct LocalMusic {
     this.songList.splice(newIndex, 0, tmp[0]);
     this.sonDataSource.pushArrayData(this.songList)
     this.curIndex = Utility.getIndexFromList(this.songList, this.videoUrl)
+    if(this.modeType==4&&this.playlistTable){
+       updateAllSongsSortOrder(this.playlistTable, this.songList,this.currentSongListID)
+    }
   }
   @Builder
   PlayList() {
@@ -8565,8 +8581,8 @@ export struct LocalMusic {
       Stack() {
 
         Image(StrUtil.isEmpty(this.cover) ? this.imageLabel : this.cover)
-          .height(62)
-          .width(62)
+          .height(58)
+          .width(58)
           .alt(this.imageLabel)
           .borderRadius(8)
           .clickEffect({ level: ClickEffectLevel.HEAVY })
@@ -8587,14 +8603,14 @@ export struct LocalMusic {
             .maxLines(1)
             .fontWeight(FontWeight.Bolder)
             .fontColor(this.currentLyricColor)
-            .margin({ left: this.currentLyricAlignMode === 0 ? 18 : 0 })
+            .margin({ left: this.currentLyricAlignMode === 0 ? 24 : 0 })
           Row() {
             Text(this.currentSong?.artist !== undefined ? this.currentSong?.artist : '')
               .fontSize(13)
               .fontWeight(FontWeight.Bold)
               .padding({ top: 8 })
               .fontColor(this.currentLyricColor)
-              .margin({ left: this.currentLyricAlignMode === 0 ? 18 : 0 })
+              .margin({ left: this.currentLyricAlignMode === 0 ? 24 : 0 })
             Blank()
 
           }

+ 2 - 1
lib/src/main/ets/bean/LyricLine.ts

@@ -13,11 +13,12 @@ export class LyricLine {
      * @param beginTime The begin timestamp of this lyric line.
      * @param nextTime The begin timestamp of the next lyric line.
      */
-    constructor(text: string, beginTime: number, nextTime: number,words?: LyricWord[] ) {
+    constructor(text: string, beginTime: number, nextTime: number,words?: LyricWord[],translation?: string ) {
         this.text = text
         this.beginTime = beginTime
         this.nextTime = nextTime
         this.words = words || []
+        this.translation = translation || ''
     }
 
     // 新增方法:判断是否有逐字歌词

+ 37 - 25
lib/src/main/ets/parse/LyricParser.ts

@@ -69,6 +69,7 @@ export class LyricParser implements IParser {
 
                 // 处理双语歌词的特殊情况(英文行+中文行交替)
                 if (this.isBilingualLyric(src,  i)) {
+                    console.info(`onecold isBilingualLyric 双语歌词处理中`)
                     const englishLine = src[i];
                     const chineseLine = src[i+1];
 
@@ -96,10 +97,17 @@ export class LyricParser implements IParser {
 
                     // 新增:逐字歌词[]检测方括号逐字歌词格式 [mm:ss.xxx] 文字
                 if (this.isSquareBracketWordByWordLyric(line))  {
-                    const { timeline, words } = this.parseSquareBracketWordLine(line,  offset);
+                    // console.info(`onecold 普通歌词处理中`)
+                    const { timeline, words,translation  } = this.parseSquareBracketWordLine(line,  offset,src[i+1]);
+                    // if (words.length  > 0) {
+                    //     lyricLines.push(new  LyricLine('', timeline, -1, words))
+                    // }
                     if (words.length  > 0) {
-                        lyricLines.push(new  LyricLine('', timeline, -1, words))
+                        const lyricLine = new LyricLine('', timeline, -1, words, translation);
+                        lyricLines.push(lyricLine);
+                        if (translation) i++; // 跳过已处理的中文行
                     }
+                    continue;
                 } else {
                     // 原逻辑处理,但支持逐字歌词
                     // [00:00.10]画心 - 张靓颖
@@ -164,33 +172,19 @@ export class LyricParser implements IParser {
         return /\[\d{2}:\d{2}\.\d{2,3}\]\S/.test(line);
     }
 
-    // 解析方括号格式的逐字歌词行
-    private parseSquareBracketWordLine(line: string, offset: number):
-        { timeline: number, words: LyricWord[] } {
 
+
+    private parseSquareBracketWordLine(
+        line: string,
+        offset: number,
+        nextLine?: string
+    ): { timeline: number, words: LyricWord[], translation?: string } {
         const words: LyricWord[] = [];
         let firstTimeline = -1;
 
-        // 正则匹配:[00:00.000]中文字
-        // const regex = /\[(\d{2}:\d{2}\.\d{2,3})\]([^\[]*)/g;
-        // let match;
-        //
-        // while ((match = regex.exec(line))  !== null) {
-        //     const timeStr = match[1];   // 时间部分 00:00.000
-        //     const word = match[2].trim(); // 歌词文本
-        //
-        //     if (!word) continue; // 跳过空词
-        //
-        //     const timeline = this.parseTimeline2(timeStr)  - offset;
-        //     if (firstTimeline < 0) firstTimeline = timeline;
-        //
-        //     words.push(new  LyricWord(word, timeline, 0));
-        // }
-
-        // 正则增强:支持高精度时间戳(如 [02:34.54001])
+        // 解析逐字歌词(原有逻辑)
         const regex = /\[(\d{2}:\d{2}[.:]\d{2,5})\]([^\[]*)/g;
         let match;
-
         while ((match = regex.exec(line))  !== null) {
             const timeStr = match[1];
             const word = match[2].trim();
@@ -201,12 +195,30 @@ export class LyricParser implements IParser {
             words.push(new  LyricWord(word, timeline, 0));
         }
 
-        // 计算每个持续时间
+        // 计算词持续时间
         for (let i = 0; i < words.length  - 1; i++) {
             words[i].duration = words[i + 1].startTime - words[i].startTime;
         }
         if (words.length  > 0 && words[words.length - 1].duration === 0) {
-            words[words.length - 1].duration = 200; // 默认200ms
+            words[words.length - 1].duration = 200;
+        }
+
+        // 双语支持
+        if (nextLine && /^\[\d{2}:\d{2}\.\d{2,3}\]/.test(nextLine)) {
+            // 提取两行时间戳(需完全一致)
+            const currentLineTime = line.match(/^\[(\d{2}:\d{2}\.\d{2,3})\]/)?.[1];
+            const nextLineTime = nextLine.match(/^\[(\d{2}:\d{2}\.\d{2,3})\]/)?.[1];
+
+            if (currentLineTime && nextLineTime && currentLineTime === nextLineTime) {
+                const chineseText = nextLine.replace(/^\[\d{2}:\d{2}\.\d{2,3}\]/,  '').trim();
+                if (chineseText) { // 非空文本才作为翻译
+                    return {
+                        timeline: firstTimeline,
+                        words,
+                        translation: chineseText
+                    };
+                }
+            }
         }
 
         return { timeline: firstTimeline, words };

+ 36 - 17
lib/src/main/ets/view/LyricView2.ets

@@ -280,23 +280,42 @@ export struct LyricView2 {
     // 普通歌词渲染(原有逻辑)
     @Builder
     NormalLyricLine(item: LyricLine, index: number) {
-        Text(this.getTransverterText(item.text))
-            .fontSize(this.textSize)
-            .opacity(this.calculateOpacityFactor(index, this.currentIndex))
-            .blur(this.calculateBlurFactor(index, this.currentIndex))
-            .textAlign(this.alignMode == 'center' ? TextAlign.Center : TextAlign.Start)
-            .scale({
-                x: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
-                y: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
-                centerX: this.alignMode == 'center' ? '50%' : 0
-            })
-            .fontColor(index == this.currentIndex ? this.textHighlightColor : this.textColor)
-            .fontWeight(index == this.currentIndex && this.isHighlightBold ? FontWeight.Bold : this.textWeight)
-            .animation({ duration: this.animDuration, curve: Curve.FastOutSlowIn })
-            .visibility(this.isSingleLine?
-                (index == this.currentIndex ?Visibility.Visible:Visibility.None)
-                :Visibility.Visible)
-            .width(this.alignMode == 'center' ? '100%' : '76%')
+        Column(){
+            Text(item.text)
+                .fontSize(this.textSize)
+                .opacity(this.calculateOpacityFactor(index, this.currentIndex))
+                .blur(this.calculateBlurFactor(index, this.currentIndex))
+                .textAlign(this.alignMode == 'center' ? TextAlign.Center : TextAlign.Start)
+                .scale({
+                    x: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
+                    y: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
+                    centerX: this.alignMode == 'center' ? '50%' : 0
+                })
+                .fontColor(index == this.currentIndex ? this.textHighlightColor : this.textColor)
+                .fontWeight(index == this.currentIndex && this.isHighlightBold ? FontWeight.Bold : this.textWeight)
+                .animation({ duration: this.animDuration, curve: Curve.FastOutSlowIn })
+                .visibility(this.isSingleLine?
+                    (index == this.currentIndex ?Visibility.Visible:Visibility.None)
+                    :Visibility.Visible)
+                .width(this.alignMode == 'center' ? '100%' : '76%')
+
+            // 中文翻译(整行显示)
+            if (item.translation)  {
+                Row({ space: 0 }) {
+                    Text(item.translation)
+                        .fontSize(index == this.currentIndex ?this.textSize*this.controller.getHighlightScale():this.textSize)
+                        .fontColor(this.currentMediaPosition >= item.beginTime ?
+                            index == this.currentIndex ? this.textHighlightColor : this.textColor : this.textColor)
+                        .fontWeight(index == this.currentIndex && this.isHighlightBold ? FontWeight.Bold : this.textWeight)
+                        .margin({ top: 4 })
+                        .opacity(this.calculateOpacityFactor(index, this.currentIndex))
+                        .blur(this.calculateBlurFactor(index, this.currentIndex))
+                }
+                .justifyContent(this.alignMode === 'center' ? FlexAlign.Center : FlexAlign.Start)
+                .width(this.alignMode == 'center' ? '100%' :index == this.currentIndex ?'95%': '85%')
+            }
+        }
+
     }
 
     @Builder