From 03f72370951ff7346189000022ea80fd558cfd8b Mon Sep 17 00:00:00 2001 From: TakChen Date: Thu, 9 Apr 2026 15:38:27 +0800 Subject: [PATCH 1/6] Update controller.dart --- lib/plugin/pl_player/controller.dart | 40 ++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index cc643246d..3d6a5faad 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -924,13 +924,27 @@ class PlPlayerController with BlockConfigMixin { if (onlyPlayAudio.value) { video = audio; } else { - extras['audio-files'] = - '"${Platform.isWindows ? audio.replaceAll(';', r'\;') : audio.replaceAll(':', r'\:')}"'; + final escapedAudio = Platform.isWindows + ? audio.replaceAll(';', r'\;') + : audio.replaceAll(':', r'\:'); + extras['audio-files'] = '"$escapedAudio"'; + // 兼容旧版 libmpv(如部分 Android TV 设备):该版本不支持 loadfile 的 options 参数, + // 通过 change-list 命令提前设置 audio-files,确保在所有 MPV 版本上均有效。 + // 新版 MPV 两种方式并存无影响(extras 会覆盖此处设置)。 + Utils.reportError('[DIAG][audio-files] hasAudio=true, escapedAudio=${escapedAudio.substring(0, escapedAudio.length.clamp(0, 80))}'); + try { + await player.command(['change-list', 'audio-files', 'clr', '']); + await player.command(['change-list', 'audio-files', 'set', audio]); + Utils.reportError('[DIAG][audio-files] change-list commands succeeded'); + } catch (e, st) { + Utils.reportError('[DIAG][audio-files] change-list command failed: $e', st); + } } if (kDebugMode || Platform.isAndroid) { String audioNormalization = AudioNormalization.getParamFromConfig( Pref.audioNormalization, ); + Utils.reportError('[DIAG][lavfi] audioNormalizationConfig=${Pref.audioNormalization}, param=${audioNormalization.isEmpty ? "(empty/disabled)" : audioNormalization.substring(0, audioNormalization.length.clamp(0, 80))}'); if (volume != null && volume.isNotEmpty) { audioNormalization = audioNormalization.replaceFirstMapped( loudnormRegExp, @@ -952,8 +966,23 @@ class PlPlayerController with BlockConfigMixin { } if (audioNormalization.isNotEmpty) { extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; + Utils.reportError('[DIAG][lavfi] setting lavfi-complex: [aid1] ${audioNormalization.substring(0, audioNormalization.length.clamp(0, 80))} [ao]'); + try { + await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']); + Utils.reportError('[DIAG][lavfi] set lavfi-complex succeeded'); + } catch (e, st) { + Utils.reportError('[DIAG][lavfi] set lavfi-complex failed: $e', st); + } + } else { + Utils.reportError('[DIAG][lavfi] audioNormalization is empty, skipping lavfi-complex'); } } + } else { + Utils.reportError('[DIAG][audio-files] audioSource is null or empty, skipping audio-files setup'); + } + + if (kDebugMode) { + debugPrint('[PlPlayer][DIAG] Opening video: $video, extras: $extras'); } await player.open( @@ -1091,6 +1120,13 @@ class PlPlayerController with BlockConfigMixin { if (kDebugMode) stream.log.listen(((PlayerLog log) { if (log.level == 'error' || log.level == 'fatal') { + // 过滤旧版 libmpv(如部分 Android TV 设备)不支持 loadfile options 参数时产生的兼容性日志 + // 这些错误不影响应用逻辑,仅表明该设备 MPV 版本较旧 + if (log.prefix == 'main' && + (log.text.startsWith('The loadfile option must be an integer:') || + log.text.startsWith('Command loadfile: argument index can\'t be parsed'))) { + return; + } Utils.reportError('${log.level}: ${log.prefix}: ${log.text}', null); } else { debugPrint(log.toString()); From 280bf835c32c1c69c98067dc06eb3b2ec225bbb3 Mon Sep 17 00:00:00 2001 From: TakChen Date: Thu, 9 Apr 2026 23:46:19 +0800 Subject: [PATCH 2/6] Update controller.dart --- lib/plugin/pl_player/controller.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 3d6a5faad..6e145c24d 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -985,6 +985,11 @@ class PlPlayerController with BlockConfigMixin { debugPrint('[PlPlayer][DIAG] Opening video: $video, extras: $extras'); } + // 记录传递给 player.open() 的 extras,便于诊断旧版 mpv 兼容性问题 + if (extras.isNotEmpty) { + Utils.reportError('[DIAG][open] extras keys: ${extras.keys.join(", ")}'); + } + await player.open( Media( video, @@ -993,6 +998,18 @@ class PlPlayerController with BlockConfigMixin { ), play: false, ); + + // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)在 loadfile replace 模式下会清除 audio-files 设置 + // 因此在 open() 之后再次通过 change-list 重新设置 audio-files,确保旧版 mpv 也能正确加载音频流 + if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { + try { + await player.command(['change-list', 'audio-files', 'clr', '']); + await player.command(['change-list', 'audio-files', 'set', audio]); + Utils.reportError('[DIAG][audio-files] post-open change-list succeeded'); + } catch (e, st) { + Utils.reportError('[DIAG][audio-files] post-open change-list failed: $e', st); + } + } } Future? refreshPlayer() { @@ -1180,6 +1197,12 @@ class PlPlayerController with BlockConfigMixin { event.startsWith("Can not open")) { return; } + // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)在 loadfile 传递 options 参数时 + // 会产生 "invalid parameter" 错误,这是已知的兼容性问题,不影响通过 change-list 预设的 audio-files + if (event == 'invalid parameter') { + Utils.reportError('[COMPAT][stream.error] invalid parameter (likely old mpv loadfile options compat issue, audio loaded via change-list)'); + return; + } Utils.reportError(event); // SmartDialog.showToast('视频加载错误, $event'); } From 7febc8962a57bb26e11f4362ef54ec6bc8625aa8 Mon Sep 17 00:00:00 2001 From: TakChen Date: Fri, 10 Apr 2026 00:29:28 +0800 Subject: [PATCH 3/6] Update controller.dart --- lib/plugin/pl_player/controller.dart | 62 ++++++++++++---------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 6e145c24d..f0e58f382 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -927,24 +927,19 @@ class PlPlayerController with BlockConfigMixin { final escapedAudio = Platform.isWindows ? audio.replaceAll(';', r'\;') : audio.replaceAll(':', r'\:'); - extras['audio-files'] = '"$escapedAudio"'; - // 兼容旧版 libmpv(如部分 Android TV 设备):该版本不支持 loadfile 的 options 参数, - // 通过 change-list 命令提前设置 audio-files,确保在所有 MPV 版本上均有效。 - // 新版 MPV 两种方式并存无影响(extras 会覆盖此处设置)。 - Utils.reportError('[DIAG][audio-files] hasAudio=true, escapedAudio=${escapedAudio.substring(0, escapedAudio.length.clamp(0, 80))}'); - try { - await player.command(['change-list', 'audio-files', 'clr', '']); - await player.command(['change-list', 'audio-files', 'set', audio]); - Utils.reportError('[DIAG][audio-files] change-list commands succeeded'); - } catch (e, st) { - Utils.reportError('[DIAG][audio-files] change-list command failed: $e', st); + // Android 上旧版 libmpv(如部分 Android TV armeabi-v7a 设备)不支持 loadfile 的 options 参数, + // 传入 extras 会导致整个 loadfile 命令失败(视频和音频都无法加载)。 + // 因此 Android 上不使用 extras 传递 audio-files,改为通过 change-list 命令方式(pre-open + post-open 双重保障)。 + if (!Platform.isAndroid) { + extras['audio-files'] = '"$escapedAudio"'; } + await player.command(['change-list', 'audio-files', 'clr', '']); + await player.command(['change-list', 'audio-files', 'set', audio]); } if (kDebugMode || Platform.isAndroid) { String audioNormalization = AudioNormalization.getParamFromConfig( Pref.audioNormalization, ); - Utils.reportError('[DIAG][lavfi] audioNormalizationConfig=${Pref.audioNormalization}, param=${audioNormalization.isEmpty ? "(empty/disabled)" : audioNormalization.substring(0, audioNormalization.length.clamp(0, 80))}'); if (volume != null && volume.isNotEmpty) { audioNormalization = audioNormalization.replaceFirstMapped( loudnormRegExp, @@ -965,31 +960,19 @@ class PlPlayerController with BlockConfigMixin { ); } if (audioNormalization.isNotEmpty) { - extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; - Utils.reportError('[DIAG][lavfi] setting lavfi-complex: [aid1] ${audioNormalization.substring(0, audioNormalization.length.clamp(0, 80))} [ao]'); - try { - await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']); - Utils.reportError('[DIAG][lavfi] set lavfi-complex succeeded'); - } catch (e, st) { - Utils.reportError('[DIAG][lavfi] set lavfi-complex failed: $e', st); + // Android 上同样不使用 extras 传递 lavfi-complex,避免旧版 mpv 的 loadfile 失败 + if (!Platform.isAndroid) { + extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; } - } else { - Utils.reportError('[DIAG][lavfi] audioNormalization is empty, skipping lavfi-complex'); + await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']); } } - } else { - Utils.reportError('[DIAG][audio-files] audioSource is null or empty, skipping audio-files setup'); } if (kDebugMode) { debugPrint('[PlPlayer][DIAG] Opening video: $video, extras: $extras'); } - // 记录传递给 player.open() 的 extras,便于诊断旧版 mpv 兼容性问题 - if (extras.isNotEmpty) { - Utils.reportError('[DIAG][open] extras keys: ${extras.keys.join(", ")}'); - } - await player.open( Media( video, @@ -999,15 +982,12 @@ class PlPlayerController with BlockConfigMixin { play: false, ); - // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)在 loadfile replace 模式下会清除 audio-files 设置 - // 因此在 open() 之后再次通过 change-list 重新设置 audio-files,确保旧版 mpv 也能正确加载音频流 - if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { - try { + // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)的 loadfile replace 命令会清除 audio-files 属性, + // 因此在 player.open() 之后再次通过 change-list 重新设置,确保旧版 mpv 也能正确加载独立音频流 + if (Platform.isAndroid) { + if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { await player.command(['change-list', 'audio-files', 'clr', '']); await player.command(['change-list', 'audio-files', 'set', audio]); - Utils.reportError('[DIAG][audio-files] post-open change-list succeeded'); - } catch (e, st) { - Utils.reportError('[DIAG][audio-files] post-open change-list failed: $e', st); } } } @@ -1017,10 +997,20 @@ class PlPlayerController with BlockConfigMixin { return null; } if (_videoPlayerController?.current.isNotEmpty ?? false) { - return _videoPlayerController!.open( + final future = _videoPlayerController!.open( _videoPlayerController!.current.last.copyWith(start: position), play: true, ); + // Android 上需要在 open() 后重新设置 audio-files(旧版 mpv loadfile replace 会清除该属性) + if (Platform.isAndroid) { + if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { + return future.then((_) async { + await _videoPlayerController?.command(['change-list', 'audio-files', 'clr', '']); + await _videoPlayerController?.command(['change-list', 'audio-files', 'set', audio]); + }); + } + } + return future; } return null; } From 745f2a2dc812f09808666476dd8054a661df78c7 Mon Sep 17 00:00:00 2001 From: TakChen Date: Fri, 10 Apr 2026 00:54:50 +0800 Subject: [PATCH 4/6] Update controller.dart --- lib/plugin/pl_player/controller.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index f0e58f382..51b33c3e6 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -930,11 +930,12 @@ class PlPlayerController with BlockConfigMixin { // Android 上旧版 libmpv(如部分 Android TV armeabi-v7a 设备)不支持 loadfile 的 options 参数, // 传入 extras 会导致整个 loadfile 命令失败(视频和音频都无法加载)。 // 因此 Android 上不使用 extras 传递 audio-files,改为通过 change-list 命令方式(pre-open + post-open 双重保障)。 + // 注意:change-list 对 audio-files 属性值的 URL 中的冒号同样需要转义(旧版 mpv 会将冒号解析为 key:value 分隔符) if (!Platform.isAndroid) { extras['audio-files'] = '"$escapedAudio"'; } await player.command(['change-list', 'audio-files', 'clr', '']); - await player.command(['change-list', 'audio-files', 'set', audio]); + await player.command(['change-list', 'audio-files', 'set', escapedAudio]); } if (kDebugMode || Platform.isAndroid) { String audioNormalization = AudioNormalization.getParamFromConfig( @@ -983,11 +984,13 @@ class PlPlayerController with BlockConfigMixin { ); // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)的 loadfile replace 命令会清除 audio-files 属性, - // 因此在 player.open() 之后再次通过 change-list 重新设置,确保旧版 mpv 也能正确加载独立音频流 + // 因此在 player.open() 之后再次通过 change-list 重新设置,确保旧版 mpv 也能正确加载独立音频流。 + // 注意:URL 中的冒号需要转义(旧版 mpv change-list 会将未转义的冒号解析为 key:value 分隔符) if (Platform.isAndroid) { if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { + final ea = audio.replaceAll(':', r'\:'); await player.command(['change-list', 'audio-files', 'clr', '']); - await player.command(['change-list', 'audio-files', 'set', audio]); + await player.command(['change-list', 'audio-files', 'set', ea]); } } } @@ -1002,11 +1005,13 @@ class PlPlayerController with BlockConfigMixin { play: true, ); // Android 上需要在 open() 后重新设置 audio-files(旧版 mpv loadfile replace 会清除该属性) + // URL 中的冒号需要转义(旧版 mpv change-list 会将未转义的冒号解析为 key:value 分隔符) if (Platform.isAndroid) { if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) { + final ea = audio.replaceAll(':', r'\:'); return future.then((_) async { await _videoPlayerController?.command(['change-list', 'audio-files', 'clr', '']); - await _videoPlayerController?.command(['change-list', 'audio-files', 'set', audio]); + await _videoPlayerController?.command(['change-list', 'audio-files', 'set', ea]); }); } } From 460561b5b5bc18781d6150acc7d03c903ab243f2 Mon Sep 17 00:00:00 2001 From: TakChen Date: Fri, 10 Apr 2026 17:01:39 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=A6=84=20refactor(pl=5Fplayer):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B9=B3=E5=8F=B0=E9=99=90=E5=88=B6=E5=B9=B6?= =?UTF-8?q?=E7=AE=80=E5=8C=96=E9=9F=B3=E9=A2=91=E6=A0=87=E5=87=86=E5=8C=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除 kDebugMode/Platform.isAndroid 的条件限制,使音频标准化在所有平台生效
精简旧版 mpv 兼容性错误处理,去除冗余的错误上报与调试日志
更新注释以准确描述 change-list 规避方案 --- lib/plugin/pl_player/controller.dart | 61 ++++++++++++---------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 51b33c3e6..aaa38b005 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -937,43 +937,37 @@ class PlPlayerController with BlockConfigMixin { await player.command(['change-list', 'audio-files', 'clr', '']); await player.command(['change-list', 'audio-files', 'set', escapedAudio]); } - if (kDebugMode || Platform.isAndroid) { - String audioNormalization = AudioNormalization.getParamFromConfig( - Pref.audioNormalization, + String audioNormalization = AudioNormalization.getParamFromConfig( + Pref.audioNormalization, + ); + if (volume != null && volume.isNotEmpty) { + audioNormalization = audioNormalization.replaceFirstMapped( + loudnormRegExp, + (i) => + 'loudnorm=${volume.format( + Map.fromEntries( + i.group(1)!.split(':').map((item) { + final parts = item.split('='); + return MapEntry(parts[0].toLowerCase(), num.parse(parts[1])); + }), + ), + )}', ); - if (volume != null && volume.isNotEmpty) { - audioNormalization = audioNormalization.replaceFirstMapped( - loudnormRegExp, - (i) => - 'loudnorm=${volume.format( - Map.fromEntries( - i.group(1)!.split(':').map((item) { - final parts = item.split('='); - return MapEntry(parts[0].toLowerCase(), num.parse(parts[1])); - }), - ), - )}', - ); - } else { - audioNormalization = audioNormalization.replaceFirst( - loudnormRegExp, - AudioNormalization.getParamFromConfig(Pref.fallbackNormalization), - ); - } - if (audioNormalization.isNotEmpty) { - // Android 上同样不使用 extras 传递 lavfi-complex,避免旧版 mpv 的 loadfile 失败 - if (!Platform.isAndroid) { - extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; - } - await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']); + } else { + audioNormalization = audioNormalization.replaceFirst( + loudnormRegExp, + AudioNormalization.getParamFromConfig(Pref.fallbackNormalization), + ); + } + if (audioNormalization.isNotEmpty) { + // Android 上不使用 extras 传递 lavfi-complex,避免旧版 mpv 的 loadfile options 参数导致整体失败 + if (!Platform.isAndroid) { + extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; } + await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']); } } - if (kDebugMode) { - debugPrint('[PlPlayer][DIAG] Opening video: $video, extras: $extras'); - } - await player.open( Media( video, @@ -1193,9 +1187,8 @@ class PlPlayerController with BlockConfigMixin { return; } // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)在 loadfile 传递 options 参数时 - // 会产生 "invalid parameter" 错误,这是已知的兼容性问题,不影响通过 change-list 预设的 audio-files + // 会产生 "invalid parameter" 错误,这是已知的兼容性问题,已通过 change-list 方式规避 if (event == 'invalid parameter') { - Utils.reportError('[COMPAT][stream.error] invalid parameter (likely old mpv loadfile options compat issue, audio loaded via change-list)'); return; } Utils.reportError(event); From 826f9cb578f5d9ff5e1e7d90f8245cad2336b174 Mon Sep 17 00:00:00 2001 From: TakChen Date: Fri, 10 Apr 2026 17:01:39 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A6=84=20refactor(pl=5Fplayer):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A7=E7=89=88=20libmpv=20=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=80=A7=E8=BF=87=E6=BB=A4=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除对旧版 libmpv 设备(如部分 Android TV armeabi-v7a)产生的兼容性错误日志的过滤代码,包括 loadfile options 参数解析错误及 invalid parameter 错误的特殊处理,统一交由错误上报流程处理 --- lib/plugin/pl_player/controller.dart | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index aaa38b005..bb4be60b8 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -1126,13 +1126,6 @@ class PlPlayerController with BlockConfigMixin { if (kDebugMode) stream.log.listen(((PlayerLog log) { if (log.level == 'error' || log.level == 'fatal') { - // 过滤旧版 libmpv(如部分 Android TV 设备)不支持 loadfile options 参数时产生的兼容性日志 - // 这些错误不影响应用逻辑,仅表明该设备 MPV 版本较旧 - if (log.prefix == 'main' && - (log.text.startsWith('The loadfile option must be an integer:') || - log.text.startsWith('Command loadfile: argument index can\'t be parsed'))) { - return; - } Utils.reportError('${log.level}: ${log.prefix}: ${log.text}', null); } else { debugPrint(log.toString()); @@ -1186,11 +1179,6 @@ class PlPlayerController with BlockConfigMixin { event.startsWith("Can not open")) { return; } - // 旧版 libmpv(如部分 armeabi-v7a Android TV 设备)在 loadfile 传递 options 参数时 - // 会产生 "invalid parameter" 错误,这是已知的兼容性问题,已通过 change-list 方式规避 - if (event == 'invalid parameter') { - return; - } Utils.reportError(event); // SmartDialog.showToast('视频加载错误, $event'); }