feat: 弹幕设置
This commit is contained in:
@ -29,6 +29,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
bool danmuPlayStatus = true;
|
bool danmuPlayStatus = true;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
late bool enableShowDanmaku;
|
late bool enableShowDanmaku;
|
||||||
|
late List blockTypes;
|
||||||
|
late double showArea;
|
||||||
|
late double opacityVal;
|
||||||
|
late double fontSizeVal;
|
||||||
|
late double danmakuSpeedVal;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -58,6 +63,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
showArea = playerController.showArea;
|
||||||
|
opacityVal = playerController.opacityVal;
|
||||||
|
fontSizeVal = playerController.fontSizeVal;
|
||||||
|
danmakuSpeedVal = playerController.danmakuSpeedVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放器状态监听
|
// 播放器状态监听
|
||||||
@ -77,6 +87,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
}
|
}
|
||||||
PlDanmakuController ctr = _plDanmakuController;
|
PlDanmakuController ctr = _plDanmakuController;
|
||||||
int currentPosition = position.inMilliseconds;
|
int currentPosition = position.inMilliseconds;
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
|
||||||
if (!playerController.isOpenDanmu.value) {
|
if (!playerController.isOpenDanmu.value) {
|
||||||
return;
|
return;
|
||||||
@ -99,14 +110,17 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
var delta = currentPosition - element.progress;
|
var delta = currentPosition - element.progress;
|
||||||
|
|
||||||
if (delta >= 0 && delta < 200) {
|
if (delta >= 0 && delta < 200) {
|
||||||
_controller!.addItems([
|
// 屏蔽彩色弹幕
|
||||||
DanmakuItem(
|
if (blockTypes.contains(6) ? element.color == 16777215 : true) {
|
||||||
element.content,
|
_controller!.addItems([
|
||||||
color: DmUtils.decimalToColor(element.color),
|
DanmakuItem(
|
||||||
time: element.progress,
|
element.content,
|
||||||
type: DmUtils.getPosition(element.mode),
|
color: DmUtils.decimalToColor(element.color),
|
||||||
)
|
time: element.progress,
|
||||||
]);
|
type: DmUtils.getPosition(element.mode),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
ctr.currentDmIndex++;
|
ctr.currentDmIndex++;
|
||||||
} else {
|
} else {
|
||||||
if (!playerController.isOpenDanmu.value) {
|
if (!playerController.isOpenDanmu.value) {
|
||||||
@ -135,9 +149,10 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
widget.playerController.danmakuController = _controller = e;
|
widget.playerController.danmakuController = _controller = e;
|
||||||
},
|
},
|
||||||
option: DanmakuOption(
|
option: DanmakuOption(
|
||||||
fontSize: 15,
|
fontSize: 15 * fontSizeVal,
|
||||||
area: 0.5,
|
area: showArea,
|
||||||
duration: 5,
|
opacity: opacityVal,
|
||||||
|
duration: danmakuSpeedVal * widget.playerController.playbackSpeed,
|
||||||
),
|
),
|
||||||
statusChanged: (isPlaying) {},
|
statusChanged: (isPlaying) {},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -17,35 +17,31 @@ class MenuRow extends StatelessWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
actionRowLineItem(
|
ActionRowLineItem(
|
||||||
context,
|
onTap: () => {},
|
||||||
() => {},
|
loadingStatus: loadingStatus,
|
||||||
loadingStatus,
|
text: '推荐',
|
||||||
'推荐',
|
|
||||||
selectStatus: true,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
actionRowLineItem(
|
|
||||||
context,
|
|
||||||
() => {},
|
|
||||||
loadingStatus,
|
|
||||||
'弹幕',
|
|
||||||
selectStatus: false,
|
selectStatus: false,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
actionRowLineItem(
|
ActionRowLineItem(
|
||||||
context,
|
onTap: () => {},
|
||||||
() => {},
|
loadingStatus: loadingStatus,
|
||||||
loadingStatus,
|
text: '弹幕',
|
||||||
'评论列表',
|
|
||||||
selectStatus: false,
|
selectStatus: false,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
actionRowLineItem(
|
ActionRowLineItem(
|
||||||
context,
|
onTap: () => {},
|
||||||
() => {},
|
loadingStatus: loadingStatus,
|
||||||
loadingStatus,
|
text: '评论列表',
|
||||||
'播放列表',
|
selectStatus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ActionRowLineItem(
|
||||||
|
onTap: () => {},
|
||||||
|
loadingStatus: loadingStatus,
|
||||||
|
text: '播放列表',
|
||||||
selectStatus: false,
|
selectStatus: false,
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@ -99,3 +95,62 @@ class MenuRow extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ActionRowLineItem extends StatelessWidget {
|
||||||
|
final bool? selectStatus;
|
||||||
|
final Function? onTap;
|
||||||
|
final bool? loadingStatus;
|
||||||
|
final String? text;
|
||||||
|
|
||||||
|
const ActionRowLineItem(
|
||||||
|
{super.key,
|
||||||
|
this.selectStatus,
|
||||||
|
this.onTap,
|
||||||
|
this.text,
|
||||||
|
this.loadingStatus = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: selectStatus!
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => {
|
||||||
|
feedBack(),
|
||||||
|
onTap!(),
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(13, 5.5, 13, 4.5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
|
border: Border.all(
|
||||||
|
color: selectStatus!
|
||||||
|
? Colors.transparent
|
||||||
|
: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: loadingStatus! ? 0 : 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Text(
|
||||||
|
text!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: selectStatus!
|
||||||
|
? Theme.of(context).colorScheme.onSecondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final PlPlayerController? controller;
|
final PlPlayerController? controller;
|
||||||
@ -29,6 +33,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -146,9 +151,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
// title: Text('播放设置', style: titleStyle),
|
// title: Text('播放设置', style: titleStyle),
|
||||||
// ),
|
// ),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () {},
|
onTap: () => {Get.back(), showSetDanmaku()},
|
||||||
dense: true,
|
dense: true,
|
||||||
enabled: false,
|
|
||||||
leading: const Icon(Icons.subtitles_outlined, size: 20),
|
leading: const Icon(Icons.subtitles_outlined, size: 20),
|
||||||
title: Text('弹幕设置', style: titleStyle),
|
title: Text('弹幕设置', style: titleStyle),
|
||||||
),
|
),
|
||||||
@ -454,6 +458,246 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 弹幕功能
|
||||||
|
void showSetDanmaku() async {
|
||||||
|
// 屏蔽类型
|
||||||
|
List<Map<String, dynamic>> blockTypesList = [
|
||||||
|
{'value': 5, 'label': '顶部'},
|
||||||
|
{'value': 2, 'label': '滚动'},
|
||||||
|
{'value': 4, 'label': '底部'},
|
||||||
|
{'value': 6, 'label': '彩色'},
|
||||||
|
];
|
||||||
|
List blockTypes = widget.controller!.blockTypes;
|
||||||
|
// 显示区域
|
||||||
|
List<Map<String, dynamic>> showAreas = [
|
||||||
|
{'value': 0.25, 'label': '1/4屏'},
|
||||||
|
{'value': 0.5, 'label': '半屏'},
|
||||||
|
{'value': 0.75, 'label': '3/4屏'},
|
||||||
|
{'value': 1.0, 'label': '满屏'},
|
||||||
|
];
|
||||||
|
double showArea = widget.controller!.showArea;
|
||||||
|
// 不透明度
|
||||||
|
double opacityVal = widget.controller!.opacityVal;
|
||||||
|
// 字体大小
|
||||||
|
double fontSizeVal = widget.controller!.fontSizeVal;
|
||||||
|
// 弹幕速度
|
||||||
|
double danmakuSpeedVal = widget.controller!.danmakuSpeedVal;
|
||||||
|
|
||||||
|
DanmakuController danmakuController = widget.controller!.danmakuController!;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 580,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: Center(child: Text('弹幕设置', style: titleStyle)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Text('按类型屏蔽'),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12, bottom: 18),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (var i in blockTypesList) ...[
|
||||||
|
ActionRowLineItem(
|
||||||
|
onTap: () async {
|
||||||
|
bool isChoose = blockTypes.contains(i['value']);
|
||||||
|
if (isChoose) {
|
||||||
|
blockTypes.remove(i['value']);
|
||||||
|
} else {
|
||||||
|
blockTypes.add(i['value']);
|
||||||
|
}
|
||||||
|
widget.controller!.blockTypes = blockTypes;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption =
|
||||||
|
danmakuController.option;
|
||||||
|
DanmakuOption updatedOption =
|
||||||
|
currentOption.copyWith(
|
||||||
|
hideTop: blockTypes.contains(5),
|
||||||
|
hideBottom: blockTypes.contains(4),
|
||||||
|
hideScroll: blockTypes.contains(2),
|
||||||
|
// 添加或修改其他需要修改的选项属性
|
||||||
|
);
|
||||||
|
danmakuController.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
text: i['label'],
|
||||||
|
selectStatus: blockTypes.contains(i['value']),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text('显示区域'),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12, bottom: 18),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (var i in showAreas) ...[
|
||||||
|
ActionRowLineItem(
|
||||||
|
onTap: () {
|
||||||
|
showArea = i['value'];
|
||||||
|
widget.controller!.showArea = showArea;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption =
|
||||||
|
danmakuController.option;
|
||||||
|
DanmakuOption updatedOption =
|
||||||
|
currentOption.copyWith(area: i['value']);
|
||||||
|
danmakuController.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
text: i['label'],
|
||||||
|
selectStatus: showArea == i['value'],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('不透明度 ${opacityVal * 100}%'),
|
||||||
|
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: 1,
|
||||||
|
value: opacityVal,
|
||||||
|
divisions: 10,
|
||||||
|
label: '${opacityVal * 100}%',
|
||||||
|
onChanged: (double val) {
|
||||||
|
opacityVal = val;
|
||||||
|
widget.controller!.opacityVal = opacityVal;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption =
|
||||||
|
danmakuController.option;
|
||||||
|
DanmakuOption updatedOption =
|
||||||
|
currentOption.copyWith(opacity: val);
|
||||||
|
danmakuController.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('字体大小 ${(fontSizeVal * 100).toStringAsFixed(1)}%'),
|
||||||
|
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.5,
|
||||||
|
max: 2.5,
|
||||||
|
value: fontSizeVal,
|
||||||
|
divisions: 20,
|
||||||
|
label: '${(fontSizeVal * 100).toStringAsFixed(1)}%',
|
||||||
|
onChanged: (double val) {
|
||||||
|
fontSizeVal = val;
|
||||||
|
widget.controller!.fontSizeVal = fontSizeVal;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption =
|
||||||
|
danmakuController.option;
|
||||||
|
DanmakuOption updatedOption =
|
||||||
|
currentOption.copyWith(
|
||||||
|
fontSize: (15 * fontSizeVal).toDouble(),
|
||||||
|
);
|
||||||
|
danmakuController.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('弹幕时长 ${danmakuSpeedVal.toString()}'),
|
||||||
|
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: 1,
|
||||||
|
max: 6,
|
||||||
|
value: danmakuSpeedVal,
|
||||||
|
divisions: 10,
|
||||||
|
label: danmakuSpeedVal.toString(),
|
||||||
|
onChanged: (double val) {
|
||||||
|
danmakuSpeedVal = val;
|
||||||
|
widget.controller!.danmakuSpeedVal = danmakuSpeedVal;
|
||||||
|
setState(() {});
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption =
|
||||||
|
danmakuController.option;
|
||||||
|
DanmakuOption updatedOption =
|
||||||
|
currentOption.copyWith(duration: val);
|
||||||
|
danmakuController.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _ = widget.controller!;
|
final _ = widget.controller!;
|
||||||
@ -556,3 +800,21 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MSliderTrackShape extends RoundedRectSliderTrackShape {
|
||||||
|
@override
|
||||||
|
Rect getPreferredRect({
|
||||||
|
required RenderBox parentBox,
|
||||||
|
Offset offset = Offset.zero,
|
||||||
|
SliderThemeData? sliderTheme,
|
||||||
|
bool isEnabled = false,
|
||||||
|
bool isDiscrete = false,
|
||||||
|
}) {
|
||||||
|
const double trackHeight = 3;
|
||||||
|
final double trackLeft = offset.dx;
|
||||||
|
final double trackTop =
|
||||||
|
offset.dy + (parentBox.size.height - trackHeight) / 2 + 4;
|
||||||
|
final double trackWidth = parentBox.size.width;
|
||||||
|
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import 'package:universal_platform/universal_platform.dart';
|
|||||||
|
|
||||||
Box videoStorage = GStrorage.video;
|
Box videoStorage = GStrorage.video;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
|
||||||
class PlPlayerController {
|
class PlPlayerController {
|
||||||
Player? _videoPlayerController;
|
Player? _videoPlayerController;
|
||||||
@ -199,12 +200,30 @@ class PlPlayerController {
|
|||||||
Rx<bool> isOpenDanmu = false.obs;
|
Rx<bool> isOpenDanmu = false.obs;
|
||||||
// 关联弹幕控制器
|
// 关联弹幕控制器
|
||||||
DanmakuController? danmakuController;
|
DanmakuController? danmakuController;
|
||||||
|
// 弹幕相关配置
|
||||||
|
late List blockTypes;
|
||||||
|
late double showArea;
|
||||||
|
late double opacityVal;
|
||||||
|
late double fontSizeVal;
|
||||||
|
late double danmakuSpeedVal;
|
||||||
|
|
||||||
// 添加一个私有构造函数
|
// 添加一个私有构造函数
|
||||||
PlPlayerController._() {
|
PlPlayerController._() {
|
||||||
_videoType = videoType;
|
_videoType = videoType;
|
||||||
isOpenDanmu.value =
|
isOpenDanmu.value =
|
||||||
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
|
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
|
||||||
|
blockTypes =
|
||||||
|
localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []);
|
||||||
|
showArea = localCache.get(LocalCacheKey.danmakuShowArea, defaultValue: 0.5);
|
||||||
|
// 不透明度
|
||||||
|
opacityVal =
|
||||||
|
localCache.get(LocalCacheKey.danmakuOpacity, defaultValue: 1.0);
|
||||||
|
// 字体大小
|
||||||
|
fontSizeVal =
|
||||||
|
localCache.get(LocalCacheKey.danmakuFontScale, defaultValue: 1.0);
|
||||||
|
// 弹幕速度
|
||||||
|
danmakuSpeedVal =
|
||||||
|
localCache.get(LocalCacheKey.danmakuSpeed, defaultValue: 4.0);
|
||||||
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
||||||
// if (status == PlayerStatus.playing) {
|
// if (status == PlayerStatus.playing) {
|
||||||
// WakelockPlus.enable();
|
// WakelockPlus.enable();
|
||||||
@ -524,6 +543,12 @@ class PlPlayerController {
|
|||||||
/// 设置倍速
|
/// 设置倍速
|
||||||
Future<void> setPlaybackSpeed(double speed) async {
|
Future<void> setPlaybackSpeed(double speed) async {
|
||||||
await _videoPlayerController?.setRate(speed);
|
await _videoPlayerController?.setRate(speed);
|
||||||
|
try {
|
||||||
|
DanmakuOption currentOption = danmakuController!.option;
|
||||||
|
DanmakuOption updatedOption = currentOption.copyWith(
|
||||||
|
duration: (currentOption.duration / speed) * playbackSpeed);
|
||||||
|
danmakuController!.updateOption(updatedOption);
|
||||||
|
} catch (_) {}
|
||||||
_playbackSpeed.value = speed;
|
_playbackSpeed.value = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -891,6 +916,13 @@ class PlPlayerController {
|
|||||||
// playerStatus.status.close();
|
// playerStatus.status.close();
|
||||||
// dataStatus.status.close();
|
// dataStatus.status.close();
|
||||||
|
|
||||||
|
/// 缓存本次弹幕选项
|
||||||
|
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
|
||||||
|
localCache.put(LocalCacheKey.danmakuShowArea, showArea);
|
||||||
|
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);
|
||||||
|
localCache.put(LocalCacheKey.danmakuFontScale, fontSizeVal);
|
||||||
|
localCache.put(LocalCacheKey.danmakuSpeed, danmakuSpeedVal);
|
||||||
|
|
||||||
removeListeners();
|
removeListeners();
|
||||||
await _videoPlayerController?.dispose();
|
await _videoPlayerController?.dispose();
|
||||||
_videoPlayerController = null;
|
_videoPlayerController = null;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:ns_danmaku/ns_danmaku.dart';
|
|||||||
|
|
||||||
class DmUtils {
|
class DmUtils {
|
||||||
static Color decimalToColor(int decimalColor) {
|
static Color decimalToColor(int decimalColor) {
|
||||||
|
// 16777215 表示白色
|
||||||
int red = (decimalColor >> 16) & 0xFF;
|
int red = (decimalColor >> 16) & 0xFF;
|
||||||
int green = (decimalColor >> 8) & 0xFF;
|
int green = (decimalColor >> 8) & 0xFF;
|
||||||
int blue = decimalColor & 0xFF;
|
int blue = decimalColor & 0xFF;
|
||||||
|
|||||||
@ -34,7 +34,12 @@ class GStrorage {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
// 本地缓存
|
// 本地缓存
|
||||||
localCache = await Hive.openBox('localCache');
|
localCache = await Hive.openBox(
|
||||||
|
'localCache',
|
||||||
|
compactionStrategy: (entries, deletedEntries) {
|
||||||
|
return deletedEntries > 4;
|
||||||
|
},
|
||||||
|
);
|
||||||
// 设置
|
// 设置
|
||||||
setting = await Hive.openBox('setting');
|
setting = await Hive.openBox('setting');
|
||||||
// 搜索历史
|
// 搜索历史
|
||||||
@ -134,6 +139,13 @@ class LocalCacheKey {
|
|||||||
//
|
//
|
||||||
static const String wbiKeys = 'wbiKeys';
|
static const String wbiKeys = 'wbiKeys';
|
||||||
static const String timeStamp = 'timeStamp';
|
static const String timeStamp = 'timeStamp';
|
||||||
|
|
||||||
|
// 弹幕相关设置 屏蔽类型 显示区域 透明度 字体大小 弹幕速度
|
||||||
|
static const String danmakuBlockType = 'danmakuBlockType';
|
||||||
|
static const String danmakuShowArea = 'danmakuShowArea';
|
||||||
|
static const String danmakuOpacity = 'danmakuOpacity';
|
||||||
|
static const String danmakuFontScale = 'danmakuFontScale';
|
||||||
|
static const String danmakuSpeed = 'danmakuSpeed';
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoBoxKey {
|
class VideoBoxKey {
|
||||||
|
|||||||
Reference in New Issue
Block a user