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

@@ -2,9 +2,24 @@ enum AudioNormalization {
disable('禁用'), disable('禁用'),
// ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7 // ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7
dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'), dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'),
loudnorm('预设 loudnorm', 'loudnorm=I=-16:LRA=11:TP=-1.5'),
custom('自定义参数'); custom('自定义参数');
final String title; final String title;
final String param; final String param;
const AudioNormalization(this.title, [this.param = '']); const AudioNormalization(this.title, [this.param = '']);
static String getTitleFromConfig(String config) => switch (config) {
'0' => disable.title,
'1' => dynaudnorm.title,
'2' => loudnorm.title,
_ => config,
};
static String getParamFromConfig(String config) => switch (config) {
'0' => disable.param,
'1' => dynaudnorm.param,
'2' => loudnorm.param,
_ => config,
};
} }

View File

@@ -37,6 +37,7 @@ class PlayUrlModel {
Dash? dash; Dash? dash;
List<Durl>? durl; List<Durl>? durl;
List<FormatItem>? supportFormats; List<FormatItem>? supportFormats;
Volume? volume;
int? lastPlayTime; int? lastPlayTime;
int? lastPlayCid; int? lastPlayCid;
String? curLanguage; String? curLanguage;
@@ -62,6 +63,7 @@ class PlayUrlModel {
supportFormats = (json['support_formats'] as List?) supportFormats = (json['support_formats'] as List?)
?.map<FormatItem>((e) => FormatItem.fromJson(e)) ?.map<FormatItem>((e) => FormatItem.fromJson(e))
.toList(); .toList();
volume = json['volume'] == null ? null : Volume.fromJson(json['volume']);
lastPlayTime = json['last_play_time']; lastPlayTime = json['last_play_time'];
lastPlayCid = json['last_play_cid']; lastPlayCid = json['last_play_cid'];
curLanguage = json['cur_language']; curLanguage = json['cur_language'];
@@ -304,3 +306,48 @@ class FormatItem {
codecs = (json['codecs'] as List?)?.fromCast<String>(); codecs = (json['codecs'] as List?)?.fromCast<String>();
} }
} }
class Volume {
Volume({
required this.measuredI,
required this.measuredLra,
required this.measuredTp,
required this.measuredThreshold,
required this.targetOffset,
required this.targetI,
required this.targetTp,
// required this.multiSceneArgs,
});
final num measuredI;
final num measuredLra;
final num measuredTp;
final num measuredThreshold;
final num targetOffset;
final num targetI;
final num targetTp;
// final MultiSceneArgs? multiSceneArgs;
factory Volume.fromJson(Map<String, dynamic> json) {
return Volume(
measuredI: json["measured_i"] ?? 0,
measuredLra: json["measured_lra"] ?? 0,
measuredTp: json["measured_tp"] ?? 0,
measuredThreshold: json["measured_threshold"] ?? 0,
targetOffset: json["target_offset"] ?? 0,
targetI: json["target_i"] ?? 0,
targetTp: json["target_tp"] ?? 0,
// multiSceneArgs: json["multi_scene_args"] == null ? null : MultiSceneArgs.fromJson(json["multi_scene_args"]),
);
}
@override
String toString() =>
'measured_I=$measuredI:measured_LRA=$measuredLra:measured_TP=$measuredTp:measured_thresh=$measuredThreshold';
bool get isNotEmpty =>
measuredI != 0 ||
measuredLra != 0 ||
measuredTp != 0 ||
measuredThreshold != 0;
}

View File

@@ -23,6 +23,7 @@ import 'package:PiliPlus/pages/setting/models/model.dart';
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
import 'package:PiliPlus/pages/setting/widgets/slide_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/slide_dialog.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/cache_manage.dart'; import 'package:PiliPlus/utils/cache_manage.dart';
import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/feed_back.dart';
@@ -32,6 +33,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/update.dart'; import 'package:PiliPlus/utils/update.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -453,94 +455,27 @@ List<SettingsModel> get extraSettings => [
} catch (_) {} } catch (_) {}
}, },
), ),
SettingsModel( if (kDebugMode || Platform.isAndroid)
settingsType: SettingsType.normal, SettingsModel(
title: '音量均衡', settingsType: SettingsType.normal,
setKey: SettingBoxKey.audioNormalization, title: '音量均衡',
leading: const Icon(Icons.multitrack_audio), setKey: SettingBoxKey.audioNormalization,
getSubtitle: () { leading: const Icon(Icons.multitrack_audio),
String audioNormalization = Pref.audioNormalization; getSubtitle: () {
// TODO: remove next version final audioNormalization = AudioNormalization.getTitleFromConfig(
if (audioNormalization == '2') { Pref.audioNormalization,
GStorage.setting.put(SettingBoxKey.audioNormalization, '1'); );
audioNormalization = '1'; String fallback = Pref.fallbackNormalization;
} if (fallback == '0') {
audioNormalization = switch (audioNormalization) { fallback = '';
'0' => AudioNormalization.disable.title,
'1' => AudioNormalization.dynaudnorm.title,
_ => audioNormalization,
};
return '当前:「$audioNormalization';
},
onTap: (setState) async {
String? result = await showDialog(
context: Get.context!,
builder: (context) {
String audioNormalization = Pref.audioNormalization;
final values = {'0', '1', audioNormalization, '2'};
return SelectDialog<String>(
title: '音量均衡',
value: audioNormalization,
values: values
.map(
(e) => (
e,
switch (e) {
'0' => AudioNormalization.disable.title,
'1' => AudioNormalization.dynaudnorm.title,
'2' => AudioNormalization.custom.title,
_ => e,
},
),
)
.toList(),
);
},
);
if (result != null) {
if (result == '2') {
String param = '';
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('自定义参数'),
content: TextField(
autofocus: true,
onChanged: (value) => param = value,
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
await GStorage.setting.put(
SettingBoxKey.audioNormalization,
param,
);
setState();
},
child: const Text('确定'),
),
],
);
},
);
} else { } else {
await GStorage.setting.put(SettingBoxKey.audioNormalization, result); fallback =
setState(); ',无参数时:「${AudioNormalization.getTitleFromConfig(fallback)}';
} }
} return '当前:「$audioNormalization$fallback';
}, },
), onTap: audioNormalization,
),
SettingsModel( SettingsModel(
settingsType: SettingsType.normal, settingsType: SettingsType.normal,
title: '超分辨率', title: '超分辨率',
@@ -1188,3 +1123,99 @@ List<SettingsModel> get extraSettings => [
}, },
), ),
]; ];
Future<void> audioNormalization(
VoidCallback setState, {
bool fallback = false,
}) async {
final key = fallback
? SettingBoxKey.fallbackNormalization
: SettingBoxKey.audioNormalization;
final result = await showDialog<String>(
context: Get.context!,
builder: (context) {
String audioNormalization = fallback
? Pref.fallbackNormalization
: Pref.audioNormalization;
Set<String> values = {
'0',
'1',
if (!fallback) '2',
audioNormalization,
'3',
};
return SelectDialog<String>(
title: fallback ? '服务器无loudnorm配置时使用' : '音量均衡',
toggleable: true,
value: audioNormalization,
values: values
.map(
(e) => (
e,
switch (e) {
'0' => AudioNormalization.disable.title,
'1' => AudioNormalization.dynaudnorm.title,
'2' => AudioNormalization.loudnorm.title,
'3' => AudioNormalization.custom.title,
_ => e,
},
),
)
.toList(),
);
},
);
if (result != null) {
if (result == '3') {
String param = '';
await showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('自定义参数'),
content: Column(
mainAxisSize: MainAxisSize.min,
spacing: 16,
children: [
const Text('等同于 --lavfi-complex="[aid1] 参数 [ao]"'),
TextField(
autofocus: true,
onChanged: (value) => param = value,
),
],
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: ColorScheme.of(context).outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
await GStorage.setting.put(key, param);
if (!fallback &&
PlPlayerController.loudnormRegExp.hasMatch(param)) {
audioNormalization(setState, fallback: true);
}
setState();
},
child: const Text('确定'),
),
],
);
},
);
} else {
await GStorage.setting.put(key, result);
if (result == '2') {
audioNormalization(setState, fallback: true);
}
setState();
}
}
}

View File

@@ -58,7 +58,7 @@ class SettingsModel {
SettingsType.sw1tch => SetSwitchItem( SettingsType.sw1tch => SetSwitchItem(
title: title, title: title,
subtitle: subtitle, subtitle: subtitle,
setKey: setKey, setKey: setKey!,
defaultVal: defaultVal, defaultVal: defaultVal,
onChanged: onChanged, onChanged: onChanged,
needReboot: needReboot, needReboot: needReboot,

View File

@@ -16,6 +16,7 @@ class SelectDialog<T> extends StatelessWidget {
final String title; final String title;
final List<(T, String)> values; final List<(T, String)> values;
final Widget Function(BuildContext, int)? subtitleBuilder; final Widget Function(BuildContext, int)? subtitleBuilder;
final bool toggleable;
const SelectDialog({ const SelectDialog({
super.key, super.key,
@@ -23,11 +24,12 @@ class SelectDialog<T> extends StatelessWidget {
required this.values, required this.values,
required this.title, required this.title,
this.subtitleBuilder, this.subtitleBuilder,
this.toggleable = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final titleMedium = Theme.of(context).textTheme.titleMedium!; final titleMedium = TextTheme.of(context).titleMedium!;
return AlertDialog( return AlertDialog(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
title: Text(title), title: Text(title),
@@ -37,7 +39,7 @@ class SelectDialog<T> extends StatelessWidget {
contentPadding: const EdgeInsets.symmetric(vertical: 12), contentPadding: const EdgeInsets.symmetric(vertical: 12),
content: SingleChildScrollView( content: SingleChildScrollView(
child: RadioGroup<T>( child: RadioGroup<T>(
onChanged: Navigator.of(context).pop, onChanged: (v) => Navigator.of(context).pop(v ?? value),
groupValue: value, groupValue: value,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -46,6 +48,7 @@ class SelectDialog<T> extends StatelessWidget {
(index) { (index) {
final item = values[index]; final item = values[index];
return RadioListTile<T>( return RadioListTile<T>(
toggleable: toggleable,
dense: true, dense: true,
value: item.$1, value: item.$1,
title: Text( title: Text(

View File

@@ -9,7 +9,7 @@ import 'package:get/get.dart';
class SetSwitchItem extends StatefulWidget { class SetSwitchItem extends StatefulWidget {
final String? title; final String? title;
final String? subtitle; final String? subtitle;
final String? setKey; final String setKey;
final bool defaultVal; final bool defaultVal;
final ValueChanged<bool>? onChanged; final ValueChanged<bool>? onChanged;
final bool needReboot; final bool needReboot;
@@ -21,7 +21,7 @@ class SetSwitchItem extends StatefulWidget {
const SetSwitchItem({ const SetSwitchItem({
this.title, this.title,
this.subtitle, this.subtitle,
this.setKey, required this.setKey,
this.defaultVal = false, this.defaultVal = false,
this.onChanged, this.onChanged,
this.needReboot = false, this.needReboot = false,

View File

@@ -1056,6 +1056,7 @@ class VideoDetailController extends GetxController
Duration? seekToTime, Duration? seekToTime,
Duration? duration, Duration? duration,
bool? autoplay, bool? autoplay,
Volume? volume,
}) async { }) async {
await plPlayerController.setDataSource( await plPlayerController.setDataSource(
DataSource( DataSource(
@@ -1095,6 +1096,7 @@ class VideoDetailController extends GetxController
}, },
width: firstVideo.width, width: firstVideo.width,
height: firstVideo.height, height: firstVideo.height,
volume: volume ?? this.volume,
); );
if (plPlayerController.enableSponsorBlock) { if (plPlayerController.enableSponsorBlock) {
@@ -1125,6 +1127,8 @@ class VideoDetailController extends GetxController
queryVideoUrl(defaultST: playedTime); queryVideoUrl(defaultST: playedTime);
} }
Volume? volume;
// 视频链接 // 视频链接
Future<void> queryVideoUrl({ Future<void> queryVideoUrl({
Duration? defaultST, Duration? defaultST,
@@ -1164,6 +1168,8 @@ class VideoDetailController extends GetxController
languages.value = data.language?.items; languages.value = data.language?.items;
currLang.value = data.curLanguage; currLang.value = data.curLanguage;
volume = data.volume;
if (data.acceptDesc?.contains('试看') == true) { if (data.acceptDesc?.contains('试看') == true) {
SmartDialog.showToast( SmartDialog.showToast(
'该视频为专属视频,仅提供试看', '该视频为专属视频,仅提供试看',

View File

@@ -43,7 +43,6 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:intl/intl.dart' show DateFormat; import 'package:intl/intl.dart' show DateFormat;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:media_kit/media_kit.dart';
class HeaderControl extends StatefulWidget { class HeaderControl extends StatefulWidget {
const HeaderControl({ const HeaderControl({
@@ -446,8 +445,9 @@ class HeaderControlState extends State<HeaderControl> {
SmartDialog.showToast('播放器未初始化'); SmartDialog.showToast('播放器未初始化');
return; return;
} }
final hwdec = await (player.platform as NativePlayer) final hwdec = await player.platform!.getProperty(
.getProperty('hwdec-current'); 'hwdec-current',
);
if (!context.mounted) return; if (!context.mounted) return;
showDialog( showDialog(
context: context, context: context,

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/super_resolution_type.dart';
import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/models/user/danmaku_rule.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/models_new/video/video_shot/data.dart';
import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.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:media_kit_video/media_kit_video.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class PlPlayerController { class PlPlayerController {
@@ -598,6 +598,7 @@ class PlPlayerController {
int? pgcType, int? pgcType,
VideoType? videoType, VideoType? videoType,
VoidCallback? callback, VoidCallback? callback,
Volume? volume,
}) async { }) async {
try { try {
_isLive = isLive; _isLive = isLive;
@@ -637,6 +638,7 @@ class PlPlayerController {
dataSource, dataSource,
_looping, _looping,
seekTo, seekTo,
volume,
); );
callback?.call(); callback?.call();
// 获取视频时长 00:00 // 获取视频时长 00:00
@@ -716,7 +718,7 @@ class PlPlayerController {
setting.put(SettingBoxKey.superResolutionType, type.index); setting.put(SettingBoxKey.superResolutionType, type.index);
} }
} }
pp ??= _videoPlayerController?.platform as NativePlayer; pp ??= _videoPlayerController!.platform!;
await pp.waitForPlayerInitialization; await pp.waitForPlayerInitialization;
await pp.waitForVideoControllerInitializationIfAttached; await pp.waitForVideoControllerInitializationIfAttached;
switch (type) { switch (type) {
@@ -745,11 +747,14 @@ class PlPlayerController {
} }
} }
static final loudnormRegExp = RegExp('loudnorm=[^,]+');
// 配置播放器 // 配置播放器
Future<Player> _createVideoController( Future<Player> _createVideoController(
DataSource dataSource, DataSource dataSource,
PlaylistMode looping, PlaylistMode looping,
Duration? seekTo, Duration? seekTo,
Volume? volume,
) async { ) async {
// 每次配置时先移除监听 // 每次配置时先移除监听
removeListeners(); removeListeners();
@@ -759,6 +764,7 @@ class PlPlayerController {
_position.value = Duration.zero; _position.value = Duration.zero;
// 初始化时清空弹幕,防止上次重叠 // 初始化时清空弹幕,防止上次重叠
danmakuController?.clear(); danmakuController?.clear();
Player player = Player player =
_videoPlayerController ?? _videoPlayerController ??
Player( Player(
@@ -769,20 +775,14 @@ class PlPlayerController {
: (isLive ? 16 * 1024 * 1024 : 4 * 1024 * 1024), : (isLive ? 16 * 1024 * 1024 : 4 * 1024 * 1024),
), ),
); );
var pp = player.platform as NativePlayer; final pp = player.platform!;
if (_videoPlayerController == null) { if (_videoPlayerController == null) {
if (isAnim) { if (isAnim) {
setShader(superResolutionType.value, pp); setShader(superResolutionType.value, pp);
} }
String audioNormalization = Pref.audioNormalization;
audioNormalization = switch (audioNormalization) {
'0' => '',
'1' => ',${AudioNormalization.dynaudnorm.param}',
_ => ',$audioNormalization',
};
await pp.setProperty( await pp.setProperty(
"af", "af",
"scaletempo2=max-speed=8$audioNormalization", "scaletempo2=max-speed=8",
); );
if (Platform.isAndroid) { if (Platform.isAndroid) {
await pp.setProperty("volume-max", "100"); await pp.setProperty("volume-max", "100");
@@ -804,7 +804,7 @@ class PlPlayerController {
if (dataSource.audioSource?.isNotEmpty == true) { if (dataSource.audioSource?.isNotEmpty == true) {
await pp.setProperty( await pp.setProperty(
'audio-files', 'audio-files',
UniversalPlatform.isWindows Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;') ? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:'), : dataSource.audioSource!.replaceAll(':', '\\:'),
); );
@@ -816,7 +816,7 @@ class PlPlayerController {
if (dataSource.subFiles?.isNotEmpty == true) { if (dataSource.subFiles?.isNotEmpty == true) {
await pp.setProperty( await pp.setProperty(
'sub-files', 'sub-files',
UniversalPlatform.isWindows Platform.isWindows
? dataSource.subFiles!.replaceAll(';', '\\;') ? dataSource.subFiles!.replaceAll(';', '\\;')
: dataSource.subFiles!.replaceAll(':', '\\:'), : dataSource.subFiles!.replaceAll(':', '\\:'),
); );
@@ -835,12 +835,44 @@ class PlPlayerController {
); );
player.setPlaylistMode(looping); 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) { if (dataSource.type == DataSourceType.asset) {
final assetUrl = dataSource.videoSource!.startsWith("asset://") final assetUrl = dataSource.videoSource!.startsWith("asset://")
? dataSource.videoSource! ? dataSource.videoSource!
: "asset://${dataSource.videoSource!}"; : "asset://${dataSource.videoSource!}";
await player.open( await player.open(
Media(assetUrl, httpHeaders: dataSource.httpHeaders, start: seekTo), Media(
assetUrl,
httpHeaders: dataSource.httpHeaders,
start: seekTo,
extras: filters,
),
play: false, play: false,
); );
} else { } else {
@@ -849,6 +881,7 @@ class PlPlayerController {
dataSource.videoSource!, dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders, httpHeaders: dataSource.httpHeaders,
start: seekTo, start: seekTo,
extras: filters,
), ),
play: false, play: false,
); );
@@ -874,9 +907,9 @@ class PlPlayerController {
if (dataSource.audioSource.isNullOrEmpty) { if (dataSource.audioSource.isNullOrEmpty) {
SmartDialog.showToast('音频源为空'); SmartDialog.showToast('音频源为空');
} else { } else {
await (_videoPlayerController!.platform as NativePlayer).setProperty( await (_videoPlayerController!.platform!).setProperty(
'audio-files', 'audio-files',
UniversalPlatform.isWindows Platform.isWindows
? dataSource.audioSource!.replaceAll(';', '\\;') ? dataSource.audioSource!.replaceAll(';', '\\;')
: dataSource.audioSource!.replaceAll(':', '\\:'), : dataSource.audioSource!.replaceAll(':', '\\:'),
); );
@@ -1658,7 +1691,7 @@ class PlPlayerController {
// dataStatus.status.close(); // dataStatus.status.close();
if (_videoPlayerController != null) { if (_videoPlayerController != null) {
var pp = _videoPlayerController!.platform as NativePlayer; var pp = _videoPlayerController!.platform!;
await pp.setProperty('audio-files', ''); await pp.setProperty('audio-files', '');
removeListeners(); removeListeners();
await _videoPlayerController!.dispose(); 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/allocation.dart';
import 'package:media_kit/ffi/src/utf8.dart'; import 'package:media_kit/ffi/src/utf8.dart';
import 'package:media_kit/generated/libmpv/bindings.dart' as generated; 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/initializer.dart';
import 'package:media_kit/src/player/native/core/native_library.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 '${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) { if (progress != null) {
_observeProperty('time-pos'); _observeProperty('time-pos');
} }
@@ -79,7 +88,7 @@ class MpvConvertWebp {
switch (event.ref.event_id) { switch (event.ref.event_id) {
case generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE: case generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
final prop = event.ref.data.cast<generated.mpv_event_property>().ref; 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) { prop.format == generated.mpv_format.MPV_FORMAT_DOUBLE) {
progress!.value = (prop.data.cast<Double>().value - start) / duration; progress!.value = (prop.data.cast<Double>().value - start) / duration;
} }
@@ -89,9 +98,9 @@ class MpvConvertWebp {
break; break;
case generated.mpv_event_id.MPV_EVENT_LOG_MESSAGE: case generated.mpv_event_id.MPV_EVENT_LOG_MESSAGE:
final log = event.ref.data.cast<generated.mpv_event_log_message>().ref; final log = event.ref.data.cast<generated.mpv_event_log_message>().ref;
final prefix = log.prefix.cast<Utf8>().toDartString().trim(); final prefix = log.prefix.toDartString().trim();
final level = log.level.cast<Utf8>().toDartString().trim(); final level = log.level.toDartString().trim();
final text = log.text.cast<Utf8>().toDartString().trim(); final text = log.text.toDartString().trim();
debugPrint('WebpConvert: $level $prefix : $text'); debugPrint('WebpConvert: $level $prefix : $text');
if (kDebugMode) { if (kDebugMode) {
_success = level != 'error' && level != 'fatal'; _success = level != 'error' && level != 'fatal';
@@ -108,18 +117,8 @@ class MpvConvertWebp {
} }
} }
void _command(List<String> args) { void _command(List<String> args) =>
final pointers = args.map((e) => e.toNativeUtf8()).toList(); NativePlayer.statiCommand(args, _mpv, _ctx);
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 _observeProperty(String property) { void _observeProperty(String property) {
final name = property.toNativeUtf8(); final name = property.toNativeUtf8();
@@ -132,47 +131,6 @@ class MpvConvertWebp {
calloc.free(name); 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 { enum WebpPreset {

View File

@@ -93,6 +93,7 @@ abstract class SettingBoxKey {
refreshDisplacement = 'refreshDisplacement', refreshDisplacement = 'refreshDisplacement',
showHotRcmd = 'showHotRcmd', showHotRcmd = 'showHotRcmd',
audioNormalization = 'audioNormalization', audioNormalization = 'audioNormalization',
fallbackNormalization = 'fallbackNormalization',
superResolutionType = 'superResolutionType', superResolutionType = 'superResolutionType',
preInitPlayer = 'preInitPlayer', preInitPlayer = 'preInitPlayer',
mainTabBarView = 'mainTabBarView', mainTabBarView = 'mainTabBarView',

View File

@@ -414,6 +414,9 @@ abstract class Pref {
static String get audioNormalization => static String get audioNormalization =>
_setting.get(SettingBoxKey.audioNormalization, defaultValue: '0'); _setting.get(SettingBoxKey.audioNormalization, defaultValue: '0');
static String get fallbackNormalization =>
_setting.get(SettingBoxKey.fallbackNormalization, defaultValue: '0');
static SuperResolutionType get superResolutionType { static SuperResolutionType get superResolutionType {
SuperResolutionType? superResolutionType; SuperResolutionType? superResolutionType;
final index = _setting.get(SettingBoxKey.superResolutionType); final index = _setting.get(SettingBoxKey.superResolutionType);

View File

@@ -1143,10 +1143,11 @@ packages:
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit path: media_kit
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" ref: "version_1.2.5"
url: "https://pub.dev" resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
source: hosted url: "https://github.com/My-Responsitories/media-kit.git"
source: git
version: "1.1.11" version: "1.1.11"
media_kit_libs_android_video: media_kit_libs_android_video:
dependency: "direct overridden" dependency: "direct overridden"
@@ -1184,10 +1185,11 @@ packages:
media_kit_libs_video: media_kit_libs_video:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit_libs_video path: "libs/universal/media_kit_libs_video"
sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288" ref: "version_1.2.5"
url: "https://pub.dev" resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
source: hosted url: "https://github.com/My-Responsitories/media-kit.git"
source: git
version: "1.0.5" version: "1.0.5"
media_kit_libs_windows_video: media_kit_libs_windows_video:
dependency: transitive dependency: transitive
@@ -1200,18 +1202,19 @@ packages:
media_kit_native_event_loop: media_kit_native_event_loop:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
name: media_kit_native_event_loop path: media_kit_native_event_loop
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40" ref: "version_1.2.5"
url: "https://pub.dev" resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
source: hosted url: "https://github.com/My-Responsitories/media-kit.git"
source: git
version: "1.0.9" version: "1.0.9"
media_kit_video: media_kit_video:
dependency: "direct main" dependency: "direct main"
description: description:
path: media_kit_video path: media_kit_video
ref: "version_1.2.5" ref: "version_1.2.5"
resolved-ref: "902e962556c0bde4d384cd8f72b814598e5dfcf2" resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
url: "https://github.com/bggRGjQaUbCoE/media-kit.git" url: "https://github.com/My-Responsitories/media-kit.git"
source: git source: git
version: "1.2.5" version: "1.2.5"
menu_base: menu_base:
@@ -1474,10 +1477,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: safe_local_storage name: safe_local_storage
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "2.0.1"
saver_gallery: saver_gallery:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1836,22 +1839,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
universal_platform:
dependency: "direct main"
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
uri_parser: uri_parser:
dependency: transitive dependency: transitive
description: description:
name: uri_parser name: uri_parser
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "3.0.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -219,7 +219,6 @@ dependencies:
stream_transform: any stream_transform: any
screen_brightness_platform_interface: any screen_brightness_platform_interface: any
mime: any mime: any
universal_platform: any
path: any path: any
collection: any collection: any
material_color_utilities: any material_color_utilities: any
@@ -234,15 +233,32 @@ dependency_overrides:
path: ^1.9.1 path: ^1.9.1
mime: ^2.0.0 mime: ^2.0.0
rxdart: ^0.28.0 rxdart: ^0.28.0
media_kit: 1.1.11 media_kit:
media_kit_libs_video: 1.0.5 git:
media_kit_native_event_loop: ^1.0.9 url: https://github.com/My-Responsitories/media-kit.git
font_awesome_flutter: 10.9.0 path: media_kit
ref: version_1.2.5
media_kit_video:
git:
url: https://github.com/My-Responsitories/media-kit.git
path: media_kit_video
ref: version_1.2.5
media_kit_libs_video:
git:
url: https://github.com/My-Responsitories/media-kit.git
path: libs/universal/media_kit_libs_video
ref: version_1.2.5
media_kit_native_event_loop:
git:
url: https://github.com/My-Responsitories/media-kit.git
path: media_kit_native_event_loop
ref: version_1.2.5
media_kit_libs_android_video: media_kit_libs_android_video:
git: git:
url: https://github.com/bggRGjQaUbCoE/media-kit.git url: https://github.com/bggRGjQaUbCoE/media-kit.git
path: libs/android/media_kit_libs_android_video path: libs/android/media_kit_libs_android_video
ref: version_1.2.5 ref: version_1.2.5
font_awesome_flutter: 10.9.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: