فهرست منبع

支持Dsf,aif,aiff,wav等格式获取内嵌歌词

onecold 1 سال پیش
والد
کامیت
85c2dca0fd

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

@@ -232,6 +232,13 @@ export default class MediaTable {
       obj.md5Str  = resultSet.getString(resultSet.getColumnIndex('md5Str'));
       obj.extra_json  = resultSet.getString(resultSet.getColumnIndex('extra_json'));
       obj.pyStr  = resultSet.getString(resultSet.getColumnIndex('pyStr'));
+
+      obj.bit_rate  = resultSet.getString(resultSet.getColumnIndex('bit_rate'));
+      obj.probe_score  = resultSet.getDouble(resultSet.getColumnIndex('probe_score'));
+      obj.year  = resultSet.getString(resultSet.getColumnIndex('year'));
+      obj.nb_streams  = resultSet.getDouble(resultSet.getColumnIndex('nb_streams'));
+      obj.nb_programs  = resultSet.getDouble(resultSet.getColumnIndex('nb_programs'));
+
       const valueBucket: relationalStore.ValuesBucket = obj
       // Step 4: 执行更新
       const updatePredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
@@ -549,6 +556,13 @@ export default class MediaTable {
     item.md5Str = safeGet('md5Str');
     item.extra_json = safeGet('extra_json');
     item.pyStr = safeGet('pyStr');
+
+    item.bit_rate = safeGet('bit_rate');
+    item.probe_score = safeGetNumber('probe_score');
+    item.year = safeGet('year');
+    item.nb_streams = safeGetNumber('nb_streams');
+    item.nb_programs = safeGetNumber('nb_programs');
+
     return item;
   }
 
@@ -620,5 +634,22 @@ function generateBucket(item: VideoItem): relationalStore.ValuesBucket {
     obj.pyStr = item.pyStr;
   }
 
+
+  if(item.bit_rate){
+    obj.bit_rate = item.bit_rate;
+  }
+  if(item.probe_score){
+    obj.probe_score = item.probe_score;
+  }
+  if(item.year){
+    obj.year = item.year;
+  }
+  if(item.nb_streams){
+    obj.nb_streams = item.nb_streams;
+  }
+  if(item.nb_programs){
+    obj.nb_programs = item.nb_programs;
+  }
+
   return obj;
 }

+ 14 - 1
entry/src/main/ets/common/util/RdbUtils.ets

@@ -61,12 +61,19 @@ export default class RdbUtils {
       '        md5Str TEXT,\n' +
       '        extra_json TEXT,\n' +
       '        pyStr TEXT,\n' +
+
+      '        bit_rate TEXT,\n' +
+      '        probe_score INTEGER DEFAULT 0,\n' +
+      '        year TEXT,\n' +
+      '        nb_streams INTEGER DEFAULT 0,\n' +
+      '        nb_programs INTEGER DEFAULT 0,\n' +
+
       '        mimeType TEXT' +
       ')',
     columns: ['id', 'name', 'filePath','mtype', 'videoSize', 'cTime','size', 'artist', 'album',
       'fileName','parentPath','isFav','pixelMapPath','pixelMapToString',
       'duration',   'sampleRate','playCount',  'lastPlayedStr', 'trackCount',
-      'lyricContent','md5Str','extra_json','pyStr','mimeType']
+      'lyricContent','md5Str','extra_json','pyStr','bit_rate','probe_score','year','nb_streams','nb_programs','mimeType']
   };
 
   constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
@@ -140,6 +147,12 @@ export default class RdbUtils {
             'extra_json': 'TEXT',
             'mimeType': 'TEXT',
             'pyStr': 'TEXT',
+
+            'bit_rate': 'TEXT',
+            'probe_score': 'INTEGER DEFAULT 0',
+            'year': 'TEXT',
+            'nb_streams': 'INTEGER DEFAULT 0',
+            'nb_programs': 'INTEGER DEFAULT 0',
           };
           
           // 逐个添加列,不依赖于检查结果

+ 258 - 1
entry/src/main/ets/common/util/Utility.ets

@@ -24,6 +24,66 @@ import { pinyin4js } from '@ohos/pinyin4js';
 import { VipData } from '../../viewmodel/VipData';
 import { LocalMusic } from '../../view/LocalMusic';
 import { VipPage } from '../../pages/VipPage';
+import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
+interface FFMpegTags {
+  album?: string;
+  artist?: string;
+  title?: string;
+  track?: string;
+  TYER?: string;
+  date?: string;
+  LYRICS?: string;
+  lyrics?: string;       // 小写变体
+  USLT?: string;         // ID3v2同步歌词
+  UNSYNCEDLYRICS?: string; // ID3v2非同步歌词
+  // Add any other tag properties you expect
+}
+
+interface FFprobeFormat {
+  filename: string;
+  nb_streams: number;
+  nb_programs: number;
+  format_name: string;
+  format_long_name: string;
+  duration: string;
+  size: string;
+  bit_rate: string;
+  probe_score: number;
+  tags?: FFMpegTags;
+}
+
+interface FFprobeStream {
+  // Define stream properties as needed
+  codec_type?: string;  // 流类型,如"audio"、"video"
+  sample_rate?: string; // 采样率
+  bit_rate?: string;    // 比特率
+  disposition?: StreamDisposition;  // 添加disposition属性
+}
+
+interface StreamDisposition {
+  default?: number;
+  dub?: number;
+  original?: number;
+  comment?: number;
+  lyrics?: number;
+  karaoke?: number;
+  forced?: number;
+  hearing_impaired?: number;
+  visual_impaired?: number;
+  clean_effects?: number;
+  attached_pic?: number;  // 添加封面图片标识
+  timed_thumbnails?: number;
+  captions?: number;
+  descriptions?: number;
+  metadata?: number;
+  dependent?: number;
+  still_image?: number;
+}
+
+interface FFprobeMetadata {
+  streams: FFprobeStream[];
+  format: FFprobeFormat;
+}
 
 export class Utility {
 
@@ -608,6 +668,17 @@ export class Utility {
 
   //获取音乐资源的属性值,
   static async uriGetMusicAssetsFromFile(context:Context,uri:string,type:number,isLoadPixelMap?:boolean): Promise<VideoItem> {
+    //华为官方不支持的格式 不支持内嵌歌词Dsf,aif,aiff,wav  不支持内嵌封面dsf,aif,aiff
+    if(StrUtil.isNotEmpty(uri)){
+      if(uri.toLowerCase().endsWith('.dsf')
+        ||uri.toLowerCase().endsWith('.aif')
+        // ||uri.toLowerCase().endsWith('.wav')
+        ||uri.toLowerCase().endsWith('.aiff')){
+        return Utility.readMetaInfoFFmpeg(context,uri,type)
+      }
+    }
+
+
     let item:VideoItem = new VideoItem('',uri,uri,type,0,'')
     try {
       console.info('asset file.uri: ', uri);
@@ -751,6 +822,8 @@ export class Utility {
         item.isFav = 0;
         item.playCount = 0;
         item.pyStr = pinyin4js.getShortPinyin(musicName)
+
+        item.lyricContent = await extractLyricsContent(uri)
         console.info('onecold pyStr = '+pinyin4js.getShortPinyin(musicName));
       })
     } catch (error) {
@@ -759,10 +832,122 @@ export class Utility {
 
     return item
 
-
   }
 
 
+  static async  readMetaInfoFFmpeg(context:Context,inputPath: string,type:number): Promise<VideoItem> {
+    return new Promise((resolve, reject) => {
+      let commands = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", inputPath];
+      let outputJson = "";
+
+      FFmpeg.execute(commands,  {
+        logCallback: (logLevel: number, logMessage: string) => console.log(`[${logLevel}]${logMessage}`),
+        outputCallback: (message: string) => {
+          outputJson += message;
+        },
+      }).then(async () => {
+        try {
+          let videoItem:VideoItem = new VideoItem('',inputPath,inputPath,type,0,'')
+          const metadata: FFprobeMetadata = JSON.parse(outputJson);
+          const format = metadata.format;
+
+          let file = fs.openSync(inputPath, fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE)
+          console.info('readMetaInfoFFmpeg asset file.path: ', file.path);
+          videoItem = new VideoItem(file.name,inputPath,inputPath,type,0,'')
+          await fs.stat(file.fd).then(async (stat: fs.Stat) => {
+
+            // 获取音频流的采样率
+            let sampleRate = '';
+            if (metadata.streams  && metadata.streams.length  > 0) {
+              // 查找第一个音频流
+              const audioStream = metadata.streams.find(stream  => StrUtil.isNotEmpty(stream.sample_rate));
+              if (audioStream&&audioStream.sample_rate)  {
+                sampleRate = audioStream.sample_rate;
+              }
+            }
+
+            let videoSize =  stat.size
+            let fileSize = Utility.formatFSize(videoSize)
+            //按照添加时间
+            let addTime = Utility.getFormatDateStr(new Date(),'yyyy-MM-dd HH:mm');
+            // Extract artist and title from tags
+            const tags = format.tags  || {};
+            const artist = tags.artist  || '';
+            const title = tags.title  || '';
+            const album = tags.album  || '';
+
+            let name: string = title;
+            if (!name) {
+              name = getFileNameWithoutExtension(inputPath);
+            }
+            // Create VideoItem
+            videoItem = new VideoItem(
+              name,
+              inputPath, // id can be generated or left empty
+              inputPath,
+              type, // assuming it's local
+              videoSize,
+              addTime // convert to ISO string
+            );
+
+            // Set additional properties from format metadata
+            videoItem.artist  = artist;
+            videoItem.album  = album;
+            videoItem.mimeType = format.format_name
+            videoItem.sampleRate = sampleRate
+            videoItem.fileName  = FileUtil.getFileName(inputPath);
+            if(format.duration)
+              videoItem.duration = convertSecondsToTime(format.duration.toString())
+            videoItem.size  = fileSize;
+            videoItem.bit_rate  = format.bit_rate;
+            videoItem.probe_score  = format.probe_score;
+            videoItem.nb_streams  = format.nb_streams;
+            videoItem.nb_programs  = format.nb_programs;
+            videoItem.year  = tags.TYER || tags.date  || ''; // try different tag names for year
+            videoItem.lyricContent  = tags.LYRICS || tags.lyrics  ||  tags.USLT || tags.UNSYNCEDLYRICS || '';
+
+            // 检查是否有封面图片流
+            const hasCover = metadata.streams.some(stream  =>
+            stream.disposition?.attached_pic  === 1
+            );
+            console.info('readMetaInfoFFmpeg asset hasCover: ', hasCover);
+            // 如果有封面图片,则提取
+            if (hasCover) {
+
+              // try {
+              //   let md5Name = await MD5.digestSync(inputPath)
+              //   // const imageName = `${md5Name}.jpg`;
+              //   const imagePath = `${context.filesDir}${FileUtil.separator}${md5Name}`;
+              //   console.info('readMetaInfoFFmpeg asset imagePath: ', imagePath);
+              //   // 提取封面图片
+              //   await extractCoverImage(inputPath, imagePath);
+              //
+              //   // 检查图片是否生成成功
+              //   if (fs.accessSync(imagePath))  {
+              //     videoItem.pixelMapPath  = imagePath;
+              //
+              //   }
+              // } catch (error) {
+              //   console.warn(' 提取封面图片失败:', error.message);
+              // }
+            }
+
+            console.info('Successfully  parsed metadata:', videoItem);
+            resolve(videoItem);
+          })
+
+
+        } catch (error) {
+          console.error('Failed  to parse metadata:', error);
+          reject(new Error('Failed to parse metadata: ' + error.message));
+        }
+      }).catch((error: Error) => {
+        console.error(`Execution  failed with error: ${error.message}`);
+        reject(error);
+      });
+    });
+  }
+
 
   private completionNum(num: number): string | number {
     if (num < 10) {
@@ -1371,4 +1556,76 @@ function parseMusicFileName(fileName: string): MusicInfo {
   }
 
   return result;
+}
+
+function getFileNameWithoutExtension(filePath: string): string {
+  const fileName = filePath.split('/').pop()  || '';
+  const lastDotIndex = fileName.lastIndexOf('.');
+  return lastDotIndex > 0 ? fileName.substring(0,  lastDotIndex) : fileName;
+}
+
+async function extractCoverImage(inputPath: string, outputPath: string): Promise<void> {
+  const commands = [
+    'ffmpeg',
+    '-i', inputPath,
+    '-an',              // 禁用音频
+    '-vcodec', 'copy',  // 直接复制视频流
+    '-f', 'image2',     // 强制输出为图片
+    '-y',               // 覆盖输出文件
+    outputPath
+  ];
+
+  return new Promise((resolve, reject) => {
+    FFmpeg.execute(commands,  {
+      logCallback: (logLevel: number, logMessage: string) => {
+        console.log(`[${logLevel}]${logMessage}`);
+      },
+      outputCallback: (message: string) => {
+        console.log(`FFmpeg  output: ${message}`);
+      },
+    }).then(() => resolve())
+      .catch((error: BusinessError) => reject(error));
+  });
+}
+
+/**
+ * 从音乐文件中提取歌词内容
+ * @param inputPath 音乐文件路径
+ * @returns Promise<string> 直接返回歌词内容,若无歌词则返回空字符串
+ */
+async function extractLyricsContent(inputPath: string): Promise<string> {
+  return new Promise(async (resolve, reject) => {
+    try {
+      // 1. 使用ffprobe获取元数据
+      const commands = [
+        'ffprobe',
+        '-v', 'quiet',
+        '-print_format', 'json',
+        '-show_format',
+        inputPath
+      ];
+
+      let outputJson = '';
+      await FFmpeg.execute(commands,  {
+        outputCallback: (message: string) => outputJson += message,
+      });
+
+      // 2. 解析歌词标签
+      const metadata:FFprobeMetadata = JSON.parse(outputJson);
+      const tags = metadata.format?.tags  || {};
+
+      // 3. 从常见标签中查找歌词(优先级顺序)
+      const lyricContent =
+        tags.LYRICS ||    // 标准标签
+        tags.lyrics  ||    // 小写变体
+        tags.USLT ||      // ID3v2标签
+        tags.UNSYNCEDLYRICS ||
+          '';
+
+      resolve(lyricContent.trim());
+
+    } catch (error) {
+      reject(`解析失败: ${error instanceof Error ? error.message  : String(error)}`);
+    }
+  });
 }

+ 28 - 0
entry/src/main/ets/pages/SettingPage.ets

@@ -55,6 +55,7 @@ export struct SettingPage {
   public static LONG_PRESS_SPEED: string = 'longPressSpeed';
   public static IS_PLAYLIST_BG_GRASS: string = 'isPlayListBgGrass';
   static readonly IS_SWIPE: string = 'isSwipe'
+  static readonly IS_AUTO_HIDE_PROGRESS: string = 'IS_AUTO_HIDE_PROGRESS';
 
   public static THEME_COLOR_LIST: Array<ThemeColorItem> = [
     { name: '玫瑰粉', color: '#FF4081', isVip: false },
@@ -112,6 +113,7 @@ export struct SettingPage {
   @State isCoverTopBig: boolean = false//顶部大封面部分手机显示会和播放控制页重叠
   @State isSwipe: boolean = false //listItem的左滑开关
   @State isPlayListBgGrass: boolean = true//是否播放列表玻璃透明效果
+  @State is_auto_hide_progress: boolean = false
 
 
   @State customizeBgPath: string | undefined = '';
@@ -232,6 +234,7 @@ export struct SettingPage {
     this.isCoverRectangle = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_RECTANGLE, true)
     this.isPlayListBgGrass = PreferencesUtil.getBooleanSync(SettingPage.IS_PLAYLIST_BG_GRASS, true)
     this.isSwipe = PreferencesUtil.getBooleanSync(SettingPage.IS_SWIPE, true)
+    this.is_auto_hide_progress = PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_HIDE_PROGRESS, false)
     // 只用 themeMode 控制主题
     this.themeMode = PreferencesUtil.getNumberSync(SettingPage.THEME_MODE, 0)
     this.applyThemeMode(this.themeMode)
@@ -1352,6 +1355,31 @@ 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() {
+        Text('自动隐藏进度条两侧的按钮')
+          .margin({ left: 18 })
+          .fontSize(15)
+          .fontColor(Color.Gray)
+          .fontWeight(480)
+          .layoutWeight(1)
+        Toggle({ type: ToggleType.Switch, isOn: this.is_auto_hide_progress })
+          .selectedColor(this.themeColor)
+          .switchPointColor(Color.White)
+          .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+          .onChange((checked: boolean) => {
+            this.is_auto_hide_progress = checked;
+            PreferencesUtil.put(SettingPage.IS_AUTO_HIDE_PROGRESS, this.is_auto_hide_progress)
+            this.sendChangeEvent()
+          })
+          .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() {

+ 34 - 56
entry/src/main/ets/view/LocalMusic.ets

@@ -539,14 +539,13 @@ export struct LocalMusic {
       this.doChangeBarHeight()
       let viewWidth = px2vp(size.width);
       let viewHeight = px2vp(size.height);
-      if (viewWidth > viewHeight) {
-        this.isLandscape = true;
+      if(this.isPhoneLan()){
+        this.is_auto_hide_progress = false
+        setTimeout(() => {
+          this.is_auto_hide_progress = true
+        }, 6000)
+      }else{
         this.startAutoHide()
-        // Logger.info('onecold 横屏 windowSizeChange isLandscape = ' + this.isLandscape)
-      } else {
-        this.isLandscape = false;
-        this.isAutoHide = false
-        // Logger.info('onecold 竖屏 windowSizeChange isLandscape = ' + this.isLandscape)
       }
 
     });
@@ -560,9 +559,12 @@ export struct LocalMusic {
   }
 
   startAutoHide() {
-    setTimeout(() => {
-      this.isAutoHide = true
-    }, 4000)
+    if(PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_HIDE_PROGRESS,false)){
+      this.is_auto_hide_progress = false
+      setTimeout(() => {
+        this.is_auto_hide_progress = true
+      }, 6000)
+    }
   }
 
   applyThemeMode(mode: number) {
@@ -4399,9 +4401,6 @@ export struct LocalMusic {
         LogUtil.info('this.currentSong.sampleRate =' + this.currentSong.sampleRate)
         LogUtil.info('this.currentSongmimeType =' + this.currentSong.mimeType)
         this.startPlayOrResumePlay()
-        // let outputPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.json'
-        // this.readCoverInfo(this.videoUrl,outputPath)
-        // this.readMediaInfo(this.videoUrl)
         break;
 
     }
@@ -4409,33 +4408,6 @@ export struct LocalMusic {
 
   }
 
-  // readCoverInfo(inputPath:string,outputPath:string){
-  //   console.info("onecold FFmpeg inputPath = "+inputPath);
-  //   console.info("onecold FFmpeg outputPath = "+outputPath);
-  //   let commands = ["ffmpeg", "-i", inputPath, "-f ffmetadata",outputPath];
-  //   FFmpeg.execute(commands, {
-  //     logCallback: (logLevel: number, logMessage: string) => console.log(`[${logLevel}]${logMessage}`),
-  //     progressCallback: (message: string) => console.log(`[onecold FFmpeg progress]${JSON.stringify(FFProgressMessageParser.parse(message))}`),
-  //   }).then(() => {
-  //     console.info("onecold FFmpeg execution succeeded.");
-  //   }).catch((error: Error) => {
-  //     console.error(`onecold FFmpeg execution failed with error: ${error.message}`);
-  //   });
-  // }
-
-
-  // readMediaInfo(inputPath:string){
-  //   let commands = ["ffprobe", "-v", "info", "-of", "json", "-show_entries", "stream=sample_rate,bit_rate", "-i", inputPath];
-  //   let outputJson = "";
-  //   FFmpeg.execute(commands, {
-  //     logCallback: (logLevel: number, logMessage: string) => console.log(`[${logLevel}]${logMessage}`),
-  //     outputCallback: (message: string) => { outputJson += message; },
-  //   }).then(() => {
-  //     console.info(`Execution succeeded with output: ${outputJson}`);
-  //   }).catch((error: Error) => {
-  //     console.error(`Execution failed with error: ${error.message}`);
-  //   });
-  // }
 
   @Builder
   PlayController() {
@@ -4475,6 +4447,8 @@ export struct LocalMusic {
             this.isShowPlay = true;
             let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc'
             this.initLyric(lyricPath);
+            this.startAutoHide()
+
 
           } else {
             ToastUtil.showToast('当前播放列表为空,请先导入音乐。')
@@ -6040,7 +6014,6 @@ export struct LocalMusic {
     .backgroundBrightness({ rate: this.isPuraWP() ? 0.1 : 0, lightUpDegree: -0.1 })
     .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
     .onClick(() => {
-      this.isAutoHide = false
       this.startAutoHide()
     })
   }
@@ -6136,7 +6109,7 @@ export struct LocalMusic {
   /**
    * 初始化歌词加载与展示逻辑
    * @param lyricPath 歌词文件路径(支持普通.lrc和加密.lrcc)
-   * @param isToast 是否弹出Toast提示(可选)获取在线歌词为true
+   * @param isOnLineAndToast 是否弹出Toast提示(可选)获取在线歌词为true
    * @param isApi2 是否使用API2获取歌词(可选)
    *
    * 主要流程:
@@ -6152,7 +6125,7 @@ export struct LocalMusic {
    * - 支持加密歌词文件(.lrcc),自动检测并解密。
    * - 歌词内容解析后通过lyricController驱动UI渲染。
    */
-  async initLyric(lyricPath: string, isToast?: boolean, isApi2?: boolean) {
+  async initLyric(lyricPath: string, isOnLineAndToast?: boolean, isApi2?: boolean) {
     if (StrUtil.isEmpty(lyricPath)) {
       return
     }
@@ -6195,11 +6168,16 @@ export struct LocalMusic {
     this.lyricController.setLyric(null)
     this.lyricControllerSingle.setLyric(null)
     let jiaMilyricPath = lyricPath.substring(0, lyricPath.lastIndexOf('.')) + '.lrcc'
-    LogUtil.debug("onecold lyricPath =" + lyricPath)
-
-    const neiqianLrc = LrcParser.getLyrics(this.videoUrl); //获取内嵌歌词
-    if (StrUtil.isNotEmpty(neiqianLrc) && !isToast) {
-      LogUtil.debug("onecold 找到内嵌歌词 neiqianLrc =" + neiqianLrc)
+    console.log("onecold lyricPath =" + lyricPath)
+    let neiqianLrc = ''
+    let lyContent = this.currentSong?.lyricContent
+    if(lyContent&&StrUtil.isNotEmpty(lyContent)){//取数据库里面的歌词lyContent
+       neiqianLrc = lyContent
+    }else{
+       neiqianLrc = LrcParser.getLyrics(this.videoUrl); //获取内嵌歌词
+    }
+    if (StrUtil.isNotEmpty(neiqianLrc) && !isOnLineAndToast) {
+      console.log("onecold 找到内嵌歌词 neiqianLrc =" + neiqianLrc)
       //赋值给this.lyricContent,播控中心才可以显示歌词
       this.lyricContent = neiqianLrc
       // 将文件内容按行分割成字符串数组
@@ -6215,7 +6193,7 @@ export struct LocalMusic {
     }
 
 
-    if (isToast || ((!FileUtil.accessSync(lyricPath) && !FileUtil.accessSync(jiaMilyricPath))
+    if (isOnLineAndToast || ((!FileUtil.accessSync(lyricPath) && !FileUtil.accessSync(jiaMilyricPath))
       && !Utility.isVideoByExtension(this.videoUrl))) {
       if (this.currentSong !== undefined) {
         // ToastUtil.showShort('正在为你搜索在线歌词!')
@@ -6264,7 +6242,7 @@ export struct LocalMusic {
               this.lyricController.setLyric(null)
               this.lyricControllerXF.setLyric(null)
               this.lyricControllerSingle.setLyric(null)
-              if (isToast) {
+              if (isOnLineAndToast) {
                 ToastUtil.showShort('未获取到歌词!')
               }
             }
@@ -6274,7 +6252,7 @@ export struct LocalMusic {
             this.lyricController.setLyric(null)
             this.lyricControllerXF.setLyric(null)
             this.lyricControllerSingle.setLyric(null)
-            if (isToast) {
+            if (isOnLineAndToast) {
               ToastUtil.showShort('未获取到歌词!')
             }
           }
@@ -6285,7 +6263,7 @@ export struct LocalMusic {
         this.lyricController.setLyric(null)
         this.lyricControllerXF.setLyric(null)
         this.lyricControllerSingle.setLyric(null)
-        if (isToast) {
+        if (isOnLineAndToast) {
           ToastUtil.showShort('未获取到歌词!')
         }
       }
@@ -6961,7 +6939,7 @@ export struct LocalMusic {
           }
         }
       }
-      .visibility(this.isPhoneLan() && this.isAutoHide ? Visibility.None : Visibility.Visible)
+      .visibility(this.is_auto_hide_progress ? Visibility.None : Visibility.Visible)
       .animation({
         duration: 666,
         curve: 'ease-in-out' // 可选动画曲线
@@ -7036,7 +7014,7 @@ export struct LocalMusic {
             }
           })
       }
-      .visibility(this.isPhoneLan() && this.isAutoHide ? Visibility.None : Visibility.Visible)
+      .visibility( this.is_auto_hide_progress ? Visibility.None : Visibility.Visible)
       .animation({
         duration: 666,
         curve: 'ease-in-out' // 可选动画曲线
@@ -7061,7 +7039,6 @@ export struct LocalMusic {
     .width(this.isPhonePortrait() ? '93.4%'
       : this.currentHeightBreakpoint == 1 && this.currentWidthBreakpoint == 2 ? '99%' : '82%')
     .height(this.isPhoneLan() ? 25 : 33)
-    // .visibility(this.isPhoneLan()&&this.isAutoHide?Visibility.None:Visibility.Visible)
     .animation({
       duration: 666,
       curve: 'ease-in-out' // 可选动画曲线
@@ -7084,7 +7061,7 @@ export struct LocalMusic {
     return false
   }
 
-  @State isAutoHide: boolean = true //手机横屏自动隐藏播放进退条,点击屏幕可以显示,倒计时4秒后又自动隐藏
+  @State is_auto_hide_progress: boolean = false //手机横屏自动隐藏播放进退条,点击屏幕可以显示,倒计时4秒后又自动隐藏
   @State isCoverRectangle: boolean = false
   @State isCoverTop: boolean = true
 
@@ -9447,6 +9424,7 @@ export struct LocalMusic {
 
 
           let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc'
+
           await this.initLyric(lyricPath);
 
 

+ 6 - 1
entry/src/main/ets/viewmodel/VideoItem.ets

@@ -45,9 +45,14 @@ export class VideoItem  {
   playCount?:number//播放次数
   lyricContent?:string//歌词内容
 
-  md5Str?:string
+  md5Str?:string//直接用来保存音质的判断
   extra_json?:string
   pyStr?:string//中文歌曲名称拼音的首字母
+  bit_rate?:string//比特率
+  probe_score?:number//评分
+  year?:string//年份
+  nb_streams?:number//流数量
+  nb_programs?:number//节目数量
 
   constructor(name: string, id: string, filePath: string, type:number,videoSize:number,cTime: string,pixelMap?: image.PixelMap,
     size?: string,pixelMapPath?: string,artist?: string,album?: string,fileName?: string, lastPlayed?:Date) {

+ 18 - 0
oh-package-lock.json5

@@ -21,9 +21,11 @@
     "@seagazer/cclyric@lib": "@seagazer/cclyric@lib",
     "@sgaolei/lrc_parser@^1.0.0": "@sgaolei/lrc_parser@1.0.0",
     "@simplepeng/spider-man@^1.0.1": "@simplepeng/spider-man@1.0.1",
+    "@sj/ffmpeg@^1.2.5": "@sj/ffmpeg@1.2.5",
     "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har",
     "class-transformer@^0.5.1": "class-transformer@0.5.1",
     "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
+    "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg": "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg",
     "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser": "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser",
     "pako@^2.1.0": "pako@2.1.0"
   },
@@ -148,6 +150,16 @@
       "resolved": "https://repo.harmonyos.com/ohpm/@simplepeng/spider-man/-/spider-man-1.0.1.har",
       "registryType": "ohpm"
     },
+    "@sj/ffmpeg@1.2.5": {
+      "name": "@sj/ffmpeg",
+      "version": "1.2.5",
+      "integrity": "sha512-IshbyU5FaIMYkEpXiyBtCQfBWbaBUHz1nt04XiVjbxiC+AjGJ1bHUrFMQINe8YIPGRPwcKrafXPxSnkidRXMRQ==",
+      "resolved": "https://repo.harmonyos.com/ohpm/@sj/ffmpeg/-/ffmpeg-1.2.5.har",
+      "registryType": "ohpm",
+      "dependencies": {
+        "libffmpeg.so": "file:./src/main/cpp/types/libffmpeg"
+      }
+    },
     "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": {
       "name": "@taobao-ohos/utdid_sdk",
       "version": "1.0.9",
@@ -168,6 +180,12 @@
       "resolved": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
       "registryType": "local"
     },
+    "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg": {
+      "name": "libffmpeg.so",
+      "version": "1.2.5",
+      "resolved": "oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg",
+      "registryType": "local"
+    },
     "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser": {
       "name": "liblrcparser.so",
       "version": "1.0.0",

+ 2 - 1
oh-package.json5

@@ -24,7 +24,8 @@
     "@sgaolei/lrc_parser": "^1.0.0",
     "@ohos/pinyin4js": "^2.0.2",
     "@simplepeng/spider-man": "^1.0.1",
-    "@ohos/juniversalchardet": "^2.0.2"
+    "@ohos/juniversalchardet": "^2.0.2",
+    "@sj/ffmpeg": "^1.2.5"
   },
   "dynamicDependencies": {}
 }