|
|
@@ -46,7 +46,7 @@ import { PointLightButton } from './PointLight/PointLightButton';
|
|
|
import { PlayConstants } from '../common/constants/PlayConstants';
|
|
|
import { effectKit } from '@kit.ArkGraphics2D';
|
|
|
import { ColorConversion } from '../common/util/ColorConversion';
|
|
|
-import { LyricController, LyricParser, LyricView2 } from '@seagazer/cclyric';
|
|
|
+import { Lyric, LyricController, LyricParser, LyricView2 } from '@seagazer/cclyric';
|
|
|
import { AvSessionController } from '../controller/AvSessionController';
|
|
|
import { repairAudioMetadata, convertDsfToWav, getApiLyric,changeMusicCover} from '../common/util/MusicTagUtils';
|
|
|
import { extractHwMediaMetadata, FFMpegTags,Utility } from '../common/util/Utility';
|
|
|
@@ -76,7 +76,7 @@ import { secondToTime,getTransverterText } from '../common/util/CommUtils';
|
|
|
import { CommonConstants2 } from '../common/util/CommonConstants2';
|
|
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
|
import { LazyDataSource } from '../common/util/LazyDataSource';
|
|
|
-import MediaTable from '../common/util/MediaTable';
|
|
|
+import MediaTable, { PageQueryOptions, PageQueryResult } from '../common/util/MediaTable';
|
|
|
import FileDeletionWatcher from '../common/util/FileDeletionWatcher';
|
|
|
import LyricUtil from '../common/util/LyricUtil';
|
|
|
import NetAxiosUtil from '../common/util/NetAxiosUtil';
|
|
|
@@ -397,6 +397,16 @@ export struct LocalMusic {
|
|
|
private playListScroller: Scroller = new Scroller()
|
|
|
@State isOneKeyTags:boolean = false
|
|
|
@State isFixMessy:boolean = false
|
|
|
+
|
|
|
+ // 分页相关状态
|
|
|
+ @State private currentPage: number = 0 // 当前页码
|
|
|
+ @State private pageSize: number = 50 // 每页数量
|
|
|
+ @State private totalCount: number = 0 // 总记录数
|
|
|
+ @State private hasMoreData: boolean = true // 是否还有更多数据
|
|
|
+ @State private isLoadingPage: boolean = false // 是否正在加载分页数据
|
|
|
+ private pageCache: Map<number, VideoItem[]> = new Map() // 页面缓存(最多保留3页)
|
|
|
+ private fullSongList: VideoItem[] = [] // 完整的播放列表(延迟加载,只在播放时加载)
|
|
|
+ private isFullSongListLoaded: boolean = false // 标记完整列表是否已加载
|
|
|
@StorageProp('isLandscape') @Watch('onIsLandscapeChange') isLandscape: boolean = false;
|
|
|
@StorageProp('topSafeHeight') topSafeHeight: number = 0;
|
|
|
@StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
|
|
|
@@ -476,21 +486,25 @@ export struct LocalMusic {
|
|
|
this.isRefreshing = false
|
|
|
}
|
|
|
|
|
|
- onModeChange() {
|
|
|
+ async onModeChange(): Promise<void> {
|
|
|
this.isFavMusic = false
|
|
|
this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
|
|
|
+
|
|
|
+ Logger.info('heanup LocalMusic', `onModeChange: 切换模式 modeType=${this.modeType}`);
|
|
|
+
|
|
|
+ // 模式切换时重置分页状态并加载第一页
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
+
|
|
|
switch (this.modeType) {
|
|
|
case 0:
|
|
|
- this.getSortedFiles(this.currentPath)
|
|
|
+ // 首页模式已在 resetAndLoadFirstPage 中处理
|
|
|
break
|
|
|
case 1:
|
|
|
this.rightTopImage = $r('sys.symbol.sort')
|
|
|
- this.updateListData(this.mediaKuList)
|
|
|
this.titleName = Utility.resourceToString(this.context,$r('app.string.media_ku'))
|
|
|
break
|
|
|
case 2:
|
|
|
this.rightTopImage = $r('sys.symbol.sort')
|
|
|
- this.updateListData(this.artistList)
|
|
|
this.titleName = Utility.resourceToString(this.context,$r('app.string.artist'))
|
|
|
break
|
|
|
|
|
|
@@ -871,7 +885,7 @@ export struct LocalMusic {
|
|
|
AppStorage.setOrCreate('themeColor', themeColor);
|
|
|
this.themeColor = themeColor;
|
|
|
this.doChangeSetting()
|
|
|
- this.windowClass.on('windowSizeChange', (size) => {
|
|
|
+ this.windowClass.on('windowSizeChange', (size: window.Size) => {
|
|
|
LogUtil.info('onecold windowSizeChange')
|
|
|
this.doChangeBarHeight()
|
|
|
let viewWidth = px2vp(size.width);
|
|
|
@@ -1138,8 +1152,9 @@ export struct LocalMusic {
|
|
|
Logger.info('onecold 查询媒体库列表 this.mediaKuList length= ' + this.mediaKuList.length)
|
|
|
Utility.doSortListAscending(this.mediaKuList)
|
|
|
AppStorage.setOrCreate('mediaKuList', this.mediaKuList);
|
|
|
+ // 媒体库模式下重新加载第一页(分页查询)
|
|
|
if (this.modeType === 1) {
|
|
|
- this.updateListData(this.mediaKuList)
|
|
|
+ this.resetAndLoadFirstPage();
|
|
|
}
|
|
|
PreferencesUtil.putSync('mediaKuCount', this.mediaKuList.length)
|
|
|
// 处理媒体库前100条记录,列表长度并存储到PreferencesUtil
|
|
|
@@ -1157,6 +1172,11 @@ export struct LocalMusic {
|
|
|
this.artistList = e.data.data2;
|
|
|
PreferencesUtil.putSync('artistCount', this.artistList.length)
|
|
|
|
|
|
+ // 艺术家模式下重新加载第一页(分页查询)
|
|
|
+ if (this.modeType === 2) {
|
|
|
+ this.resetAndLoadFirstPage();
|
|
|
+ }
|
|
|
+
|
|
|
// 处理艺术家前100条记录,列表长度并存储到PreferencesUtil
|
|
|
if (ArrayUtil.isNotEmpty(this.artistList)&&this.artistList.length > 100) {
|
|
|
// 只保存前100条记录到缓存中
|
|
|
@@ -1178,6 +1198,12 @@ export struct LocalMusic {
|
|
|
this.albumMap = e.data.data1;
|
|
|
|
|
|
this.albumList = e.data.data2
|
|
|
+
|
|
|
+ // 专辑模式下重新加载第一页(分页查询)
|
|
|
+ if (this.modeType === 3) {
|
|
|
+ this.resetAndLoadFirstPage();
|
|
|
+ }
|
|
|
+
|
|
|
//查询完毕在启动是否自动播放
|
|
|
if (this.isStartAutoPlay&&this.isCanAuto) {
|
|
|
this.isCanAuto = false
|
|
|
@@ -1292,7 +1318,7 @@ export struct LocalMusic {
|
|
|
if (payload.lyricContent) {
|
|
|
this.applyInlineLyricContent(payload.lyricContent);
|
|
|
}
|
|
|
- let avSessionDurationMs = this.duration;
|
|
|
+ let avSessionDurationMs: number = this.duration;
|
|
|
if (payload.duration) {
|
|
|
const durationSeconds = Number(payload.duration);
|
|
|
if (!Number.isNaN(durationSeconds) && durationSeconds > 0) {
|
|
|
@@ -1342,7 +1368,7 @@ export struct LocalMusic {
|
|
|
this.name = this.titleStr;
|
|
|
|
|
|
// 同步当前播放的歌曲信息
|
|
|
- if (this.editingItem && this.editingItem.filePath == this.currentSong?.filePath) {
|
|
|
+ if (this.editingItem && this.currentSong && this.editingItem.filePath == this.currentSong.filePath) {
|
|
|
this.currentSong.name = this.titleStr;
|
|
|
this.artist = this.artistStr;
|
|
|
this.currentSong.artist = this.artistStr;
|
|
|
@@ -1514,8 +1540,8 @@ export struct LocalMusic {
|
|
|
return;
|
|
|
}
|
|
|
this.lyricContent = content;
|
|
|
- const lines = content.split('\n').map(line => line.trim());
|
|
|
- const lyric = this.parser.parse(lines);
|
|
|
+ const lines: string[] = content.split('\n').map((line: string) => line.trim());
|
|
|
+ const lyric: Lyric = this.parser.parse(lines);
|
|
|
this.lyricController.setLyric(lyric);
|
|
|
this.lyricControllerXF.setLyric(lyric);
|
|
|
this.lyricControllerSingle.setLyric(lyric);
|
|
|
@@ -2091,137 +2117,14 @@ export struct LocalMusic {
|
|
|
}
|
|
|
|
|
|
|
|
|
- doSortType(index: number) {
|
|
|
- switch (index) {
|
|
|
- case 0:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem) => {
|
|
|
- // 类型排序优先级
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // 处理艺术家可能为undefined的字符串比较
|
|
|
- const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
- const artistB = b.artist?.trim() || '';
|
|
|
- return artistA.localeCompare(artistB);
|
|
|
- });
|
|
|
-
|
|
|
- break;
|
|
|
- case 1:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem) => {
|
|
|
- // 类型排序优先级
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // 处理艺术家可能为undefined的字符串比较
|
|
|
- const artistA = a.artist?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
- const artistB = b.artist?.trim() || '';
|
|
|
- return artistB.localeCompare(artistA);
|
|
|
- });
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem) => {
|
|
|
- // 类型排序优先级
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // 处理专辑可能为undefined的字符串比较
|
|
|
- const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
- const albumB = b.album?.trim() || '';
|
|
|
- return albumA.localeCompare(albumB);
|
|
|
- });
|
|
|
-
|
|
|
- break;
|
|
|
- case 3:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem) => {
|
|
|
- // 类型排序优先级
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // 处理专辑可能为undefined的字符串比较
|
|
|
- const albumA = a.album?.trim() || ''; // 可选添加 trim() 处理空格
|
|
|
- const albumB = b.album?.trim() || '';
|
|
|
- return albumB.localeCompare(albumA);
|
|
|
- });
|
|
|
-
|
|
|
- break;
|
|
|
- case 4:
|
|
|
- Utility.doSortListAscending(this.videoLocalList,this.isShowFileName)
|
|
|
- break;
|
|
|
- case 5:
|
|
|
- Utility.doSortListDescending(this.videoLocalList,this.isShowFileName)
|
|
|
- break;
|
|
|
- case 6:
|
|
|
- this.videoLocalList.sort((a, b) => {
|
|
|
- // First, sort by type
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // If types are the same, sort by cTime in ascending order
|
|
|
- return a.cTime.localeCompare(b.cTime);
|
|
|
- });
|
|
|
- break;
|
|
|
- case 7:
|
|
|
- this.videoLocalList.sort((a, b) => {
|
|
|
- // First, sort by type
|
|
|
- const typeOrder = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // If types are the same, sort by cTime in descending order
|
|
|
- return b.cTime.localeCompare(a.cTime);
|
|
|
- });
|
|
|
- break;
|
|
|
- case 8:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem): number => {
|
|
|
- // 1. Sort by type first
|
|
|
- const typeOrder: number = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- // 2. Then sort by artist count (numeric comparison)
|
|
|
- let sortMap:Map<string, VideoItem[]>= new Map<string, VideoItem[]>();
|
|
|
- if(this.modeType === 2){
|
|
|
- sortMap = this.artistMap
|
|
|
- }else{
|
|
|
- sortMap = this.albumMap
|
|
|
- }
|
|
|
- const aArtistCount: number = sortMap.get(a.name)?.length || 0;
|
|
|
- const bArtistCount: number = sortMap.get(b.name)?.length || 0;
|
|
|
- return aArtistCount - bArtistCount;
|
|
|
- });
|
|
|
- break;
|
|
|
- case 9:
|
|
|
- this.videoLocalList.sort((a: VideoItem, b: VideoItem): number => {
|
|
|
- // 1. Sort by type first
|
|
|
- const typeOrder: number = getTypeOrder(a.type) - getTypeOrder(b.type);
|
|
|
- if (typeOrder !== 0) {
|
|
|
- return typeOrder;
|
|
|
- }
|
|
|
-
|
|
|
- let sortMap:Map<string, VideoItem[]>= new Map<string, VideoItem[]>();
|
|
|
- if(this.modeType === 2){
|
|
|
- sortMap = this.artistMap
|
|
|
- }else{
|
|
|
- sortMap = this.albumMap
|
|
|
- }
|
|
|
- const aArtistCount: number = sortMap.get(a.name)?.length || 0;
|
|
|
- const bArtistCount: number = sortMap.get(b.name)?.length || 0;
|
|
|
- return bArtistCount - aArtistCount;
|
|
|
- });
|
|
|
- break;
|
|
|
- }
|
|
|
+ async doSortType(index: number): Promise<void> {
|
|
|
+ Logger.info('heanup LocalMusic', `doSortType: 切换排序方式 sortType=${index}, mode=${this.modeType}`);
|
|
|
+
|
|
|
+ this.sortType = index;
|
|
|
+ PreferencesUtil.putSync(SettingPage.SORT_TYPE, index);
|
|
|
+
|
|
|
+ // 分页模式下,排序需要重新从数据库查询
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
}
|
|
|
|
|
|
showSheelDialog() {
|
|
|
@@ -2970,7 +2873,7 @@ export struct LocalMusic {
|
|
|
if(this.isGridMusic){
|
|
|
this.getGridView()
|
|
|
}else {
|
|
|
- this.getList()
|
|
|
+ this.getListView()
|
|
|
}
|
|
|
}
|
|
|
if (this.modeType ==0||this.modeType==4) {
|
|
|
@@ -4888,41 +4791,26 @@ export struct LocalMusic {
|
|
|
@State isSearchMode: boolean = false
|
|
|
|
|
|
// 实时搜索逻辑(带防抖)
|
|
|
- private onSearchInput(value: string) {
|
|
|
+ private async onSearchInput(value: string): Promise<void> {
|
|
|
this.searchText = value.trim();
|
|
|
- let mSearchList: Array<VideoItem> = []
|
|
|
- switch (this.modeType) {
|
|
|
- case 0:
|
|
|
- case 1:
|
|
|
- mSearchList = this.mediaKuList
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- mSearchList = this.artistList
|
|
|
- break;
|
|
|
- case 3:
|
|
|
- mSearchList = this.albumList
|
|
|
- break;
|
|
|
- default :
|
|
|
- mSearchList = this.videoLocalList
|
|
|
- break;
|
|
|
-
|
|
|
- }
|
|
|
- // 新增条件判断:空输入时显示所有数据
|
|
|
+
|
|
|
+ Logger.info('heanup LocalMusic', `onSearchInput: keyword="${this.searchText}", mode=${this.modeType}`);
|
|
|
+
|
|
|
+ // 新增条件判断:空输入时退出搜索模式
|
|
|
if (this.searchText === '') {
|
|
|
- this.filteredList = [...mSearchList]; // 创建新数组以触发状态更新
|
|
|
- } else {
|
|
|
- this.filteredList = mSearchList.filter((item: VideoItem) => {
|
|
|
- //支持模糊匹配和艺术家 专辑匹配
|
|
|
- const regex = new RegExp(this.searchText.toLowerCase().replace(/\s+/g, '.*'), 'i');
|
|
|
- return regex.test(item.name.toLowerCase())||
|
|
|
- regex.test(item.fileName?.toLowerCase() ?? "") ||
|
|
|
- regex.test(item.artist?.toLowerCase() ?? "") ||
|
|
|
- regex.test(item.album?.toLowerCase() ?? "")
|
|
|
- });
|
|
|
+ this.isSearchMode = false;
|
|
|
+ // 重新加载第一页(不带搜索条件)
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
+ return;
|
|
|
}
|
|
|
-
|
|
|
- this.updateListData(this.filteredList);
|
|
|
- // this.isSearchMode = false; // 根据业务需求决定是否启用
|
|
|
+
|
|
|
+ // 进入搜索模式
|
|
|
+ if (!this.isSearchMode) {
|
|
|
+ this.isSearchMode = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用数据库分页查询(带搜索条件)
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
}
|
|
|
|
|
|
@State dragItem: number = -1
|
|
|
@@ -4966,7 +4854,7 @@ export struct LocalMusic {
|
|
|
private oldColumn: number = this.columns;
|
|
|
private pinchTime: number = 0;
|
|
|
// 根据缩放阈值改变列数,触发WaterFlow重新布局
|
|
|
- changeColumns(scale: number) {
|
|
|
+ changeColumns(scale: number): void {
|
|
|
if (scale > (this.columns / (this.columns - 0.5)) && this.columns > 1) {
|
|
|
this.columns--;
|
|
|
this.columnChanged = true;
|
|
|
@@ -6202,6 +6090,25 @@ export struct LocalMusic {
|
|
|
|
|
|
}, (item: VideoItem) => item.filePath + '_' + this.listRefreshKey)
|
|
|
|
|
|
+ // 加载中指示器
|
|
|
+ if (this.isLoadingPage) {
|
|
|
+ ListItem() {
|
|
|
+ Row() {
|
|
|
+ LoadingProgress()
|
|
|
+ .width(30)
|
|
|
+ .height(30)
|
|
|
+ .color(this.themeColor)
|
|
|
+ Text('加载中...')
|
|
|
+ .margin({ left: 10 })
|
|
|
+ .fontSize(14)
|
|
|
+ .fontColor($r('app.color.text_color'))
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .padding(20)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
.onScrollStart(() => {
|
|
|
this.isScrolling = true
|
|
|
@@ -6219,6 +6126,13 @@ export struct LocalMusic {
|
|
|
}
|
|
|
this.isShowAlphaBet = false
|
|
|
})
|
|
|
+ .onReachEnd(() => {
|
|
|
+ // 触底加载下一页
|
|
|
+ Logger.info('heanup LocalMusic', `onReachEnd: hasMore=${this.hasMoreData}, isLoading=${this.isLoadingPage}`);
|
|
|
+ if (this.hasMoreData && !this.isLoadingPage) {
|
|
|
+ this.loadNextPage();
|
|
|
+ }
|
|
|
+ })
|
|
|
.scrollBar(BarState.Off)
|
|
|
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
|
|
|
.gesture(PinchGesture({ fingers: 2 })
|
|
|
@@ -6597,12 +6511,23 @@ export struct LocalMusic {
|
|
|
// 确保播放列表是完整的歌单歌曲列表(已在finishLoadingPlaylist中设置)
|
|
|
Logger.info(`heanup isFromSonPlayList分支 - 当前播放列表长度: ${this.songList.length}, 当前索引: ${this.curIndex}`)
|
|
|
}else {
|
|
|
- let globalVideoList = Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL) as VideoItem[];
|
|
|
+ // 延迟加载完整播放列表
|
|
|
+ let globalVideoList: VideoItem[] = await this.loadFullSongList();
|
|
|
+
|
|
|
+ // 如果延迟加载失败或返回空列表,回退到原逻辑
|
|
|
+ if (globalVideoList.length === 0) {
|
|
|
+ globalVideoList = Utility.getGlobalList(this.videoLocalList, CommonConstants.TYPE_LOCAL) as VideoItem[];
|
|
|
+ Logger.info(`heanup doPlay回退到videoLocalList - 列表长度: ${globalVideoList.length}`);
|
|
|
+ } else {
|
|
|
+ Logger.info(`heanup doPlay使用完整播放列表 - 列表长度: ${globalVideoList.length}`);
|
|
|
+ }
|
|
|
+
|
|
|
this.curIndex = Utility.getCurIndexFromGlobalList(globalVideoList, item.filePath)
|
|
|
this.songList = globalVideoList
|
|
|
|
|
|
this.sonDataSource.pushArrayData(this.songList)
|
|
|
this.currentSong = globalVideoList[this.curIndex]
|
|
|
+ Logger.info(`heanup doPlay设置播放列表 - 总歌曲数: ${this.songList.length}, 当前索引: ${this.curIndex}, 当前歌曲: ${this.currentSong?.name}`)
|
|
|
}
|
|
|
|
|
|
AppStorage.setOrCreate('currentSong',this.currentSong) ;
|
|
|
@@ -6851,23 +6776,26 @@ export struct LocalMusic {
|
|
|
@State discStr: string = ''
|
|
|
|
|
|
doUpdateData() {
|
|
|
- setTimeout(() => {
|
|
|
+ setTimeout(async () => {
|
|
|
|
|
|
if (this.modeType === 0) {
|
|
|
this.deleteCache(this.currentPath)
|
|
|
- this.getSortedFiles(this.currentPath)
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
}else if (this.modeType === 1){
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName });
|
|
|
}
|
|
|
}, 500)
|
|
|
}
|
|
|
|
|
|
doAllUpdateData() {
|
|
|
- setTimeout(() => {
|
|
|
+ setTimeout(async () => {
|
|
|
|
|
|
if (this.modeType === 0) {
|
|
|
this.deleteCache(this.currentPath)
|
|
|
- this.getSortedFiles(this.currentPath)
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
+ }else{
|
|
|
+ await this.resetAndLoadFirstPage();
|
|
|
}
|
|
|
workerInstance.postMessage({ code: 2, data: this.context,data2:this.packName });
|
|
|
workerInstance.postMessage({ code: 3, data: this.context });
|
|
|
@@ -6876,6 +6804,272 @@ export struct LocalMusic {
|
|
|
}, 999)
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 加载指定页的数据
|
|
|
+ */
|
|
|
+ private async loadPage(pageIndex: number, append: boolean = true): Promise<void> {
|
|
|
+ try {
|
|
|
+ // 1. 检查缓存
|
|
|
+ if (this.pageCache.has(pageIndex)) {
|
|
|
+ Logger.info('heanup LocalMusic', `loadPage: 从缓存加载 page=${pageIndex}`);
|
|
|
+ const cachedData = this.pageCache.get(pageIndex)!;
|
|
|
+ if (append) {
|
|
|
+ this.videoLocalList = [...this.videoLocalList, ...cachedData];
|
|
|
+ } else {
|
|
|
+ this.videoLocalList = cachedData;
|
|
|
+ }
|
|
|
+ this.dataSource.pushArrayData(this.videoLocalList);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isLoadingPage = true;
|
|
|
+ Logger.info('heanup LocalMusic', `loadPage: 开始加载 page=${pageIndex}, size=${this.pageSize}, mode=${this.modeType}`);
|
|
|
+
|
|
|
+ // 2. 构造查询参数
|
|
|
+ const options: PageQueryOptions = {
|
|
|
+ pageIndex: pageIndex,
|
|
|
+ pageSize: this.pageSize,
|
|
|
+ sortType: this.sortType,
|
|
|
+ searchKeyword: this.isSearchMode ? this.searchText : undefined
|
|
|
+ };
|
|
|
+
|
|
|
+ // 根据 modeType 补充查询条件
|
|
|
+ if (this.modeType === 0) {
|
|
|
+ options.parentPath = this.currentPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 执行查询
|
|
|
+ let result: PageQueryResult = { items: [], totalCount: 0, hasMore: false };
|
|
|
+ switch (this.modeType) {
|
|
|
+ case 0:
|
|
|
+ result = await this.table.queryByParentPathPaged(options);
|
|
|
+ // modeType=0时,第一页需要添加文件夹列表
|
|
|
+ if (pageIndex === 0 && !this.isSearchMode) {
|
|
|
+ const directories: VideoItem[] = await this.loadDirectories(this.currentPath);
|
|
|
+ result.items = [...directories, ...result.items];
|
|
|
+ result.totalCount += directories.length;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ result = await this.table.queryMediaLibraryPaged(options);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ result = await this.table.queryArtistsPaged(options);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ result = await this.table.queryAlbumsPaged(options);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ result = await this.table.queryMediaLibraryPaged(options);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!result || result.items.length === 0) {
|
|
|
+ Logger.info('heanup LocalMusic', 'loadPage: 查询结果为空');
|
|
|
+ this.isLoadingPage = false;
|
|
|
+ this.hasMoreData = false;
|
|
|
+ // 即使为空也要更新UI,显示空状态
|
|
|
+ this.totalCount = 0;
|
|
|
+ this.videoLocalList = [];
|
|
|
+ this.dataSource.pushArrayData(this.videoLocalList);
|
|
|
+ this.setButtonStatus();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 更新状态和缓存
|
|
|
+ this.totalCount = result.totalCount;
|
|
|
+ this.hasMoreData = result.hasMore;
|
|
|
+
|
|
|
+ Logger.info('heanup LocalMusic', `loadPage: 查询完成 items=${result.items.length}, total=${result.totalCount}, hasMore=${result.hasMore}`);
|
|
|
+
|
|
|
+ // 缓存管理 - 只保留最近3页
|
|
|
+ this.pageCache.set(pageIndex, result.items);
|
|
|
+ this.cleanOldCache(pageIndex);
|
|
|
+
|
|
|
+ // 5. 更新显示列表
|
|
|
+ if (append) {
|
|
|
+ this.videoLocalList = [...this.videoLocalList, ...result.items];
|
|
|
+ } else {
|
|
|
+ this.videoLocalList = result.items;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.dataSource.pushArrayData(this.videoLocalList);
|
|
|
+ this.setButtonStatus();
|
|
|
+ this.setAlphaBet();
|
|
|
+
|
|
|
+ this.isLoadingPage = false;
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error('heanup LocalMusic', `loadPage error: ${error.message}`);
|
|
|
+ ToastUtil.showToast('加载数据失败');
|
|
|
+ this.isLoadingPage = false;
|
|
|
+ this.hasMoreData = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清理旧缓存 - 只保留当前页前后各1页
|
|
|
+ */
|
|
|
+ private cleanOldCache(currentPage: number): void {
|
|
|
+ const pagesToKeep = new Set([currentPage - 1, currentPage, currentPage + 1]);
|
|
|
+ const keysToDelete: number[] = [];
|
|
|
+
|
|
|
+ this.pageCache.forEach((_, page) => {
|
|
|
+ if (!pagesToKeep.has(page)) {
|
|
|
+ keysToDelete.push(page);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keysToDelete.forEach(page => this.pageCache.delete(page));
|
|
|
+
|
|
|
+ if (keysToDelete.length > 0) {
|
|
|
+ Logger.info('heanup LocalMusic', `cleanOldCache: 清理了 ${keysToDelete.length} 页缓存`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载文件夹列表(仅用于modeType=0的首页模式)
|
|
|
+ */
|
|
|
+ private async loadDirectories(curPath: string): Promise<VideoItem[]> {
|
|
|
+ try {
|
|
|
+ Logger.info('heanup LocalMusic', `loadDirectories: 开始加载文件夹 path=${curPath}`);
|
|
|
+
|
|
|
+ // 检查缓存
|
|
|
+ const cacheKey = curPath + "_dirList";
|
|
|
+ const cachedDirList = this.findCache(cacheKey);
|
|
|
+ if (cachedDirList !== undefined && Array.isArray(cachedDirList)) {
|
|
|
+ Logger.info('heanup LocalMusic', `loadDirectories: 从缓存加载 count=${cachedDirList.length}`);
|
|
|
+ return cachedDirList as VideoItem[];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从文件系统读取
|
|
|
+ const fileList = FileUtil.listFileSync(curPath);
|
|
|
+ const directories: VideoItem[] = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < fileList.length; i++) {
|
|
|
+ const path = curPath + '/' + fileList[i];
|
|
|
+ if (FileUtil.isDirectory(path)) {
|
|
|
+ const item: VideoItem = new VideoItem(
|
|
|
+ fileList[i].toString(),
|
|
|
+ fileList[i].toString(),
|
|
|
+ path,
|
|
|
+ CommonConstants.TYPE_IS_DIR,
|
|
|
+ 0,
|
|
|
+ ''
|
|
|
+ );
|
|
|
+ if (this.isShowDir(item.name)) {
|
|
|
+ directories.push(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ Utility.doSortListAscending(directories);
|
|
|
+
|
|
|
+ // 缓存文件夹列表
|
|
|
+ if (curPath === this.rootPath) {
|
|
|
+ this.dirList = directories;
|
|
|
+ this.addCache(cacheKey, directories);
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.info('heanup LocalMusic', `loadDirectories: 加载完成 count=${directories.length}`);
|
|
|
+ return directories;
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error('heanup LocalMusic', `loadDirectories error: ${error.message}`);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 延迟加载完整播放列表(仅在播放时调用)
|
|
|
+ * 一次性加载当前模式/文件夹下的所有音乐文件
|
|
|
+ */
|
|
|
+ private async loadFullSongList(): Promise<VideoItem[]> {
|
|
|
+ // 如果已经加载过,直接返回
|
|
|
+ if (this.isFullSongListLoaded && this.fullSongList.length > 0) {
|
|
|
+ Logger.info('heanup LocalMusic', `loadFullSongList: 使用缓存 列表长度=${this.fullSongList.length}`);
|
|
|
+ return this.fullSongList;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Logger.info('heanup LocalMusic', `loadFullSongList: 开始加载完整列表 mode=${this.modeType}`);
|
|
|
+
|
|
|
+ const options: PageQueryOptions = {
|
|
|
+ pageIndex: 0,
|
|
|
+ pageSize: 10000, // 一次性加载全部(设置足够大的值)
|
|
|
+ sortType: this.sortType,
|
|
|
+ searchKeyword: this.isSearchMode ? this.searchText : undefined
|
|
|
+ };
|
|
|
+
|
|
|
+ if (this.modeType === 0) {
|
|
|
+ options.parentPath = this.currentPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ let result: PageQueryResult = { items: [], totalCount: 0, hasMore: false };
|
|
|
+
|
|
|
+ switch (this.modeType) {
|
|
|
+ case 0:
|
|
|
+ result = await this.table.queryByParentPathPaged(options);
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ result = await this.table.queryMediaLibraryPaged(options);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ result = await this.table.queryArtistsPaged(options);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ result = await this.table.queryAlbumsPaged(options);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ result = await this.table.queryMediaLibraryPaged(options);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤出音乐文件(排除文件夹、艺术家、专辑等)
|
|
|
+ this.fullSongList = result.items.filter((item: VideoItem) =>
|
|
|
+ item.type !== CommonConstants.TYPE_IS_DIR &&
|
|
|
+ item.type !== CommonConstants.TYPE_IS_ARTIST &&
|
|
|
+ item.type !== CommonConstants.TYPE_IS_ALBUM
|
|
|
+ );
|
|
|
+
|
|
|
+ this.isFullSongListLoaded = true;
|
|
|
+ Logger.info('heanup LocalMusic', `loadFullSongList: 加载完成 总歌曲数=${this.fullSongList.length}`);
|
|
|
+
|
|
|
+ return this.fullSongList;
|
|
|
+ } catch (err) {
|
|
|
+ const error = err as Error;
|
|
|
+ Logger.error('heanup LocalMusic', `loadFullSongList error: ${error.message}`);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置分页状态并加载首页
|
|
|
+ */
|
|
|
+ private async resetAndLoadFirstPage(): Promise<void> {
|
|
|
+ Logger.info('heanup LocalMusic', 'resetAndLoadFirstPage: 重置分页状态');
|
|
|
+ this.currentPage = 0;
|
|
|
+ this.videoLocalList = [];
|
|
|
+ this.fullSongList = []; // 清空完整播放列表
|
|
|
+ this.isFullSongListLoaded = false; // 重置加载标记
|
|
|
+ this.pageCache.clear();
|
|
|
+ this.hasMoreData = true;
|
|
|
+ this.totalCount = 0;
|
|
|
+ await this.loadPage(0, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载下一页
|
|
|
+ */
|
|
|
+ private async loadNextPage(): Promise<void> {
|
|
|
+ if (this.hasMoreData && !this.isLoadingPage) {
|
|
|
+ this.currentPage++;
|
|
|
+ Logger.info('heanup LocalMusic', `loadNextPage: 加载第 ${this.currentPage} 页`);
|
|
|
+ await this.loadPage(this.currentPage, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
//编辑信息
|
|
|
async doEdit(item: VideoItem) {
|
|
|
if (!item) {
|
|
|
@@ -9319,7 +9513,7 @@ export struct LocalMusic {
|
|
|
console.log("onecold 找到内嵌歌词 neiqianLrc =" + neiqianLrc)
|
|
|
// Navidrome JSON歌词转换
|
|
|
if (this.currentSong && isNavidromeType(this.currentSong.type)) {
|
|
|
- const convertedLyric = LyricUtil.convertNavidromeJsonLyricToLrc(neiqianLrc);
|
|
|
+ const convertedLyric: string | undefined = LyricUtil.convertNavidromeJsonLyricToLrc(neiqianLrc);
|
|
|
if (convertedLyric) {
|
|
|
neiqianLrc = convertedLyric;
|
|
|
console.log("onecold Navidrome歌词转换成功");
|
|
|
@@ -9477,8 +9671,8 @@ export struct LocalMusic {
|
|
|
}
|
|
|
|
|
|
this.lyricContent = lyricText;
|
|
|
- let lines = lyricText.split('\n').map(line => line.trim());
|
|
|
- let lyric = this.parser.parse(lines);
|
|
|
+ const lines: string[] = lyricText.split('\n').map((line: string) => line.trim());
|
|
|
+ const lyric: Lyric = this.parser.parse(lines);
|
|
|
|
|
|
this.lyricController.setLyric(lyric);
|
|
|
this.lyricControllerXF.setLyric(lyric);
|