Răsfoiți Sursa

发现页的随心所欲 播放列表也要分页加载 不然占用内存卡顿

onecold 4 luni în urmă
părinte
comite
36bfa464c1

+ 32 - 1
entry/src/main/ets/common/util/FindPlaylistStore.ets

@@ -1,15 +1,41 @@
 import { VideoItem } from '../../viewmodel/VideoItem';
 
+export interface FindPlaylistMeta {
+  // 发现页“随心所欲”本地随机分页队列的附加元数据。
+  isPagedLocalQueue?: boolean;
+  pageIndex?: number;
+  pageSize?: number;
+  totalCount?: number;
+  sortType?: number;
+}
+
 let findPlaylistId: string = '';
 let findPlaylistName: string = '';
 let findVideoItems: VideoItem[] = [];
 let findCurrentPlayIndex: number = 0;
+let findPlaylistMeta: FindPlaylistMeta = {};
+
+function cloneFindPlaylistMeta(meta?: FindPlaylistMeta): FindPlaylistMeta {
+  if (!meta) {
+    return {};
+  }
+  return {
+    isPagedLocalQueue: meta.isPagedLocalQueue,
+    pageIndex: meta.pageIndex,
+    pageSize: meta.pageSize,
+    totalCount: meta.totalCount,
+    sortType: meta.sortType
+  };
+}
 
-export function setFindPlaylist(playlistId: string, playlistName: string, items: VideoItem[], startIndex: number): void {
+export function setFindPlaylist(playlistId: string, playlistName: string, items: VideoItem[], startIndex: number,
+  meta?: FindPlaylistMeta): void {
   findPlaylistId = playlistId;
   findPlaylistName = playlistName;
   findVideoItems = items.slice();
   findCurrentPlayIndex = startIndex;
+  // 发现页播放请求会先落到这里,播放器再按 playlistId 读取这些内存数据恢复队列。
+  findPlaylistMeta = cloneFindPlaylistMeta(meta);
 }
 
 export function getFindPlaylistId(): string {
@@ -28,9 +54,14 @@ export function getFindCurrentPlayIndex(): number {
   return findCurrentPlayIndex;
 }
 
+export function getFindPlaylistMeta(): FindPlaylistMeta {
+  return cloneFindPlaylistMeta(findPlaylistMeta);
+}
+
 export function clearFindPlaylist(): void {
   findPlaylistId = '';
   findPlaylistName = '';
   findVideoItems = [];
   findCurrentPlayIndex = 0;
+  findPlaylistMeta = {};
 }

+ 54 - 0
entry/src/main/ets/common/util/LocalRandomPagedQueueHelper.ets

@@ -0,0 +1,54 @@
+import { VideoItem } from '../../viewmodel/VideoItem'
+
+export const FIND_LOCAL_RANDOM_PAGED_PLAYLIST_ID = 'find-local-random-paged'
+export const DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE = 53
+
+export interface LocalRandomPagedQueueSeed {
+  items: VideoItem[]
+  totalCount: number
+  pageIndex: number
+  pageSize: number
+  startIndex: number
+  globalIndex: number
+}
+
+function normalizePageSize(pageSize: number): number {
+  if (pageSize <= 0) {
+    return DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE
+  }
+  return Math.max(1, Math.floor(pageSize))
+}
+
+export function isFindLocalRandomPagedPlaylist(playlistId: string): boolean {
+  return playlistId === FIND_LOCAL_RANDOM_PAGED_PLAYLIST_ID
+}
+
+export function buildLocalRandomPagedQueueSeed(allSongs: VideoItem[], randomIndex: number,
+  pageSize: number = DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE): LocalRandomPagedQueueSeed {
+  const normalizedPageSize = normalizePageSize(pageSize)
+  if (!allSongs || allSongs.length === 0) {
+    return {
+      items: [],
+      totalCount: 0,
+      pageIndex: 0,
+      pageSize: normalizedPageSize,
+      startIndex: 0,
+      globalIndex: 0
+    }
+  }
+
+  const safeIndex = Math.min(Math.max(randomIndex, 0), allSongs.length - 1)
+  // 以“随机命中的那首歌所在页”作为初始队列,避免首次就把全量歌曲塞进播放列表。
+  const pageIndex = Math.floor(safeIndex / normalizedPageSize)
+  const pageStart = pageIndex * normalizedPageSize
+  const pageItems = allSongs.slice(pageStart, Math.min(pageStart + normalizedPageSize, allSongs.length))
+
+  return {
+    items: pageItems,
+    totalCount: allSongs.length,
+    pageIndex,
+    pageSize: normalizedPageSize,
+    startIndex: safeIndex - pageStart,
+    globalIndex: safeIndex
+  }
+}

+ 23 - 0
entry/src/main/ets/common/util/PlaylistPlayDispatchHelper.ets

@@ -0,0 +1,23 @@
+import { VideoItem } from '../../viewmodel/VideoItem';
+
+function isFindPlaylistRequest(playlistId: string): boolean {
+  return playlistId === 'find-album-playlist' || playlistId.indexOf('find-') === 0;
+}
+
+export function shouldUseInMemoryPlaylistPayload(playlistId: string): boolean {
+  return playlistId === 'webdav-playlist' ||
+    playlistId === 'navidrome-playlist' ||
+    isFindPlaylistRequest(playlistId);
+}
+
+export function buildPlaylistDispatchFilePaths(playlistId: string, songs: VideoItem[]): string[] {
+  // 这些播放请求会直接从内存仓库恢复完整队列,不需要额外复制全量路径数组。
+  if (shouldUseInMemoryPlaylistPayload(playlistId)) {
+    return [];
+  }
+  return songs.map((item: VideoItem): string => item.filePath);
+}
+
+export function resolvePlaylistDisplayCount(currentSongListLength: number, songListLength: number): number {
+  return currentSongListLength > 0 ? currentSongListLength : songListLength;
+}

+ 32 - 7
entry/src/main/ets/view/FindView.ets

@@ -23,6 +23,7 @@ import { SettingPage } from '../pages/SettingPage'
 import { FindAlbumDetail } from './FindAlbumDetail'
 import { PlayingIndicator } from './PlayingIndicator'
 import {
+  FindPlaylistMeta,
   getFindPlaylistId,
   getFindPlaylistName,
   getFindVideoItems,
@@ -41,8 +42,13 @@ import {
   resolveQueueStartIndex,
   shouldWaitForIndexedRemotePlayback
 } from '../common/util/FindDiscoveryHelper'
+import { buildPlaylistDispatchFilePaths } from '../common/util/PlaylistPlayDispatchHelper'
 import { insertSongToNextPlayQueue, QueueInsertStatus } from '../common/util/PlayQueueHelper'
 import { resolveRemoteCoverForSong } from '../common/util/RemoteCoverResolver'
+import {
+  buildLocalRandomPagedQueueSeed,
+  FIND_LOCAL_RANDOM_PAGED_PLAYLIST_ID
+} from '../common/util/LocalRandomPagedQueueHelper'
 
 @Builder
 export function FindViewBuilder() {
@@ -1145,27 +1151,30 @@ export struct FindView {
   }
 
   private emitPlaylistPlay(playlistId: string, playlistName: string, songs: VideoItem[], startIndex: number,
-    playType?: number): void {
+    playType?: number, meta?: FindPlaylistMeta): void {
     if (songs.length === 0) {
       ToastUtil.showToast('暂无可播放歌曲')
       return
     }
     const safeIndex = Math.min(Math.max(startIndex, 0), songs.length - 1)
+    const dispatchCount = meta?.totalCount && meta.totalCount > 0 ? meta.totalCount : songs.length
     Logger.info(
       TAG,
       `[remote-debug] emitPlaylistPlay id=${playlistId}, name=${playlistName}, count=${songs.length}, ` +
-      `startIndex=${safeIndex}, playType=${playType ?? -1}, sample=${this.buildSongDebugLog(songs)}`
+      `dispatchCount=${dispatchCount}, startIndex=${safeIndex}, playType=${playType ?? -1}, ` +
+      `sample=${this.buildSongDebugLog(songs)}`
     )
     if (playlistId === 'find-album-playlist' || playlistId.indexOf('find-') === 0) {
-      setFindPlaylist(playlistId, playlistName, songs, safeIndex)
+      // 发现页队列统一走内存仓库,避免事件负载里重复塞大数组。
+      setFindPlaylist(playlistId, playlistName, songs, safeIndex, meta)
     }
     const eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }
     const playlistData = new PlaylistPlayRequest(
       playlistId,
       playlistName,
-      songs.length,
+      dispatchCount,
       safeIndex,
-      songs.map((item: VideoItem): string => item.filePath),
+      buildPlaylistDispatchFilePaths(playlistId, songs),
       false,
       playType
     )
@@ -1401,8 +1410,24 @@ export struct FindView {
         ToastUtil.showToast('本地歌曲为空')
         return
       }
-      const startIndex = Math.floor(Math.random() * allLocalSongs.length)
-      this.emitPlaylistPlay('find-local-random-all', '随心所欲', allLocalSongs, startIndex, 3)
+      const randomIndex = Math.floor(Math.random() * allLocalSongs.length)
+      // “随心所欲”仍然按全库随机,但首次只下发命中歌曲所在页,后续由播放器继续分页补齐。
+      const queueSeed = buildLocalRandomPagedQueueSeed(allLocalSongs, randomIndex)
+      this.emitPlaylistPlay(
+        FIND_LOCAL_RANDOM_PAGED_PLAYLIST_ID,
+        '随心所欲',
+        queueSeed.items,
+        queueSeed.startIndex,
+        3,
+        {
+          // queryAllVideos 默认按名称升序,这里把分页查询所需的顺序信息一并带给播放器。
+          isPagedLocalQueue: true,
+          pageIndex: queueSeed.pageIndex,
+          pageSize: queueSeed.pageSize,
+          totalCount: queueSeed.totalCount,
+          sortType: 4
+        }
+      )
       return
     }
     const allRemoteSongs = await this.getFullRemoteSongsPool()

+ 183 - 26
entry/src/main/ets/view/LocalMusic.ets

@@ -14,8 +14,12 @@ import {
   getNavidromeTotalCount,
   setNavidromeTotalCount
 } from '../common/util/NavidromePlaylistStore';
-import { getFindVideoItems, getFindCurrentPlayIndex } from '../common/util/FindPlaylistStore';
+import { FindPlaylistMeta, getFindVideoItems, getFindCurrentPlayIndex, getFindPlaylistMeta } from '../common/util/FindPlaylistStore';
 import { triggerLoadMoreSongs, hasMoreData } from '../common/util/NavidromeRandomLoader';
+import {
+  DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE,
+  isFindLocalRandomPagedPlaylist
+} from '../common/util/LocalRandomPagedQueueHelper';
 import {
   ImplOnBufferingUpdateListener,
   ImplOnCompletionListener,
@@ -143,6 +147,7 @@ import {
   consumePendingPlaylistPlay,
   clearPendingPlaylistPlay
 } from '../common/util/PlaylistPlayRequestStore';
+import { resolvePlaylistDisplayCount } from '../common/util/PlaylistPlayDispatchHelper'
 
 import { HSBColorPicker, HSBColorPickerLayout } from '@keke/color-picker'
 import app from '@system.app';
@@ -471,6 +476,7 @@ export struct LocalMusic {
   @State maxRefreshingHeight: number = 100.0;
   @State isPlayerLoading: boolean = false; // 播放器加载状态
   private contentNode?: ComponentContent<Object> = undefined;
+  private skipNextPlaylistPersist: boolean = false
 
   // WebDAV相关实例变量(现在改为从内存读取,不再需要存储)
   @State ratioL: number = 1;
@@ -617,8 +623,16 @@ export struct LocalMusic {
   private coverThumbCache: CoverThumbCache = new CoverThumbCache(1, 960)
   private coverThumbCacheReady: boolean = false
   private pendingCoverThumbRefresh: boolean = false
-  private playQueueScope: 'view' | 'history' | 'fav' | 'playlist' | 'single' = 'view'
+  private playQueueScope: 'view' | 'history' | 'fav' | 'playlist' | 'single' | 'find_paged_local' = 'view'
   private isHydratingPlayList: boolean = false
+  private preserveExistingQueueOnNextPlay: boolean = false
+  private nextPlayQueueScope: 'view' | 'history' | 'fav' | 'playlist' | 'single' | 'find_paged_local' = 'view'
+  private findPagedLocalQueuePageIndex: number = 0
+  private findPagedLocalQueuePageSize: number = DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE
+  private findPagedLocalQueueTotalCount: number = 0
+  private findPagedLocalQueueHasMore: boolean = false
+  private findPagedLocalQueueSortType: number = 4
+  private isLoadingFindPagedLocalQueue: boolean = false
   @StorageProp('isLandscape') @Watch('onIsLandscapeChange')  isLandscape: boolean = false;
   @StorageProp('topSafeHeight') topSafeHeight: number = 0;
   @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
@@ -2751,6 +2765,12 @@ export struct LocalMusic {
     if (this.playQueueScope === 'fav') {
       return this.favList.length;
     }
+    if (this.playQueueScope === 'find_paged_local') {
+      const pagedCount = this.findPagedLocalQueueTotalCount > 0 ? this.findPagedLocalQueueTotalCount : this.totalCount;
+      if (pagedCount > 0) {
+        return pagedCount;
+      }
+    }
     if (this.playQueueScope === 'playlist') {
       // 检测是否是流媒体播放(Navidrome/Jellyfin/Emby/AudioStation/Plex/道理鱼等)
       // 如果是,优先返回服务端总数
@@ -2767,8 +2787,10 @@ export struct LocalMusic {
         Logger.info('heanup getPlayListDisplayCount', `返回服务端总数: ${navidromeTotalCount}`);
         return navidromeTotalCount;
       }
-      Logger.info('heanup getPlayListDisplayCount', `返回 currentSongList.length: ${this.currentSongList.length}`);
-      return this.currentSongList.length;
+      const displayCount = resolvePlaylistDisplayCount(this.currentSongList.length, this.songList.length);
+      Logger.info('heanup getPlayListDisplayCount',
+        `返回歌单数量: currentSongList=${this.currentSongList.length}, songList=${this.songList.length}, display=${displayCount}`);
+      return displayCount;
     }
     if (this.playQueueScope === 'view' && this.modeType === 1 && this.totalCount > 0) {
       return this.totalCount;
@@ -2779,7 +2801,8 @@ export struct LocalMusic {
 
   private restoreLastPlayContext(): void {
     const scope = PreferencesUtil.getStringSync('LastPlayQueueScope', 'view') as string;
-    if (scope === 'history' || scope === 'fav' || scope === 'playlist' || scope === 'single') {
+    if (scope === 'history' || scope === 'fav' || scope === 'playlist' || scope === 'single' ||
+      scope === 'find_paged_local') {
       this.playQueueScope = scope;
     } else {
       this.playQueueScope = 'view';
@@ -2789,6 +2812,49 @@ export struct LocalMusic {
     if (lastModeType === 1 && this.totalCount === 0 && lastTotalCount > 0) {
       this.totalCount = lastTotalCount;
     }
+    if (this.playQueueScope === 'find_paged_local' && lastTotalCount > 0) {
+      this.findPagedLocalQueueTotalCount = lastTotalCount;
+      this.findPagedLocalQueueHasMore = this.songList.length < lastTotalCount;
+    }
+  }
+
+  private resetFindPagedLocalQueueState(): void {
+    // 退出“随心所欲”分页队列时,避免旧分页状态污染后续普通播放队列。
+    this.findPagedLocalQueuePageIndex = 0;
+    this.findPagedLocalQueuePageSize = DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE;
+    this.findPagedLocalQueueTotalCount = 0;
+    this.findPagedLocalQueueHasMore = false;
+    this.findPagedLocalQueueSortType = 4;
+    this.isLoadingFindPagedLocalQueue = false;
+  }
+
+  private applyFindPagedLocalQueueState(meta?: FindPlaylistMeta, totalCount?: number): void {
+    // 播放器只持有当前页数据,但总数、页码和排序要单独记住,后面才能继续按页补队列。
+    this.findPagedLocalQueuePageIndex = meta?.pageIndex ?? 0;
+    this.findPagedLocalQueuePageSize = meta?.pageSize && meta.pageSize > 0
+      ? Math.max(1, Math.floor(meta.pageSize))
+      : DEFAULT_LOCAL_RANDOM_QUEUE_PAGE_SIZE;
+    this.findPagedLocalQueueSortType = meta?.sortType !== undefined ? meta.sortType : 4;
+    const actualTotalCount = meta?.totalCount && meta.totalCount > 0
+      ? meta.totalCount
+      : (totalCount ?? 0);
+    this.findPagedLocalQueueTotalCount = actualTotalCount;
+    this.findPagedLocalQueueHasMore =
+      (this.findPagedLocalQueuePageIndex + 1) * this.findPagedLocalQueuePageSize < actualTotalCount;
+    if (actualTotalCount > 0) {
+      this.totalCount = actualTotalCount;
+    }
+  }
+
+  private isFindPagedLocalQueueActive(): boolean {
+    return this.playQueueScope === 'find_paged_local';
+  }
+
+  private shouldHydrateFullMediaPlayListOnOpen(): boolean {
+    // 特殊分页队列禁止打开播放列表时自动补全全量歌曲,否则会回到 3000+ 首卡顿问题。
+    return !!this.currentSong &&
+      this.currentSong.type === CommonConstants.TYPE_LOCAL &&
+      !this.isFindPagedLocalQueueActive();
   }
 
   private async hydrateFullMediaPlayListIfNeeded(isSonList:boolean = true): Promise<void> {
@@ -8110,8 +8176,13 @@ export struct LocalMusic {
           console.info('kkMusic doPlay stop =' )
           this.stop();
         }
+        const useExistingQueueForPlayback = this.preserveExistingQueueOnNextPlay;
+        const nextQueueScope = this.nextPlayQueueScope;
+        this.preserveExistingQueueOnNextPlay = false;
+        this.nextPlayQueueScope = 'view';
 
         if (isOpen) {
+          this.resetFindPagedLocalQueueState();
           this.currentSong = item
           if (index !== undefined) {
             this.curIndex = index
@@ -8125,15 +8196,30 @@ export struct LocalMusic {
           if (index !== undefined) {
             this.curIndex = index
           }
-          // 确保播放列表是完整的歌单歌曲列表(已在finishLoadingPlaylist中设置)
           Logger.info(`heanup isFromSonPlayList分支 - 当前播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`)
-          this.playQueueScope = 'playlist'
-          // 确保歌单路径列表已设置(从songList重新构建,以防被清空)
-          if (this.currentPlaylistSongFilePaths.length === 0 && this.songList.length > 0) {
-            this.currentPlaylistSongFilePaths = this.songList.map(song => song.filePath);
-            Logger.info(`heanup isFromSonPlayList - 重新构建歌单路径列表,数量=${this.currentPlaylistSongFilePaths.length}`);
+          if (this.isFindPagedLocalQueueActive()) {
+            // 这类队列来自发现页分页随机,点击播放列表里的歌也要继续保持“分页队列”身份。
+            this.playQueueScope = 'find_paged_local'
+            this.currentPlaylistSongFilePaths = [];
+          } else {
+            this.resetFindPagedLocalQueueState();
+            this.playQueueScope = 'playlist'
+            if (this.currentPlaylistSongFilePaths.length === 0 && this.songList.length > 0) {
+              this.currentPlaylistSongFilePaths = this.songList.map(song => song.filePath);
+              Logger.info(`heanup isFromSonPlayList - 重新构建歌单路径列表,数量=${this.currentPlaylistSongFilePaths.length}`);
+            }
+          }
+        }else if (useExistingQueueForPlayback) {
+          // 特殊分页队列初始化时,直接复用 finishLoadingPlaylist 准备好的当前页,不再按视图重建全量队列。
+          this.currentSong = item
+          if (index !== undefined) {
+            this.curIndex = index
           }
+          this.playQueueScope = nextQueueScope
+          this.currentPlaylistSongFilePaths = [];
+          this.sonDataSource.pushArrayData(this.songList)
         }else {
+          this.resetFindPagedLocalQueueState();
           const globalVideoList = this.buildPlayQueueFromView();
           this.curIndex = findSongIndexByFilePath(globalVideoList, item.filePath)
           if (this.curIndex < 0) {
@@ -8191,6 +8277,9 @@ export struct LocalMusic {
         if (this.CONTROL_PlayStatus !== PlayStatus.INIT) {
           this.stop();
         }
+        this.resetFindPagedLocalQueueState();
+        this.preserveExistingQueueOnNextPlay = false;
+        this.nextPlayQueueScope = 'view';
 
         if (isOpen) {
           this.currentSong = item
@@ -9162,8 +9251,7 @@ export struct LocalMusic {
       LogUtil.info('onecold scrollToIndex = ' + this.curIndex)
 
       this.scrollIndex()
-      // 只有在播放本地音乐时才自动加载完整列表
-      if (this.currentSong && this.currentSong.type === CommonConstants.TYPE_LOCAL) {
+      if (this.shouldHydrateFullMediaPlayListOnOpen()) {
         void this.hydrateFullMediaPlayListIfNeeded();
       }
 
@@ -9754,6 +9842,11 @@ export struct LocalMusic {
   }
 
   private async tryLoadNextPageForPlayback(): Promise<void> {
+    if (this.playQueueScope === 'find_paged_local') {
+      // 发现页的本地随机分页队列单独补页,不去动主媒体库列表状态。
+      await this.loadMoreFindPagedLocalSongs();
+      return;
+    }
     if (this.playQueueScope !== 'view') {
       return;
     }
@@ -9771,6 +9864,49 @@ export struct LocalMusic {
     }
   }
 
+  private async loadMoreFindPagedLocalSongs(): Promise<void> {
+    if (!this.findPagedLocalQueueHasMore || this.isLoadingFindPagedLocalQueue) {
+      return;
+    }
+    this.isLoadingFindPagedLocalQueue = true;
+    const nextPage = this.findPagedLocalQueuePageIndex + 1;
+    Logger.info(TAG,
+      `loadMoreFindPagedLocalSongs: page=${nextPage}, size=${this.findPagedLocalQueuePageSize}, ` +
+      `loaded=${this.songList.length}, total=${this.findPagedLocalQueueTotalCount}`);
+    try {
+      // 继续按发现页建立队列时的排序规则查询下一页,保证后续页和初始页顺序一致。
+      const result = await this.table.queryMediaLibraryPaged({
+        pageIndex: nextPage,
+        pageSize: this.findPagedLocalQueuePageSize,
+        sortType: this.findPagedLocalQueueSortType,
+        isShowFileName: this.isShowFileName
+      });
+      this.findPagedLocalQueuePageIndex = nextPage;
+      this.findPagedLocalQueueHasMore = result.hasMore;
+      if (result.totalCount > 0) {
+        this.findPagedLocalQueueTotalCount = result.totalCount;
+        this.totalCount = result.totalCount;
+      }
+      if (!result.items || result.items.length === 0) {
+        return;
+      }
+      const existingPaths = new Set(this.songList.map((item: VideoItem) => item.filePath));
+      // 随机播放过程中可能已经把“未加载页里的歌曲”临时插进队列,这里要去重后再追加。
+      const newItems = result.items.filter((item: VideoItem) => !existingPaths.has(item.filePath));
+      if (newItems.length === 0) {
+        return;
+      }
+      this.songList.push(...newItems);
+      this.currentSongList = this.songList;
+      this.sonDataSource.appendArrayData(newItems);
+      PreferencesUtil.putSync('LastMusicList', this.songList);
+    } catch (error) {
+      Logger.error(TAG, `loadMoreFindPagedLocalSongs failed: ${(error as Error).message}`);
+    } finally {
+      this.isLoadingFindPagedLocalQueue = false;
+    }
+  }
+
   /**
    * 加载更多流媒体歌曲(Navidrome/Jellyfin/Emby/AudioStation/Plex)
    * 当播放列表弹窗滚动到底部时调用
@@ -12139,7 +12275,7 @@ export struct LocalMusic {
     .width('100%')
     .height('100%')
     .onReachEnd(() => {
-      if(this.modeType === 4 )
+      if(this.modeType === 4 && !this.isFindPagedLocalQueueActive())
         return;
       // 检测是否是流媒体类型(Navidrome/Jellyfin/Emby/AudioStation/Plex),如果是则调用网盘加载更多逻辑
       const isStreamingSong = this.currentSong && (
@@ -14115,8 +14251,7 @@ export struct LocalMusic {
           this.isFrontWhite = false
         }
         this.scrollIndex()
-        // 只有在播放本地音乐时才自动加载完整列表
-        if (this.currentSong && this.currentSong.type === CommonConstants.TYPE_LOCAL) {
+        if (this.shouldHydrateFullMediaPlayListOnOpen()) {
           void this.hydrateFullMediaPlayListIfNeeded();
         }
       })
@@ -18386,7 +18521,12 @@ export struct LocalMusic {
   //保存最后播放的那首歌和已经对应的播放列表
   saveLastPlayList() {
     PreferencesUtil.putSync('LastMusicInfo', this.currentSong)
-    PreferencesUtil.putSync('LastMusicList', this.songList)
+    // 新队列刚刚完成落盘时,首帧 prepared 会立刻再进一次这里,跳过这次重复写入即可。
+    if (this.skipNextPlaylistPersist) {
+      this.skipNextPlaylistPersist = false
+    } else {
+      PreferencesUtil.putSync('LastMusicList', this.songList)
+    }
     PreferencesUtil.putSync('LastPlayQueueScope', this.playQueueScope)
     PreferencesUtil.putSync('LastPlayModeType', this.modeType)
     PreferencesUtil.putSync('LastPlayTotalCount', this.totalCount)
@@ -20701,6 +20841,7 @@ export struct LocalMusic {
       this.currentPlaylistSongFilePaths = this.songList.map(song => song.filePath);
       this.curIndex = targetIndex;
       PreferencesUtil.putSync('LastMusicList', this.songList);
+      this.skipNextPlaylistPersist = true
       Logger.info(TAG, `WebDAV播放队列已刷新,当前索引=${this.curIndex}, 队列长度=${this.songList.length}`);
     } catch (error) {
       Logger.error(TAG, `刷新WebDAV播放队列失败: ${(error as Error).message}`);
@@ -20758,6 +20899,7 @@ export struct LocalMusic {
         Logger.info(`heanup 检测到发现页播放请求,从内存读取videoItems`)
         const videoItems = getFindVideoItems();
         const currentPlayIndex = getFindCurrentPlayIndex();
+        const findPlaylistMeta = getFindPlaylistMeta();
         Logger.info(`heanup 发现页歌曲列表长度: ${videoItems.length}, 索引: ${currentPlayIndex}`)
         Logger.info(
           TAG,
@@ -20768,7 +20910,10 @@ export struct LocalMusic {
           if (isJump) {
             this.setShowPlayTrue()
           }
-          this.finishLoadingPlaylist(videoItems, currentPlayIndex, playlistName, totalCount)
+          const findMeta = isFindLocalRandomPagedPlaylist(playlistId) && findPlaylistMeta.isPagedLocalQueue
+            ? findPlaylistMeta
+            : undefined;
+          this.finishLoadingPlaylist(videoItems, currentPlayIndex, playlistName, totalCount, findMeta)
           return
         } else {
           Logger.warn('heanup 发现页播放请求缺少歌曲数据')
@@ -20850,27 +20995,32 @@ export struct LocalMusic {
   /**
    * 完成歌单加载并开始播放
    */
-  private finishLoadingPlaylist(songs: VideoItem[], startIndex: number, playlistName: string, totalCount?: number) {
+  private finishLoadingPlaylist(songs: VideoItem[], startIndex: number, playlistName: string, totalCount?: number,
+    findMeta?: FindPlaylistMeta) {
     if (songs.length === 0) {
       Logger.error('heanup 没有找到任何可播放的歌曲')
       ToastUtil.showToast('没有找到可播放的歌曲')
       return
     }
+    const isFindPagedLocalQueue = !!findMeta?.isPagedLocalQueue;
     this.songList = songs
     this.currentSongList = songs  // 同步更新 currentSongList
 
     this.sonDataSource.pushArrayData(songs)
 
-    this.sonDataSource.notifyDataReload()
-
     this.curIndex = startIndex
 
-    // 保存歌单的歌曲路径列表,用于随机播放
-    this.currentPlaylistSongFilePaths = songs.map(song => song.filePath);
     const actualTotal = totalCount ?? songs.length;
+    if (isFindPagedLocalQueue) {
+      this.applyFindPagedLocalQueueState(findMeta, actualTotal);
+      this.currentPlaylistSongFilePaths = [];
+    } else {
+      this.resetFindPagedLocalQueueState();
+      this.currentPlaylistSongFilePaths = songs.map(song => song.filePath);
+    }
 
     // 如果是网盘播放,保存服务端总数到全局存储
-    if (totalCount && totalCount > 0 && songs.length > 0) {
+    if (!isFindPagedLocalQueue && totalCount && totalCount > 0 && songs.length > 0) {
       const firstSong = songs[0];
       if (firstSong && (isNavidromeType(firstSong.type) ||
       isJellyfinType(firstSong.type) ||
@@ -20893,11 +21043,18 @@ export struct LocalMusic {
     if (songs[startIndex]) {
       // 确保当前播放的歌曲也更新到存储
       AppStorage.setOrCreate('currentSong', songs[startIndex])
-      // 设置isFromSonPlayList=true,避免doPlay方法重新从全局列表构建播放列表
-      this.doPlay(songs[startIndex], startIndex, true)
+      if (isFindPagedLocalQueue) {
+        this.preserveExistingQueueOnNextPlay = true
+        this.nextPlayQueueScope = 'find_paged_local'
+        this.doPlay(songs[startIndex], startIndex, false)
+      } else {
+        // 设置isFromSonPlayList=true,避免doPlay方法重新从全局列表构建播放列表
+        this.doPlay(songs[startIndex], startIndex, true)
+      }
     }
 
     PreferencesUtil.putSync('LastMusicList', this.songList)
+    this.skipNextPlaylistPersist = true
 
     // ToastUtil.showToast(`开始播放歌单: ${playlistName}`)
   }