Просмотр исходного кода

feat(cache): 新增百度网盘缓存支持

- 在 RemoteCacheManager 中新增百度网盘文件信息字段
- 注册百度网盘缓存策略到 RemoteCacheRegistry
- 扩展 RemoteCacheType 枚举以支持 BAIDU 类型
- 新增清除百度网盘缓存的相关函数
- 定义 BaiduFileInfo 接口并实现文件信息获取逻辑
- 更新 LocalMusic 页面以支持百度网盘文件播放与缓存
- 实现播放前检查本地缓存,若无则异步缓存文件
- 添加文件存在性和大小校验逻辑以确保缓存有效性
chendeben 8 месяцев назад
Родитель
Сommit
ca81baea69

+ 3 - 0
entry/src/main/ets/common/network/RemoteCacheManager.ets

@@ -5,6 +5,9 @@ export interface RemoteCacheRequest {
   account: WebDavAccount;
   remotePath: string;
   fullUrl?: string;
+  fsId?: string;
+  downloadUrl?: string;
+  fileSize?: number;
 }
 
 export interface RemoteCacheStrategy {

+ 1 - 0
entry/src/main/ets/common/network/RemoteCacheRegistry.ets

@@ -1,3 +1,4 @@
 // 触发各协议策略注册的集中入口
 import './SmbFileCache';
 import './FtpFileCache';
+import './BaiduFileCache';

+ 11 - 1
entry/src/main/ets/common/network/RemoteSongCache.ets

@@ -14,7 +14,8 @@ const CACHE_ROOT_DIR = 'remote_cache';
 export enum RemoteCacheType {
   WEBDAV = 'webdav',
   SMB = 'smb',
-  FTP = 'ftp'
+  FTP = 'ftp',
+  BAIDU = 'baidu'
 }
 
 export interface CachePathInfo {
@@ -189,6 +190,7 @@ export async function clearAllRemoteCaches(): Promise<void> {
   await clearRemoteCacheByAccount(RemoteCacheType.WEBDAV);
   await clearRemoteCacheByAccount(RemoteCacheType.SMB);
   await clearRemoteCacheByAccount(RemoteCacheType.FTP);
+  await clearRemoteCacheByAccount(RemoteCacheType.BAIDU);
 }
 
 export async function clearWebDavCacheByAccount(accountId?: string | number): Promise<void> {
@@ -198,3 +200,11 @@ export async function clearWebDavCacheByAccount(accountId?: string | number): Pr
 export async function clearWebDavCaches(): Promise<void> {
   await clearWebDavCacheByAccount();
 }
+
+export async function clearBaiduCacheByAccount(accountId?: string | number): Promise<void> {
+  await clearRemoteCacheByAccount(RemoteCacheType.BAIDU, accountId);
+}
+
+export async function clearBaiduCaches(): Promise<void> {
+  await clearBaiduCacheByAccount();
+}

+ 32 - 0
entry/src/main/ets/common/util/RemoteDriveManager.ets

@@ -52,6 +52,12 @@ export interface StreamAuthInfo {
   headers: Record<string, string>;
 }
 
+// 百度网盘文件信息接口
+export interface BaiduFileInfo {
+  size: number;
+  dlink: string;
+}
+
 export interface TransferTask {
   song: VideoItem;
   account: WebDavAccount;
@@ -1613,6 +1619,32 @@ export class RemoteDriveManager {
     return urlWithToken;
   }
 
+  public async getBaiduFileInfo(account: WebDavAccount, fsId: string): Promise<BaiduFileInfo> {
+    const accessToken = await this.ensureBaiduAccessToken(account);
+    if (!fsId) {
+      throw new Error('缺少百度网盘文件fs_id');
+    }
+    Logger.info(TAG, `查询百度网盘文件信息: fsId=${fsId}`);
+    const metas = await fetchBaiduFileMetas(accessToken, [fsId]);
+    if (!metas || metas.length === 0) {
+      throw new Error('无法获取文件信息');
+    }
+    const meta = metas[0];
+    if (!meta.dlink) {
+      throw new Error('无法获取下载链接');
+    }
+    const rawLink = meta.dlink.startsWith('https://') ? meta.dlink : meta.dlink.replace('http://', 'https://');
+    const urlWithToken = appendAccessTokenToDlink(rawLink, accessToken);
+    Logger.info(TAG, `百度网盘文件信息获取成功: fsId=${fsId}, size=${meta.size}, dlink=${urlWithToken}`);
+    // 缓存 dlink
+    this.cacheBaiduDlink(account, fsId, accessToken, urlWithToken);
+    const result: BaiduFileInfo = {
+      size: meta.size,
+      dlink: urlWithToken
+    };
+    return result;
+  }
+
   private smbEntryToFileInfo(entry: SmbDirectoryEntry, basePath: string): FileInfo {
     const normalizedPath = this.combineRemotePath(basePath, entry.name, entry.isDirectory);
     const info = new FileInfo('', entry.name, entry.size, Date.now());

+ 56 - 5
entry/src/main/ets/view/LocalMusic.ets

@@ -89,6 +89,8 @@ import { findWebDavCacheIfExists, triggerWebDavCacheDownload } from '../common/n
 import { navidromeApi } from '../common/network/NavidromeApi';
 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 { HSBColorPicker, HSBColorPickerLayout } from '@keke/color-picker'
 import app from '@system.app';
@@ -621,13 +623,62 @@ async function setVideoUrlForSong(song: VideoItem, options?: MetadataExtractionO
       if (!account) {
         throw new Error('百度网盘账户不可用');
       }
-      Logger.info(TAG, `Baidu播放准备 - accountId: ${account.id}, fsId: ${song.baiduFsId || song.id}`);
-      const downloadUrl = await manager.getBaiduDownloadUrl(account, song);
-      Logger.info(TAG, `Baidu播放URL已经构建完成: ${downloadUrl}`);
-      return downloadUrl;
+      Logger.info(TAG, `百度网盘播放准备 - accountId: ${account.id}, fsId: ${song.baiduFsId || song.id}`);
+
+      const fsId = song.baiduFsId || song.id;
+      if (!fsId) {
+        throw new Error('缺少百度网盘文件fs_id');
+      }
+
+      // 优先使用remote_rel_path,回退到文件名
+      const relativePath = song.remote_rel_path || song.name || song.fileName || song.filePath;
+      const cachedPath = await resolveCacheFilePath(
+        RemoteCacheType.BAIDU,
+        account.id?.toString(),
+        relativePath
+      );
+
+      const fileExists = await FileManager.isExist(cachedPath.cachePath);
+      if (fileExists) {
+        const fileSize = await FileManager.getFileSize(cachedPath.cachePath);
+        if (fileSize > 0) {
+          Logger.info(TAG, `百度网盘文件已缓存: ${cachedPath.cachePath}`);
+          void scheduleMetadataExtractionFromCache(song, cachedPath.cachePath, metadataOptions, song.videoSize);
+          return cachedPath.cachePath;
+        }
+      }
+
+      // 缓存不存在或无效,获取文件信息(包含大小和下载链接)
+      Logger.info(TAG, `百度网盘文件未缓存,准备获取文件信息: ${relativePath}`);
+      const fileInfo = await manager.getBaiduFileInfo(account, fsId);
+      Logger.info(TAG, `百度网盘文件信息获取成功: 大小=${fileInfo.size}字节,链接已获取`);
+
+      // 立即返回下载链接,开始播放
+      Logger.info(TAG, `返回下载链接进行播放,同时后台缓存: ${fileInfo.dlink}`);
+
+      // 异步进行缓存,不阻塞播放
+      void (async () => {
+        try {
+          Logger.info(TAG, `后台开始缓存百度网盘文件: ${relativePath},大小: ${fileInfo.size}字节`);
+          const cachedFilePath = await ensureBaiduFileCached(
+            account,
+            fsId,
+            relativePath,
+            fileInfo.dlink,
+            fileInfo.size
+          );
+          Logger.info(TAG, `后台缓存完成: ${cachedFilePath}`);
+          void scheduleMetadataExtractionFromCache(song, cachedFilePath, metadataOptions, song.videoSize);
+        } catch (cacheError) {
+          const cacheErr = cacheError as Error;
+          Logger.warn(TAG, `后台缓存失败: ${cacheErr.message}`);
+        }
+      })();
+
+      return fileInfo.dlink;
     } catch (error) {
       const err = error as Error;
-      Logger.error(TAG, `百度网盘获取播放链接失败: ${err.message}`);
+      Logger.error(TAG, `百度网盘播放链接获取失败: ${err.message}`);
       throw err;
     }
   }