瀏覽代碼

修复损坏的音乐拖动闪退的问题(这个是百度网盘下载的原因 没有下载完整)

onecold 4 月之前
父節點
當前提交
250a4fadac

+ 47 - 0
entry/src/main/ets/common/util/PlaybackSeekGuard.ets

@@ -0,0 +1,47 @@
+import { CommonConstants } from '../constants/CommonConstants';
+
+export interface SeekGuardDecision {
+  allow: boolean;
+  reason: string;
+  toastMessage: string;
+}
+
+export function isBaiduStreamingPlaybackUrl(url?: string): boolean {
+  if (!url) {
+    return false;
+  }
+  const lowerUrl = url.toLowerCase();
+  return lowerUrl.includes('pan.baidu.com/rest/2.0/xpan/file') && lowerUrl.includes('method=streaming');
+}
+
+export function resolveSeekGuardDecision(url: string, targetMs: number, songType?: number): SeekGuardDecision {
+  if (targetMs <= 0) {
+    return {
+      allow: true,
+      reason: 'non-positive-seek',
+      toastMessage: ''
+    };
+  }
+
+  if (isBaiduStreamingPlaybackUrl(url)) {
+    return {
+      allow: false,
+      reason: 'baidu-streaming-positive-seek',
+      toastMessage: '当前流播音源暂不支持拖动定位,已阻止本次跳转以避免闪退'
+    };
+  }
+
+  if (songType === CommonConstants.TYPE_BAIDU) {
+    return {
+      allow: false,
+      reason: 'baidu-song-positive-seek',
+      toastMessage: '当前百度流播音源暂不支持拖动定位,已阻止本次跳转以避免闪退'
+    };
+  }
+
+  return {
+    allow: true,
+    reason: 'allowed',
+    toastMessage: ''
+  };
+}

+ 44 - 15
entry/src/main/ets/view/LocalMusic.ets

@@ -116,6 +116,7 @@ import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { lyricService, SongData } from '../common/service/LyricService';
 import { shouldResolveRemoteLyricOnPlay, tryResolveRemotePlaybackLyric } from '../common/util/RemotePlaybackLyricUtil';
 import { resolvePlayerDismissMorphTarget } from '../common/util/PlayerDismissHelper';
+import { isBaiduStreamingPlaybackUrl, resolveSeekGuardDecision } from '../common/util/PlaybackSeekGuard';
 import {
   cloneVideoItem,
   preloadNextSongIfNeeded,
@@ -17395,6 +17396,39 @@ export struct LocalMusic {
     );
   }
 
+  private shouldShowSeekBlockedToast(source: string): boolean {
+    return source === 'top-progress-slider' ||
+      source === 'landscape-progress-slider' ||
+      source === 'fast-forward-button' ||
+      source === 'rewind-button' ||
+      source === 'session-fast-forward' ||
+      source === 'session-rewind' ||
+      source === 'session-seek';
+  }
+
+  private handleBlockedSeek(seekPos: number, source: string): boolean {
+    const decision = resolveSeekGuardDecision(this.videoUrl, seekPos, this.currentSong?.type);
+    if (decision.allow) {
+      return false;
+    }
+    Logger.warn(
+      TAG,
+      `[seek] blocked source=${source}, target=${seekPos}, reason=${decision.reason}, ` +
+      `url=${this.sanitizePlaybackUrl(this.videoUrl)}`
+    );
+    if (this.shouldShowSeekBlockedToast(source) && StrUtil.isNotEmpty(decision.toastMessage)) {
+      ToastUtil.showToast(decision.toastMessage);
+    }
+    if (this.waitInitialSeekBeforeStart) {
+      this.finishInitialSeekWait(`blocked-${decision.reason}`, true);
+    } else {
+      this.hideLoadIng();
+    }
+    this.isSeekTo = false;
+    this.logSeekTrace(`seek-blocked-${decision.reason}`, undefined, undefined, true);
+    return true;
+  }
+
   private stringForTime(timeMs: number): string {
     let totalSeconds: number | string = (timeMs / 1000);
     let newSeconds: number | string = totalSeconds % 60;
@@ -17726,7 +17760,7 @@ export struct LocalMusic {
 
 
     let that = this;
-    const isBaiduStreamingPlaybackUrl = this.isBaiduStreamingUrl(url);
+    const isBaiduStreamingPlayback = isBaiduStreamingPlaybackUrl(url);
     if (this.waitInitialSeekBeforeStart) {
       that.showLoadIng();
     } else {
@@ -17833,7 +17867,7 @@ export struct LocalMusic {
       this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", "3"); // 重连3次
 
       console.log(`heanup WebDAV IjkPlayer选项设置完成`);
-    } else if ((this.currentSong && isBaiduType(this.currentSong.type)) || isBaiduStreamingPlaybackUrl) {
+    } else if ((this.currentSong && isBaiduType(this.currentSong.type)) || isBaiduStreamingPlayback) {
       console.log(`heanup 为百度网盘播放设置IjkPlayer选项`);
       this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "15000000");
       this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "15000000");
@@ -19026,7 +19060,7 @@ export struct LocalMusic {
     }
     Logger.info('heanup sessionFastForwardCallback', `快进: ${time}秒, 目标位置: ${seeTime}ms`);
     this.setSeekToActionProgress(seeTime);
-    await this.seekTo(seeTime + "");
+    await this.seekTo(seeTime + "", 'session-fast-forward');
   };
   private getActiveDuration(): number {
     if (this.currentSong && isCueSplitItem(this.currentSong)) {
@@ -19045,14 +19079,6 @@ export struct LocalMusic {
     return duration;
   }
 
-  private isBaiduStreamingUrl(url: string): boolean {
-    if (StrUtil.isEmpty(url)) {
-      return false;
-    }
-    const lowerUrl = url.toLowerCase();
-    return lowerUrl.includes('pan.baidu.com/rest/2.0/xpan/file') && lowerUrl.includes('method=streaming');
-  }
-
   @Builder
   InlineSpectrumView() {
     SpectrumView({
@@ -19186,13 +19212,13 @@ export struct LocalMusic {
     }
     Logger.info('heanup sessionRewindCallback', `快退: ${time}秒, 目标位置: ${seeTime}ms`);
     this.setSeekToActionProgress(seeTime);
-    await this.seekTo(seeTime + "");
+    await this.seekTo(seeTime + "", 'session-rewind');
   };
   private sessionSeekCallback = async (seekTime: number) => {
     Logger.info('heanup sessionSeekCallback', `收到seek请求: ${seekTime}ms, 投播模式: ${this.isCastPlaying}`);
 
     // 投播模式直接调用seekTo,它会自动处理投播控制器的seek命令
-    await this.seekTo(seekTime + "");
+    await this.seekTo(seekTime + "", 'session-seek');
   };
 
   private async getFileSize(filePath: string): Promise<number> {
@@ -19444,6 +19470,9 @@ export struct LocalMusic {
       if (source !== 'internal') {
         this.beginSeekTrace(source, seekPos);
       }
+      if (this.handleBlockedSeek(seekPos, source)) {
+        return;
+      }
       this.isSeekTo = true
       try {
         if (isRemoteSong || source !== 'internal' || this.isSeekTraceActive()) {
@@ -19513,7 +19542,7 @@ export struct LocalMusic {
     if (value < 0) {
       value = 0;
     }
-    await this.seekTo(value + "");
+    await this.seekTo(value + "", 'rewind-button');
   }
 
   //快进10s,可再设置设置默认几秒
@@ -19537,7 +19566,7 @@ export struct LocalMusic {
     if (this.duration > 0 && value > this.duration) {
       value = this.duration;
     }
-    await this.seekTo(value + "");
+    await this.seekTo(value + "", 'fast-forward-button');
   }
 
   //下一个

+ 43 - 0
entry/src/ohosTest/ets/test/PlaybackSeekGuard.test.ets

@@ -0,0 +1,43 @@
+import { describe, it, expect } from '@ohos/hypium'
+import { resolveSeekGuardDecision } from '../../../main/ets/common/util/PlaybackSeekGuard'
+import { CommonConstants } from '../../../main/ets/common/constants/CommonConstants'
+
+export default function playbackSeekGuardTest() {
+  describe('PlaybackSeekGuardTest', () => {
+    it('blocksPositiveSeekForBaiduStreamingUrl', 0, () => {
+      const decision = resolveSeekGuardDecision(
+        'https://pan.baidu.com/rest/2.0/xpan/file?method=streaming&path=%2Fmusic%2Fbad.flac',
+        93000
+      )
+
+      expect(decision.allow).assertFalse()
+      expect(decision.reason).assertEqual('baidu-streaming-positive-seek')
+    })
+
+    it('allowsStartSeekForBaiduStreamingUrl', 0, () => {
+      const decision = resolveSeekGuardDecision(
+        'https://pan.baidu.com/rest/2.0/xpan/file?method=streaming&path=%2Fmusic%2Fbad.flac',
+        0
+      )
+
+      expect(decision.allow).assertTrue()
+    })
+
+    it('allowsSeekForLocalFile', 0, () => {
+      const decision = resolveSeekGuardDecision('/storage/media/100/local.flac', 93000)
+
+      expect(decision.allow).assertTrue()
+    })
+
+    it('blocksPositiveSeekForBaiduSongTypeEvenIfUrlDoesNotContainStreamingFlag', 0, () => {
+      const decision = resolveSeekGuardDecision(
+        'https://example-cdn.baidu.test/audio/segment-list.m3u8',
+        93000,
+        CommonConstants.TYPE_BAIDU
+      )
+
+      expect(decision.allow).assertFalse()
+      expect(decision.reason).assertEqual('baidu-song-positive-seek')
+    })
+  })
+}

+ 35 - 5
ijkplayer/src/main/cpp/ijkplayer/ff_ffplay.c

@@ -3134,6 +3134,22 @@ static int is_network_time_seekable_input(AVFormatContext *ic)
            strstr(format_name, "applehttp") != NULL;
 }
 
+static int has_renderable_video_stream(VideoState *is)
+{
+    if (!is || is->video_stream < 0 || !is->video_st) {
+        return 0;
+    }
+    return !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC);
+}
+
+static int allow_network_time_seek_for_stream(AVFormatContext *ic, VideoState *is)
+{
+    if (!is_network_time_seekable_input(ic) || !is) {
+        return 0;
+    }
+    return has_renderable_video_stream(is);
+}
+
 
 
 /* this thread gets the stream from the disk or the network */
@@ -3475,7 +3491,13 @@ static int read_thread(void *arg)
 
             int seek_flags = is->seek_flags;
             const int network_input = is_network_url(ffp->input_filename);
-            const int allow_network_time_seek = network_input && is_network_time_seekable_input(ic);
+            const int allow_network_time_seek = network_input && allow_network_time_seek_for_stream(ic, is);
+
+            if (network_input && is_network_time_seekable_input(ic) && !allow_network_time_seek) {
+                av_log(ffp, AV_LOG_WARNING,
+                       "heanup: disable network time seek for audio-only stream, format_name=%s\n",
+                       (ic->iformat && ic->iformat->name) ? ic->iformat->name : "unknown");
+            }
 
             if (network_input && !allow_network_time_seek && !(seek_flags & AVSEEK_FLAG_BYTE)) {
                 // 网络流优先使用字节seek,规避时间戳seek触发 ff_seek_frame_binary 的脆弱路径
@@ -3547,7 +3569,8 @@ static int read_thread(void *arg)
                                      (strcmp(ic->iformat->name, "asf") == 0 ||
                                       strcmp(ic->iformat->name, "asf_o") == 0 ||
                                       strstr(ic->iformat->name, "asf") != NULL));
-                int use_safe_byte_seek = is_asf_format;
+                int is_audio_only_stream = !has_renderable_video_stream(is);
+                int use_safe_byte_seek = is_asf_format || is_audio_only_stream;
                 int64_t local_seek_target = seek_target;
                 int64_t local_seek_min = seek_min;
                 int64_t local_seek_max = seek_max;
@@ -3569,8 +3592,8 @@ static int read_thread(void *arg)
                 effective_seek_target = local_seek_target;
 
                 if (use_safe_byte_seek) {
-                    // Avoid avformat_seek_file for fragile local ASF streams.
-                    const char *safe_seek_name = "ASF";
+                    // Avoid avformat_seek_file for fragile local ASF/audio-only streams.
+                    const char *safe_seek_name = is_asf_format ? "ASF" : "audio-only";
                     if (!ic->pb || !(ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
                         av_log(ffp, AV_LOG_WARNING, "heanup: %s stream is not byte-seekable, reject seek\n", safe_seek_name);
                         ret = -1;
@@ -4668,7 +4691,14 @@ int ffp_seek_to_l(FFPlayer *ffp, long msec)
     if (!is->ic || !is->ic->pb)
         return EIJK_NULL_IS_PTR;
 
-    allow_unseekable_network_seek = network_input && is_network_time_seekable_input(is->ic);
+    allow_unseekable_network_seek = network_input && allow_network_time_seek_for_stream(is->ic, is);
+
+    if (network_input && is_network_time_seekable_input(is->ic) && !allow_unseekable_network_seek) {
+        av_log(ffp, AV_LOG_WARNING,
+               "ffp_seek_to_l disable network time seek for audio-only stream, format_name=%s, msec=%ld\n",
+               (is->ic->iformat && is->ic->iformat->name) ? is->ic->iformat->name : "unknown",
+               msec);
+    }
 
     if (!(is->ic->pb->seekable & AVIO_SEEKABLE_NORMAL) && !allow_unseekable_network_seek) {
         av_log(ffp, AV_LOG_WARNING, "ffp_seek_to_l reject: stream not seekable, msec=%ld\n", msec);