feat: custom subtitle padding

Closes #77

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-01 12:24:54 +08:00
parent 144a9b604a
commit dbc93883e8
5 changed files with 239 additions and 172 deletions

View File

@@ -954,7 +954,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
? bangumiIntroController ? bangumiIntroController
: null, : null,
headerControl: HeaderControl( headerControl: HeaderControl(
controller: plPlayerController, controller: plPlayerController!,
videoDetailCtr: videoDetailController, videoDetailCtr: videoDetailController,
heroTag: heroTag, heroTag: heroTag,
), ),

View File

@@ -33,14 +33,14 @@ import 'package:marquee/marquee.dart';
class HeaderControl extends StatefulWidget implements PreferredSizeWidget { class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
const HeaderControl({ const HeaderControl({
this.controller, required this.controller,
this.videoDetailCtr, required this.videoDetailCtr,
this.floating, this.floating,
required this.heroTag, required this.heroTag,
super.key, super.key,
}); });
final PlPlayerController? controller; final PlPlayerController controller;
final VideoDetailController? videoDetailCtr; final VideoDetailController videoDetailCtr;
final Floating? floating; final Floating? floating;
final String heroTag; final String heroTag;
@@ -52,32 +52,24 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
} }
class _HeaderControlState extends State<HeaderControl> { class _HeaderControlState extends State<HeaderControl> {
late PlayUrlModel videoInfo; PlayUrlModel get videoInfo => widget.videoDetailCtr.data;
static const TextStyle subTitleStyle = TextStyle(fontSize: 12); static const TextStyle subTitleStyle = TextStyle(fontSize: 12);
static const TextStyle titleStyle = TextStyle(fontSize: 14); static const TextStyle titleStyle = TextStyle(fontSize: 14);
Size get preferredSize => const Size(double.infinity, kToolbarHeight); Size get preferredSize => const Size(double.infinity, kToolbarHeight);
double buttonSpace = 8; double buttonSpace = 8;
// bool isFullScreen = false; String get heroTag => widget.heroTag;
late String heroTag;
late VideoIntroController videoIntroController; late VideoIntroController videoIntroController;
late VideoDetailData videoDetail; late VideoDetailData videoDetail;
// late StreamSubscription<bool> fullScreenStatusListener;
late bool horizontalScreen; late bool horizontalScreen;
RxString now = ''.obs; RxString now = ''.obs;
Timer? clock; Timer? clock;
late String defaultCDNService; late String defaultCDNService;
bool get isFullScreen => widget.controller!.isFullScreen.value; bool get isFullScreen => widget.controller.isFullScreen.value;
Box get setting => GStorage.setting; Box get setting => GStorage.setting;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
videoInfo = widget.videoDetailCtr!.data;
// listenFullScreenStatus();
heroTag = widget.heroTag;
// if (Get.arguments != null && Get.arguments['heroTag'] != null) {
// heroTag = Get.arguments['heroTag'];
// }
videoIntroController = Get.put(VideoIntroController(), tag: heroTag); videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
horizontalScreen = horizontalScreen =
setting.get(SettingBoxKey.horizontalScreen, defaultValue: false); setting.get(SettingBoxKey.horizontalScreen, defaultValue: false);
@@ -85,23 +77,8 @@ class _HeaderControlState extends State<HeaderControl> {
defaultValue: CDNService.backupUrl.code); defaultValue: CDNService.backupUrl.code);
} }
// void listenFullScreenStatus() {
// fullScreenStatusListener = widget
// .videoDetailCtr!.plPlayerController.isFullScreen
// .listen((bool status) {
// isFullScreen = status;
// /// TODO setState() called after dispose()
// if (mounted) {
// setState(() {});
// }
// });
// }
@override @override
void dispose() { void dispose() {
// widget.floating?.dispose();
// fullScreenStatusListener.cancel();
clock?.cancel(); clock?.cancel();
super.dispose(); super.dispose();
} }
@@ -190,13 +167,13 @@ class _HeaderControlState extends State<HeaderControl> {
// ), // ),
// ), // ),
// ), // ),
// if (widget.videoDetailCtr?.userInfo != null) // if (widget.videoDetailCtr.userInfo != null)
ListTile( ListTile(
dense: true, dense: true,
onTap: () async { onTap: () async {
Get.back(); Get.back();
final res = await UserHttp.toViewLater( final res = await UserHttp.toViewLater(
bvid: widget.videoDetailCtr!.bvid); bvid: widget.videoDetailCtr.bvid);
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
}, },
leading: leading:
@@ -214,7 +191,7 @@ class _HeaderControlState extends State<HeaderControl> {
dense: true, dense: true,
onTap: () => { onTap: () => {
Get.back(), Get.back(),
widget.videoDetailCtr!.queryVideoUrl() widget.videoDetailCtr.queryVideoUrl()
}, },
leading: const Icon(Icons.refresh_outlined, size: 20), leading: const Icon(Icons.refresh_outlined, size: 20),
title: const Text('重载视频', style: titleStyle), title: const Text('重载视频', style: titleStyle),
@@ -249,7 +226,7 @@ class _HeaderControlState extends State<HeaderControl> {
SmartDialog.showToast( SmartDialog.showToast(
'已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频'); '已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频');
setState(() {}); setState(() {});
widget.videoDetailCtr!.queryVideoUrl(); widget.videoDetailCtr.queryVideoUrl();
} }
}, },
), ),
@@ -258,7 +235,7 @@ class _HeaderControlState extends State<HeaderControl> {
onTap: () { onTap: () {
Get.back(); Get.back();
Player? player = Player? player =
widget.controller?.videoPlayerController; widget.controller.videoPlayerController;
if (player == null) { if (player == null) {
SmartDialog.showToast('播放器未初始化'); SmartDialog.showToast('播放器未初始化');
return; return;
@@ -278,17 +255,17 @@ class _HeaderControlState extends State<HeaderControl> {
const Icon(Icons.play_circle_outline, size: 20), const Icon(Icons.play_circle_outline, size: 20),
title: const Text('选择画质', style: titleStyle), title: const Text('选择画质', style: titleStyle),
subtitle: Text( subtitle: Text(
'当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}', '当前画质 ${widget.videoDetailCtr.currentVideoQa.description}',
style: subTitleStyle), style: subTitleStyle),
), ),
if (widget.videoDetailCtr!.currentAudioQa != null) if (widget.videoDetailCtr.currentAudioQa != null)
ListTile( ListTile(
dense: true, dense: true,
onTap: () => {Get.back(), showSetAudioQa()}, onTap: () => {Get.back(), showSetAudioQa()},
leading: const Icon(Icons.album_outlined, size: 20), leading: const Icon(Icons.album_outlined, size: 20),
title: const Text('选择音质', style: titleStyle), title: const Text('选择音质', style: titleStyle),
subtitle: Text( subtitle: Text(
'当前音质 ${widget.videoDetailCtr!.currentAudioQa!.description}', '当前音质 ${widget.videoDetailCtr.currentAudioQa!.description}',
style: subTitleStyle), style: subTitleStyle),
), ),
ListTile( ListTile(
@@ -298,7 +275,7 @@ class _HeaderControlState extends State<HeaderControl> {
const Icon(Icons.av_timer_outlined, size: 20), const Icon(Icons.av_timer_outlined, size: 20),
title: const Text('解码格式', style: titleStyle), title: const Text('解码格式', style: titleStyle),
subtitle: Text( subtitle: Text(
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}', '当前解码格式 ${widget.videoDetailCtr.currentDecodeFormats.description}',
style: subTitleStyle), style: subTitleStyle),
), ),
ListTile( ListTile(
@@ -307,7 +284,7 @@ class _HeaderControlState extends State<HeaderControl> {
leading: const Icon(Icons.repeat, size: 20), leading: const Icon(Icons.repeat, size: 20),
title: const Text('播放顺序', style: titleStyle), title: const Text('播放顺序', style: titleStyle),
subtitle: Text( subtitle: Text(
widget.controller!.playRepeat.description, widget.controller.playRepeat.description,
style: subTitleStyle), style: subTitleStyle),
), ),
ListTile( ListTile(
@@ -323,7 +300,7 @@ class _HeaderControlState extends State<HeaderControl> {
leading: const Icon(Icons.info_outline, size: 20), leading: const Icon(Icons.info_outline, size: 20),
onTap: () { onTap: () {
Player? player = Player? player =
widget.controller?.videoPlayerController; widget.controller.videoPlayerController;
if (player == null) { if (player == null) {
SmartDialog.showToast('播放器未初始化'); SmartDialog.showToast('播放器未初始化');
return; return;
@@ -483,14 +460,14 @@ class _HeaderControlState extends State<HeaderControl> {
ListTile( ListTile(
dense: true, dense: true,
onTap: () { onTap: () {
if (widget.videoDetailCtr?.userInfo == null) { if (widget.videoDetailCtr.userInfo == null) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.back(); Get.back();
Get.toNamed('/webviewnew', parameters: { Get.toNamed('/webviewnew', parameters: {
'url': 'url':
'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr!.bvid)}&bvid=${widget.videoDetailCtr!.bvid}' 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr.bvid)}&bvid=${widget.videoDetailCtr.bvid}'
}); });
}, },
leading: const Icon(Icons.error_outline, size: 20), leading: const Icon(Icons.error_outline, size: 20),
@@ -713,7 +690,7 @@ class _HeaderControlState extends State<HeaderControl> {
return; return;
} }
final List<FormatItem> videoFormat = videoInfo.supportFormats!; final List<FormatItem> videoFormat = videoInfo.supportFormats!;
final VideoQuality currentVideoQa = widget.videoDetailCtr!.currentVideoQa; final VideoQuality currentVideoQa = widget.videoDetailCtr.currentVideoQa;
/// 总质量分类 /// 总质量分类
final int totalQaSam = videoFormat.length; final int totalQaSam = videoFormat.length;
@@ -790,9 +767,9 @@ class _HeaderControlState extends State<HeaderControl> {
} }
Get.back(); Get.back();
final int quality = videoFormat[i].quality!; final int quality = videoFormat[i].quality!;
widget.videoDetailCtr!.currentVideoQa = widget.videoDetailCtr.currentVideoQa =
VideoQualityCode.fromCode(quality)!; VideoQualityCode.fromCode(quality)!;
widget.videoDetailCtr!.updatePlayer(); widget.videoDetailCtr.updatePlayer();
// String oldQualityDesc = // String oldQualityDesc =
// VideoQualityCode.fromCode(setting.get( // VideoQualityCode.fromCode(setting.get(
// SettingBoxKey.defaultVideoQa, // SettingBoxKey.defaultVideoQa,
@@ -837,7 +814,7 @@ class _HeaderControlState extends State<HeaderControl> {
/// 选择音质 /// 选择音质
void showSetAudioQa() { void showSetAudioQa() {
final AudioQuality currentAudioQa = widget.videoDetailCtr!.currentAudioQa!; final AudioQuality currentAudioQa = widget.videoDetailCtr.currentAudioQa!;
final List<AudioItem> audio = videoInfo.dash!.audio!; final List<AudioItem> audio = videoInfo.dash!.audio!;
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
@@ -879,9 +856,9 @@ class _HeaderControlState extends State<HeaderControl> {
} }
Get.back(); Get.back();
final int quality = i.id!; final int quality = i.id!;
widget.videoDetailCtr!.currentAudioQa = widget.videoDetailCtr.currentAudioQa =
AudioQualityCode.fromCode(quality)!; AudioQualityCode.fromCode(quality)!;
widget.videoDetailCtr!.updatePlayer(); widget.videoDetailCtr.updatePlayer();
// String oldQualityDesc = AudioQualityCode.fromCode( // String oldQualityDesc = AudioQualityCode.fromCode(
// setting.get(SettingBoxKey.defaultAudioQa, // setting.get(SettingBoxKey.defaultAudioQa,
// defaultValue: // defaultValue:
@@ -924,8 +901,8 @@ class _HeaderControlState extends State<HeaderControl> {
void showSetDecodeFormats() { void showSetDecodeFormats() {
// 当前选中的解码格式 // 当前选中的解码格式
final VideoDecodeFormats currentDecodeFormats = final VideoDecodeFormats currentDecodeFormats =
widget.videoDetailCtr!.currentDecodeFormats; widget.videoDetailCtr.currentDecodeFormats;
final VideoItem firstVideo = widget.videoDetailCtr!.firstVideo; final VideoItem firstVideo = widget.videoDetailCtr.firstVideo;
// 当前视频可用的解码格式 // 当前视频可用的解码格式
final List<FormatItem> videoFormat = videoInfo.supportFormats!; final List<FormatItem> videoFormat = videoInfo.supportFormats!;
final List? list = videoFormat final List? list = videoFormat
@@ -974,9 +951,9 @@ class _HeaderControlState extends State<HeaderControl> {
if (i.startsWith(currentDecodeFormats.code)) { if (i.startsWith(currentDecodeFormats.code)) {
return; return;
} }
widget.videoDetailCtr!.currentDecodeFormats = widget.videoDetailCtr.currentDecodeFormats =
VideoDecodeFormatsCode.fromString(i)!; VideoDecodeFormatsCode.fromString(i)!;
widget.videoDetailCtr!.updatePlayer(); widget.videoDetailCtr.updatePlayer();
Get.back(); Get.back();
}, },
contentPadding: contentPadding:
@@ -1017,7 +994,7 @@ class _HeaderControlState extends State<HeaderControl> {
{'value': 4, 'label': '底部'}, {'value': 4, 'label': '底部'},
{'value': 6, 'label': '彩色'}, {'value': 6, 'label': '彩色'},
]; ];
final List blockTypes = widget.controller!.blockTypes; final List blockTypes = widget.controller.blockTypes;
// 显示区域 // 显示区域
final List<Map<String, dynamic>> showAreas = [ final List<Map<String, dynamic>> showAreas = [
{'value': 0.25, 'label': '1/4屏'}, {'value': 0.25, 'label': '1/4屏'},
@@ -1026,28 +1003,30 @@ class _HeaderControlState extends State<HeaderControl> {
{'value': 1.0, 'label': '满屏'}, {'value': 1.0, 'label': '满屏'},
]; ];
// 智能云屏蔽 // 智能云屏蔽
int danmakuWeight = widget.controller!.danmakuWeight; int danmakuWeight = widget.controller.danmakuWeight;
// 显示区域 // 显示区域
double showArea = widget.controller!.showArea; double showArea = widget.controller.showArea;
// 不透明度 // 不透明度
double opacityVal = widget.controller!.opacityVal; double opacityVal = widget.controller.opacityVal;
// 字体大小 // 字体大小
double fontSizeVal = widget.controller!.fontSizeVal; double fontSizeVal = widget.controller.fontSizeVal;
// 全屏字体大小 // 全屏字体大小
double fontSizeFSVal = widget.controller!.fontSizeFSVal; double fontSizeFSVal = widget.controller.fontSizeFSVal;
double subtitleFontScale = widget.controller!.subtitleFontScale.value; double subtitleFontScale = widget.controller.subtitleFontScale;
double subtitleFontScaleFS = widget.controller!.subtitleFontScaleFS.value; double subtitleFontScaleFS = widget.controller.subtitleFontScaleFS;
double danmakuLineHeight = widget.controller!.danmakuLineHeight; double danmakuLineHeight = widget.controller.danmakuLineHeight;
// 弹幕速度 // 弹幕速度
double danmakuDurationVal = widget.controller!.danmakuDurationVal; double danmakuDurationVal = widget.controller.danmakuDurationVal;
// 弹幕描边 // 弹幕描边
double strokeWidth = widget.controller!.strokeWidth; double strokeWidth = widget.controller.strokeWidth;
// 字体粗细 // 字体粗细
int fontWeight = widget.controller!.fontWeight; int fontWeight = widget.controller.fontWeight;
bool massiveMode = widget.controller!.massiveMode; bool massiveMode = widget.controller.massiveMode;
int subtitlePaddingH = widget.controller.subtitlePaddingH;
int subtitlePaddingB = widget.controller.subtitlePaddingB;
final DanmakuController danmakuController = final DanmakuController? danmakuController =
widget.controller!.danmakuController!; widget.controller.danmakuController;
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
elevation: 0, elevation: 0,
@@ -1068,9 +1047,8 @@ class _HeaderControlState extends State<HeaderControl> {
left: 12, left: 12,
top: 12, top: 12,
right: 12, right: 12,
bottom: bottom: (widget.controller.isFullScreen.value == true ? 70 : 12) +
(widget.controller?.isFullScreen.value == true ? 70 : 12) + MediaQuery.paddingOf(context).bottom,
MediaQuery.paddingOf(context).bottom,
), ),
padding: const EdgeInsets.only(left: 14, right: 14), padding: const EdgeInsets.only(left: 14, right: 14),
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -1098,7 +1076,7 @@ class _HeaderControlState extends State<HeaderControl> {
arguments: widget.controller) arguments: widget.controller)
}, },
child: Text( child: Text(
"屏蔽管理(${widget.controller!.danmakuFilterRule.length})")), "屏蔽管理(${widget.controller.danmakuFilterRule.length})")),
], ],
), ),
Padding( Padding(
@@ -1125,8 +1103,9 @@ class _HeaderControlState extends State<HeaderControl> {
label: '$danmakuWeight', label: '$danmakuWeight',
onChanged: (double val) { onChanged: (double val) {
danmakuWeight = val.toInt(); danmakuWeight = val.toInt();
widget.controller!.danmakuWeight = danmakuWeight; widget.controller
widget.controller!.putDanmakuSettings(); ..danmakuWeight = danmakuWeight
..putDanmakuSettings();
setState(() {}); setState(() {});
}, },
), ),
@@ -1148,11 +1127,12 @@ class _HeaderControlState extends State<HeaderControl> {
} else { } else {
blockTypes.add(i['value']); blockTypes.add(i['value']);
} }
widget.controller!.blockTypes = blockTypes; widget.controller
widget.controller?.putDanmakuSettings(); ..blockTypes = blockTypes
..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith( danmakuController.option.copyWith(
hideTop: blockTypes.contains(5), hideTop: blockTypes.contains(5),
hideBottom: blockTypes.contains(4), hideBottom: blockTypes.contains(4),
@@ -1179,11 +1159,12 @@ class _HeaderControlState extends State<HeaderControl> {
ActionRowLineItem( ActionRowLineItem(
onTap: () { onTap: () {
showArea = i['value']; showArea = i['value'];
widget.controller!.showArea = showArea; widget.controller
widget.controller?.putDanmakuSettings(); ..showArea = showArea
..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option danmakuController.option
.copyWith(area: i['value']), .copyWith(area: i['value']),
); );
@@ -1205,10 +1186,10 @@ class _HeaderControlState extends State<HeaderControl> {
setKey: SettingBoxKey.danmakuMassiveMode, setKey: SettingBoxKey.danmakuMassiveMode,
onChanged: (value) { onChanged: (value) {
massiveMode = value; massiveMode = value;
widget.controller!.massiveMode = value; widget.controller.massiveMode = value;
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith(massiveMode: value), danmakuController.option.copyWith(massiveMode: value),
); );
} catch (_) {} } catch (_) {}
@@ -1239,11 +1220,12 @@ class _HeaderControlState extends State<HeaderControl> {
label: '${opacityVal * 100}%', label: '${opacityVal * 100}%',
onChanged: (double val) { onChanged: (double val) {
opacityVal = val; opacityVal = val;
widget.controller!.opacityVal = opacityVal; widget.controller
widget.controller?.putDanmakuSettings(); ..opacityVal = opacityVal
..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith(opacity: val), danmakuController.option.copyWith(opacity: val),
); );
} catch (_) {} } catch (_) {}
@@ -1276,11 +1258,12 @@ class _HeaderControlState extends State<HeaderControl> {
label: '${fontWeight + 1}', label: '${fontWeight + 1}',
onChanged: (double val) { onChanged: (double val) {
fontWeight = val.toInt(); fontWeight = val.toInt();
widget.controller!.fontWeight = fontWeight; widget.controller
widget.controller?.putDanmakuSettings(); ..fontWeight = fontWeight
..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option danmakuController.option
.copyWith(fontWeight: fontWeight), .copyWith(fontWeight: fontWeight),
); );
@@ -1314,11 +1297,12 @@ class _HeaderControlState extends State<HeaderControl> {
label: '$strokeWidth', label: '$strokeWidth',
onChanged: (double val) { onChanged: (double val) {
strokeWidth = val; strokeWidth = val;
widget.controller!.strokeWidth = val; widget.controller
widget.controller?.putDanmakuSettings(); ..strokeWidth = val
..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option danmakuController.option
.copyWith(strokeWidth: val), .copyWith(strokeWidth: val),
); );
@@ -1352,12 +1336,13 @@ class _HeaderControlState extends State<HeaderControl> {
label: '${(fontSizeVal * 100).toStringAsFixed(1)}%', label: '${(fontSizeVal * 100).toStringAsFixed(1)}%',
onChanged: (double val) { onChanged: (double val) {
fontSizeVal = val; fontSizeVal = val;
widget.controller!.fontSizeVal = fontSizeVal; widget.controller
widget.controller?.putDanmakuSettings(); ..fontSizeVal = fontSizeVal
..putDanmakuSettings();
setState(() {}); setState(() {});
if (widget.controller?.isFullScreen.value == false) { if (widget.controller.isFullScreen.value == false) {
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith( danmakuController.option.copyWith(
fontSize: (15 * fontSizeVal).toDouble(), fontSize: (15 * fontSizeVal).toDouble(),
), ),
@@ -1393,12 +1378,13 @@ class _HeaderControlState extends State<HeaderControl> {
label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%', label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%',
onChanged: (double val) { onChanged: (double val) {
fontSizeFSVal = val; fontSizeFSVal = val;
widget.controller!.fontSizeFSVal = fontSizeFSVal; widget.controller
widget.controller?.putDanmakuSettings(); ..fontSizeFSVal = fontSizeFSVal
..putDanmakuSettings();
setState(() {}); setState(() {});
if (widget.controller?.isFullScreen.value == true) { if (widget.controller.isFullScreen.value == true) {
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith( danmakuController.option.copyWith(
fontSize: (15 * fontSizeFSVal).toDouble(), fontSize: (15 * fontSizeFSVal).toDouble(),
), ),
@@ -1435,15 +1421,15 @@ class _HeaderControlState extends State<HeaderControl> {
onChanged: (double val) { onChanged: (double val) {
danmakuDurationVal = danmakuDurationVal =
(pow(val, 4) as double).toPrecision(2); (pow(val, 4) as double).toPrecision(2);
widget.controller!.danmakuDurationVal = widget.controller
danmakuDurationVal; ..danmakuDurationVal = danmakuDurationVal
widget.controller?.putDanmakuSettings(); ..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith( danmakuController.option.copyWith(
duration: danmakuDurationVal ~/ duration: danmakuDurationVal ~/
widget.controller!.playbackSpeed), widget.controller.playbackSpeed),
); );
} catch (_) {} } catch (_) {}
}, },
@@ -1474,12 +1460,12 @@ class _HeaderControlState extends State<HeaderControl> {
label: '$danmakuLineHeight', label: '$danmakuLineHeight',
onChanged: (double val) { onChanged: (double val) {
danmakuLineHeight = val.toPrecision(1); danmakuLineHeight = val.toPrecision(1);
widget.controller!.danmakuLineHeight = widget.controller
danmakuLineHeight; ..danmakuLineHeight = danmakuLineHeight
widget.controller?.putDanmakuSettings(); ..putDanmakuSettings();
setState(() {}); setState(() {});
try { try {
danmakuController.updateOption( danmakuController?.updateOption(
danmakuController.option.copyWith( danmakuController.option.copyWith(
lineHeight: danmakuLineHeight, lineHeight: danmakuLineHeight,
), ),
@@ -1516,9 +1502,10 @@ class _HeaderControlState extends State<HeaderControl> {
'${(subtitleFontScale * 100).toStringAsFixed(1)}%', '${(subtitleFontScale * 100).toStringAsFixed(1)}%',
onChanged: (double val) { onChanged: (double val) {
subtitleFontScale = val; subtitleFontScale = val;
widget.controller!.subtitleFontScale.value = widget.controller
subtitleFontScale; ..subtitleFontScale = subtitleFontScale
widget.controller?.putDanmakuSettings(); ..updateSubtitleStyle()
..putDanmakuSettings();
setState(() {}); setState(() {});
}, },
), ),
@@ -1551,9 +1538,78 @@ class _HeaderControlState extends State<HeaderControl> {
'${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%',
onChanged: (double val) { onChanged: (double val) {
subtitleFontScaleFS = val; subtitleFontScaleFS = val;
widget.controller!.subtitleFontScaleFS.value = widget.controller
subtitleFontScaleFS; ..subtitleFontScaleFS = subtitleFontScaleFS
widget.controller?.putDanmakuSettings(); ..updateSubtitleStyle()
..putDanmakuSettings();
setState(() {});
},
),
),
),
Text('字幕左右边距 $subtitlePaddingH'),
Padding(
padding: const EdgeInsets.only(
top: 0,
bottom: 6,
left: 10,
right: 10,
),
child: SliderTheme(
data: SliderThemeData(
trackShape: MSliderTrackShape(),
thumbColor: Theme.of(context).colorScheme.primary,
activeTrackColor: Theme.of(context).colorScheme.primary,
trackHeight: 10,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6.0),
),
child: Slider(
min: 0,
max: 100,
value: subtitlePaddingH.toDouble(),
divisions: 100,
label: '$subtitlePaddingH',
onChanged: (double val) {
subtitlePaddingH = val.round();
widget.controller
..subtitlePaddingH = subtitlePaddingH
..updateSubtitleStyle()
..putDanmakuSettings();
setState(() {});
},
),
),
),
Text('字幕底部边距 $subtitlePaddingB'),
Padding(
padding: const EdgeInsets.only(
top: 0,
bottom: 6,
left: 10,
right: 10,
),
child: SliderTheme(
data: SliderThemeData(
trackShape: MSliderTrackShape(),
thumbColor: Theme.of(context).colorScheme.primary,
activeTrackColor: Theme.of(context).colorScheme.primary,
trackHeight: 10,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6.0),
),
child: Slider(
min: 0,
max: 100,
value: subtitlePaddingB.toDouble(),
divisions: 100,
label: '$subtitlePaddingB',
onChanged: (double val) {
subtitlePaddingB = val.round();
widget.controller
..subtitlePaddingB = subtitlePaddingB
..updateSubtitleStyle()
..putDanmakuSettings();
setState(() {}); setState(() {});
}, },
), ),
@@ -1605,13 +1661,13 @@ class _HeaderControlState extends State<HeaderControl> {
ListTile( ListTile(
dense: true, dense: true,
onTap: () { onTap: () {
widget.controller!.setPlayRepeat(i); widget.controller.setPlayRepeat(i);
Get.back(); Get.back();
}, },
contentPadding: contentPadding:
const EdgeInsets.only(left: 20, right: 20), const EdgeInsets.only(left: 20, right: 20),
title: Text(i.description), title: Text(i.description),
trailing: widget.controller!.playRepeat == i trailing: widget.controller.playRepeat == i
? Icon( ? Icon(
Icons.done, Icons.done,
color: color:
@@ -1643,7 +1699,7 @@ class _HeaderControlState extends State<HeaderControl> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final plPlayerController = widget.controller!; final plPlayerController = widget.controller;
// final bool isLandscape = // final bool isLandscape =
// MediaQuery.of(context).orientation == Orientation.landscape; // MediaQuery.of(context).orientation == Orientation.landscape;
@@ -1669,11 +1725,11 @@ class _HeaderControlState extends State<HeaderControl> {
color: Colors.white, color: Colors.white,
), ),
onPressed: () { onPressed: () {
if (widget.videoDetailCtr?.bsController != null) { if (widget.videoDetailCtr.bsController != null) {
widget.videoDetailCtr?.bsController!.close(); widget.videoDetailCtr.bsController!.close();
widget.videoDetailCtr?.bsController = null; widget.videoDetailCtr.bsController = null;
} else if (isFullScreen) { } else if (isFullScreen) {
widget.controller!.triggerFullScreen(status: false); widget.controller.triggerFullScreen(status: false);
} else if (MediaQuery.of(context).orientation == } else if (MediaQuery.of(context).orientation ==
Orientation.landscape && Orientation.landscape &&
!horizontalScreen) { !horizontalScreen) {
@@ -1770,7 +1826,7 @@ class _HeaderControlState extends State<HeaderControl> {
// ), // ),
// fuc: () => _.screenshot(), // fuc: () => _.screenshot(),
// ), // ),
if (widget.videoDetailCtr?.enableSponsorBlock == true) if (widget.videoDetailCtr.enableSponsorBlock == true)
SizedBox( SizedBox(
width: 42, width: 42,
height: 34, height: 34,
@@ -1779,7 +1835,7 @@ class _HeaderControlState extends State<HeaderControl> {
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero), padding: WidgetStateProperty.all(EdgeInsets.zero),
), ),
onPressed: () => widget.videoDetailCtr?.onBlock(context), onPressed: () => widget.videoDetailCtr.onBlock(context),
icon: Stack( icon: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@@ -1798,7 +1854,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
Obx( Obx(
() => widget.videoDetailCtr?.segmentList.isNotEmpty == true () => widget.videoDetailCtr.segmentList.isNotEmpty == true
? SizedBox( ? SizedBox(
width: 42, width: 42,
height: 34, height: 34,
@@ -1808,7 +1864,7 @@ class _HeaderControlState extends State<HeaderControl> {
padding: WidgetStateProperty.all(EdgeInsets.zero), padding: WidgetStateProperty.all(EdgeInsets.zero),
), ),
onPressed: () => onPressed: () =>
widget.videoDetailCtr?.showSBDetail(context), widget.videoDetailCtr.showSBDetail(context),
icon: Icon( icon: Icon(
MdiIcons.advertisements, MdiIcons.advertisements,
size: 19, size: 19,
@@ -1826,7 +1882,7 @@ class _HeaderControlState extends State<HeaderControl> {
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero), padding: WidgetStateProperty.all(EdgeInsets.zero),
), ),
onPressed: widget.videoDetailCtr?.showShootDanmakuSheet, onPressed: widget.videoDetailCtr.showShootDanmakuSheet,
icon: const Icon( icon: const Icon(
Icons.comment_outlined, Icons.comment_outlined,
size: 19, size: 19,
@@ -1875,7 +1931,7 @@ class _HeaderControlState extends State<HeaderControl> {
onPressed: () async { onPressed: () async {
bool canUsePiP = widget.floating != null && bool canUsePiP = widget.floating != null &&
await widget.floating!.isPipAvailable; await widget.floating!.isPipAvailable;
widget.controller!.hiddenControls(false); widget.controller.hiddenControls(false);
if (canUsePiP) { if (canUsePiP) {
bool enableBackgroundPlay = setting.get( bool enableBackgroundPlay = setting.get(
SettingBoxKey.enableBackgroundPlay, SettingBoxKey.enableBackgroundPlay,
@@ -1945,8 +2001,8 @@ class _HeaderControlState extends State<HeaderControl> {
await Future.delayed(const Duration(seconds: 3), () {}); await Future.delayed(const Duration(seconds: 3), () {});
} }
final Rational aspectRatio = Rational( final Rational aspectRatio = Rational(
widget.videoDetailCtr!.data.dash!.video!.first.width!, widget.videoDetailCtr.data.dash!.video!.first.width!,
widget.videoDetailCtr!.data.dash!.video!.first.height!, widget.videoDetailCtr.data.dash!.video!.first.height!,
); );
if (!context.mounted) return; if (!context.mounted) return;
await widget.floating!.enable(EnableManual( await widget.floating!.enable(EnableManual(

View File

@@ -251,13 +251,47 @@ class PlPlayerController {
double? defaultDuration; double? defaultDuration;
late bool enableAutoLongPressSpeed = false; late bool enableAutoLongPressSpeed = false;
late bool enableLongShowControl; late bool enableLongShowControl;
RxDouble subtitleFontScale = (1.0).obs; double subtitleFontScale = 1.0;
RxDouble subtitleFontScaleFS = (1.5).obs; double subtitleFontScaleFS = 1.5;
late double danmakuLineHeight = GStorage.danmakuLineHeight; late double danmakuLineHeight = GStorage.danmakuLineHeight;
late int subtitlePaddingH = GStorage.subtitlePaddingH;
late int subtitlePaddingB = GStorage.subtitlePaddingB;
// 播放顺序相关 // 播放顺序相关
PlayRepeat playRepeat = PlayRepeat.pause; PlayRepeat playRepeat = PlayRepeat.pause;
final GlobalKey<VideoState> key = GlobalKey<VideoState>();
TextStyle get subTitleStyle => TextStyle(
height: 1.5,
fontSize:
16 * (isFullScreen.value ? subtitleFontScaleFS : subtitleFontScale),
letterSpacing: 0.1,
wordSpacing: 0.1,
color: Colors.white,
fontWeight: FontWeight.normal,
backgroundColor: Color(0xaa000000),
);
void updateSubtitleStyle([double? value]) {
key.currentState?.update(
subtitleViewConfiguration: SubtitleViewConfiguration(
style: subTitleStyle.copyWith(
fontSize: 16 *
(value ??
(isFullScreen.value
? subtitleFontScaleFS
: subtitleFontScale))),
padding: EdgeInsets.only(
left: subtitlePaddingH.toDouble(),
right: subtitlePaddingH.toDouble(),
bottom: subtitlePaddingB.toDouble(),
),
textScaleFactor: MediaQuery.textScalerOf(Get.context!).scale(1),
),
);
}
void updateSliderPositionSecond() { void updateSliderPositionSecond() {
int newSecond = _sliderPosition.value.inSeconds; int newSecond = _sliderPosition.value.inSeconds;
if (sliderPositionSeconds.value != newSecond) { if (sliderPositionSeconds.value != newSecond) {
@@ -346,8 +380,8 @@ class PlPlayerController {
setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0); setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0);
// 全屏字体大小 // 全屏字体大小
fontSizeFSVal = GStorage.danmakuFontScaleFS; fontSizeFSVal = GStorage.danmakuFontScaleFS;
subtitleFontScale.value = GStorage.subtitleFontScale; subtitleFontScale = GStorage.subtitleFontScale;
subtitleFontScaleFS.value = GStorage.subtitleFontScaleFS; subtitleFontScaleFS = GStorage.subtitleFontScaleFS;
massiveMode = GStorage.danmakuMassiveMode; massiveMode = GStorage.danmakuMassiveMode;
// 弹幕时间 // 弹幕时间
danmakuDurationVal = danmakuDurationVal =
@@ -1154,6 +1188,7 @@ class PlPlayerController {
void toggleFullScreen(bool val) { void toggleFullScreen(bool val) {
_isFullScreen.value = val; _isFullScreen.value = val;
updateSubtitleStyle();
} }
// 全屏 // 全屏
@@ -1279,12 +1314,14 @@ class PlPlayerController {
setting.put(SettingBoxKey.danmakuOpacity, opacityVal); setting.put(SettingBoxKey.danmakuOpacity, opacityVal);
setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal); setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal);
setting.put(SettingBoxKey.danmakuFontScaleFS, fontSizeFSVal); setting.put(SettingBoxKey.danmakuFontScaleFS, fontSizeFSVal);
setting.put(SettingBoxKey.subtitleFontScale, subtitleFontScale.value); setting.put(SettingBoxKey.subtitleFontScale, subtitleFontScale);
setting.put(SettingBoxKey.subtitleFontScaleFS, subtitleFontScaleFS.value); setting.put(SettingBoxKey.subtitleFontScaleFS, subtitleFontScaleFS);
setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal); setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal);
setting.put(SettingBoxKey.strokeWidth, strokeWidth); setting.put(SettingBoxKey.strokeWidth, strokeWidth);
setting.put(SettingBoxKey.fontWeight, fontWeight); setting.put(SettingBoxKey.fontWeight, fontWeight);
setting.put(SettingBoxKey.danmakuLineHeight, danmakuLineHeight); setting.put(SettingBoxKey.danmakuLineHeight, danmakuLineHeight);
setting.put(SettingBoxKey.subtitlePaddingH, subtitlePaddingH);
setting.put(SettingBoxKey.subtitlePaddingB, subtitlePaddingB);
} }
Future<void> dispose({String type = 'single'}) async { Future<void> dispose({String type = 'single'}) async {

View File

@@ -78,7 +78,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
late BangumiIntroController? bangumiIntroController; late BangumiIntroController? bangumiIntroController;
final GlobalKey _playerKey = GlobalKey(); final GlobalKey _playerKey = GlobalKey();
final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
final RxBool _mountSeekBackwardButton = false.obs; final RxBool _mountSeekBackwardButton = false.obs;
final RxBool _mountSeekForwardButton = false.obs; final RxBool _mountSeekForwardButton = false.obs;
@@ -146,7 +145,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} }
StreamSubscription? _listener; StreamSubscription? _listener;
StreamSubscription? _listenerFS;
@override @override
void initState() { void initState() {
@@ -226,7 +224,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
@override @override
void dispose() { void dispose() {
_listener?.cancel(); _listener?.cancel();
_listenerFS?.cancel();
animationController.dispose(); animationController.dispose();
FlutterVolumeController.removeListener(); FlutterVolumeController.removeListener();
super.dispose(); super.dispose();
@@ -573,39 +570,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
bool get isFullScreen => plPlayerController.isFullScreen.value; bool get isFullScreen => plPlayerController.isFullScreen.value;
TextStyle get subTitleStyle => TextStyle(
height: 1.5,
fontSize: 16 *
(isFullScreen
? plPlayerController.subtitleFontScaleFS.value
: plPlayerController.subtitleFontScale.value),
letterSpacing: 0.1,
wordSpacing: 0.1,
color: Colors.white,
fontWeight: FontWeight.normal,
backgroundColor: Color(0xaa000000),
);
void _updateSubtitle(double value) {
_key.currentState?.update(
subtitleViewConfiguration: SubtitleViewConfiguration(
style: subTitleStyle.copyWith(fontSize: 16 * value),
padding: const EdgeInsets.all(24.0),
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_listenerFS?.cancel();
_listenerFS = isFullScreen
? plPlayerController.subtitleFontScaleFS.listen((value) {
_updateSubtitle(value);
})
: _listenerFS = plPlayerController.subtitleFontScale.listen((value) {
_updateSubtitle(value);
});
final Color colorTheme = Theme.of(context).colorScheme.primary; final Color colorTheme = Theme.of(context).colorScheme.primary;
const TextStyle textStyle = TextStyle( const TextStyle textStyle = TextStyle(
color: Colors.white, color: Colors.white,
@@ -747,7 +713,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
_gestureType = null; _gestureType = null;
}, },
child: Video( child: Video(
key: _key, key: plPlayerController.key,
controller: videoController, controller: videoController,
controls: NoVideoControls, controls: NoVideoControls,
pauseUponEnteringBackgroundMode: pauseUponEnteringBackgroundMode:
@@ -755,7 +721,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
resumeUponEnteringForegroundMode: true, resumeUponEnteringForegroundMode: true,
// 字幕尺寸调节 // 字幕尺寸调节
subtitleViewConfiguration: SubtitleViewConfiguration( subtitleViewConfiguration: SubtitleViewConfiguration(
style: subTitleStyle, style: plPlayerController.subTitleStyle,
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
textScaleFactor: MediaQuery.textScalerOf(context).scale(1), textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
), ),

View File

@@ -151,6 +151,12 @@ class GStorage {
static bool get reverseFromFirst => static bool get reverseFromFirst =>
setting.get(SettingBoxKey.reverseFromFirst, defaultValue: true); setting.get(SettingBoxKey.reverseFromFirst, defaultValue: true);
static int get subtitlePaddingH =>
setting.get(SettingBoxKey.subtitlePaddingH, defaultValue: 24);
static int get subtitlePaddingB =>
setting.get(SettingBoxKey.subtitlePaddingB, defaultValue: 24);
static List<double> get dynamicDetailRatio => List<double>.from(setting static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -354,6 +360,8 @@ class SettingBoxKey {
replyLengthLimit = 'replyLengthLimit', replyLengthLimit = 'replyLengthLimit',
showArgueMsg = 'showArgueMsg', showArgueMsg = 'showArgueMsg',
reverseFromFirst = 'reverseFromFirst', reverseFromFirst = 'reverseFromFirst',
subtitlePaddingH = 'subtitlePaddingH',
subtitlePaddingB = 'subtitlePaddingB',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',