Przeglądaj źródła

歌词的逻辑重构了下

onecold 7 miesięcy temu
rodzic
commit
b08706077f

+ 164 - 0
entry/src/main/ets/common/service/LyricService.ets

@@ -0,0 +1,164 @@
+import { navidromeRestApi } from '../network/NavidromeRestApi';
+import { jellyfinApi } from '../network/JellyfinApi';
+import { embyApi } from '../network/EmbyApi';
+import { WebDavAccount } from '../../viewmodel/WebDavAccount';
+import { ServerLogUtil } from '../util/ServerLogUtil';
+import { StrUtil } from '@pura/harmony-utils';
+
+const TAG = 'heanup LyricService';
+
+/**
+ * 歌曲数据接口
+ */
+export interface SongData {
+  webdav_account_id?: string;
+  artist?: string;
+  name?: string;
+  webdav_id?: string;
+  lyricIndex?: number;
+  type?: number;
+}
+
+/**
+ * 账号获取回调类型
+ */
+export type AccountGetter = (accountId: string) => Promise<WebDavAccount | null>;
+
+/**
+ * 歌词服务类
+ * 负责从各种媒体服务器获取歌词
+ */
+class LyricService {
+
+  /**这个方法不能用,返回的歌词没时间戳
+   * 从Navidrome服务器获取歌词
+   * @param currentSong 当前歌曲对象
+   * @param getAccountById 根据账号ID获取账号信息的回调
+   * @returns 歌词文本,如果获取失败返回空字符串
+   */
+  async fetchNavidromeLyric(
+    currentSong: SongData,
+    getAccountById: AccountGetter
+  ): Promise<string> {
+    if (!currentSong || !currentSong.webdav_account_id) {
+      return '';
+    }
+
+    try {
+      ServerLogUtil.info(TAG, '尝试从Navidrome服务器获取歌词');
+      const account = await getAccountById(currentSong.webdav_account_id);
+      if (!account) {
+        throw new Error('Navidrome账号不可用');
+      }
+
+      const artist = currentSong.artist || '';
+      const title = currentSong.name || '';
+      const navidromeLyric = await navidromeRestApi.getLyrics(account, artist, title);
+
+      if (StrUtil.isNotEmpty(navidromeLyric)) {
+        ServerLogUtil.info(TAG, '成功从Navidrome服务器获取到歌词');
+        return navidromeLyric;
+      } else {
+        ServerLogUtil.info(TAG, 'Navidrome服务器未返回歌词');
+        return '';
+      }
+    } catch (error) {
+      const err = error as Error;
+      ServerLogUtil.error(TAG, `从Navidrome服务器获取歌词失败: ${err.message}`);
+      return '';
+    }
+  }
+
+  /**
+   * 从Jellyfin服务器获取歌词
+   * @param currentSong 当前歌曲对象
+   * @param getAccountById 根据账号ID获取账号信息的回调
+   * @returns 歌词文本,如果获取失败返回空字符串
+   */
+  async fetchJellyfinLyric(
+    currentSong: SongData,
+    getAccountById: AccountGetter
+  ): Promise<string> {
+    if (!currentSong || !currentSong.webdav_account_id) {
+      return '';
+    }
+
+    try {
+      ServerLogUtil.info(TAG, '尝试从Jellyfin服务器获取歌词');
+      const account = await getAccountById(currentSong.webdav_account_id);
+      if (!account) {
+        throw new Error('Jellyfin账号不可用');
+      }
+
+      const itemId = currentSong.webdav_id || '';
+      if (!itemId) {
+        throw new Error('Jellyfin歌曲ID不可用');
+      }
+
+      const jellyfinLyric = await jellyfinApi.getLyric(account, itemId);
+
+      if (StrUtil.isNotEmpty(jellyfinLyric)) {
+        ServerLogUtil.info(TAG, '成功从Jellyfin服务器获取到歌词');
+        return jellyfinLyric;
+      } else {
+        ServerLogUtil.info(TAG, 'Jellyfin服务器未返回歌词');
+        return '';
+      }
+    } catch (error) {
+      const err = error as Error;
+      ServerLogUtil.error(TAG, `从Jellyfin服务器获取歌词失败: ${err.message}`);
+      return '';
+    }
+  }
+
+  /**
+   * 从Emby服务器获取歌词
+   * @param currentSong 当前歌曲对象
+   * @param getAccountById 根据账号ID获取账号信息的回调
+   * @returns 歌词文本,如果获取失败返回空字符串
+   */
+  async fetchEmbyLyric(
+    currentSong: SongData,
+    getAccountById: AccountGetter
+  ): Promise<string> {
+    if (!currentSong || !currentSong.webdav_account_id) {
+      return '';
+    }
+
+    try {
+      ServerLogUtil.info(TAG, '尝试从Emby服务器获取歌词');
+      const account = await getAccountById(currentSong.webdav_account_id);
+      if (!account) {
+        throw new Error('Emby账号不可用');
+      }
+
+      const itemId = currentSong.webdav_id || '';
+      const lyricIndex = currentSong.lyricIndex;
+
+      if (!itemId) {
+        throw new Error('Emby歌曲ID不可用');
+      }
+
+      if (lyricIndex === undefined || lyricIndex === null) {
+        ServerLogUtil.info(TAG, 'Emby歌曲没有歌词索引,跳过获取歌词');
+        return '';
+      }
+
+      const embyLyric = await embyApi.getLyric(account, itemId, lyricIndex);
+
+      if (StrUtil.isNotEmpty(embyLyric)) {
+        ServerLogUtil.info(TAG, '成功从Emby服务器获取到歌词');
+        return embyLyric;
+      } else {
+        ServerLogUtil.info(TAG, 'Emby服务器未返回歌词');
+        return '';
+      }
+    } catch (error) {
+      const err = error as Error;
+      ServerLogUtil.error(TAG, `从Emby服务器获取歌词失败: ${err.message}`);
+      return '';
+    }
+  }
+}
+
+export const lyricService = new LyricService();

+ 141 - 218
entry/src/main/ets/view/LocalMusic.ets

@@ -97,6 +97,7 @@ import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { ensureSmbFileStreaming, SmbStreamingMeta } from '../common/network/SmbFileCache';
 import { ensureBaiduFileCached } from '../common/network/BaiduFileCache';
 import FileManager from '../common/util/FileManager';
+import { lyricService, SongData } from '../common/service/LyricService';
 
 import { HSBColorPicker, HSBColorPickerLayout } from '@keke/color-picker'
 import app from '@system.app';
@@ -194,8 +195,8 @@ function isSmbType(type: number): boolean {
   return type === CommonConstants.TYPE_SMB;
 }
 
-function isNavidromeType(type: number): boolean {
-  return type === CommonConstants.TYPE_NAVIDROME;
+function isNavidromeType(type: number | undefined): boolean {
+  return type !== undefined && type === CommonConstants.TYPE_NAVIDROME;
 }
 
 function isFtpType(type: number): boolean {
@@ -206,12 +207,12 @@ function isBaiduType(type: number): boolean {
   return type === CommonConstants.TYPE_BAIDU;
 }
 
-function isJellyfinType(type: number): boolean {
-  return type === CommonConstants.TYPE_JELLYFIN;
+function isJellyfinType(type: number | undefined): boolean {
+  return type !== undefined && type === CommonConstants.TYPE_JELLYFIN;
 }
 
-function isEmbyType(type: number): boolean {
-  return type === CommonConstants.TYPE_EMBY;
+function isEmbyType(type: number | undefined): boolean {
+  return type !== undefined && type === CommonConstants.TYPE_EMBY;
 }
 
 function isRemoteCloudType(type: number): boolean {
@@ -9822,276 +9823,137 @@ export struct LocalMusic {
     this.lyricControllerSingle.setLyric(null)
     let jiaMilyricPath = lyricPath.substring(0, lyricPath.lastIndexOf('.')) + '.lrcc'
     console.log("onecold lyricPath =" + lyricPath)
+
+    // 1. 检查内嵌歌词
     let neiqianLrc = ''
     let lyContent = this.currentSong?.lyricContent
-    if(lyContent&&StrUtil.isNotEmpty(lyContent)){//取数据库里面的歌词lyContent
+    if(lyContent && StrUtil.isNotEmpty(lyContent)){
       neiqianLrc = lyContent
     }
-    if (StrUtil.isNotEmpty(neiqianLrc) && !isOnLineAndToast&&!isLocal&&!isLocal) {
+
+    if (StrUtil.isNotEmpty(neiqianLrc) && !isOnLineAndToast && !isLocal) {
       console.log("onecold 找到内嵌歌词 neiqianLrc =" + neiqianLrc)
-      //赋值给this.lyricContent,播控中心才可以显示歌词
+      // Navidrome JSON歌词转换
       if (this.currentSong && isNavidromeType(this.currentSong.type)) {
-        //这里判断如果是Navidrome的歌词,需要把json格式的歌词转化下lrc标准格式
         const convertedLyric = LyricUtil.convertNavidromeJsonLyricToLrc(neiqianLrc);
         if (convertedLyric) {
           neiqianLrc = convertedLyric;
           console.log("onecold Navidrome歌词转换成功");
         }
       }
-      this.lyricContent = neiqianLrc
-      // 将文件内容按行分割成字符串数组
-      let lines = neiqianLrc.split('\n').map(line => line.trim());
-      // 3.解析歌词
-      let lyric = this.parser.parse(lines);
-      // 4.设置歌词
-      this.lyricController.setLyric(lyric);
-      this.lyricControllerXF.setLyric(lyric)
-      this.lyricControllerSingle.setLyric(lyric)
+      this.setLyricToControllers(neiqianLrc);
       return
-
     }
 
+    // 2. 尝试从服务器获取歌词 (Navidrome/Jellyfin/Emby)
+    if (this.currentSong && (isJellyfinType(this.currentSong.type)|| isEmbyType(this.currentSong.type))) {
+      const manager = RemoteDriveManager.getInstance();
+      const getAccount = (accountId: string) => manager.getWebDavAccountById(accountId);
+
+      // 将currentSong转换为SongData接口
+      const songData: SongData = {
+        webdav_account_id: this.currentSong.webdav_account_id,
+        artist: this.currentSong.artist,
+        name: this.currentSong.name,
+        webdav_id: this.currentSong.webdav_id,
+        lyricIndex: this.currentSong.lyricIndex,
+        type: this.currentSong.type
+      };
 
-    // 检查是否为 Jellyfin 类型的歌曲,如果是则从 Jellyfin 服务器获取歌词
-    if (this.currentSong && isJellyfinType(this.currentSong.type)) {
-      console.info('heanup', '检测到Jellyfin类型歌曲,尝试从Jellyfin服务器获取歌词');
-      try {
-        const manager = RemoteDriveManager.getInstance();
-        const accountId = this.currentSong.webdav_account_id;
-        if (!accountId) {
-          throw new Error('Jellyfin账号ID不可用');
-        }
-        const account = await manager.getWebDavAccountById(accountId);
-        if (!account) {
-          throw new Error('Jellyfin账号不可用');
-        }
-        const itemId = this.currentSong.webdav_id || '';
-        if (!itemId) {
-          throw new Error('Jellyfin歌曲ID不可用');
-        }
-        const jellyfinLyric = await jellyfinApi.getLyric(account, itemId);
-
-        if (StrUtil.isNotEmpty(jellyfinLyric)) {
-          console.info('heanup', '成功从Jellyfin服务器获取到歌词');
-          this.lyricContent = jellyfinLyric;
-          let lines = jellyfinLyric.split('\n').map(line => line.trim());
-          let lyric = this.parser.parse(lines);
-          this.lyricController.setLyric(lyric);
-          this.lyricControllerXF.setLyric(lyric);
-          this.lyricControllerSingle.setLyric(lyric);
-          this.showSingleLyric = true;
-          // 保存歌词到本地
-          this.saveDataToFile(jellyfinLyric, lyricPath, false);
-          return;
-        } else {
-          console.info('heanup', 'Jellyfin服务器未返回歌词,继续尝试其他方式');
-        }
-      } catch (error) {
-        const err = error as Error;
-        console.info('heanup', `从Jellyfin服务器获取歌词失败: ${err.message}`);
+      let serverLyric = '';
+      if (!serverLyric && isJellyfinType(songData.type)) {
+        serverLyric = await lyricService.fetchJellyfinLyric(songData, getAccount);
+      }
+      if (!serverLyric && isEmbyType(songData.type)) {
+        serverLyric = await lyricService.fetchEmbyLyric(songData, getAccount);
       }
-    }
 
-    // 检查是否为 Emby 类型的歌曲,如果是则从 Emby 服务器获取歌词
-    if (this.currentSong && isEmbyType(this.currentSong.type)) {
-      console.info('heanup', '检测到Emby类型歌曲,尝试从Emby服务器获取歌词');
-      try {
-        const manager = RemoteDriveManager.getInstance();
-        const accountId = this.currentSong.webdav_account_id;
-        if (!accountId) {
-          throw new Error('Emby账号ID不可用');
-        }
-        const account = await manager.getWebDavAccountById(accountId);
-        if (!account) {
-          throw new Error('Emby账号不可用');
-        }
-        const itemId = this.currentSong.webdav_id || '';
-        const lyricIndex = this.currentSong.lyricIndex; // 从歌曲信息中获取歌词索引
-        if (!itemId) {
-          throw new Error('Emby歌曲ID不可用');
-        }
-        if (lyricIndex === undefined || lyricIndex === null) {
-          console.info('heanup', 'Emby歌曲没有歌词索引,跳过获取歌词');
-        } else {
-          const embyLyric = await embyApi.getLyric(account, itemId, lyricIndex);
-
-          if (StrUtil.isNotEmpty(embyLyric)) {
-            console.info('heanup', '成功从Emby服务器获取到歌词');
-            this.lyricContent = embyLyric;
-            let lines = embyLyric.split('\n').map(line => line.trim());
-            let lyric = this.parser.parse(lines);
-            this.lyricController.setLyric(lyric);
-            this.lyricControllerXF.setLyric(lyric);
-            this.lyricControllerSingle.setLyric(lyric);
-            this.showSingleLyric = true;
-            // 保存歌词到本地
-            this.saveDataToFile(embyLyric, lyricPath, false);
-            return;
-          } else {
-            console.info('heanup', 'Emby服务器未返回歌词,继续尝试其他方式');
-          }
-        }
-      } catch (error) {
-        const err = error as Error;
-        console.info('heanup', `从Emby服务器获取歌词失败: ${err.message}`);
+      if (serverLyric) {
+        this.setLyricToControllers(serverLyric, lyricPath);
+        return;
       }
     }
 
+    // 3. 尝试在线歌词API
     if (isOnLineAndToast || ((!FileUtil.accessSync(lyricPath) && !FileUtil.accessSync(jiaMilyricPath))
       && !Utility.isVideoByExtension(this.videoUrl))) {
-      if (this.currentSong !== undefined) {
-        // ToastUtil.showShort('正在为你搜索在线歌词!')
-        //判断是不是赞助会员,是会员的话才开启歌词功能
-        // if (!this.isDebug) {
-        // if (!Utility.isNoble()) {
-        //     LogUtil.debug("onecold 不是赞助会员")
-        //     return
-        //   }
-        // if(!Utility.isPassInstallTime(33)){
-        //     LogUtil.debug("onecold not pass time")
-        //     // LogUtil.debug("onecold 用户安装app没超过12天" )
-        //     return
-        // }
-        // }
-
-        let artist = this.currentSong?.artist
-        if (artist === undefined) {
-          artist = ''
-        }
-        if (isApi2 === undefined) {
-          isApi2 = false
-        }
-        NetAxiosUtil.getLyric(this.name, artist, isApi2).then((res) => {
-
-          // LogUtil.debug("onecold res =" + res)
-          if (StrUtil.isNotEmpty(res) && res !== 'unknown' && res !== 'Timeout was reached') {
-
-            if (StrUtil.isNotEmpty(res)) {
-              this.lyricContent = res
-              let lines = res.split('\n').map(line => line.trim());
-
-              // 3.解析歌词
-              let lyric = this.parser.parse(lines);
-              // 4.设置歌词
-              this.lyricController.setLyric(lyric);
-              this.lyricControllerXF.setLyric(lyric)
-              this.lyricControllerSingle.setLyric(lyric)
-              this.showSingleLyric = true
-              // 4.保存歌词文件lyric到本地
-              //2025年8月28日歌词不再加密下载
-              this.saveDataToFile(res, lyricPath, false)
-
-
-            } else {
-              this.lyricController.setLyric(null)
-              this.lyricControllerXF.setLyric(null)
-              this.lyricControllerSingle.setLyric(null)
-              if (isOnLineAndToast) {
-                ToastUtil.showShort('未获取到歌词!')
-              }
-            }
-
-
-          } else {
-            this.lyricController.setLyric(null)
-            this.lyricControllerXF.setLyric(null)
-            this.lyricControllerSingle.setLyric(null)
-            if (isOnLineAndToast) {
-              ToastUtil.showShort('未获取到歌词!')
-            }
-          }
-
-
-        })
-      } else {
-        this.lyricController.setLyric(null)
-        this.lyricControllerXF.setLyric(null)
-        this.lyricControllerSingle.setLyric(null)
-        if (isOnLineAndToast) {
-          ToastUtil.showShort('未获取到歌词!')
-        }
-      }
-
-      return
+      this.fetchOnlineLyric(lyricPath, isOnLineAndToast || false, isApi2);
+      return;
     }
 
+    // 4. 从本地文件读取歌词
+    await this.loadLyricFromFile(lyricPath, jiaMilyricPath, isLocal || false);
+  }
+
+  /**
+   * 从本地文件加载歌词
+   */
+  private async loadLyricFromFile(lyricPath: string, jiaMilyricPath: string, isLocal: boolean): Promise<void> {
     try {
       let isJiaMi = false;
-      let realLyricPath = lyricPath
+      let realLyricPath = lyricPath;
+
       if (FileUtil.accessSync(jiaMilyricPath)) {
-        isJiaMi = true
-        realLyricPath = jiaMilyricPath
+        isJiaMi = true;
+        realLyricPath = jiaMilyricPath;
         console.info("onecold 找到加密本地歌词 ");
       } else {
-        isJiaMi = false
-        realLyricPath = lyricPath
-
+        isJiaMi = false;
+        realLyricPath = lyricPath;
         console.info("onecold 找到本地歌词 ");
       }
-      // 3.读取文件内容并指定编码为 UTF-8
-      let file = fs.openSync(realLyricPath,  fs.OpenMode.READ_ONLY);
+
+      // 读取文件内容
+      let file = fs.openSync(realLyricPath, fs.OpenMode.READ_ONLY);
       const stat = await fileIo.stat(file.fd);
       const arrayBuffer = new ArrayBuffer(stat.size);
+
       fs.read(file.fd, arrayBuffer)
         .then((readLen: number) => {
           console.info("read file data succeed");
-          // let buf = buffer.from(arrayBuffer, 0, readLen);
-
           let buf = new Uint8Array(arrayBuffer, 0, readLen);
-          // 检查 buf 是否为空
+
           if (buf.length === 0) {
             throw new Error("Buffer is empty after reading the file");
           }
+
           try {
             // 检测文件编码
-            // let detectedEncoding = chardet.detect(buf);
-            // console.info("onecoldT Detected encoding: " + detectedEncoding);
-            let detectedEncoding = this.detect(arrayBuffer)
+            let detectedEncoding = this.detect(arrayBuffer);
             console.info("onecoldT Detected encoding: " + detectedEncoding);
+
             // 使用检测到的编码解码,解决中文乱码问题
             let textDecoder = new util.TextDecoder(detectedEncoding || 'utf-8');
             console.info("onecoldT Detected textDecoder: " + textDecoder);
             let fileContent = textDecoder.decode(buf);
             console.info(`onecoldT The content of file1: ${fileContent}`);
-            //解密
+
+            // 解密
             if (isJiaMi) {
-              fileContent = StrUtil.unit8ArrayToStr(Base64Util.decodeSync(fileContent))
+              fileContent = StrUtil.unit8ArrayToStr(Base64Util.decodeSync(fileContent));
             }
+
             console.info(`onecold The content of file2: ${fileContent}`);
-            this.lyricContent = fileContent
-            if(isLocal){//编辑标签选择本地歌词需要赋值给this.lyricConStr
-              this.lyricConStr = fileContent
+            this.lyricContent = fileContent;
+
+            if(isLocal){
+              // 编辑标签选择本地歌词需要赋值给this.lyricConStr
+              this.lyricConStr = fileContent;
             }
-            // 将文件内容按行分割成字符串数组
-            let lines = fileContent.split('\n').map(line => line.trim());
-            console.info(`onecold The content of file: ${fileContent}`);
-            // 3.解析歌词
-            let lyric = this.parser.parse(lines);
-
-            // 4.设置歌词
-            this.lyricController.setLyric(lyric);
-            this.lyricControllerXF.setLyric(lyric)
-            this.lyricControllerSingle.setLyric(lyric)
-
-            this.showSingleLyric = true
+
+            this.setLyricToControllers(fileContent);
             console.info(`The content of file: ${fileContent}`);
           } catch (chardetError) {
             console.error("chardet.detect failed with error: " + chardetError.message);
-            this.lyricController.setLyric(null);
-            this.lyricControllerXF.setLyric(null)
-            this.lyricControllerSingle.setLyric(null)
+            this.clearLyricControllers();
           }
         })
         .catch((err: BusinessError) => {
-          this.lyricController.setLyric(null)
-          this.lyricControllerXF.setLyric(null)
-          this.lyricControllerSingle.setLyric(null)
+          this.clearLyricControllers();
           console.error("read file data failed with error message: " + err.message + ", error code: " + err.code);
         })
         .catch((err: Error) => {
-          this.lyricController.setLyric(null)
-          this.lyricControllerXF.setLyric(null)
-          this.lyricControllerSingle.setLyric(null)
+          this.clearLyricControllers();
           console.error("read file 2 data failed with error message: " + err.message);
         })
         .finally(() => {
@@ -10099,12 +9961,9 @@ export struct LocalMusic {
         });
 
     } catch (error) {
-      this.lyricController.setLyric(null)
-      this.lyricControllerSingle.setLyric(null)
-      this.lyricControllerXF.setLyric(null)
+      this.clearLyricControllers();
       Logger.error(TAG, 'init Lyric failed with err: ' + JSON.stringify(error));
     }
-
   }
 
   detect(data: ArrayBuffer): string {
@@ -10122,6 +9981,70 @@ export struct LocalMusic {
     return detected;
   }
 
+  /**
+   * 设置歌词到所有控制器
+   * @param lyricText 歌词文本
+   * @param savePath 可选,如果提供则保存到本地文件
+   */
+  private setLyricToControllers(lyricText: string, savePath?: string): void {
+    if (StrUtil.isEmpty(lyricText)) {
+      this.clearLyricControllers();
+      return;
+    }
+
+    this.lyricContent = lyricText;
+    let lines = lyricText.split('\n').map(line => line.trim());
+    let lyric = this.parser.parse(lines);
+
+    this.lyricController.setLyric(lyric);
+    this.lyricControllerXF.setLyric(lyric);
+    this.lyricControllerSingle.setLyric(lyric);
+    this.showSingleLyric = true;
+
+    // 如果提供了保存路径,保存歌词到本地
+    if (savePath) {
+      this.saveDataToFile(lyricText, savePath, false);
+    }
+  }
+
+  /**
+   * 清空所有歌词控制器
+   */
+  private clearLyricControllers(): void {
+    this.lyricController.setLyric(null);
+    this.lyricControllerXF.setLyric(null);
+    this.lyricControllerSingle.setLyric(null);
+  }
+
+  /**
+   * 从在线API获取歌词
+   */
+  private fetchOnlineLyric(lyricPath: string, isOnLineAndToast: boolean, isApi2?: boolean): void {
+    if (!this.currentSong) {
+      this.clearLyricControllers();
+      if (isOnLineAndToast) {
+        ToastUtil.showShort('未获取到歌词!');
+      }
+      return;
+    }
+
+    let artist = this.currentSong.artist || '';
+    if (isApi2 === undefined) {
+      isApi2 = false;
+    }
+
+    NetAxiosUtil.getLyric(this.name, artist, isApi2).then((res) => {
+      if (StrUtil.isNotEmpty(res) && res !== 'unknown' && res !== 'Timeout was reached') {
+        this.setLyricToControllers(res, lyricPath);
+      } else {
+        this.clearLyricControllers();
+        if (isOnLineAndToast) {
+          ToastUtil.showShort('未获取到歌词!');
+        }
+      }
+    });
+  }
+
   //初始化悬浮歌词的自定义设置
   initPipLyricSetting() {
     this.lyricControllerXF