mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: loudnorm (#1358)
* feat: loudnorm * fix * fix: android only * fix: toString
This commit is contained in:
committed by
GitHub
parent
046412b709
commit
22c57bf468
@@ -2,9 +2,24 @@ enum AudioNormalization {
|
||||
disable('禁用'),
|
||||
// ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7
|
||||
dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'),
|
||||
loudnorm('预设 loudnorm', 'loudnorm=I=-16:LRA=11:TP=-1.5'),
|
||||
custom('自定义参数');
|
||||
|
||||
final String title;
|
||||
final String 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class PlayUrlModel {
|
||||
Dash? dash;
|
||||
List<Durl>? durl;
|
||||
List<FormatItem>? supportFormats;
|
||||
Volume? volume;
|
||||
int? lastPlayTime;
|
||||
int? lastPlayCid;
|
||||
String? curLanguage;
|
||||
@@ -62,6 +63,7 @@ class PlayUrlModel {
|
||||
supportFormats = (json['support_formats'] as List?)
|
||||
?.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
.toList();
|
||||
volume = json['volume'] == null ? null : Volume.fromJson(json['volume']);
|
||||
lastPlayTime = json['last_play_time'];
|
||||
lastPlayCid = json['last_play_cid'];
|
||||
curLanguage = json['cur_language'];
|
||||
@@ -304,3 +306,48 @@ class FormatItem {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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/slide_dialog.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/cache_manage.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/update.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -453,93 +455,26 @@ List<SettingsModel> get extraSettings => [
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (kDebugMode || Platform.isAndroid)
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
title: '音量均衡',
|
||||
setKey: SettingBoxKey.audioNormalization,
|
||||
leading: const Icon(Icons.multitrack_audio),
|
||||
getSubtitle: () {
|
||||
String audioNormalization = Pref.audioNormalization;
|
||||
// TODO: remove next version
|
||||
if (audioNormalization == '2') {
|
||||
GStorage.setting.put(SettingBoxKey.audioNormalization, '1');
|
||||
audioNormalization = '1';
|
||||
}
|
||||
audioNormalization = switch (audioNormalization) {
|
||||
'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('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
final audioNormalization = AudioNormalization.getTitleFromConfig(
|
||||
Pref.audioNormalization,
|
||||
);
|
||||
String fallback = Pref.fallbackNormalization;
|
||||
if (fallback == '0') {
|
||||
fallback = '';
|
||||
} else {
|
||||
await GStorage.setting.put(SettingBoxKey.audioNormalization, result);
|
||||
setState();
|
||||
}
|
||||
fallback =
|
||||
',无参数时:「${AudioNormalization.getTitleFromConfig(fallback)}」';
|
||||
}
|
||||
return '当前:「$audioNormalization」$fallback';
|
||||
},
|
||||
onTap: audioNormalization,
|
||||
),
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class SettingsModel {
|
||||
SettingsType.sw1tch => SetSwitchItem(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
setKey: setKey,
|
||||
setKey: setKey!,
|
||||
defaultVal: defaultVal,
|
||||
onChanged: onChanged,
|
||||
needReboot: needReboot,
|
||||
|
||||
@@ -16,6 +16,7 @@ class SelectDialog<T> extends StatelessWidget {
|
||||
final String title;
|
||||
final List<(T, String)> values;
|
||||
final Widget Function(BuildContext, int)? subtitleBuilder;
|
||||
final bool toggleable;
|
||||
|
||||
const SelectDialog({
|
||||
super.key,
|
||||
@@ -23,11 +24,12 @@ class SelectDialog<T> extends StatelessWidget {
|
||||
required this.values,
|
||||
required this.title,
|
||||
this.subtitleBuilder,
|
||||
this.toggleable = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final titleMedium = Theme.of(context).textTheme.titleMedium!;
|
||||
final titleMedium = TextTheme.of(context).titleMedium!;
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
title: Text(title),
|
||||
@@ -37,7 +39,7 @@ class SelectDialog<T> extends StatelessWidget {
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
content: SingleChildScrollView(
|
||||
child: RadioGroup<T>(
|
||||
onChanged: Navigator.of(context).pop,
|
||||
onChanged: (v) => Navigator.of(context).pop(v ?? value),
|
||||
groupValue: value,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -46,6 +48,7 @@ class SelectDialog<T> extends StatelessWidget {
|
||||
(index) {
|
||||
final item = values[index];
|
||||
return RadioListTile<T>(
|
||||
toggleable: toggleable,
|
||||
dense: true,
|
||||
value: item.$1,
|
||||
title: Text(
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:get/get.dart';
|
||||
class SetSwitchItem extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? subtitle;
|
||||
final String? setKey;
|
||||
final String setKey;
|
||||
final bool defaultVal;
|
||||
final ValueChanged<bool>? onChanged;
|
||||
final bool needReboot;
|
||||
@@ -21,7 +21,7 @@ class SetSwitchItem extends StatefulWidget {
|
||||
const SetSwitchItem({
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.setKey,
|
||||
required this.setKey,
|
||||
this.defaultVal = false,
|
||||
this.onChanged,
|
||||
this.needReboot = false,
|
||||
|
||||
@@ -1056,6 +1056,7 @@ class VideoDetailController extends GetxController
|
||||
Duration? seekToTime,
|
||||
Duration? duration,
|
||||
bool? autoplay,
|
||||
Volume? volume,
|
||||
}) async {
|
||||
await plPlayerController.setDataSource(
|
||||
DataSource(
|
||||
@@ -1095,6 +1096,7 @@ class VideoDetailController extends GetxController
|
||||
},
|
||||
width: firstVideo.width,
|
||||
height: firstVideo.height,
|
||||
volume: volume ?? this.volume,
|
||||
);
|
||||
|
||||
if (plPlayerController.enableSponsorBlock) {
|
||||
@@ -1125,6 +1127,8 @@ class VideoDetailController extends GetxController
|
||||
queryVideoUrl(defaultST: playedTime);
|
||||
}
|
||||
|
||||
Volume? volume;
|
||||
|
||||
// 视频链接
|
||||
Future<void> queryVideoUrl({
|
||||
Duration? defaultST,
|
||||
@@ -1164,6 +1168,8 @@ class VideoDetailController extends GetxController
|
||||
languages.value = data.language?.items;
|
||||
currLang.value = data.curLanguage;
|
||||
|
||||
volume = data.volume;
|
||||
|
||||
if (data.acceptDesc?.contains('试看') == true) {
|
||||
SmartDialog.showToast(
|
||||
'该视频为专属视频,仅提供试看',
|
||||
|
||||
@@ -43,7 +43,6 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
class HeaderControl extends StatefulWidget {
|
||||
const HeaderControl({
|
||||
@@ -446,8 +445,9 @@ class HeaderControlState extends State<HeaderControl> {
|
||||
SmartDialog.showToast('播放器未初始化');
|
||||
return;
|
||||
}
|
||||
final hwdec = await (player.platform as NativePlayer)
|
||||
.getProperty('hwdec-current');
|
||||
final hwdec = await player.platform!.getProperty(
|
||||
'hwdec-current',
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -93,6 +93,7 @@ abstract class SettingBoxKey {
|
||||
refreshDisplacement = 'refreshDisplacement',
|
||||
showHotRcmd = 'showHotRcmd',
|
||||
audioNormalization = 'audioNormalization',
|
||||
fallbackNormalization = 'fallbackNormalization',
|
||||
superResolutionType = 'superResolutionType',
|
||||
preInitPlayer = 'preInitPlayer',
|
||||
mainTabBarView = 'mainTabBarView',
|
||||
|
||||
@@ -414,6 +414,9 @@ abstract class Pref {
|
||||
static String get audioNormalization =>
|
||||
_setting.get(SettingBoxKey.audioNormalization, defaultValue: '0');
|
||||
|
||||
static String get fallbackNormalization =>
|
||||
_setting.get(SettingBoxKey.fallbackNormalization, defaultValue: '0');
|
||||
|
||||
static SuperResolutionType get superResolutionType {
|
||||
SuperResolutionType? superResolutionType;
|
||||
final index = _setting.get(SettingBoxKey.superResolutionType);
|
||||
|
||||
47
pubspec.lock
47
pubspec.lock
@@ -1143,10 +1143,11 @@ packages:
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: media_kit
|
||||
ref: "version_1.2.5"
|
||||
resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
|
||||
url: "https://github.com/My-Responsitories/media-kit.git"
|
||||
source: git
|
||||
version: "1.1.11"
|
||||
media_kit_libs_android_video:
|
||||
dependency: "direct overridden"
|
||||
@@ -1184,10 +1185,11 @@ packages:
|
||||
media_kit_libs_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_libs_video
|
||||
sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "libs/universal/media_kit_libs_video"
|
||||
ref: "version_1.2.5"
|
||||
resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
|
||||
url: "https://github.com/My-Responsitories/media-kit.git"
|
||||
source: git
|
||||
version: "1.0.5"
|
||||
media_kit_libs_windows_video:
|
||||
dependency: transitive
|
||||
@@ -1200,18 +1202,19 @@ packages:
|
||||
media_kit_native_event_loop:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: media_kit_native_event_loop
|
||||
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: media_kit_native_event_loop
|
||||
ref: "version_1.2.5"
|
||||
resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
|
||||
url: "https://github.com/My-Responsitories/media-kit.git"
|
||||
source: git
|
||||
version: "1.0.9"
|
||||
media_kit_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: media_kit_video
|
||||
ref: "version_1.2.5"
|
||||
resolved-ref: "902e962556c0bde4d384cd8f72b814598e5dfcf2"
|
||||
url: "https://github.com/bggRGjQaUbCoE/media-kit.git"
|
||||
resolved-ref: "4d68e69281b44f2c8e3c444cca3d8d8dc6dcff88"
|
||||
url: "https://github.com/My-Responsitories/media-kit.git"
|
||||
source: git
|
||||
version: "1.2.5"
|
||||
menu_base:
|
||||
@@ -1474,10 +1477,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: safe_local_storage
|
||||
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
|
||||
sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "2.0.1"
|
||||
saver_gallery:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1836,22 +1839,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uri_parser
|
||||
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
|
||||
sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "3.0.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
26
pubspec.yaml
26
pubspec.yaml
@@ -219,7 +219,6 @@ dependencies:
|
||||
stream_transform: any
|
||||
screen_brightness_platform_interface: any
|
||||
mime: any
|
||||
universal_platform: any
|
||||
path: any
|
||||
collection: any
|
||||
material_color_utilities: any
|
||||
@@ -234,15 +233,32 @@ dependency_overrides:
|
||||
path: ^1.9.1
|
||||
mime: ^2.0.0
|
||||
rxdart: ^0.28.0
|
||||
media_kit: 1.1.11
|
||||
media_kit_libs_video: 1.0.5
|
||||
media_kit_native_event_loop: ^1.0.9
|
||||
font_awesome_flutter: 10.9.0
|
||||
media_kit:
|
||||
git:
|
||||
url: https://github.com/My-Responsitories/media-kit.git
|
||||
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:
|
||||
git:
|
||||
url: https://github.com/bggRGjQaUbCoE/media-kit.git
|
||||
path: libs/android/media_kit_libs_android_video
|
||||
ref: version_1.2.5
|
||||
font_awesome_flutter: 10.9.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user