feat: loudnorm (#1358)

* feat: loudnorm

* fix

* fix: android only

* fix: toString
This commit is contained in:
My-Responsitories
2025-09-28 22:16:33 +08:00
committed by GitHub
parent 046412b709
commit 22c57bf468
14 changed files with 307 additions and 199 deletions

View File

@@ -15,6 +15,7 @@ import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/models/user/danmaku_rule.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
@@ -54,7 +55,6 @@ import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:window_manager/window_manager.dart';
class PlPlayerController {
@@ -598,6 +598,7 @@ class PlPlayerController {
int? pgcType,
VideoType? videoType,
VoidCallback? callback,
Volume? volume,
}) async {
try {
_isLive = isLive;
@@ -637,6 +638,7 @@ class PlPlayerController {
dataSource,
_looping,
seekTo,
volume,
);
callback?.call();
// 获取视频时长 00:00
@@ -716,7 +718,7 @@ class PlPlayerController {
setting.put(SettingBoxKey.superResolutionType, type.index);
}
}
pp ??= _videoPlayerController?.platform as NativePlayer;
pp ??= _videoPlayerController!.platform!;
await pp.waitForPlayerInitialization;
await pp.waitForVideoControllerInitializationIfAttached;
switch (type) {
@@ -745,11 +747,14 @@ class PlPlayerController {
}
}
static final loudnormRegExp = RegExp('loudnorm=[^,]+');
// 配置播放器
Future<Player> _createVideoController(
DataSource dataSource,
PlaylistMode looping,
Duration? seekTo,
Volume? volume,
) async {
// 每次配置时先移除监听
removeListeners();
@@ -759,6 +764,7 @@ class PlPlayerController {
_position.value = Duration.zero;
// 初始化时清空弹幕,防止上次重叠
danmakuController?.clear();
Player player =
_videoPlayerController ??
Player(
@@ -769,20 +775,14 @@ class PlPlayerController {
: (isLive ? 16 * 1024 * 1024 : 4 * 1024 * 1024),
),
);
var pp = player.platform as NativePlayer;
final pp = player.platform!;
if (_videoPlayerController == null) {
if (isAnim) {
setShader(superResolutionType.value, pp);
}
String audioNormalization = Pref.audioNormalization;
audioNormalization = switch (audioNormalization) {
'0' => '',
'1' => ',${AudioNormalization.dynaudnorm.param}',
_ => ',$audioNormalization',
};
await pp.setProperty(
"af",
"scaletempo2=max-speed=8$audioNormalization",
"scaletempo2=max-speed=8",
);
if (Platform.isAndroid) {
await pp.setProperty("volume-max", "100");
@@ -804,7 +804,7 @@ class PlPlayerController {
if (dataSource.audioSource?.isNotEmpty == true) {
await pp.setProperty(
'audio-files',
UniversalPlatform.isWindows
Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:'),
);
@@ -816,7 +816,7 @@ class PlPlayerController {
if (dataSource.subFiles?.isNotEmpty == true) {
await pp.setProperty(
'sub-files',
UniversalPlatform.isWindows
Platform.isWindows
? dataSource.subFiles!.replaceAll(';', '\\;')
: dataSource.subFiles!.replaceAll(':', '\\:'),
);
@@ -835,12 +835,44 @@ class PlPlayerController {
);
player.setPlaylistMode(looping);
final Map<String, String>? filters;
if (kDebugMode || Platform.isAndroid) {
String audioNormalization = '';
audioNormalization = AudioNormalization.getParamFromConfig(
Pref.audioNormalization,
);
if (volume != null && volume.isNotEmpty) {
audioNormalization = audioNormalization.replaceFirstMapped(
loudnormRegExp,
(i) => '${i[0]}:$volume',
);
} else {
audioNormalization = audioNormalization.replaceFirst(
loudnormRegExp,
AudioNormalization.getParamFromConfig(Pref.fallbackNormalization),
);
}
filters = audioNormalization.isEmpty
? null
: {'lavfi-complex': '"[aid1] $audioNormalization [ao]"'};
} else {
filters = null;
}
if (kDebugMode) debugPrint(filters.toString());
if (dataSource.type == DataSourceType.asset) {
final assetUrl = dataSource.videoSource!.startsWith("asset://")
? dataSource.videoSource!
: "asset://${dataSource.videoSource!}";
await player.open(
Media(assetUrl, httpHeaders: dataSource.httpHeaders, start: seekTo),
Media(
assetUrl,
httpHeaders: dataSource.httpHeaders,
start: seekTo,
extras: filters,
),
play: false,
);
} else {
@@ -849,6 +881,7 @@ class PlPlayerController {
dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders,
start: seekTo,
extras: filters,
),
play: false,
);
@@ -874,9 +907,9 @@ class PlPlayerController {
if (dataSource.audioSource.isNullOrEmpty) {
SmartDialog.showToast('音频源为空');
} else {
await (_videoPlayerController!.platform as NativePlayer).setProperty(
await (_videoPlayerController!.platform!).setProperty(
'audio-files',
UniversalPlatform.isWindows
Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:'),
);
@@ -1658,7 +1691,7 @@ class PlPlayerController {
// dataStatus.status.close();
if (_videoPlayerController != null) {
var pp = _videoPlayerController!.platform as NativePlayer;
var pp = _videoPlayerController!.platform!;
await pp.setProperty('audio-files', '');
removeListeners();
await _videoPlayerController!.dispose();

View File

@@ -9,6 +9,7 @@ import 'package:get/get_rx/get_rx.dart';
import 'package:media_kit/ffi/src/allocation.dart';
import 'package:media_kit/ffi/src/utf8.dart';
import 'package:media_kit/generated/libmpv/bindings.dart' as generated;
import 'package:media_kit/media_kit.dart';
import 'package:media_kit/src/player/native/core/initializer.dart';
import 'package:media_kit/src/player/native/core/native_library.dart';
@@ -54,7 +55,15 @@ class MpvConvertWebp {
'${Pref.hardwareDecoding},auto-copy', // transcode only support copy
},
);
_setHeader();
NativePlayer.setHeader(
const {
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'referer': HttpString.baseUrl,
},
_mpv,
_ctx,
);
if (progress != null) {
_observeProperty('time-pos');
}
@@ -79,7 +88,7 @@ class MpvConvertWebp {
switch (event.ref.event_id) {
case generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
final prop = event.ref.data.cast<generated.mpv_event_property>().ref;
if (prop.name.cast<Utf8>().toDartString() == 'time-pos' &&
if (prop.name.toDartString() == 'time-pos' &&
prop.format == generated.mpv_format.MPV_FORMAT_DOUBLE) {
progress!.value = (prop.data.cast<Double>().value - start) / duration;
}
@@ -89,9 +98,9 @@ class MpvConvertWebp {
break;
case generated.mpv_event_id.MPV_EVENT_LOG_MESSAGE:
final log = event.ref.data.cast<generated.mpv_event_log_message>().ref;
final prefix = log.prefix.cast<Utf8>().toDartString().trim();
final level = log.level.cast<Utf8>().toDartString().trim();
final text = log.text.cast<Utf8>().toDartString().trim();
final prefix = log.prefix.toDartString().trim();
final level = log.level.toDartString().trim();
final text = log.text.toDartString().trim();
debugPrint('WebpConvert: $level $prefix : $text');
if (kDebugMode) {
_success = level != 'error' && level != 'fatal';
@@ -108,18 +117,8 @@ class MpvConvertWebp {
}
}
void _command(List<String> args) {
final pointers = args.map((e) => e.toNativeUtf8()).toList();
final arr = calloc<Pointer<Utf8>>(128);
for (int i = 0; i < args.length; i++) {
arr[i] = pointers[i];
}
_mpv.mpv_command(_ctx, arr.cast());
calloc.free(arr);
pointers.forEach(calloc.free);
}
void _command(List<String> args) =>
NativePlayer.statiCommand(args, _mpv, _ctx);
void _observeProperty(String property) {
final name = property.toNativeUtf8();
@@ -132,47 +131,6 @@ class MpvConvertWebp {
calloc.free(name);
}
void _setHeader() {
final property = 'http-header-fields'.toNativeUtf8();
// Allocate & fill the [mpv_node] with the headers.
final value = calloc<generated.mpv_node>();
final valRef = value.ref
..format = generated.mpv_format.MPV_FORMAT_NODE_ARRAY;
valRef.u.list = calloc<generated.mpv_node_list>();
final valList = valRef.u.list.ref
..num = 2
..values = calloc<generated.mpv_node>(2);
const entries = [
(
'user-agent',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
),
('referer', HttpString.baseUrl),
];
for (int i = 0; i < 2; i++) {
final (k, v) = entries[i];
valList.values[i]
..format = generated.mpv_format.MPV_FORMAT_STRING
..u.string = '$k: $v'.toNativeUtf8().cast();
}
_mpv.mpv_set_property(
_ctx,
property.cast(),
generated.mpv_format.MPV_FORMAT_NODE,
value.cast(),
);
// Free the allocated memory.
calloc.free(property);
for (int i = 0; i < valList.num; i++) {
calloc.free(valList.values[i].u.string);
}
calloc
..free(valList.values)
..free(valRef.u.list)
..free(value);
}
}
enum WebpPreset {