Browse Source

Merge remote-tracking branch 'origin/master'

onecold 7 months ago
parent
commit
b428bcb3e3

+ 32 - 2
entry/src/main/ets/common/network/JellyfinApi.ets

@@ -405,11 +405,41 @@ export class JellyfinApi {
     return songs;
   }
 
-  async buildStreamUrl(account: WebDavAccount, itemId: string): Promise<string> {
+  async buildStreamUrl(account: WebDavAccount, itemId: string, useStatic: boolean = false): Promise<string> {
     const auth = await this.ensureAuth(account);
     const baseUrl = this.buildBaseUrl(account);
     const apiKey = encodeURIComponent(auth.token);
-    return `${baseUrl}/Audio/${encodeURIComponent(itemId)}/stream?api_key=${apiKey}`;
+    const params: string[] = [`api_key=${apiKey}`];
+    if (useStatic) {
+      params.push('static=true');
+    }
+    return `${baseUrl}/Audio/${encodeURIComponent(itemId)}/stream?${params.join('&')}`;
+  }
+
+  async buildDownloadUrl(account: WebDavAccount, itemId: string): Promise<string> {
+    const auth = await this.ensureAuth(account);
+    const baseUrl = this.buildBaseUrl(account);
+    const apiKey = encodeURIComponent(auth.token);
+    return `${baseUrl}/Items/${encodeURIComponent(itemId)}/Download?api_key=${apiKey}`;
+  }
+
+  async canAccessUrl(account: WebDavAccount, url: string): Promise<boolean> {
+    const httpRequest = http.createHttp();
+    try {
+      const auth = await this.ensureAuth(account);
+      const response = await httpRequest.request(url, {
+        method: http.RequestMethod.HEAD,
+        connectTimeout: 10000,
+        readTimeout: 10000,
+        expectDataType: http.HttpDataType.STRING,
+        header: this.buildAuthHeaderObject(auth)
+      });
+      return response.responseCode === 200 || response.responseCode === 206;
+    } catch (_error) {
+      return false;
+    } finally {
+      httpRequest.destroy();
+    }
   }
 
   async buildPrimaryImageUrl(account: WebDavAccount, itemId: string, width: number = 600, height: number = 600): Promise<string> {

+ 20 - 3
entry/src/main/ets/common/network/RemoteSongCache.ets

@@ -74,10 +74,27 @@ async function deletePathIfExists(path: string): Promise<void> {
     return;
   }
   try {
-    if (FileUtil.accessSync(path)) {
-      await FileUtil.rmdir(path);
-      Logger.info(TAG, `已删除缓存目录: ${path}`);
+    if (!FileUtil.accessSync(path)) {
+      return;
     }
+    const deleteRecursively = (targetPath: string): void => {
+      if (FileUtil.isDirectory(targetPath)) {
+        const files = FileUtil.listFileSync(targetPath);
+        files.forEach((file: string) => {
+          const filePath = `${targetPath}${FileUtil.separator}${file}`;
+          deleteRecursively(filePath);
+        });
+        FileUtil.rmdirSync(targetPath);
+      } else {
+        FileUtil.unlinkSync(targetPath);
+      }
+    };
+    if (FileUtil.isDirectory(path)) {
+      deleteRecursively(path);
+    } else {
+      deleteRecursively(path);
+    }
+    Logger.info(TAG, `已删除缓存目录: ${path}`);
   } catch (error) {
     Logger.error(TAG, `删除缓存目录失败 ${path}: ${(error as Error).message}`);
   }

+ 20 - 7
entry/src/main/ets/common/util/RemotePlayerUtil.ets

@@ -178,12 +178,18 @@ export async function preloadNextSongIfNeeded(
       const account = await manager.getWebDavAccountById(nextSong.webdav_account_id);
       if (account) {
         const itemId = nextSong.remote_rel_path || nextSong.id || nextSong.filePath;
+        const downloadUrl = await jellyfinApi.buildDownloadUrl(account, itemId);
+        const canDownload = await jellyfinApi.canAccessUrl(account, downloadUrl);
+        const staticStreamUrl = await jellyfinApi.buildStreamUrl(account, itemId, true);
+        const canStaticStream = await jellyfinApi.canAccessUrl(account, staticStreamUrl);
         const streamUrl = await jellyfinApi.buildStreamUrl(account, itemId);
+        const cacheUrl = canDownload ? downloadUrl : (canStaticStream ? staticStreamUrl : streamUrl);
+        const cacheMode = canDownload ? 'download' : (canStaticStream ? 'static' : 'stream');
 
         void (async () => {
           try {
-            console.info(TAG, `后台提前缓存Jellyfin下一首: ${itemId}`);
-            await ensureJellyfinFileCached(account, itemId, streamUrl, nextSong.videoSize);
+            console.info(TAG, `后台提前缓存Jellyfin下一首(${cacheMode}): ${itemId}`);
+            await ensureJellyfinFileCached(account, itemId, cacheUrl, nextSong.videoSize);
             console.info(TAG, `Jellyfin下一首提前缓存完成`);
           } catch (error) {
             const err = error as Error;
@@ -412,21 +418,28 @@ export async function setVideoUrlForSong(
         }
       }
 
-      // 缓存不存在,获取流媒体URL并开始后台缓存
+      // 缓存不存在,优先尝试Download直链,失败则尝试Static流,再回退普通Stream
+      const downloadUrl = await jellyfinApi.buildDownloadUrl(account, jellyfinSongId);
+      const canDownload = await jellyfinApi.canAccessUrl(account, downloadUrl);
+      const staticStreamUrl = await jellyfinApi.buildStreamUrl(account, jellyfinSongId, true);
+      const canStaticStream = await jellyfinApi.canAccessUrl(account, staticStreamUrl);
       const streamUrl = await jellyfinApi.buildStreamUrl(account, jellyfinSongId);
-      void ServerLogUtil.info(TAG, `Jellyfin 流地址构建成功: ${streamUrl}`);
+      const cacheUrl = canDownload ? downloadUrl : (canStaticStream ? staticStreamUrl : streamUrl);
+      const cacheMode = canDownload ? 'Download' : (canStaticStream ? 'StaticStream' : 'Stream');
+      void ServerLogUtil.info(TAG, `Jellyfin 流地址构建成功(播放): ${streamUrl}`);
+      void ServerLogUtil.info(TAG, `Jellyfin 缓存地址选择(${cacheMode}): ${cacheUrl}`);
       void ServerLogUtil.debug(TAG, `播放songId=${jellyfinSongId}, account=${ServerLogUtil.sanitizeAccount(account)}`);
 
       // 立即返回流媒体URL,同时后台异步缓存
       const sanitizedUrl = sanitizePlaybackUrl(streamUrl);
       void (async () => {
         try {
-          Logger.info(TAG, `后台开始缓存Jellyfin文件: ${jellyfinSongId}`);
-          void ServerLogUtil.info('JellyfinStream', `开始缓存: itemId=${jellyfinSongId}`);
+          Logger.info(TAG, `后台开始缓存Jellyfin文件(${cacheMode}): ${jellyfinSongId}`);
+          void ServerLogUtil.info('JellyfinStream', `开始缓存: itemId=${jellyfinSongId}, mode=${cacheMode}`);
           const cachedFilePath = await ensureJellyfinFileCached(
             account,
             jellyfinSongId,
-            streamUrl,
+            cacheUrl,
             song.videoSize
           );
           Logger.info(TAG, `后台缓存完成: ${cachedFilePath}`);