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('禁用'),
// 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,
};
}

View File

@@ -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;
}

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/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();
}
}
}

View File

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

View File

@@ -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(

View File

@@ -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,

View File

@@ -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(
'该视频为专属视频,仅提供试看',

View File

@@ -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,

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 {

View File

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

View File

@@ -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);

View File

@@ -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:

View File

@@ -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: