Browse Source

歌词支持简体繁体互转,支持设置是否开启智能解析文件名和首页默认媒体库

onecold 10 tháng trước cách đây
mục cha
commit
bb94326405

+ 2 - 2
AppScope/app.json5

@@ -2,8 +2,8 @@
   "app": {
     "bundleName": "com.xgplayer.ttmusic.hm",
     "vendor": "example",
-    "versionCode": 20250915,
-    "versionName": "1.5.9",
+    "versionCode": 20250924,
+    "versionName": "1.6.0",
     "icon": "$media:app_icon",
     "label": "$string:app_name",
     "multiAppMode": {

+ 2 - 2
entry/src/main/ets/common/constants/CommonConstants.ets

@@ -79,12 +79,12 @@ export class CommonConstants {
 
   static readonly VIDEO_FORMAT = ['.mp4','.mov','.m4v','.avi','.3gv','.wmv',
     '.mkv','.flv','.3g2','.rmvb','.mpg','.webm','.ogv','.f4v','.swf','.srt',
-    '.vtt','.vob','.3gp','.mpeg','.ts','.cue', '.asf','.m2ts','.m4b','.mts','.rm','.wtv' ]
+    '.vtt','.vob','.3gp','.mpeg','.ts', '.asf','.m2ts','.m4b','.mts','.rm','.wtv' ]
 
   static readonly MEDIA_FORMAT = ['.mp3','.wav','.wma','.mp2','.mov','.flac',
     '.midi','.ra','.aac','.ape','.cda','.lrc','.alac','.m4a','.mp4','.wmv','.mkv',
     '.3gv','.m4v','.avi','.rmvb','.flv','.3g2','.rmvb','.mpg','.webm','.ogv','.f4v','.swf'
-    ,'.srt','.vtt','.cue','.dsf','.vob','.3gp','.mpeg','.ts','.ogg','.opus','.wv','.aiff','.amr','.aif','.dff'
+    ,'.srt','.vtt','.dsf','.vob','.3gp','.mpeg','.ts','.ogg','.opus','.wv','.aiff','.amr','.aif','.dff'
     ,'.aif','.au','.eac3','.mlp','.tak','.thd','.tta','.wv','.ac3','.amr','.mka','.mpc','.ra','.dts'
   , '.asf','.m2ts','.m4b','.mts','.rm','.wtv','.av3a','.dsd']
 

+ 20 - 0
entry/src/main/ets/common/util/CommUtils.ets

@@ -15,6 +15,7 @@
 import { CommonConstants2 } from "./CommonConstants2";
 import { image } from "@kit.ImageKit";
 import { fileIo } from "@kit.CoreFileKit";
+import { transverter, TransverterLanguage, TransverterType } from "@nutpi/chinese_transverter";
 
 export function imagePathToPixelMap(imagePath: string): Promise<image.PixelMap> {
   return new Promise((resolve, reject) => {
@@ -56,4 +57,23 @@ export function secondToTime(seconds: number): string {
   } else {
     return `00:${secondStr}`;
   }
+}
+
+export function getTransverterText(message: string, transverterType:number):string{
+  if(transverterType==1){
+    return    transverter({
+      type: TransverterType.TRADITIONAL,
+      str: message,
+      language: TransverterLanguage.ZH_TW
+    });
+  }else if(transverterType==2){
+    return    transverter({
+      type: TransverterType.SIMPLIFIED,
+      str: message,
+      language: TransverterLanguage.ZH_CN
+    });
+  }else{
+    return message;
+  }
+
 }

+ 32 - 0
entry/src/main/ets/common/util/MediaTable.ets

@@ -738,6 +738,38 @@ export default class MediaTable {
     });
   }
 
+  /**
+   * Check if a record exists in the database by filePath
+   * @param filePath The file path to check
+   * @returns Promise that resolves to true if record exists, false otherwise
+   */
+  public isRecordExists(filePath: string): Promise<boolean> {
+    return new Promise((resolve, reject) => {
+      try {
+        // Create query predicates
+        const predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
+        predicates.equalTo(DB_COLUMNS.FILE_PATH,  filePath);
+
+        // Execute the query
+        this.accountTable.query(predicates,  (resultSet: relationalStore.ResultSet) => {
+          try {
+            // If rowCount > 0, record exists
+            resolve(resultSet.rowCount  > 0);
+          } catch (err) {
+            Logger.error(RdbUtils.RDB_TAG,  `Error checking record existence: ${err.message}`);
+            reject(err);
+          } finally {
+            // Ensure the result set is closed
+            resultSet.close();
+          }
+        });
+      } catch (err) {
+        Logger.error(RdbUtils.RDB_TAG,  `Error creating query: ${err.message}`);
+        reject(err);
+      }
+    });
+  }
+
 
 }
 

+ 26 - 23
entry/src/main/ets/common/util/RdbUtils.ets

@@ -253,30 +253,33 @@ export default class RdbUtils {
         if (resultSet.rowCount > 0) {
           Logger.info(RdbUtils.RDB_TAG, `Record with id ${id} already exists. Skipping insert.`);
           //检查是否存在相同id的记录,存在则不插入,进一步判断pixelMapPath字段,如果旧值为空而新值不为空,则更新该字段。
+          //20250924不判断pixelMapPath字段了,如果存在不插入,则返回
           if (resultSet.goToFirstRow()) {
-            const existingPixelMapPath = resultSet.getString(resultSet.getColumnIndex('pixelMapPath'));
-            const name = resultSet.getString(resultSet.getColumnIndex('name'));
-            const artist = resultSet.getString(resultSet.getColumnIndex('artist'));
-            if ((!existingPixelMapPath || existingPixelMapPath.trim() === '')&& cover_api!==undefined) {
-              let newPixelMapPath =  await NetAxiosUtil.getLyricCover(name,artist,cover_api)
-              console.info('onecold getLyricCover success. name= '+name);
-              console.info('onecold getLyricCover success. newPixelMapPath= '+newPixelMapPath);
-              if(StrUtil.isNotEmpty(newPixelMapPath)){
-                // Update pixelMapPath
-                const updateValues: relationalStore.ValuesBucket = { pixelMapPath: newPixelMapPath };
-                this.updateData(predicates, updateValues, (updateSuccess: boolean) => {
-                  if (updateSuccess) {
-                    Logger.info(RdbUtils.RDB_TAG, `onecold Updated pixelMapPath for id ${id}.`);
-                  } else {
-                    Logger.error(RdbUtils.RDB_TAG, `onecold Failed to update pixelMapPath for id ${id}.`);
-                  }
-                  callback(updateSuccess);
-                });
-              }
-
-              resultSet.close();
-              return;
-            }
+            resultSet.close();
+            return;
+            // const existingPixelMapPath = resultSet.getString(resultSet.getColumnIndex('pixelMapPath'));
+            // const name = resultSet.getString(resultSet.getColumnIndex('name'));
+            // const artist = resultSet.getString(resultSet.getColumnIndex('artist'));
+            // if ((!existingPixelMapPath || existingPixelMapPath.trim() === '')&& cover_api!==undefined) {
+            //   let newPixelMapPath =  await NetAxiosUtil.getLyricCover(name,artist,cover_api)
+            //   console.info('onecold getLyricCover success. name= '+name);
+            //   console.info('onecold getLyricCover success. newPixelMapPath= '+newPixelMapPath);
+            //   if(StrUtil.isNotEmpty(newPixelMapPath)){
+            //     // Update pixelMapPath
+            //     const updateValues: relationalStore.ValuesBucket = { pixelMapPath: newPixelMapPath };
+            //     this.updateData(predicates, updateValues, (updateSuccess: boolean) => {
+            //       if (updateSuccess) {
+            //         Logger.info(RdbUtils.RDB_TAG, `onecold Updated pixelMapPath for id ${id}.`);
+            //       } else {
+            //         Logger.error(RdbUtils.RDB_TAG, `onecold Failed to update pixelMapPath for id ${id}.`);
+            //       }
+            //       callback(updateSuccess);
+            //     });
+            //   }
+            //
+            //   resultSet.close();
+            //   return;
+            // }
           }
 
           Logger.info(RdbUtils.RDB_TAG, `No update needed for id ${id}.`);

+ 24 - 21
entry/src/main/ets/common/util/Utility.ets

@@ -8,7 +8,6 @@ import {
   PreferencesUtil,
   RandomUtil, StrUtil } from '@pura/harmony-utils';
 import { media } from '@kit.MediaKit';
-import * as chardet from '@changwei/chardet';
 import fs, { ReadTextOptions } from '@ohos.file.fs';
 import { image } from '@kit.ImageKit';
 import fileIo from '@ohos.file.fs';
@@ -25,8 +24,7 @@ import { pinyin4js } from '@ohos/pinyin4js';
 import { VipData } from '../../viewmodel/VipData';
 import { VipPage } from '../../pages/VipPage';
 import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
-import { util } from '@kit.ArkTS';
-import { UniversalDetector } from '@ohos/juniversalchardet';
+
 
 export interface FFMpegTags {
   album?: string;
@@ -650,8 +648,8 @@ export class Utility {
   }
 
   //获取音乐资源的属性值,
-  static async uriGetMusicAssetsFromFile(context:Context,uri:string,type:number,isLoadPixelMap?:boolean): Promise<VideoItem> {
-    return Utility.readMetaInfoFFmpeg(context,uri,type)
+  static async uriGetMusicAssetsFromFile(context:Context,uri:string,type:number,autoParseMusicName?:boolean): Promise<VideoItem> {
+    return Utility.readMetaInfoFFmpeg(context,uri,type,autoParseMusicName)
 
     //华为官方不支持的格式 不支持内嵌歌词Dsf,aif,aiff,wav  不支持内嵌封面dsf,aif,aiff
     if(StrUtil.isNotEmpty(uri)){
@@ -659,7 +657,7 @@ export class Utility {
         ||uri.toLowerCase().endsWith('.aif')
         // ||uri.toLowerCase().endsWith('.wav')
         ||uri.toLowerCase().endsWith('.aiff')){
-        return Utility.readMetaInfoFFmpeg(context,uri,type)
+        return Utility.readMetaInfoFFmpeg(context,uri,type,autoParseMusicName)
       }
     }
 
@@ -759,7 +757,7 @@ export class Utility {
 
             let name = await MD5.digestSync(uri)
 
-            if(isLoadPixelMap){
+            // if(isLoadPixelMap){
               // 获取专辑封面(promise模式)
               // pixelMap = await avMetadataExtractor.fetchAlbumCover();
               // // 释放资源(promise模式)
@@ -780,10 +778,10 @@ export class Utility {
               }
 
 
-            }else{
-              imagePath = context.filesDir + FileUtil.separator + name
-              imagePath = fileUri.getUriFromPath(imagePath)
-            }
+            // }else{
+            //   imagePath = context.filesDir + FileUtil.separator + name
+            //   imagePath = fileUri.getUriFromPath(imagePath)
+            // }
             console.info('onecold release success. imagePath= '+imagePath);
 
 
@@ -829,7 +827,7 @@ export class Utility {
   }
 
 
-  static async  readMetaInfoFFmpeg(context:Context,inputPath: string,type:number): Promise<VideoItem> {
+  static async  readMetaInfoFFmpeg(context:Context,inputPath: string,type:number,autoParseMusicName?:boolean): Promise<VideoItem> {
     return new Promise((resolve, reject) => {
       let commands = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", inputPath];
       let outputJson = "";
@@ -892,17 +890,22 @@ export class Utility {
             if(StrUtil.isEmpty(title)){
               //自动解析像这样的获取不到元数据的 周杰伦-七里香.mp3文件
               console.log(`onecold musicName为空:${file.name}`);
-              const musicData = parseMusicFileName(file.name);
-              if (musicData.isValid)  {
-                // console.log(`onecold 艺术家:${musicData.artist}`);   // 输出:周杰伦
-                // console.log(`onecold 歌曲名:${musicData.title}`);   // 输出:七里香
-                title = musicData.title
-                if(artist==''||artist==undefined)
-                  artist = musicData.artist
-              } else {
+              // let autoParseMusicName = PreferencesUtil.getBooleanSync('autoParseMusicName',false)
+              if(autoParseMusicName){
+                const musicData = parseMusicFileName(file.name);
+                if (musicData.isValid)  {
+                  // console.log(`onecold 艺术家:${musicData.artist}`);   // 输出:周杰伦
+                  // console.log(`onecold 歌曲名:${musicData.title}`);   // 输出:七里香
+                  title = musicData.title
+                  if(artist==''||artist==undefined)
+                    artist = musicData.artist
+                } else {
+                  title = file.name
+                }
+              }else{
                 title = file.name
-                // console.log("onecold 文件名格式不符合要求");
               }
+
             }
 
             let name: string = title;

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

@@ -67,14 +67,14 @@ struct NewIndex {
   @State offsetY: number = 0;
   /** 主内容类型(0:本地音乐,1:网络内容) */
   @Provide mType: number = 0
-  /** 模式类型(如歌手、专辑等) */
+  /** 模式类型(如文件夹 媒体库 艺术家、专辑等) */
   @Provide modeType: number = 0
   /** 是否为零状态(预留) */
   @Provide('isZero') isZero: boolean = false;
   /** 是否显示赞助入口 */
   @State isShowSponsorship: boolean = false
   /** 页面上下文 */
-  context = getContext(this);
+  context = this.getUIContext().getHostContext() as common.UIAbilityContext
   /** 音乐是否为零状态(预留) */
   @Provide('isMusicZero') isMusicZero: boolean = false;
   @State isShowTitleBar: boolean = true //是否显示分类导航条
@@ -85,6 +85,7 @@ struct NewIndex {
   @StorageProp('windowHeight') windowHeight: number = 0;
   @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false;
   @StorageProp('curDisplayIsHiCar') curDisplayIsHiCar: boolean = false;
+  @State idDefaultMediaKu: boolean = false
   /**
    * 是否显示更新日志开关
    */
@@ -189,7 +190,10 @@ struct NewIndex {
 
   async aboutToAppear() {
     this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
-
+    this.idDefaultMediaKu = PreferencesUtil.getBooleanSync('idDefaultMediaKu', false)
+    if(this.idDefaultMediaKu){
+      this.modeType = 1
+    }
     Utility.enableFullScreen()
     this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
     Utility.getAppName(getContext(this)).then((appName: string) => {

+ 41 - 30
entry/src/main/ets/pages/ScanFilePage.ets

@@ -3,7 +3,7 @@ import {  router, window } from '@kit.ArkUI'
 import { AppUtil, DeviceUtil, DisplayUtil, FileUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils'
 import lottie from '@ohos/lottie'
 import { AnimationItem } from '@ohos/lottie'
-import { MessageEvents, taskpool, worker } from '@kit.ArkTS'
+import { JSON, MessageEvents, taskpool, worker } from '@kit.ArkTS'
 import { fileUri, picker } from '@kit.CoreFileKit'
 import { Utility } from '../common/util/Utility'
 import MediaTable from '../common/util/MediaTable'
@@ -13,7 +13,7 @@ import { CommonConstants, STR_LOCK_VIDEO } from '../common/constants/CommonConst
 import { emitter } from '@kit.BasicServicesKit'
 import { BreakpointTypeEnum } from '../common/util/BreakpointSystem'
 import { resourceManager } from '@kit.LocalizationKit'
-import { ConfigurationConstant } from '@kit.AbilityKit'
+import { common, ConfigurationConstant } from '@kit.AbilityKit'
 import { DialogHelper } from '@pura/harmony-dialog'
 
 
@@ -21,7 +21,8 @@ import { DialogHelper } from '@pura/harmony-dialog'
 // @Entry
 @Component
 export struct ScanFilePage{
-  @StorageProp('mediaKuList')  mediaKuList: Array<VideoItem> = []; //媒体库文件
+  context =  this.getUIContext().getHostContext() as common.UIAbilityContext
+  // @StorageProp('mediaKuList')  mediaKuList: Array<VideoItem> = []; //媒体库文件
   @State rootPath:string = '' //音频根目录
   @State lockPath:string = ''
   @Consume isShowDrawer: boolean;
@@ -32,6 +33,7 @@ export struct ScanFilePage{
   @State isStartCover:boolean = false
   @State isStartSync:boolean = false
   @State appName:string = ''
+  @State packName: string = ''
   @Consume mType: number;
   //lottie动画构建渲染上下文
   private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
@@ -70,6 +72,7 @@ export struct ScanFilePage{
   }
   // 组件生命周期
   async aboutToAppear() {
+    this.packName = AppUtil.getBundleName()
     this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
     Utility.getAppName(getContext(this)).then((appName:string)=>{
       this.appName = appName
@@ -148,12 +151,12 @@ export struct ScanFilePage{
   doOptimize(isOnekey:boolean){
     this.watchStatus(true)
     this.initState(isOnekey)
-    const task = new taskpool.Task(scanDirectoryTask, getContext(this), this.rootPath,
-      this.lockPath,PreferencesUtil.getStringSync('COVER_API',''));
+    const task = new taskpool.Task(scanDirectoryTask, this.context, this.rootPath,
+      this.lockPath,PreferencesUtil.getStringSync('COVER_API',''),PreferencesUtil.getBooleanSync('autoParseMusicName', false));
     taskpool.execute(task, taskpool.Priority.HIGH).then(()=>{
       this.endScan(isOnekey)
     }).catch((e:object)=>{
-      console.info("task1 catch e: " + e);
+      console.info("onecold task1 catch e: " + JSON.stringify(e));
     })
 
   }
@@ -301,7 +304,6 @@ export struct ScanFilePage{
             }
             // .width('100%')
             .margin({ left:25,right: 25 ,top:20,bottom:20 })
-
             Row() {
 
 
@@ -311,22 +313,26 @@ export struct ScanFilePage{
                 .fillColor(this.themeColor)
                 .margin({ left:10})
                 .alignSelf(ItemAlign.Center)
-              Text($r('app.string.sync_tips'))
+              Text(Utility.resourceToString(getContext(this),$r('app.string.pcfile_scan_tip_one'))
+                +`${this.packName} `+
+              Utility.resourceToString(getContext(this),$r('app.string.pcfile_scan_tip_one_1')))
                 .margin({ left: 10, right: 20 })
                 .fontSize(15)
                 .fontColor(Color.Gray)
                 .fontWeight(480)
                 .layoutWeight(1)
-
             }
-            // .width('100%')
             .margin({ left:25,right: 25 ,bottom:20 })
 
+
           }
           .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?450:350)
-          .backgroundColor($r('app.color.index_background'))
           .margin({ left:25,right: 25,top:20,bottom:20  })
           .borderRadius(20)
+          .border({
+            color: Color.Gray,
+            width: 1.8
+          })
           .justifyContent(FlexAlign.Center)
           .transition(TransitionEffect.move(TransitionEdge.END)
             .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
@@ -393,6 +399,7 @@ export struct ScanFilePage{
           }
           .transition(TransitionEffect.move(TransitionEdge.END)
             .animation({ duration: 380, curve: Curve.Ease,delay:300 }))
+          .visibility(Visibility.None)
 
         }
 
@@ -406,21 +413,21 @@ export struct ScanFilePage{
   }
 
   doSyncData() {
-    this.watchStatus(true)
-    this.initLottie(this.path,true)
-    this.isStartSync = true
-    this.textVisi = Visibility.Visible
-    this.strText = Utility.resourceToString(getContext(this),$r('app.string.sync_dataing'))
-    lottie.play()
-    // 仅提取 filePath 列表
-    const filePaths = this.mediaKuList.map(item => item.filePath);
-    Logger.info('xiaozheng 校正数据开始 this.filePaths length= ' + filePaths.length)
-    const task2 = new taskpool.Task(syncDataTask, getContext(this), filePaths);
-    taskpool.execute(task2, taskpool.Priority.HIGH).then(()=>{
-      this.endScan(false)
-    }).catch((e:object)=>{
-      console.info("task2 catch e: " + e);
-    })
+    // this.watchStatus(true)
+    // this.initLottie(this.path,true)
+    // this.isStartSync = true
+    // this.textVisi = Visibility.Visible
+    // this.strText = Utility.resourceToString(getContext(this),$r('app.string.sync_dataing'))
+    // lottie.play()
+    // // 仅提取 filePath 列表
+    // const filePaths = this.mediaKuList.map(item => item.filePath);
+    // Logger.info('xiaozheng 校正数据开始 this.filePaths length= ' + filePaths.length)
+    // const task2 = new taskpool.Task(syncDataTask, getContext(this), filePaths);
+    // taskpool.execute(task2, taskpool.Priority.HIGH).then(()=>{
+    //   this.endScan(false)
+    // }).catch((e:object)=>{
+    //   console.info("task2 catch e: " + e);
+    // })
   }
 
   showTipsDialog() {
@@ -511,24 +518,27 @@ async function syncDataTask(context: Context, mediaKuList: Array<string>) {
 
 //扫描文件入库
 @Concurrent
-async function  scanDirectoryTask(context: Context, dirPath: string, lockPath: string,cover_api:string) {
+async function  scanDirectoryTask(context: Context, dirPath: string,
+  lockPath: string,cover_api:string,autoParseMusicName:boolean) {
   const stack: string[] = [dirPath];
   const table: MediaTable = new MediaTable(context);
   while (stack.length  > 0) {
     const currentPath = stack.pop()!;
     const files = FileUtil.listFileSync(currentPath);
     await Promise.all(files.map(async  (file) => {
+
       const fPath = `${currentPath}/${file}`;
+      console.info('onecold scanDirectoryTask fPath = ' + fPath)
       if (fPath === lockPath) return;
 
       if (FileUtil.isDirectory(fPath))  {
         stack.push(fPath);  // 使用栈结构代替递归
       } else {
         if (fPath.endsWith('.lrc')  || fPath.endsWith('.srt'))  return;
-
+        console.info('onecold scanDirectoryTask fPath2 = ' + fPath)
         if (Utility.isMeidaByExtension(fPath))  {
           const mediaItem = await Utility.uriGetMusicAssetsFromFile(
-            context, fPath, CommonConstants.TYPE_LOCAL, true
+            context, fPath, CommonConstants.TYPE_LOCAL, autoParseMusicName
           );
           table.insert(mediaItem,  (id: number) => {
 
@@ -537,4 +547,5 @@ async function  scanDirectoryTask(context: Context, dirPath: string, lockPath: s
       }
     }));
   }
-}
+}
+

+ 65 - 4
entry/src/main/ets/pages/SettingPage.ets

@@ -57,7 +57,8 @@ export struct SettingPage {
   static readonly IS_SWIPE: string = 'isSwipe'
   static readonly IS_AUTO_HIDE_PROGRESS: string = 'IS_AUTO_HIDE_PROGRESS';
   public static OPEN_SKIPSONG_ANIMATE: string = 'openSkipSongAnimate';
-
+  @State autoParseMusicName: boolean = false
+  @State idDefaultMediaKu: boolean = false
   public static THEME_COLOR_LIST: Array<ThemeColorItem> = [
     { name: '玫瑰粉', color: '#FF4081', isVip: false },
     { name: '经典蓝', color: '#0A59F7', isVip: false },
@@ -240,6 +241,9 @@ export struct SettingPage {
     this.is_auto_hide_progress = PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_HIDE_PROGRESS, false)
     this.openSkipSongAnimate = PreferencesUtil.getBooleanSync(SettingPage.OPEN_SKIPSONG_ANIMATE, true)
     this.volumeSmall = PreferencesUtil.getBooleanSync('volumeSmall', false)
+    this.autoParseMusicName = PreferencesUtil.getBooleanSync('autoParseMusicName', false)
+    this.idDefaultMediaKu = PreferencesUtil.getBooleanSync('idDefaultMediaKu', false)
+
     // 只用 themeMode 控制主题
     this.themeMode = PreferencesUtil.getNumberSync(SettingPage.THEME_MODE, 0)
     this.applyThemeMode(this.themeMode)
@@ -1204,9 +1208,36 @@ export struct SettingPage {
             }
             .height(55)
             .clickEffect({ level: ClickEffectLevel.HEAVY })
-
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
-            // 保存播放模式
+            // 首页默认切换媒体库
+            Row() {
+              SymbolGlyph($r('sys.symbol.identify_song'))
+                .fontSize(20)
+                .fontColor([this.themeColor])
+                .alignSelf(ItemAlign.Center)
+                .margin({ left: 15 })
+              Text('首页默认媒体库')
+                .margin({ left: 8 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Toggle({ type: ToggleType.Switch, isOn: this.idDefaultMediaKu })
+                .selectedColor(this.themeColor)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+                .switchPointColor(Color.White)
+                .margin({ right: 18 })
+                .onChange((checked: boolean) => {
+                  this.idDefaultMediaKu = checked;
+                  PreferencesUtil.put('idDefaultMediaKu', this.idDefaultMediaKu)
+                })
+                .width(50)
+                .height(30);
+            }
+            .height(55)
+            .clickEffect({ level: ClickEffectLevel.HEAVY })
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+            // 音量优化
             Row() {
               SymbolGlyph($r('sys.symbol.speaker'))
                 .fontSize(20)
@@ -1238,6 +1269,36 @@ export struct SettingPage {
             .clickEffect({ level: ClickEffectLevel.HEAVY })
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+            // 智能解析
+            Row() {
+              SymbolGlyph($r('sys.symbol.t_circle'))
+                .fontSize(20)
+                .fontColor([this.themeColor])
+                .alignSelf(ItemAlign.Center)
+                .margin({ left: 15 })
+              Text('智能文件名解析歌曲标签')
+                .margin({ left: 8 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Toggle({ type: ToggleType.Switch, isOn: this.autoParseMusicName })
+                .selectedColor(this.themeColor)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+                .switchPointColor(Color.White)
+                .margin({ right: 18 })
+                .onChange((checked: boolean) => {
+                  this.autoParseMusicName = checked;
+                  PreferencesUtil.put('autoParseMusicName', this.autoParseMusicName)
+                })
+                .width(50)
+                .height(30);
+            }
+            .height(55)
+            .clickEffect({ level: ClickEffectLevel.HEAVY })
+
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+
             // 保存播放模式
             Row() {
               SymbolGlyph($r('sys.symbol.clock'))
@@ -1724,7 +1785,7 @@ export struct SettingPage {
       Row() {
         Text(){
           Span('API设置')
-          Span('\n(请注意:使用此功能您需拥有相关著作人的授权,否则可能导致版权法律纠纷,本软件对此不负任何责任。)')
+          Span('\n(请注意:使用此功能您需拥有相关著作人的授权,否则可能导致版权法律纠纷,本软件不提供任何API,本软件对此不负任何责任。)')
             .fontColor(this.themeColor).fontSize(14)
             // .fontStyle(FontStyle.Italic)
         }

+ 82 - 48
entry/src/main/ets/view/DeleteComptent.ets

@@ -3,6 +3,7 @@ import { CommonConstants } from '../common/constants/CommonConstants';
 import { VideoItem } from '../viewmodel/VideoItem';
 import { ArrayUtil, FileUtil, LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
 import MediaTable from '../common/util/MediaTable';
+import { taskpool } from '@kit.ArkTS';
 
 // 批量删除
 @Component
@@ -10,25 +11,20 @@ export struct DeleteComptent {
   @State isDeleteYuan: boolean = true
   @State isDeletePicture: boolean = true
   @State isDeleteLrc: boolean = true
-  onDeleteResult = (_result: boolean,item: VideoItem) => {
+  onDeleteResult = (_result: boolean) => {
   }
   onCancel = () => {
   }
   @Prop selectedFiles: Array<VideoItem>
   @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
   context = this.getUIContext().getHostContext() as common.UIAbilityContext
-  private table: MediaTable = new MediaTable(this.context)
 
 
   async aboutToAppear() {
     this.isDeleteYuan = PreferencesUtil.getBooleanSync('isDeleteYuan', true);
     this.isDeletePicture = PreferencesUtil.getBooleanSync('isDeletePicture', true);
     this.isDeleteLrc = PreferencesUtil.getBooleanSync('isDeleteLrc', true)
-    await new Promise<void>((resolve, reject) => {
-      this.table.getRdbStore(this.context,  (err:Error) => {
-        err ? reject(err) : resolve();
-      });
-    });
+
   }
 
   build() {
@@ -149,7 +145,7 @@ export struct DeleteComptent {
           .padding(15)
           .width(120)
           .onClick(() => {
-            this.deleteMultipleFiles()
+            this.doDeleteTask()
           })
           .backgroundColor($r('app.color.silvery'))
           .backgroundBlurStyle(BlurStyle.COMPONENT_THICK)
@@ -160,57 +156,95 @@ export struct DeleteComptent {
     .backgroundBlurStyle(BlurStyle.Regular)
   }
 
-  //多选删除文件
-  deleteMultipleFiles() {
-    if (ArrayUtil.isNotEmpty(this.selectedFiles)) {
-      this.selectedFiles.forEach((item) => {
-        console.info('onecold delete filePath = ' + item.filePath);
-        if (item.type === CommonConstants.TYPE_IS_DIR) {
-          this.table.deleteDataForParentPath(item.filePath, () => {
-            FileUtil.rmdir(item.filePath).then(() => {
-              this.onDeleteResult(true,item)
-            }).catch((error: Error) => {
-              console.error(error.message);
-              this.onDeleteResult(false,item)
-            });
-          });
 
+  doDeleteTask(){
+    const task = new taskpool.Task(
+      deleteMultipleFiles,
+      JSON.stringify(this.selectedFiles),
+      this.isDeleteYuan,this.isDeletePicture,this.isDeleteLrc,this.context
+    );
+    taskpool.execute(task, taskpool.Priority.HIGH).then((result) => {
+      if(result){
+        this.onDeleteResult(true)
+      }else {
+        this.onDeleteResult(false)
+      }
+
+    }).catch((error: Error) => {
+      console.error('Subtitle sync failed:', error);
+    });
+  }
 
-        } else {
-          this.table.deleteData(item, async () => {
-            if(this.isDeleteYuan){
-              await FileUtil.unlink(item.filePath)
 
-            }
-            console.info(`onecold 封面文件=: ${item.pixelMapPath}`);
-            const picPath = FileUtil.getFilePath(item.pixelMapPath)//获取图
-            if (this.isDeletePicture && item.pixelMapPath &&
-            FileUtil.accessSync(picPath)) {
-              await FileUtil.unlink(picPath);
-              console.info(`onecold 封面文件已删除: ${picPath}`);
-            }
-            const lyricPath = item.filePath?.replace(/\.[^/.]+$/, ".lrc");
-            console.info(`onecold 歌词文件=: ${lyricPath}`);
-            if(this.isDeleteLrc) {
-
-              if (lyricPath && FileUtil.accessSync(lyricPath)) {
-                await FileUtil.unlink(lyricPath);
-                console.info( `onecold 歌词文件已删除: ${lyricPath}`);
-              }
-            }
+}
 
-            this.onDeleteResult(true,item)
 
+//多选删除文件
+@Concurrent
+async function deleteMultipleFiles(
+  selectedFilesStr:string,
+  isDeleteYuan:boolean,
+  isDeletePicture:boolean,
+  isDeleteLrc:boolean,
+  context:Context
+) {
 
+  const selectedFiles: VideoItem[] = JSON.parse(selectedFilesStr)
+  if (ArrayUtil.isNotEmpty(selectedFiles)) {
+    const table: MediaTable = new MediaTable(context)
+    await new Promise<void>((resolve, reject) => {
+      table.getRdbStore(context,  (err:Error) => {
+        err ? reject(err) : resolve();
+      });
+    });
+    selectedFiles.forEach((item) => {
+      console.info('onecold delete filePath = ' + item.filePath);
+      if (item.type === CommonConstants.TYPE_IS_DIR) {
+        table.deleteDataForParentPath(item.filePath, () => {
+          FileUtil.rmdir(item.filePath).then(() => {
+            return true
+          }).catch((error: Error) => {
+            console.error(error.message);
+            return false
           });
+        });
 
 
-        }
-      });
+      } else {
+        table.deleteData(item, async () => {
+          if(isDeleteYuan){
+            await FileUtil.unlink(item.filePath)
 
+          }
+          console.info(`onecold 封面文件=: ${item.pixelMapPath}`);
+          const picPath = FileUtil.getFilePath(item.pixelMapPath)//获取图
+          if (isDeletePicture && item.pixelMapPath &&
+          FileUtil.accessSync(picPath)) {
+            await FileUtil.unlink(picPath);
+            console.info(`onecold 封面文件已删除: ${picPath}`);
+          }
+          const lyricPath = item.filePath?.replace(/\.[^/.]+$/, ".lrc");
+          console.info(`onecold 歌词文件=: ${lyricPath}`);
+          if(isDeleteLrc) {
 
-    }
-  }
+            if (lyricPath && FileUtil.accessSync(lyricPath)) {
+              await FileUtil.unlink(lyricPath);
+              console.info( `onecold 歌词文件已删除: ${lyricPath}`);
+            }
+
+          }
 
+          // this.onDeleteResult(true,item)
 
+
+        });
+
+
+      }
+    });
+
+
+  }
+  return true
 }
+

+ 57 - 14
entry/src/main/ets/view/LocalMusic.ets

@@ -61,7 +61,7 @@ import { image } from '@kit.ImageKit';
 import { AVCastPicker, AVCastPickerState, AVCastPickerStyle, avSession } from '@kit.AVSessionKit';
 import { UniversalDetector } from '@ohos/juniversalchardet';
 import { SettingPage } from '../pages/SettingPage';
-import { secondToTime } from '../common/util/CommUtils';
+import { secondToTime,getTransverterText } from '../common/util/CommUtils';
 import { CommonConstants2 } from '../common/util/CommonConstants2';
 import { hilog } from '@kit.PerformanceAnalysisKit';
 import { LazyDataSource } from '../common/util/LazyDataSource';
@@ -666,6 +666,9 @@ export struct LocalMusic {
     this.isSwipe = PreferencesUtil.getBooleanSync(SettingPage.IS_SWIPE, false)
     this.openSkipSongAnimate = PreferencesUtil.getBooleanSync(SettingPage.OPEN_SKIPSONG_ANIMATE, true)
     this.volumeSmall = PreferencesUtil.getBooleanSync('volumeSmall', false)
+    this.autoParseMusicName = PreferencesUtil.getBooleanSync('autoParseMusicName', false)
+
+
     if(this.volumeSmall){
       this.volume = 0.5
     }else{
@@ -744,9 +747,11 @@ export struct LocalMusic {
         //   data2: this.rootPath,
         //   data3: this.lockPath,
         //   data4: PreferencesUtil.getStringSync('COVER_API', ''),
+        //   data5: PreferencesUtil.getBooleanSync('autoParseMusicName', false),
         // });
         PreferencesUtil.putSync('isFirstApp', false)
       } else {
+
         workerInstance.postMessage({ code: 2, data: this.context });
         workerInstance.postMessage({ code: 3, data: this.context });
         workerInstance.postMessage({ code: 4, data: this.context });
@@ -767,7 +772,7 @@ export struct LocalMusic {
           this.mediaKuList = e.data.data
           Logger.info('onecold 查询媒体库列表 this.mediaKuList length= ' + this.mediaKuList.length)
           Utility.doSortListAscending(this.mediaKuList)
-          AppStorage.setOrCreate('mediaKuList', this.mediaKuList);
+          // AppStorage.setOrCreate('mediaKuList', this.mediaKuList);
           if (this.modeType === 1) {
             this.updateListData(this.mediaKuList)
           }
@@ -831,7 +836,7 @@ export struct LocalMusic {
     const documentViewPicker = new picker.DocumentViewPicker()
     let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
     this.rootPath = new fileUri.FileUri(documentSaveResult[0]).path
-    // this.rootPath = this.download_path
+    this.currentPath = this.rootPath
     this.lockPath = this.rootPath + '/' + LocalMusic.STR_LOCK_VIDEO
     this.favPath = this.rootPath + '/' + LocalMusic.STR_FAC_VIDEO
     this.historyPath = this.rootPath + '/' + LocalMusic.STR_HISTORY_MUSIC
@@ -931,6 +936,7 @@ export struct LocalMusic {
 
   async getSortedFiles(curPath: string, isWorkerPost?: boolean, destPath?: string, isOpen?: boolean) {
     this.isFavMusic = false
+    console.info('onecold getSortedFiles 1 curPath '+curPath)
     if (isWorkerPost) {
       workerInstance.postMessage({ code: 2, data: this.context }); //刷新媒体库列表
     }
@@ -938,8 +944,10 @@ export struct LocalMusic {
 
       return
     }
+    console.info('onecold getSortedFiles 2')
     this.isCanBack = false
     this.currentPath = curPath
+    console.info('onecold getSortedFiles this.currentPath ='+this.currentPath)
     this.titleBarModel.setTitleName(getFileDirName(this.currentPath, this.rootPath))
     if (curPath === this.rootPath) {
       this.titleBarModel.setLeftIconMain($r('app.media.menu'))
@@ -1104,7 +1112,7 @@ export struct LocalMusic {
         // Process media files
         if (!fPath.endsWith('.lrc') && Utility.isMeidaByExtension(fPath) && !fPath.endsWith('.srt')) {
           let mediaItem: VideoItem =
-            await Utility.uriGetMusicAssetsFromFile(this.context, fPath, CommonConstants.TYPE_LOCAL, true);
+            await Utility.uriGetMusicAssetsFromFile(this.context, fPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
           mediaItems.push(mediaItem);
           this.table.insert(mediaItem, (id: number) => {
 
@@ -1579,7 +1587,7 @@ export struct LocalMusic {
         if (this.currentPath !== this.lockPath) { //判断不是私密音乐,才入库
           if (!filePath.endsWith('.lrc') && Utility.isMeidaByExtension(filePath) && !filePath.endsWith('.srt')) {
             let mediaItem: VideoItem =
-              await Utility.uriGetMusicAssetsFromFile(this.context, filePath, CommonConstants.TYPE_LOCAL, true);
+              await Utility.uriGetMusicAssetsFromFile(this.context, filePath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
             this.table.insert(mediaItem, (id: number) => {
 
               // 删除目标路径缓存
@@ -1769,7 +1777,7 @@ export struct LocalMusic {
         // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap,
         //   item.size,item.pixelMapToString,item.artist,item.album,item.fileName)
         let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath,
-          CommonConstants.TYPE_LOCAL, true);
+          CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
         if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库
           this.table.insert(newItem, (id: number) => {
             //加入数据库
@@ -1844,7 +1852,7 @@ export struct LocalMusic {
         // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap,
         //   item.size,item.pixelMapToString,item.artist,item.album,item.fileName)
         let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath,
-          CommonConstants.TYPE_LOCAL, true);
+          CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
         if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库
           this.table.insert(newItem, (id: number) => {
             //加入数据库
@@ -1920,7 +1928,7 @@ export struct LocalMusic {
         }
 
         let mediaItem: VideoItem =
-          await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, true);
+          await Utility.uriGetMusicAssetsFromFile(this.context, newPath, CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
 
         this.videoLocalList[index] = mediaItem;
 
@@ -2231,6 +2239,7 @@ export struct LocalMusic {
         data2: this.currentPath,
         data3: this.lockPath,
         data4: PreferencesUtil.getStringSync('COVER_API', ''),
+        data5: PreferencesUtil.getBooleanSync('autoParseMusicName', false),
       });
     }
   }
@@ -2283,11 +2292,10 @@ export struct LocalMusic {
         this.isMultiSelect = false
         this.getUIContext().getPromptAction().closeCustomDialog(this.deleteComponentId)
       },
-      onDeleteResult:(result: boolean,item: VideoItem)=>{
+      onDeleteResult:(result: boolean)=>{
         this.isMultiSelect = false
         this.getUIContext().getPromptAction().closeCustomDialog(this.deleteComponentId)
         if(result){//如果删除成功,更新数据
-          this.videoLocalList = this.videoLocalList.filter(v => v !== item);
           this.selectedFiles = [];
           this.isAllSelected = false
           this.cache.delete(this.currentPath);
@@ -2332,7 +2340,7 @@ export struct LocalMusic {
           // let newItem = new VideoItem(item.name,newPath,newPath,item.type,item.videoSize,item.cTime,item.pixelMap,
           //   item.size,item.pixelMapToString,item.artist,item.album,item.fileName)
           let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath,
-            CommonConstants.TYPE_LOCAL, true);
+            CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
           if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库
             this.table.insert(newItem, (id: number) => {
               //加入数据库
@@ -2429,7 +2437,7 @@ export struct LocalMusic {
           //   item.size,item.pixelMapToString,item.artist,item.album,item.fileName)
 
           let newItem: VideoItem = await Utility.uriGetMusicAssetsFromFile(this.context, newPath,
-            CommonConstants.TYPE_LOCAL, true);
+            CommonConstants.TYPE_LOCAL, this.autoParseMusicName);
           if (!newPath.includes(this.lockPath)) { //如果移动到私密,就不插入数据库
             this.table.insert(newItem, (id: number) => {
               //加入数据库
@@ -2790,7 +2798,7 @@ export struct LocalMusic {
                 .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
                 .onClick(() => {
                   this.modeType = index;
-                  this.onModeChange();
+                  // this.onModeChange();
                 });
             }
             .layoutWeight(1)
@@ -5667,6 +5675,39 @@ export struct LocalMusic {
               .onClick( ()=>{
                 this.callFilePickerSelectFileForLyric()
               })
+
+              Button({ type: ButtonType.Capsule, stateEffect: true }) {
+                SymbolGlyph($r('sys.symbol.traditional_square'))
+                  .fontSize(25)
+                  .alignSelf(ItemAlign.Center)
+                  .margin({ left: 22,top:25})
+              }
+              .backgroundColor(Color.Transparent)
+              .visibility(StrUtil.isEmpty(this.tempLyricContent)&&StrUtil.isEmpty(this.lyricConStr)?
+              Visibility.None:Visibility.Visible)
+              .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
+              .onClick( ()=>{
+                 let lyricC = StrUtil.isNotEmpty(this.lyricConStr)?this.lyricConStr: this.tempLyricContent
+                this.lyricConStr = getTransverterText(lyricC,1)//1是简体转繁体 ,2是繁体转简体
+
+              })
+
+              Button({ type: ButtonType.Capsule, stateEffect: true }) {
+                Image($r('app.media.jianti'))
+                  .height(25)
+                  .fillColor($r('app.color.text_color'))
+                  .alignSelf(ItemAlign.Center)
+                  .margin({ left: 22,top:25})
+              }
+              .backgroundColor(Color.Transparent)
+              .visibility(StrUtil.isEmpty(this.tempLyricContent)&&StrUtil.isEmpty(this.lyricConStr)?
+              Visibility.None:Visibility.Visible)
+              .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
+              .onClick( ()=>{
+                let lyricC = StrUtil.isNotEmpty(this.lyricConStr)?this.lyricConStr: this.tempLyricContent
+                this.lyricConStr = getTransverterText(lyricC,2)//1是简体转繁体 ,2是繁体转简体
+
+              })
             }
             .layoutWeight(1)
 
@@ -5686,7 +5727,7 @@ export struct LocalMusic {
             })
         }
         .width('100%')
-        .height(StrUtil.isEmpty(this.lyricConStr)&&StrUtil.isEmpty(this.tempLyricContent)?100:'auto')
+        .height(StrUtil.isEmpty(this.lyricConStr)&&StrUtil.isEmpty(this.tempLyricContent)?120:'auto')
         .margin({ top: 10, bottom: 10 })
         .justifyContent(FlexAlign.Start)
 
@@ -7363,6 +7404,8 @@ export struct LocalMusic {
   private windowClass: window.Window = globalThis.windowClass
   @State volume: number = 0.5;
   @State volumeSmall: boolean = false
+  @State autoParseMusicName: boolean = false
+
   @State volumeShow: boolean = PlayConstants.VOLUME_SHOW;
   @State bright: number = PlayConstants.BRIGHT;
   @State brightShow: boolean = PlayConstants.BRIGHT_SHOW;

+ 6 - 4
entry/src/main/ets/workers/Worker.ets

@@ -19,7 +19,7 @@ workerPort.onmessage = async (e: MessageEvents) => {
 
   switch (e.data.code){
     case 1://第一次扫描文件夹入库
-      await scanDirectory(e.data.data1,e.data.data2,e.data.data3,e.data.data4).then(()=>{
+      await scanDirectory(e.data.data1,e.data.data2,e.data.data3,e.data.data4,e.data.data5).then(()=>{
         Logger.info('onecold scanDirectory 22 全部扫描完成后执行  ')
         // 全部扫描完成后执行
         workerPort.postMessage({  code: 101, data: {} });
@@ -47,7 +47,7 @@ workerPort.onmessage = async (e: MessageEvents) => {
 
 
 
-async function scanDirectory(context: Context, curPath: string, lockPath: string,cover_api:string): Promise<VideoItem[]> {
+async function scanDirectory(context: Context, curPath: string, lockPath: string,cover_api:string,autoParseMusicName:boolean): Promise<VideoItem[]> {
   const table: MediaTable = new MediaTable(context);
   let mediaItems: VideoItem[] = [];
 
@@ -70,15 +70,17 @@ async function scanDirectory(context: Context, curPath: string, lockPath: string
           if (FileUtil.isDirectory(fPath))  {
             // 用立即执行函数处理异步递归
             pendingTasks.push((async  () => {
-              const subItems = await scanDirectory(context, fPath, lockPath,cover_api);
+              const subItems = await scanDirectory(context, fPath, lockPath,cover_api,autoParseMusicName);
               mediaItems = mediaItems.concat(subItems);
             })());
           } else {
+            const isExists = await table.isRecordExists(fPath);
+            if(isExists )  return;
             if(!fPath.endsWith('.lrc')&&Utility.isMeidaByExtension(fPath)&&!fPath.endsWith('.srt')){
               // 用 Promise 包装媒体处理逻辑
               pendingTasks.push((async  () => {
                 let mediaItem:VideoItem = await Utility.uriGetMusicAssetsFromFile(context, fPath,
-                  CommonConstants.TYPE_LOCAL, true);
+                  CommonConstants.TYPE_LOCAL, autoParseMusicName);
 
                 mediaItems.push(mediaItem);
                 await new Promise<void>((resolve: (value: void) => void) => {

+ 8 - 0
entry/src/main/resources/base/element/string.json

@@ -395,6 +395,14 @@
       "name": "file_scan_tip_two",
       "value": "如果下载文件夹下新增歌曲,需要到这个文件扫描页面手动扫描下。"
     },
+    {
+      "name": "pcfile_scan_tip_one",
+      "value": "电脑和平板端的路径是:/Download/"
+    },
+    {
+      "name": "pcfile_scan_tip_one_1",
+      "value": "/文件夹下,即可扫描。"
+    },
     {
       "name": "start_scan",
       "value": "开始扫描"

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
entry/src/main/resources/base/media/jianti.svg


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác