Răsfoiți Sursa

修复双语歌词 英语歌词没有空格的bug 双语歌词同时高亮展示

onecold 10 luni în urmă
părinte
comite
ac442572fd

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

@@ -7,6 +7,7 @@ export class LyricLine {
     readonly beginTime: number
     nextTime: number
     words: LyricWord[] = []
+    translation?: string; // 存储翻译文本
     /**
      * @param text The text of lyric line.
      * @param beginTime The begin timestamp of this lyric line.

+ 38 - 0
lib/src/main/ets/parse/LyricParser.ts

@@ -67,6 +67,26 @@ export class LyricParser implements IParser {
             }
             else {
 
+                // 处理双语歌词的特殊情况(英文行+中文行交替)
+                if (this.isBilingualLyric(src,  i)) {
+                    const englishLine = src[i];
+                    const chineseLine = src[i+1];
+
+                    // 解析英文逐字歌词
+                    const { timeline, words } = this.parseWordByWordLine(englishLine,  offset);
+
+                    // 获取中文文本(去掉时间戳)
+                    const chineseText = chineseLine.replace(/^\[\d{2}:\d{2}\.\d{2}\]/,  '').trim();
+
+                    // 创建双语歌词行(将中文作为附加文本)
+                    const bilingualLine = new LyricLine('', timeline, -1, words);
+                    bilingualLine.translation  = chineseText; // 新增translation字段存储翻译
+
+                    lyricLines.push(bilingualLine);
+                    i++; // 跳过已处理的中文行
+                    continue;
+                }
+
                 // 新增逐字歌词解析逻辑[mm:ss.xx] <mm:ss.xx>
                 if (this.isWordByWordLyric(line)) {
                     const { timeline, words } = this.parseWordByWordLine(line, offset);
@@ -120,6 +140,24 @@ export class LyricParser implements IParser {
         return result
     }
 
+    // 新增判断是否为双语歌词的方法
+    private isBilingualLyric(lines: string[], currentIndex: number): boolean {
+        if (currentIndex + 1 >= lines.length)  return false;
+
+        const currentLine = lines[currentIndex];
+        const nextLine = lines[currentIndex + 1];
+
+        // 检查当前行是否是英文逐字歌词
+        const isEnglishLine = this.isWordByWordLyric(currentLine)  &&
+        /[a-zA-Z]/.test(currentLine);
+
+        // 检查下一行是否是纯中文歌词(带时间戳但不含逐字标记)
+        const isChineseLine = /^\[\d{2}:\d{2}\.\d{2}\]/.test(nextLine) &&
+            !this.isWordByWordLyric(nextLine)  &&
+        /[\u4e00-\u9fa5]/.test(nextLine);
+
+        return isEnglishLine && isChineseLine;
+    }
 
     // 检测方括号格式的逐字歌词 [00:00.000]文[00:01.000]字
     private isSquareBracketWordByWordLyric(line: string): boolean {

+ 76 - 27
lib/src/main/ets/view/LyricView2.ets

@@ -300,38 +300,87 @@ export struct LyricView2 {
     }
     // 逐字歌词渲染
     @Builder
+    WordByWordLyric2(item: LyricLine, index: number) {
+        Column() {
+            // 英文逐字歌词
+            Row({ space: 0 }) {
+                ForEach(item.words,  (word: LyricWord, wordIndex: number) => {
+                    Text(word.word)
+                        .fontSize(index == this.currentIndex  ?
+                            this.textSize*this.controller.getHighlightScale()  : this.textSize)
+                        .fontColor(this.currentMediaPosition  >= word.startTime  ?
+                            index == this.currentIndex  ? this.textHighlightColor  : this.textColor  : this.textColor)
+                        .margin({ right: 2 })
+                })
+            }
+            .justifyContent(this.alignMode  === 'center' ? FlexAlign.Center : FlexAlign.Start)
+
+            // 中文翻译(整行显示)
+            if (item.translation)  {
+                Text(item.translation)
+                    .fontSize(this.textSize)
+                    .fontColor(index == this.currentIndex  ?
+                    this.textHighlightColor  : this.textColor)
+                    .margin({ top: 4 })
+            }
+        }
+        .width(this.alignMode  == 'center' ? '100%' : '95%')
+        .opacity(this.calculateOpacityFactor(index,  this.currentIndex))
+    }
+    @Builder
     WordByWordLyric(item: LyricLine, index: number) {
-        Row({ space: 0 }) {
-            ForEach(item.words, (word: LyricWord, wordIndex: number) => {
-
-
-                Text(this.getTransverterText(word.word))
-                    .fontSize(index == this.currentIndex ?this.textSize*this.controller.getHighlightScale():this.textSize)
-                    .fontColor(this.currentMediaPosition >= word.startTime ?
-                        index == this.currentIndex ? this.textHighlightColor : this.textColor : this.textColor)
-                    .fontWeight(this.currentMediaPosition >= word.startTime && this.isHighlightBold ?
-                        index == this.currentIndex? FontWeight.Bold : this.textWeight: this.textWeight)
-                    .margin(0)
-                    .opacity(this.calculateOpacityFactor(index, this.currentIndex))
-                    .blur(this.calculateBlurFactor(index, this.currentIndex))
-                    .visibility(this.isSingleLine?
-                        (index == this.currentIndex ?Visibility.Visible:Visibility.None)
-                        :Visibility.Visible)
-                    .animation({
-                        // 动画播放速度
-                        tempo: 0.8,
-                        // 动画持续时间,单位是毫秒
-                        duration: 777,
-                        // 动画缓动函数
-                        curve: Curve.FastOutSlowIn
-                    })
-            })
+        Column() {
+            Row({ space: 0 }) {
+                ForEach(item.words, (word: LyricWord, wordIndex: number) => {
+
+
+                    Text(word.word)
+                        .fontSize(index == this.currentIndex ?this.textSize*this.controller.getHighlightScale():this.textSize)
+                        .fontColor(this.currentMediaPosition >= word.startTime ?
+                            index == this.currentIndex ? this.textHighlightColor : this.textColor : this.textColor)
+                        .fontWeight(this.currentMediaPosition >= word.startTime && this.isHighlightBold ?
+                            index == this.currentIndex? FontWeight.Bold : this.textWeight: this.textWeight)
+                        .margin({ right: 4 })
+                        .opacity(this.calculateOpacityFactor(index, this.currentIndex))
+                        .blur(this.calculateBlurFactor(index, this.currentIndex))
+                        .visibility(this.isSingleLine?
+                            (index == this.currentIndex ?Visibility.Visible:Visibility.None)
+                            :Visibility.Visible)
+                        .animation({
+                            // 动画播放速度
+                            tempo: 0.8,
+                            // 动画持续时间,单位是毫秒
+                            duration: 777,
+                            // 动画缓动函数
+                            curve: Curve.FastOutSlowIn
+                        })
+                })
+            }
+            .justifyContent(this.alignMode === 'center' ? FlexAlign.Center : FlexAlign.Start)
+            .width(this.alignMode == 'center' ? '100%' :index == this.currentIndex ?'95%': '85%')
+
+            // 中文翻译(整行显示)
+            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%')
+            }
         }
-        .justifyContent(this.alignMode === 'center' ? FlexAlign.Center : FlexAlign.Start)
-        .width(this.alignMode == 'center' ? '100%' :index == this.currentIndex ?'95%': '85%')
+
+
     }
 
 
+
     isTopBottomLine(index:number){
         return  index === 0 || index === this.listAdapter.totalCount()  - 1;
     }