瀏覽代碼

RemoteMusicPage增加对音质的显示

onecold 5 月之前
父節點
當前提交
baeecb721a

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

@@ -39,6 +39,7 @@ export interface NavidromeRestSong {
   artistId?: string;
   duration?: number;
   bitRate?: number;
+  sampleRate?: number;
   suffix?: string;
   size?: number;
   createdAt?: string;

+ 2 - 2
entry/src/main/ets/view/LocalMusic.ets

@@ -6044,7 +6044,7 @@ export struct LocalMusic {
         }
         .width('100%')
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
 
 
         Checkbox({ name: 'checkbox' + index })
@@ -6418,7 +6418,7 @@ export struct LocalMusic {
         }
         .width(this.getGridWight())
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
 
 
         Checkbox({ name: 'checkbox' + index })

+ 162 - 12
entry/src/main/ets/view/RemoteMusicPage.ets

@@ -14,7 +14,7 @@ import { WebDavAccount } from '../viewmodel/WebDavAccount';
 import Logger from '../common/util/Logger';
 import { RemoteDriveType } from '../common/enums/RemoteDriveType';
 import { navidromeRestApi, NavidromeRestAlbum, NavidromeRestArtist, NavidromeRestSong, NavidromeRestPlaylist } from '../common/network/NavidromeRestApi';
-import { Utility } from '../common/util/Utility';
+import { resolveAudioQualityTag, Utility } from '../common/util/Utility';
 import { Constants } from '../Constants';
 import { EventConstants } from '../common/constants/EventConstants';
 import { emitter } from '@kit.BasicServicesKit';
@@ -77,6 +77,85 @@ class AudioStationAlbumKey {
   }
 }
 
+function parseAudioMetricValue(value?: string): number | undefined {
+  if (!value || value.trim().length === 0) {
+    return undefined;
+  }
+  const normalized = value.trim().toLowerCase();
+  const directValue = Number(normalized);
+  if (Number.isFinite(directValue) && directValue > 0) {
+    return directValue;
+  }
+  const numericValue = parseFloat(normalized);
+  if (!Number.isFinite(numericValue) || numericValue <= 0) {
+    return undefined;
+  }
+  if (normalized.includes('kbps') || normalized.includes('khz')) {
+    return numericValue * 1000;
+  }
+  return numericValue;
+}
+
+function buildRemoteSongFileName(song: NavidromeRestSong): string {
+  const title = song.title ?? Constants.UNKNOWN_TITLE;
+  return `${title}${song.suffix ? '.' + song.suffix : ''}`;
+}
+
+function extractAudioExtension(fileName: string): string {
+  if (!fileName || fileName.length === 0) {
+    return '';
+  }
+  const lastDotIndex = fileName.lastIndexOf('.');
+  if (lastDotIndex < 0 || lastDotIndex >= fileName.length - 1) {
+    return '';
+  }
+  return fileName.substring(lastDotIndex);
+}
+
+function resolveInitialRemoteSongQuality(formatHint: string, fallbackFileName: string, bitrate?: number,
+  sampleRate?: number): string {
+  const directQuality = resolveAudioQualityTag(formatHint, bitrate, sampleRate);
+  if (directQuality.length > 0) {
+    return directQuality;
+  }
+  const fallbackExtension = extractAudioExtension(fallbackFileName);
+  if (fallbackExtension.length === 0) {
+    return '';
+  }
+  return resolveAudioQualityTag(fallbackExtension, bitrate, sampleRate);
+}
+
+function applyInitialRemoteSongQuality(videoItem: VideoItem, song: NavidromeRestSong, fallbackFileName: string): void {
+  const quality = resolveInitialRemoteSongQuality(song.suffix ?? song.contentType ?? '', fallbackFileName, song.bitRate,
+    song.sampleRate);
+  if (quality.length > 0) {
+    videoItem.md5Str = quality;
+  }
+  if (song.sampleRate !== undefined && song.sampleRate !== null && song.sampleRate > 0) {
+    videoItem.sampleRate = song.sampleRate.toString();
+  }
+}
+
+function applyInitialQualityToExistingVideoItem(videoItem: VideoItem): void {
+  if (!videoItem || (videoItem.md5Str && videoItem.md5Str.length > 0)) {
+    return;
+  }
+  const fallbackFileName = videoItem.fileName ?? videoItem.name ?? '';
+  const bitrate = parseAudioMetricValue(videoItem.bit_rate);
+  const sampleRate = parseAudioMetricValue(videoItem.sampleRate);
+  const quality = resolveInitialRemoteSongQuality(videoItem.mimeType ?? '', fallbackFileName, bitrate, sampleRate);
+  if (quality.length > 0) {
+    videoItem.md5Str = quality;
+  }
+}
+
+function buildSongQualityLabelText(quality?: string): string {
+  if (!quality || quality.length === 0) {
+    return '';
+  }
+  return quality.includes('Lossless') ? '无损' : quality;
+}
+
 /**
  * taskpool 任务结果接口
  * 返回带封面的完整数据
@@ -505,6 +584,7 @@ export struct RemoteMusicPage {
 
       let loadedFromCache = false;
       if (cachedData.songs && cachedData.songs.length > 0) {
+        this.hydrateSongQuality(cachedData.songs);
         this.allVideos = cachedData.songs;
         this.songDataSource.pushArrayData(cachedData.songs);
         loadedFromCache = true;
@@ -560,6 +640,7 @@ export struct RemoteMusicPage {
 
           // 直接更新数据(封面已在后台处理)
           if (taskResult.songs && taskResult.songs.length > 0) {
+            this.hydrateSongQuality(taskResult.songs);
             this.allVideos = taskResult.songs;
             this.songDataSource.pushArrayData(taskResult.songs);
           }
@@ -1889,6 +1970,7 @@ export struct RemoteMusicPage {
         artistId: song.artistId,
         duration: song.durationSeconds,
         bitRate: song.bitRate,
+        sampleRate: song.sampleRate,
         suffix: song.suffix,
         size: song.size,
         track: song.track,
@@ -1910,6 +1992,7 @@ export struct RemoteMusicPage {
         artistId: song.artistId,
         duration: song.durationSeconds,
         bitRate: song.bitRate,
+        sampleRate: song.sampleRate,
         suffix: song.suffix,
         size: song.size,
         track: song.track,
@@ -1934,6 +2017,7 @@ export struct RemoteMusicPage {
         artistId: artistName,
         duration: song.durationSeconds,
         bitRate: song.bitRate,
+        sampleRate: song.sampleRate,
         suffix: song.container,
         size: song.size,
         track: song.track,
@@ -1957,6 +2041,7 @@ export struct RemoteMusicPage {
         artistId: song.artistId,
         duration: song.durationSeconds,
         bitRate: song.bitRate,
+        sampleRate: song.sampleRate,
         suffix: song.suffix,
         size: song.size,
         track: song.track,
@@ -1980,6 +2065,7 @@ export struct RemoteMusicPage {
         artistId: song.artistId,
         duration: song.durationSeconds,
         bitRate: song.bitRate,
+        sampleRate: song.sampleRate ?? undefined,
         suffix: song.suffix,
         size: song.size,
         track: song.track,
@@ -2398,6 +2484,7 @@ export struct RemoteMusicPage {
   private convertSongToVideoItem(song: NavidromeRestSong, account: WebDavAccount, coverUrl?: string): VideoItem {
     const title = song.title ?? Constants.UNKNOWN_TITLE;
     const libraryInfo = this.resolveLibraryInfo(account);
+    const fileName = buildRemoteSongFileName(song);
 
     // 调试日志:记录从playlist或其他API获取的歌曲的id字段
     void ServerLogUtil.debug('SongConvert', `转换歌曲: ${title}`);
@@ -2416,7 +2503,7 @@ export struct RemoteMusicPage {
       undefined,
       song.artist ?? Constants.UNKNOWN_ARTIST,
       song.album ?? '',
-      `${title}${song.suffix ? '.' + song.suffix : ''}`
+      fileName
     );
     const durationStr = this.formatSongDuration(song.duration);
     if (durationStr) {
@@ -2445,6 +2532,7 @@ export struct RemoteMusicPage {
     if (song.contentType) {
       videoItem.mimeType = song.contentType;
     }
+    applyInitialRemoteSongQuality(videoItem, song, fileName);
 
     // 记录最终生成的VideoItem路径
     void ServerLogUtil.debug('SongConvert', `- VideoItem.filePath: ${videoItem.filePath}`);
@@ -2939,6 +3027,17 @@ export struct RemoteMusicPage {
             .textOverflow({ overflow: TextOverflow.Ellipsis })
 
           Row() {
+            if (StrUtil.isNotEmpty(song.md5Str)) {
+              Text(this.buildSongQualityLabel(song))
+                .fontSize(this.twoFingerType == 1 ? 8 : 10)
+                .fontColor($r('app.color.index_tab_font_color'))
+                .fontWeight(500)
+                .padding({ top: 2, right: 6, left: 6, bottom: 2 })
+                .borderRadius(4)
+                .backgroundColor('#FFC107')
+                .opacity(0.92)
+                .margin({ right: 6 })
+            }
             Text((song.artist ?? '') + "  ")
               .fontSize(this.twoFingerType == 1 ? 11 : this.twoFingerType == 2 ? 12 : 14)
               .fontColor(this.currentSong?.filePath == song.filePath ? this.themeColor : $r('app.color.index_tab_font_color'))
@@ -3141,6 +3240,19 @@ export struct RemoteMusicPage {
     })
   }
 
+  private hydrateSongQuality(items: VideoItem[]): void {
+    if (!items || items.length === 0) {
+      return;
+    }
+    for (let i = 0; i < items.length; i++) {
+      applyInitialQualityToExistingVideoItem(items[i]);
+    }
+  }
+
+  private buildSongQualityLabel(song: VideoItem): string {
+    return buildSongQualityLabelText(song.md5Str);
+  }
+
   private buildSongMetaLine(song: VideoItem): string {
     const parts: string[] = [];
     if (song.duration) {
@@ -3720,7 +3832,18 @@ export struct RemoteMusicPage {
               //   .fontSize(this.twoFingerType == 1 ? 10 : this.twoFingerType == 2 ? 12 : 14)
               //   .fontColor($r('app.color.text_color'))
               Row(){
-
+                if (StrUtil.isNotEmpty(item.md5Str)) {
+                  Text(this.buildSongQualityLabel(item))
+                    .fontSize(this.twoFingerType == 1 ? 8 : 9)
+                    .fontColor($r('app.color.index_tab_font_color'))
+                    .fontWeight(500)
+                    .padding({ top: 2, right: 5, left: 5, bottom: 2 })
+                    .borderRadius(4)
+                    .backgroundColor('#FFC107')
+                    .opacity(0.92)
+                    .margin({ top: 2, right: 6 })
+                    .visibility(StrUtil.isEmpty(item.md5Str) ? Visibility.None : Visibility.Visible)
+                }
 
                 Text((item.artist ?? '') + "  ")
                   .fontSize(this.twoFingerType == 1 ? 10 : this.twoFingerType == 2 ? 12 : 13)
@@ -3751,7 +3874,7 @@ export struct RemoteMusicPage {
         }
         .width(this.getGridWight())
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
 
@@ -3887,7 +4010,7 @@ export struct RemoteMusicPage {
         }
         .width(this.getGridWight())
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
       }
@@ -3971,7 +4094,7 @@ export struct RemoteMusicPage {
         }
         .width(this.getGridWight())
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
       }
@@ -4055,7 +4178,7 @@ export struct RemoteMusicPage {
         }
         .width(this.getGridWight())
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
       }
@@ -4437,6 +4560,17 @@ export struct RemoteMusicPage {
               .fontColor(this.currentSong?.filePath === item.filePath ? this.themeColor : $r('app.color.text_color'))
 
             Row() {
+              if (StrUtil.isNotEmpty(item.md5Str)) {
+                Text(this.buildSongQualityLabel(item))
+                  .fontSize(this.columns == 4 || this.columns == 3 ? 8 : 9)
+                  .fontColor($r('app.color.index_tab_font_color'))
+                  .fontWeight(500)
+                  .padding({ top: 2, right: 5, left: 5, bottom: 2 })
+                  .borderRadius(4)
+                  .backgroundColor('#FFC107')
+                  .opacity(0.92)
+                  .margin({ top: 2, right: 6 })
+              }
               Text(item.artist || '')
                 .fontSize(this.columns == 4 || this.columns == 3 ? 10 : this.columns == 2 ? 12 : 13)
                 .maxLines(1)
@@ -4458,7 +4592,7 @@ export struct RemoteMusicPage {
         }
         .width('100%')
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
       }
     }
@@ -4525,7 +4659,7 @@ export struct RemoteMusicPage {
         }
         .width('100%')
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
       }
@@ -4592,7 +4726,7 @@ export struct RemoteMusicPage {
         .width('100%')
         .height('auto')
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
 
       }
@@ -4658,7 +4792,7 @@ export struct RemoteMusicPage {
         }
         .width('100%')
         .borderRadius(12)
-        .backgroundColor(this.isDarkMode ? '#141414' : '#F7F7F7')
+        .backgroundColor($r('app.color.com_bg'))
         .animation({ curve: Curve.Sharp, duration: 300 })
       }
     }
@@ -4716,6 +4850,7 @@ async function loadNavidromeDataTask(accountData: AccountData, ticket: number):
       for (let i = 0; i < chunk.length; i++) {
         const song: NavidromeRestSong = chunk[i];
         const title: string = song.title ?? Constants.UNKNOWN_TITLE;
+        const fileName: string = `${title}${song.suffix ? '.' + song.suffix : ''}`;
 
         // 获取库类型
         let libraryType = CommonConstants.TYPE_NAVIDROME;
@@ -4739,7 +4874,7 @@ async function loadNavidromeDataTask(accountData: AccountData, ticket: number):
           undefined,
           song.artist ?? Constants.UNKNOWN_ARTIST,
           song.album ?? '',
-          `${title}${song.suffix ? '.' + song.suffix : ''}`
+          fileName
         );
 
         // 设置时长
@@ -4769,6 +4904,21 @@ async function loadNavidromeDataTask(accountData: AccountData, ticket: number):
         if (song.contentType) {
           videoItem.mimeType = song.contentType;
         }
+        let initialQuality: string = resolveAudioQualityTag(song.suffix ?? song.contentType ?? '', song.bitRate,
+          song.sampleRate);
+        if (initialQuality.length === 0) {
+          const lastDotIndex = fileName.lastIndexOf('.');
+          if (lastDotIndex >= 0 && lastDotIndex < fileName.length - 1) {
+            const fallbackExtension = fileName.substring(lastDotIndex);
+            initialQuality = resolveAudioQualityTag(fallbackExtension, song.bitRate, song.sampleRate);
+          }
+        }
+        if (initialQuality.length > 0) {
+          videoItem.md5Str = initialQuality;
+        }
+        if (song.sampleRate !== undefined && song.sampleRate !== null && song.sampleRate > 0) {
+          videoItem.sampleRate = song.sampleRate.toString();
+        }
 
         // 处理封面(在 worker 线程中调用 API)
         let coverUrl: string | undefined = undefined;

+ 4 - 0
entry/src/main/resources/base/element/color.json

@@ -231,6 +231,10 @@
     {
       "name": "brand",
       "value": "#007DFF"
+    },
+    {
+      "name": "com_bg",
+      "value": "#F7F7F7"
     }
   ]
 }

+ 4 - 0
entry/src/main/resources/dark/element/color.json

@@ -216,6 +216,10 @@
     {
       "name": "common_fill_color",
       "value": "#000000"
+    },
+    {
+      "name": "com_bg",
+      "value": "#141414"
     }
   ]
 }