chendeben 9 месяцев назад
Родитель
Сommit
3d59c0bf54

+ 72 - 59
entry/src/main/ets/common/util/RcpSocketUtil.ets

@@ -10,12 +10,23 @@ const UtilName = "heanup RcpSocket"
 
 export class RcpSocket {
   private static instance: RcpSocket;
-  private backgroundManager = BackgroundManager.getInstance()
   public ErrorMessage: string | BusinessError = ''
   public filesInfo: FileInfo[] = []
+  private backgroundManager = BackgroundManager.getInstance()
 
   //private rcpSession : rcp.Session | null = null
 
+  constructor() {
+    console.info(UtilName, 'testTag', 'RcpSocketUtil单例已创建')
+  }
+
+  static getInstance(): RcpSocket {
+    if (!RcpSocket.instance) {
+      RcpSocket.instance = new RcpSocket();
+    }
+    return RcpSocket.instance;
+  }
+
   public RcpSendHead(host: string, port: number, account: string, password: string, path: string,
     enableHttps: boolean): Promise<number> {
     return new Promise<number>((resolve, reject) => {
@@ -159,7 +170,6 @@ export class RcpSocket {
     });
   }
 
-
   public RcpSendMove(host: string, port: number, account: string, password: string, path: string, enableHttps: boolean,
     newPath: string): Promise<void> {
     return new Promise<void>((resolve, reject) => {
@@ -225,7 +235,6 @@ export class RcpSocket {
     });
   }
 
-
   // rcp方法
   public async RcpSendPropFind(
     host: string,
@@ -300,7 +309,7 @@ export class RcpSocket {
       const encodedCredentials = buffer
         .from(`${account}:${password}`)
         .toString("base64");
-      console.info(UtilName,'testTag',account,password)
+      console.info(UtilName, 'testTag', account, password)
 
 
       const headers: rcp.RequestHeaders = {
@@ -319,7 +328,7 @@ export class RcpSocket {
             console.info(UtilName, 'testTag', 'PROPFIND执行完毕')
             if (response != '') {
               // 提取文件信息
-              const filesInfo = this.extractHrefContents(response, path,url);
+              const filesInfo = this.extractHrefContents(response, path, url);
               if (filesInfo.length !== 0) {
                 console.info(UtilName, 'testTag', '请求成功')
                 rcpSession.close()
@@ -349,7 +358,6 @@ export class RcpSocket {
     });
   }
 
-
   // PROPFIND递归方法
   public async RcpSendPropFindInfinity(
     host: string,
@@ -454,20 +462,20 @@ export class RcpSocket {
       };
       let sendSinglePropfind = async (url: string, root: string): Promise<FileInfo[]> => {
         return new Promise<FileInfo[]>(async (resolve, reject) => {
-          if(root.includes('%23recycle')){
+          if (root.includes('%23recycle')) {
             resolve([])
             return
           }
           // 发起请求
           try {
-            AppStorage.setOrCreate('CurrentPropfindInfinityRoot',root)
+            AppStorage.setOrCreate('CurrentPropfindInfinityRoot', root)
             response = ''
             const req = new rcp.Request(url, "PROPFIND", headers, requestBody)
             await rcpSession.fetch(req)
               .finally(async () => {
                 if (response != '') {
                   // 提取文件信息
-                  let filesInfo = this.extractHrefContents(response, root,url);
+                  let filesInfo = this.extractHrefContents(response, root, url);
                   let folderInfos: FileInfo[] = []
                   for (const info of filesInfo) {
                     if (this.isFileFolder(info.name)) {
@@ -517,23 +525,14 @@ export class RcpSocket {
             }
           }
           resolve(filesInfo)
-        }).
-        catch((err: BusinessError) => {
+        }).catch((err: BusinessError) => {
           rcpSession?.close()
-          console.error(UtilName,'testTag','PROPFIND目录递归失败',JSON.stringify(err))
+          console.error(UtilName, 'testTag', 'PROPFIND目录递归失败', JSON.stringify(err))
           reject(err)
         })
     });
   }
 
-  // 判断文件是否属于文件夹
-  private isFileFolder(filename: string):boolean{
-    return filename.toLowerCase().endsWith('/')
-  }
-
-
-
-
   //ArrayBuffer转utf8字符串
   buf2String(buf: ArrayBuffer) {
     let msgArray = new Uint8Array(buf);
@@ -551,41 +550,15 @@ export class RcpSocket {
   }
 
   stringToNumber(str: string): number {
-    let result:number = 0;
+    let result: number = 0;
     for (let i = 0; i < str.length; i++) {
       result += str.charCodeAt(i);
     }
     return result;
   }
 
-  // 将 HTTP 日期字符串转换为 Unix 时间戳
-  private convertToUnixTimestamp(dateString: string): number {
-    const date = new Date(dateString);
-    if (isNaN(date.getTime())) {
-      console.error(UtilName,'testTag',"非法日期字符串:", dateString);
-      return 0;
-    }
-    return Math.floor(date.getTime() / 1000);
-  }
-
-  private decodeXMLEntities(str: string): string {
-    const entityMap: HashMap<string,string> = new HashMap()
-    entityMap.set('&amp;', '&')
-    entityMap.set('&lt;', '<')
-    entityMap.set('&gt;','>')
-    entityMap.set('&quot;', '"')
-    entityMap.set('&apos;', "'")
-
-
-    // 正则匹配替换
-    return str.replace(/&(amp|lt|gt|quot|apos);/g, (match:string, entity:string) => {
-      const decoded = entityMap.get(`&${entity};`);
-      return decoded ? decoded : match;
-    });
-  }
-
   // 使用正则表达式提取所有 <D:href> 标签的内容
-  extractHrefContents(xmlContent: string,rootpath: string,url: string): FileInfo[] {
+  extractHrefContents(xmlContent: string, rootpath: string, url: string): FileInfo[] {
     const filesInfo: FileInfo[] = [];
     // 匹配每个 <D:response>
     const responseRegex = /<D:response\b[^>]*>([\s\S]*?)<\/D:response>/gi;
@@ -597,7 +570,9 @@ export class RcpSocket {
 
       // 提取 <D:href> 内容
       const hrefMatch = responseBlock.match(/<D:href>(.*?)<\/D:href>/i);
-      if (!hrefMatch) continue;
+      if (!hrefMatch) {
+        continue;
+      }
 
       // 获取完整的href路径
       const fullHref = this.decodeXMLEntities(decodeURI(hrefMatch[1]));
@@ -629,7 +604,7 @@ export class RcpSocket {
 
       // 提取<D:getlastmodified> 内容
       let lastModifiedMatch = responseBlock.match(/<D:getlastmodified>(.*?)<\/D:getlastmodified>/i);
-      if(!lastModifiedMatch){
+      if (!lastModifiedMatch) {
         lastModifiedMatch = responseBlock.match(/<lp1:getlastmodified>(.*?)<\/lp1:getlastmodified>/i);
       }
       const lastModified = lastModifiedMatch ? this.convertToUnixTimestamp(lastModifiedMatch[1]) : 0;
@@ -646,11 +621,18 @@ export class RcpSocket {
     return filesInfo;
   }
 
-  constructor() {
-    console.info(UtilName,'testTag','RcpSocketUtil单例已创建')
-  }
-
-  // 获取文件列表(简化版包装方法)
+  /**
+   * 获取文件列表
+   * @param host 主机名
+   * @param localHost 本地主机名
+   * @param isUseLocalHost 是否使用本地主机名
+   * @param port 端口号
+   * @param path 路径
+   * @param account 用户名
+   * @param password 密码
+   * @param enableHttps 是否启用HTTPS
+   * @returns
+   */
   public async getFileList(
     host: string,
     localHost: string,
@@ -671,10 +653,41 @@ export class RcpSocket {
     console.info(UtilName, 'testTag', '订阅HTTP数据传输事件(占位)');
   }
 
-  static getInstance(): RcpSocket {
-    if (!RcpSocket.instance) {
-      RcpSocket.instance = new RcpSocket();
+  // 获取文件列表(简化版包装方法)
+
+  // 判断文件是否属于文件夹
+  /**
+   * 判断文件是否属于文件夹
+   * @param filename 文件名
+   * @returns
+   */
+  private isFileFolder(filename: string): boolean {
+    return filename.toLowerCase().endsWith('/')
+  }
+
+  // 将 HTTP 日期字符串转换为 Unix 时间戳
+  private convertToUnixTimestamp(dateString: string): number {
+    const date = new Date(dateString);
+    if (isNaN(date.getTime())) {
+      console.error(UtilName, 'testTag', "非法日期字符串:", dateString);
+      return 0;
     }
-    return RcpSocket.instance;
+    return Math.floor(date.getTime() / 1000);
+  }
+
+  private decodeXMLEntities(str: string): string {
+    const entityMap: HashMap<string, string> = new HashMap()
+    entityMap.set('&amp;', '&')
+    entityMap.set('&lt;', '<')
+    entityMap.set('&gt;', '>')
+    entityMap.set('&quot;', '"')
+    entityMap.set('&apos;', "'")
+
+
+    // 正则匹配替换
+    return str.replace(/&(amp|lt|gt|quot|apos);/g, (match: string, entity: string) => {
+      const decoded = entityMap.get(`&${entity};`);
+      return decoded ? decoded : match;
+    });
   }
 }

+ 11 - 1
entry/src/main/ets/common/util/WebdavManager.ets

@@ -369,8 +369,18 @@ export class WebdavManager {
     const protocol = account.enableHttps ? 'https' : 'http';
     const host = account.isUseLocalHost ? account.localHost : account.host;
     const port = account.port;
+
+    // 构建带认证信息的URL(如果提供了用户名和密码)
+    let authUrl = '';
+    if (account.account && account.password) {
+      // 对用户名和密码进行URL编码以处理特殊字符
+      const encodedUsername = encodeURIComponent(account.account);
+      const encodedPassword = encodeURIComponent(account.password);
+      authUrl = `${encodedUsername}:${encodedPassword}@`;
+    }
+
     // href已经包含完整路径,直接使用
-    song.src = `${protocol}://${host}:${port}${fileInfo.href}`;
+    song.src = `${protocol}://${authUrl}${host}:${port}${fileInfo.href}`;
 
     song.songType = SongType.WebDav;
     song.webDavAccountId = account.id;

+ 7 - 1
entry/src/main/ets/pages/NewIndex.ets

@@ -245,7 +245,13 @@ struct NewIndex {
     this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
     this.breakpointSystem.register();
     let params = router.getParams() as Record<string, Object>;
-    this.videoLocalList = params.videoList as VideoItem[];
+    if (params && params.videoList) {
+      this.videoLocalList = params.videoList as VideoItem[];
+      LogUtil.info('heanup NewIndex', '从路由参数获取到播放列表,长度:' + this.videoLocalList.length)
+    } else {
+      this.videoLocalList = []
+      LogUtil.info('heanup NewIndex', '路由参数中没有播放列表,使用空数组')
+    }
     // Utility.setStatusBarLight()
     ScreenUtil.setScreenSize();
     this.bundleName = AppUtil.getBundleName()

+ 46 - 6
entry/src/main/ets/pages/WebDavMainPage.ets

@@ -9,6 +9,19 @@ import { VideoItem } from '../viewmodel/VideoItem';
 import { GlobalContext } from '../common/util/GlobalContext';
 import { display } from '@kit.ArkUI';
 import { FileInfo } from '../viewmodel/FileInfo';
+import { emitter } from '@kit.BasicServicesKit';
+import { EventConstants } from '../common/constants/EventConstants';
+
+/**
+ * 歌单播放事件数据
+ */
+interface PlaylistEventData {
+  playlistId: string;
+  playlistName: string;
+  songCount: number;
+  startIndex: number;
+  songFilePaths: string[];
+}
 
 const TAG = 'heanup WebDavMainPage';
 
@@ -244,29 +257,56 @@ export struct WebDavMainPage {
   // 播放WebDAV歌曲
   private playSong(song: Song, index: number): void {
     try {
+      Logger.info(TAG, `heanup === WebDAV playSong 方法被调用 ===`);
+      Logger.info(TAG, `heanup 播放指定歌曲: ${song.title}, 索引: ${index}`);
+      Logger.info(TAG, `heanup 歌曲列表长度: ${this.songs.length}`);
+
       // 将当前歌曲列表转换为VideoItem数组
       const videoItems: VideoItem[] = [];
+      const songFilePaths: string[] = [];
+
       for (let i = 0; i < this.songs.length; i++) {
         const item = this.convertSongToVideoItem(this.songs[i], i);
         videoItems.push(item);
+        songFilePaths.push(item.filePath); // 使用filePath作为文件路径
       }
 
-      // 保存到全局上下文
+      Logger.info(TAG, `heanup 所有WebDAV歌曲文件路径: ${JSON.stringify(songFilePaths)}`);
+
+      // 保存WebDAV歌曲数据到全局上下文
       const globalContext = GlobalContext.getContext();
       globalContext.setObject('videoItems', videoItems);
       globalContext.setObject('currentPlayIndex', index);
+      Logger.info(TAG, `heanup 已将WebDAV歌曲列表保存到全局上下文,长度:${videoItems.length}`);
+
+      // 发送播放事件,类似歌单播放的方式
+      const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY };
+      const playlistData: PlaylistEventData = {
+        playlistId: 'webdav-playlist', // 使用特殊的ID标识WebDAV播放列表
+        playlistName: `WebDAV - ${this.selectedAccount?.name || '未知账户'}`,
+        songCount: this.songs.length,
+        startIndex: index,
+        songFilePaths: songFilePaths
+      };
+
+      Logger.info(TAG, `heanup 准备发送WebDAV播放事件,eventId: ${eventPlaylistPlay.eventId}`);
+      Logger.info(TAG, `heanup 发送WebDAV播放事件数据: ${JSON.stringify(playlistData)}`);
+
+      const eventData: emitter.EventData = {
+        data: playlistData
+      };
+
+      emitter.emit(eventPlaylistPlay, eventData);
 
-      // 跳转到播放器页面
+      // 跳转到首页播放器
       router.pushUrl({
         url: 'pages/NewIndex',
         params: {
-          videoList: videoItems,
-          playIndex: index,
           fromWebDAV: true
         }
       }).catch((error: Error) => {
-        Logger.error(TAG, `跳转播放器失败: ${error.message}`);
-        promptAction.showToast({ message: '播放失败' });
+        Logger.error(TAG, `跳转首页失败: ${error.message}`);
+        promptAction.showToast({ message: '跳转失败' });
       });
     } catch (error) {
       const err = error as Error;

+ 195 - 1
entry/src/main/ets/view/LocalMusic.ets

@@ -10,6 +10,7 @@ import {
   DeviceUtil,
   DisplayUtil,
   FileUtil,
+  GlobalContext,
   ImageUtil,
   LogUtil,
   MD5,
@@ -31,6 +32,7 @@ import { common, ConfigurationConstant } from '@kit.AbilityKit';
 import { AnimationHelper, DialogAction, DialogHelper } from '@pura/harmony-dialog';
 import { fileIo, fileUri, picker } from '@kit.CoreFileKit';
 import { MessageEvents, util, worker, ErrorEvent } from '@kit.ArkTS';
+import Base64 from '@ohos.util';
 import { Verify } from './Verify';
 import { taskpool } from '@kit.ArkTS';
 import { RotatingCover } from './RotatingCover';
@@ -92,6 +94,9 @@ import { showAddToPlaylistDialog } from '../dialog/AddToPlaylistDialog';
 import { showCreatePlaylistDialog } from '../dialog/PlaylistDialog';
 import { convertPlaylistSongsToVideoItems, emptyView } from '../pages/PlaylistDetailPage';
 import { Playlist, PlaylistSong } from '../viewmodel/Playlist';
+import { Song } from '../viewmodel/Song';
+import { SongType } from '../common/enums/SongType';
+import { WebdavManager } from '../common/util/WebdavManager';
 const TAG = 'LocalMusic';
 
 /**
@@ -5503,6 +5508,54 @@ export struct LocalMusic {
         this.startPlayOrResumePlay()
         break;
 
+      case CommonConstants.TYPE_INTERNET:
+        // 处理网络音频播放(WebDAV)
+        Logger.info(`heanup 处理网络音频播放: ${item.name}, URL: ${item.filePath}`)
+
+        if (this.CONTROL_PlayStatus !== PlayStatus.INIT) {
+          this.stop();
+        }
+
+        if (isOpen) {
+          this.currentSong = item
+          if (index !== undefined) {
+            this.curIndex = index
+          }
+          this.songList = []
+          this.songList.push(item)
+          this.sonDataSource.pushArrayData(this.songList)
+        } else if (isFromSonPlayList) {
+          // 点击来自右下角的播放列表
+          this.currentSong = item
+          if (index !== undefined) {
+            this.curIndex = index
+          }
+          // 确保播放列表是完整的歌单歌曲列表(已在finishLoadingPlaylist中设置)
+          Logger.info(`heanup 网络音频 isFromSonPlayList分支 - 当前播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`)
+        } else {
+          // 从全局网络音频列表中查找
+          let globalVideoList = this.videoLocalList.filter(video => video.type === CommonConstants.TYPE_INTERNET) as VideoItem[];
+          this.curIndex = globalVideoList.findIndex(video => video.filePath === item.filePath);
+          if (this.curIndex === -1 && index !== undefined) {
+            this.curIndex = index;
+          }
+          this.songList = globalVideoList.length > 0 ? globalVideoList : [item];
+          this.currentSong = this.songList[this.curIndex];
+
+          this.sonDataSource.pushArrayData(this.songList)
+        }
+
+        AppStorage.setOrCreate('currentSong', this.currentSong);
+        this.videoUrl = this.currentSong.filePath  // 网络URL
+        this.name = this.currentSong.name
+        this.cover = this.currentSong.pixelMapPath
+        this.artist = this.currentSong.artist
+
+        Logger.info(`heanup 准备播放网络音频 - 歌名: ${this.name}, 艺术家: ${this.artist}, URL: ${this.videoUrl}`)
+
+        this.startPlayOrResumePlay()
+        break;
+
     }
 
 
@@ -11481,6 +11534,12 @@ export struct LocalMusic {
   }
 
   updateLastPlayTimeStr(filePath: string) {
+    // 检查是否为网络音频(WebDAV),如果是则不更新本地数据库
+    if (this.currentSong && this.currentSong.type === CommonConstants.TYPE_INTERNET) {
+      Logger.info(`heanup 检测到网络音频播放,跳过数据库更新: ${filePath}`)
+      return;
+    }
+
     let lastPlayTime = DateUtil.getTodayStr('yyyy-MM-dd HH:mm:ss')
     this.table.updateLastPlayedStrByFilePath(filePath, lastPlayTime, (success: boolean, error?: string) => {
       if (success) {
@@ -11625,6 +11684,63 @@ export struct LocalMusic {
     this.loadingVisible = Visibility.None;
     this.replayVisible = Visibility.Visible;
   }
+  /**
+   * 为WebDAV请求添加认证头
+   */
+  private addWebDavAuthHeader(headers: Map<string, string>, accountId: number): void {
+    try {
+      // 从WebDAV管理器获取账户信息
+      const webdavManager = WebdavManager.getInstance();
+      const accounts = webdavManager.getAllWebDavAccounts();
+      const account = accounts.find(acc => acc.id === accountId);
+
+      if (account && account.account && account.password) {
+        // 构建Basic认证头
+        const credentials = `${account.account}:${account.password}`;
+
+        // 简单的Base64编码实现
+        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+        let encoded = '';
+        let i = 0;
+
+        while (i < credentials.length) {
+          const a = credentials.charCodeAt(i++);
+          const b = i < credentials.length ? credentials.charCodeAt(i++) : 0;
+          const c = i < credentials.length ? credentials.charCodeAt(i++) : 0;
+
+          const bitmap = (a << 16) | (b << 8) | c;
+
+          encoded += chars.charAt((bitmap >> 18) & 63);
+          encoded += chars.charAt((bitmap >> 12) & 63);
+          encoded += chars.charAt((bitmap >> 6) & 63);
+          encoded += chars.charAt(bitmap & 63);
+        }
+
+        // 处理剩余字符
+        const remainder = credentials.length % 3;
+        if (remainder === 1) {
+          const bitmap = credentials.charCodeAt(credentials.length - 1) << 16;
+          encoded += chars.charAt((bitmap >> 18) & 63);
+          encoded += chars.charAt((bitmap >> 12) & 63);
+          encoded += '==';
+        } else if (remainder === 2) {
+          const bitmap = (credentials.charCodeAt(credentials.length - 2) << 16) | (credentials.charCodeAt(credentials.length - 1) << 8);
+          encoded += chars.charAt((bitmap >> 18) & 63);
+          encoded += chars.charAt((bitmap >> 12) & 63);
+          encoded += chars.charAt((bitmap >> 6) & 63);
+          encoded += '=';
+        }
+
+        headers.set("Authorization", `Basic ${encoded}`);
+        Logger.info(`heanup 为WebDAV添加Basic认证头,账户: ${account.account}`);
+      } else {
+        Logger.warn(`heanup 未找到WebDAV账户信息或账户缺少认证信息,accountId: ${accountId}`);
+      }
+    } catch (error) {
+      Logger.error(`heanup 添加WebDAV认证头失败: ${error}`);
+    }
+  }
+
   private async play(url: string,startOffset?:number) {
     let that = this;
     that.showLoadIng();
@@ -11659,6 +11775,28 @@ export struct LocalMusic {
       ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],
       ["referer", "https://www.bilibili.com"]
     ]);
+
+    // 如果是WebDAV网络音频,添加适当的HTTP头
+    if (this.currentSong && this.currentSong.type === CommonConstants.TYPE_INTERNET) {
+      const currentSong = this.currentSong;
+
+      // 安全地检查是否为WebDAV歌曲(通过检查URL格式)
+      const isWebDavSong = currentSong.filePath.startsWith('http://') ||
+                          currentSong.filePath.startsWith('https://');
+
+      if (isWebDavSong) {
+        Logger.info(`heanup 检测到WebDAV播放,添加专用HTTP头`);
+
+        // 为WebDAV添加适当的请求头
+        headers.set("user_agent", "TTMusic-WebDAV/1.0");
+        headers.set("accept", "*/*");
+        headers.set("accept-range", "bytes");
+
+        // 注意:认证信息已经在URL中(在WebdavManager中处理),无需额外添加认证头
+        Logger.info(`heanup WebDAV认证信息已在URL中,跳过额外认证头设置`);
+      }
+    }
+
     this.mIjkMediaPlayer.setDataSourceHeader(headers);
     // if(PreferencesUtil.getBooleanSync(SettingPage.IS_MIDIACODEC_OPEN,false)){
     //   this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", "1"); // 启用硬件解码,会导致无法顺序播放
@@ -11960,7 +12098,11 @@ export struct LocalMusic {
         LogUtils.getInstance().LOGI("OnErrorListener-->go:" + what + "===" + extra + " this.videoUrl=" + this.videoUrl)
         that.hideLoadIng();
 
-        if (StrUtil.isNotEmpty(this.videoUrl) && !FileUtil.accessSync(this.videoUrl)) {
+        // 检查文件是否存在,但跳过网络URL(WebDAV)
+        if (StrUtil.isNotEmpty(this.videoUrl) &&
+            !this.videoUrl.startsWith('http://') &&
+            !this.videoUrl.startsWith('https://') &&
+            !FileUtil.accessSync(this.videoUrl)) {
           ToastUtil.showToast(Utility.resourceToString(getContext(), $r('app.string.file_not_exist')))
         } else {
           ToastUtil.showToast(getContext()
@@ -13193,6 +13335,58 @@ export struct LocalMusic {
     try {
       Logger.info(`heanup 处理歌单播放请求: ${playlistName}, 歌曲数量: ${songFilePaths.length}, 开始索引: ${startIndex}`)
 
+      // 检查是否为WebDAV播放请求
+      if (playlistId === 'webdav-playlist') {
+        Logger.info(`heanup 检测到WebDAV播放请求,直接从全局上下文获取歌曲数据`)
+        const globalContext = GlobalContext.getContext();
+
+        // 先检查GlobalContext中是否有数据
+        const allKeys = globalContext['_objects'] ? Array.from(globalContext['_objects'].keys()) : [];
+        Logger.info(`heanup GlobalContext中的所有键: ${JSON.stringify(allKeys)}`);
+
+        const videoItems = globalContext.getObject('videoItems') as VideoItem[];
+        const currentPlayIndex = globalContext.getObject('currentPlayIndex') as number;
+
+        Logger.info(`heanup 从GlobalContext获取数据: videoItems长度=${videoItems?.length || 0}, currentPlayIndex=${currentPlayIndex}`);
+
+        if (videoItems && videoItems.length > 0) {
+          Logger.info(`heanup 从全局上下文获取到WebDAV歌曲列表,长度:${videoItems.length}`)
+          // 验证歌曲数据是否完整
+          for (let i = 0; i < Math.min(3, videoItems.length); i++) {
+            Logger.info(`heanup 歌曲[${i}]: ${videoItems[i]?.name}, 路径: ${videoItems[i]?.filePath}`)
+          }
+          this.finishLoadingPlaylist(videoItems, startIndex, playlistName)
+          return
+        } else {
+          Logger.error(`heanup 无法从全局上下文获取WebDAV歌曲数据,尝试使用传入的文件路径创建备用数据`)
+
+          // 备用方案:直接使用传入的文件路径创建VideoItem
+          const fallbackVideoItems: VideoItem[] = [];
+          for (let i = 0; i < songFilePaths.length; i++) {
+            const fileName = songFilePaths[i].split('/').pop() || `WebDAV歌曲${i+1}`;
+            const videoItem = new VideoItem(
+              fileName.replace(/\.(flac|mp3|wav|m4a)$/i, ''), // name (去掉扩展名)
+              i.toString(), // id
+              songFilePaths[i], // filePath (URL)
+              CommonConstants.TYPE_INTERNET, // type
+              0, // fileSize
+              "0", // time
+              undefined, // pixelMap
+              undefined, // size
+              undefined, // pixelMapPath
+              "WebDAV艺术家", // artist
+              undefined, // album
+              fileName // fileName
+            );
+            fallbackVideoItems.push(videoItem);
+          }
+
+          Logger.info(`heanup 使用备用方案创建了${fallbackVideoItems.length}首WebDAV歌曲`)
+          this.finishLoadingPlaylist(fallbackVideoItems, startIndex, playlistName)
+          return
+        }
+      }
+
       // 从数据库重新加载这些歌曲,使用索引记录位置以保持顺序
       const songMap: Map<number, VideoItem> = new Map()
       let completedQueries = 0