Compare commits

..

3 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
d3c99cc1c6 fix: #191
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-17 07:27:07 +08:00
bggRGjQaUbCoE
cc4f08e500 feat: audio normalization
Closes #182

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-16 22:07:05 +08:00
bggRGjQaUbCoE
0afb6a3523 opt: player
ref orz12/main
2025-01-16 22:06:51 +08:00
5 changed files with 133 additions and 15 deletions

View File

@@ -0,0 +1,12 @@
enum AudioNormalization { disable, dynaudnorm, loudnorm, custom }
extension AudioNormalizationExt on AudioNormalization {
String get title => ['禁用', '预设 dynaudnorm', '预设 loudnorm', '自定义参数'][index];
String get param => [
'',
// ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7
'dynaudnorm=g=5:f=250:r=0.9:p=0.5',
'loudnorm=I=-16:LRA=11:TP=-1.5',
'',
][index];
}

View File

@@ -4,6 +4,7 @@ import 'dart:math';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'
show kDragContainerExtentPercentage, displacement;
import 'package:PiliPlus/http/interceptor_anonymity.dart';
import 'package:PiliPlus/models/common/audio_normalization.dart';
import 'package:PiliPlus/models/common/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/dynamics_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
@@ -1776,6 +1777,89 @@ List<SettingsModel> get extraSettings => [
} catch (_) {}
},
),
SettingsModel(
settingsType: SettingsType.normal,
title: '音量均衡',
setKey: SettingBoxKey.audioNormalization,
leading: const Icon(Icons.multitrack_audio),
getSubtitle: () {
String audioNormalization = GStorage.audioNormalization;
audioNormalization = switch (audioNormalization) {
'0' => AudioNormalization.disable.title,
'1' => AudioNormalization.dynaudnorm.title,
'2' => AudioNormalization.loudnorm.title,
_ => audioNormalization,
};
return '当前:「$audioNormalization';
},
onTap: (setState) async {
String? result = await showDialog(
context: Get.context!,
builder: (context) {
String audioNormalization = GStorage.audioNormalization;
Set<String> values = {'0', '1', '2', audioNormalization, '3'};
return SelectDialog<String>(
title: '音量均衡',
value: audioNormalization,
values: values.map((e) {
return {
'title': switch (e) {
'0' => AudioNormalization.disable.title,
'1' => AudioNormalization.dynaudnorm.title,
'2' => AudioNormalization.loudnorm.title,
'3' => AudioNormalization.custom.title,
_ => e,
},
'value': e,
};
}).toList());
},
);
if (result != null) {
if (result == '3') {
String param = '';
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text(
'自定义参数',
style: TextStyle(fontSize: 18),
),
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: Text('确定'),
),
],
);
});
} else {
await GStorage.setting
.put(SettingBoxKey.audioNormalization, result);
setState();
}
}
},
),
SettingsModel(
settingsType: SettingsType.sw1tch,
enableFeedback: true,

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:PiliPlus/common/widgets/segment_progress_bar.dart';
import 'package:PiliPlus/models/common/audio_normalization.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
@@ -513,7 +514,7 @@ class PlPlayerController {
}
// 配置Player 音轨、字幕等等
_videoPlayerController = await _createVideoController(
dataSource, _looping, enableHA, hwdec, width, height);
dataSource, _looping, enableHA, hwdec, width, height, seekTo);
callback?.call();
// 获取视频时长 00:00
_duration.value = duration ?? _videoPlayerController!.state.duration;
@@ -530,7 +531,7 @@ class PlPlayerController {
if (!_listenersInitialized) {
startListeners();
}
await _initializePlayer(seekTo: seekTo ?? Duration.zero);
await _initializePlayer();
setSubtitle(this.vttSubtitlesIndex.value);
} catch (err, stackTrace) {
dataStatus.status.value = DataStatus.error;
@@ -547,6 +548,7 @@ class PlPlayerController {
String? hwdec,
double? width,
double? height,
Duration? seekTo,
) async {
// 每次配置时先移除监听
removeListeners();
@@ -571,7 +573,19 @@ class PlPlayerController {
);
var pp = player.platform as NativePlayer;
// 解除倍速限制
await pp.setProperty("af", "scaletempo2=max-speed=8");
if (_videoPlayerController == null) {
String audioNormalization = GStorage.audioNormalization;
audioNormalization = switch (audioNormalization) {
'0' => '',
'1' => ',${AudioNormalization.dynaudnorm.param}',
'2' => ',${AudioNormalization.loudnorm.param}',
_ => ',$audioNormalization',
};
await pp.setProperty(
"af",
"scaletempo2=max-speed=8$audioNormalization",
);
}
// 音量不一致
if (Platform.isAndroid) {
await pp.setProperty("volume-max", "100");
@@ -633,12 +647,13 @@ class PlPlayerController {
? dataSource.videoSource!
: "asset://${dataSource.videoSource!}";
await player.open(
Media(assetUrl, httpHeaders: dataSource.httpHeaders),
Media(assetUrl, httpHeaders: dataSource.httpHeaders, start: seekTo),
play: false,
);
} else {
await player.open(
Media(dataSource.videoSource!, httpHeaders: dataSource.httpHeaders),
Media(dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders, start: seekTo),
play: false,
);
}
@@ -650,15 +665,15 @@ class PlPlayerController {
return player;
}
Future refreshPlayer() async {
Future<bool> refreshPlayer() async {
Duration currentPos = _position.value;
if (_videoPlayerController == null) {
SmartDialog.showToast('视频播放器为空,请重新进入本页面');
return;
return false;
}
if (dataSource.videoSource?.isEmpty ?? true) {
SmartDialog.showToast('视频源为空,请重新进入本页面');
return;
return false;
}
if (dataSource.audioSource?.isEmpty ?? true) {
SmartDialog.showToast('音频源为空');
@@ -674,14 +689,16 @@ class PlPlayerController {
Media(
dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders,
start: currentPos,
),
play: true,
);
seekTo(currentPos);
return true;
// seekTo(currentPos);
}
// 开始播放
Future _initializePlayer({Duration seekTo = Duration.zero}) async {
Future _initializePlayer() async {
if (_instance == null) return;
// 设置倍速
if (videoType.value == 'live') {
@@ -699,9 +716,9 @@ class PlPlayerController {
// }
// 跳转播放
if (seekTo != Duration.zero) {
await this.seekTo(seekTo);
}
// if (seekTo != Duration.zero) {
// await this.seekTo(seekTo);
// }
// 自动播放
if (_autoPlay) {
@@ -830,7 +847,8 @@ class PlPlayerController {
SmartDialog.showToast('无法加载解码器, $event,可能会切换至软解');
return;
}
SmartDialog.showToast('视频加载错误, $event');
// SmartDialog.showToast('视频加载错误, $event');
debugPrint('视频加载错误, $event');
}),
// videoPlayerController!.stream.volume.listen((event) {
// if (!mute.value && _volumeBeforeMute != event) {

View File

@@ -345,6 +345,9 @@ class GStorage {
static bool get showHotRcmd =>
GStorage.setting.get(SettingBoxKey.showHotRcmd, defaultValue: false);
static String get audioNormalization =>
GStorage.setting.get(SettingBoxKey.audioNormalization, defaultValue: '0');
static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -566,6 +569,7 @@ class SettingBoxKey {
showVipDanmaku = 'showVipDanmaku',
mergeDanmaku = 'mergeDanmaku',
showHotRcmd = 'showHotRcmd',
audioNormalization = 'audioNormalization',
// Sponsor Block
enableSponsorBlock = 'enableSponsorBlock',

View File

@@ -252,7 +252,7 @@ packages:
description:
path: "."
ref: main
resolved-ref: f58dd3bce638a07b4fab480222b6dbd1189687a9
resolved-ref: ce086d5d13770d8cabb78204f9af3f6edd4af3b1
url: "https://github.com/bggRGjQaUbCoE/canvas_danmaku.git"
source: git
version: "0.2.6"