Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-13 14:16:19 +08:00
parent b2100f3872
commit 685852c0a4
18 changed files with 618 additions and 675 deletions

View File

@@ -1,40 +1,23 @@
enum LiveQuality { enum LiveQuality {
dolby, dolby(30000, '杜比'),
super4K, origin4K(25000, '4K 原画'),
origin, super4K(20000, '4K'),
bluRay, origin(10000, '原画'),
superHD, bluRay(400, '蓝光'),
smooth, superHD(250, '超清'),
flunt, smooth(150, '高清'),
} flunt(80, '流畅');
extension LiveQualityExt on LiveQuality { final int code;
static const List<int> _codeList = [ final String desc;
30000, const LiveQuality(this.code, this.desc);
20000,
10000,
400,
250,
150,
80,
];
int get code => _codeList[index];
static LiveQuality? fromCode(int code) { static LiveQuality? fromCode(int? code) {
final index = _codeList.indexOf(code); for (var e in LiveQuality.values) {
if (index != -1) { if (e.code == code) {
return LiveQuality.values[index]; return e;
}
} }
return null; return null;
} }
String get description => const [
'杜比',
'4K',
'原画',
'蓝光',
'超清',
'高清',
'流畅',
][index];
} }

View File

@@ -97,12 +97,11 @@ class _AboutPageState extends State<AboutPage> {
); );
} }
}, },
child: ConstrainedBox( child: ExcludeSemantics(
constraints: const BoxConstraints(maxHeight: 150), child: Image.asset(
child: ExcludeSemantics( width: 150,
child: Image.asset( height: 150,
'assets/images/logo/logo.png', 'assets/images/logo/logo.png',
),
), ),
), ),
), ),

View File

@@ -106,20 +106,21 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
} }
(bool inputDisable, String? hint) get replyHint { (bool inputDisable, String? hint) get replyHint {
bool inputDisable = false;
String? hint;
try { try {
if (subjectControl != null && subjectControl!.hasRootText()) { if (subjectControl != null && subjectControl!.hasRootText()) {
if (subjectControl!.inputDisable) { final rootText = subjectControl!.rootText;
SmartDialog.showToast(subjectControl!.rootText); inputDisable = subjectControl!.inputDisable;
return (true, null); if (inputDisable) {
SmartDialog.showToast(rootText);
} }
if ((subjectControl!.hasSwitcherType() && if (rootText.contains('可发') || rootText.contains('可见')) {
subjectControl!.switcherType != 1) || hint = rootText;
subjectControl!.rootText.contains('可发评论')) {
return (false, subjectControl!.rootText);
} }
} }
} catch (_) {} } catch (_) {}
return (false, null); return (inputDisable, hint);
} }
void onReply( void onReply(

View File

@@ -144,18 +144,11 @@ class LiveRoomController extends GetxController {
acceptQnList = item.acceptQn!.map((e) { acceptQnList = item.acceptQn!.map((e) {
return ( return (
code: e, code: e,
desc: desc: LiveQuality.fromCode(e)?.desc ?? e.toString(),
LiveQuality.values
.firstWhereOrNull((element) => element.code == e)
?.description ??
e.toString(),
); );
}).toList(); }).toList();
currentQnDesc.value = currentQnDesc.value =
LiveQuality.values LiveQuality.fromCode(currentQn)?.desc ?? currentQn.toString();
.firstWhereOrNull((element) => element.code == currentQn)
?.description ??
currentQn.toString();
videoUrl = VideoUtils.getCdnUrl(item); videoUrl = VideoUtils.getCdnUrl(item);
await playerInit(); await playerInit();
isLoaded.value = true; isLoaded.value = true;
@@ -286,10 +279,7 @@ class LiveRoomController extends GetxController {
} }
currentQn = qn; currentQn = qn;
currentQnDesc.value = currentQnDesc.value =
LiveQuality.values LiveQuality.fromCode(currentQn)?.desc ?? currentQn.toString();
.firstWhereOrNull((element) => element.code == currentQn)
?.description ??
currentQn.toString();
return queryLiveUrl(); return queryLiveUrl();
} }

View File

@@ -13,6 +13,7 @@ import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart';
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
import 'package:PiliPlus/plugin/pl_player/view.dart'; import 'package:PiliPlus/plugin/pl_player/view.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/duration_util.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
@@ -46,8 +47,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
final GlobalKey chatKey = GlobalKey(); final GlobalKey chatKey = GlobalKey();
final GlobalKey playerKey = GlobalKey(); final GlobalKey playerKey = GlobalKey();
final Color _color = const Color(0xFFEEEEEE);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -630,13 +629,14 @@ class _LiveRoomPageState extends State<LiveRoomPage>
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 5, bottom: 10), padding: const EdgeInsets.only(top: 5, bottom: 10),
child: Row( child: Row(
spacing: 6,
children: [ children: [
Obx( Obx(
() { () {
final enableShowDanmaku = final enableShowDanmaku =
plPlayerController.enableShowDanmaku.value; plPlayerController.enableShowDanmaku.value;
return IconButton( return ComBtn(
onPressed: () { onTap: () {
final newVal = !enableShowDanmaku; final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal; plPlayerController.enableShowDanmaku.value = newVal;
if (!plPlayerController.tempPlayerConf) { if (!plPlayerController.tempPlayerConf) {
@@ -646,19 +646,24 @@ class _LiveRoomPageState extends State<LiveRoomPage>
); );
} }
}, },
icon: Icon( icon: enableShowDanmaku
enableShowDanmaku ? const Icon(
? Icons.subtitles_outlined size: 22,
: Icons.subtitles_off_outlined, Icons.subtitles_outlined,
color: _color, color: Color(0xFFEEEEEE),
), )
: const Icon(
size: 22,
Icons.subtitles_off_outlined,
color: Color(0xFFEEEEEE),
),
); );
}, },
), ),
Expanded( const Expanded(
child: Text( child: Text(
'发送弹幕', '发送弹幕',
style: TextStyle(color: _color), style: TextStyle(color: Color(0xFFEEEEEE)),
), ),
), ),
Builder( Builder(
@@ -675,9 +680,13 @@ class _LiveRoomPageState extends State<LiveRoomPage>
onTapDown: _liveRoomController.onLikeTapDown, onTapDown: _liveRoomController.onLikeTapDown,
onTapUp: _liveRoomController.onLikeTapUp, onTapUp: _liveRoomController.onLikeTapUp,
onTapCancel: _liveRoomController.onLikeTapUp, onTapCancel: _liveRoomController.onLikeTapUp,
child: Padding( child: const SizedBox.square(
padding: const EdgeInsets.all(8.0), dimension: 34,
child: Icon(Icons.thumb_up_off_alt, color: _color), child: Icon(
size: 22,
color: Color(0xFFEEEEEE),
Icons.thumb_up_off_alt,
),
), ),
), ),
Positioned( Positioned(
@@ -715,9 +724,13 @@ class _LiveRoomPageState extends State<LiveRoomPage>
); );
}, },
), ),
IconButton( ComBtn(
onPressed: () => onSendDanmaku(true), onTap: () => onSendDanmaku(true),
icon: Icon(Icons.emoji_emotions_outlined, color: _color), icon: const Icon(
size: 22,
color: Color(0xFFEEEEEE),
Icons.emoji_emotions_outlined,
),
), ),
], ],
), ),

View File

@@ -37,6 +37,7 @@ class BottomControl extends StatelessWidget {
children: [ children: [
PlayOrPauseButton(plPlayerController: plPlayerController), PlayOrPauseButton(plPlayerController: plPlayerController),
ComBtn( ComBtn(
height: 30,
icon: const Icon( icon: const Icon(
Icons.refresh, Icons.refresh,
size: 18, size: 18,
@@ -45,92 +46,81 @@ class BottomControl extends StatelessWidget {
onTap: onRefresh, onTap: onRefresh,
), ),
const Spacer(), const Spacer(),
SizedBox( ComBtn(
width: 35, height: 30,
height: 35, icon: const Icon(
child: IconButton( size: 18,
tooltip: '弹幕屏蔽', Icons.block,
style: ButtonStyle( color: Colors.white,
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
if (liveRoomCtr.isLogin) {
Get.toNamed(
'/liveDmBlockPage',
parameters: {
'roomId': liveRoomCtr.roomId.toString(),
},
);
} else {
SmartDialog.showToast('账号未登录');
}
},
icon: const Icon(
size: 18,
Icons.block,
color: Colors.white,
),
), ),
onTap: () {
if (liveRoomCtr.isLogin) {
Get.toNamed(
'/liveDmBlockPage',
parameters: {
'roomId': liveRoomCtr.roomId.toString(),
},
);
} else {
SmartDialog.showToast('账号未登录');
}
},
), ),
const SizedBox(width: 10), const SizedBox(width: 3),
Obx( Obx(
() { () {
final enableShowDanmaku = final enableShowDanmaku =
plPlayerController.enableShowDanmaku.value; plPlayerController.enableShowDanmaku.value;
return SizedBox( return ComBtn(
width: 35, icon: enableShowDanmaku
height: 35, ? const Icon(
child: IconButton( size: 18,
tooltip: '弹幕开关', Icons.subtitles_outlined,
style: ButtonStyle( color: Colors.white,
padding: WidgetStateProperty.all(EdgeInsets.zero), )
), : const Icon(
onPressed: () { size: 18,
final newVal = !enableShowDanmaku; Icons.subtitles_off_outlined,
plPlayerController.enableShowDanmaku.value = newVal; color: Colors.white,
if (!plPlayerController.tempPlayerConf) { ),
GStorage.setting.put( onTap: () {
SettingBoxKey.enableShowDanmaku, final newVal = !enableShowDanmaku;
newVal, plPlayerController.enableShowDanmaku.value = newVal;
); if (!plPlayerController.tempPlayerConf) {
} GStorage.setting.put(
}, SettingBoxKey.enableShowDanmaku,
icon: Icon( newVal,
size: 18, );
enableShowDanmaku }
? Icons.subtitles_outlined },
: Icons.subtitles_off_outlined,
color: Colors.white,
),
),
); );
}, },
), ),
Obx( Obx(
() => Container( () => PopupMenuButton<BoxFit>(
height: 30, initialValue: plPlayerController.videoFit.value,
margin: const EdgeInsets.symmetric(horizontal: 10), color: Colors.black.withValues(alpha: 0.8),
alignment: Alignment.center, itemBuilder: (context) {
child: PopupMenuButton<BoxFit>( return BoxFit.values
initialValue: plPlayerController.videoFit.value, .map(
color: Colors.black.withValues(alpha: 0.8), (BoxFit boxFit) => PopupMenuItem<BoxFit>(
itemBuilder: (BuildContext context) { height: 35,
return BoxFit.values.map((BoxFit boxFit) { padding: const EdgeInsets.only(left: 30),
return PopupMenuItem<BoxFit>( value: boxFit,
height: 35, onTap: () => plPlayerController.toggleVideoFit(boxFit),
padding: const EdgeInsets.only(left: 30), child: Text(
value: boxFit, boxFit.desc,
onTap: () => plPlayerController.toggleVideoFit(boxFit), style: const TextStyle(
child: Text( color: Colors.white,
boxFit.desc, fontSize: 13,
style: const TextStyle( ),
color: Colors.white,
fontSize: 13,
), ),
), ),
); )
}).toList(); .toList();
}, },
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text( child: Text(
plPlayerController.videoFit.value.desc, plPlayerController.videoFit.value.desc,
style: const TextStyle(color: Colors.white, fontSize: 13), style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -138,46 +128,54 @@ class BottomControl extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 10),
Obx( Obx(
() => SizedBox( () => PopupMenuButton<int>(
width: 30, padding: EdgeInsets.zero,
child: PopupMenuButton<int>( initialValue: liveRoomCtr.currentQn,
padding: EdgeInsets.zero, color: Colors.black.withValues(alpha: 0.8),
initialValue: liveRoomCtr.currentQn, itemBuilder: (context) {
color: Colors.black.withValues(alpha: 0.8), return liveRoomCtr.acceptQnList
.map(
(e) => PopupMenuItem<int>(
height: 35,
padding: const EdgeInsets.only(left: 30),
value: e.code,
onTap: () => liveRoomCtr.changeQn(e.code),
child: Text(
e.desc,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
),
),
),
)
.toList();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text( child: Text(
liveRoomCtr.currentQnDesc.value, liveRoomCtr.currentQnDesc.value,
style: const TextStyle(color: Colors.white, fontSize: 13), style: const TextStyle(color: Colors.white, fontSize: 13),
), ),
itemBuilder: (BuildContext context) {
return liveRoomCtr.acceptQnList.map((e) {
return PopupMenuItem<int>(
height: 35,
padding: const EdgeInsets.only(left: 30),
value: e.code,
onTap: () => liveRoomCtr.changeQn(e.code),
child: Text(
e.desc,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
),
),
);
}).toList();
},
), ),
), ),
), ),
const SizedBox(width: 10),
ComBtn( ComBtn(
icon: const Icon( height: 30,
Icons.fullscreen, icon: plPlayerController.isFullScreen.value
semanticLabel: '全屏切换', ? const Icon(
size: 20, Icons.fullscreen_exit,
color: Colors.white, semanticLabel: '退出全屏',
), size: 24,
color: Colors.white,
)
: const Icon(
Icons.fullscreen,
semanticLabel: '全屏',
size: 24,
color: Colors.white,
),
onTap: () => plPlayerController.triggerFullScreen( onTap: () => plPlayerController.triggerFullScreen(
status: !plPlayerController.isFullScreen.value, status: !plPlayerController.isFullScreen.value,
), ),

View File

@@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:floating/floating.dart'; import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -27,6 +28,39 @@ class LiveHeaderControl extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isFullScreen = plPlayerController.isFullScreen.value; final isFullScreen = plPlayerController.isFullScreen.value;
Widget child;
if (title != null) {
child = Text(
title!,
maxLines: 1,
style: const TextStyle(
fontSize: 15,
height: 1,
color: Colors.white,
),
);
if (isFullScreen && upName != null) {
child = Column(
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
child,
Text(
upName!,
maxLines: 1,
style: const TextStyle(
fontSize: 12,
height: 1,
color: Colors.white,
),
),
],
);
}
child = Expanded(child: child);
} else {
child = const Spacer();
}
return AppBar( return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@@ -37,147 +71,75 @@ class LiveHeaderControl extends StatelessWidget {
spacing: 10, spacing: 10,
children: [ children: [
if (isFullScreen) if (isFullScreen)
SizedBox( ComBtn(
width: 35, icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
height: 35, onTap: () => plPlayerController.triggerFullScreen(status: false),
child: IconButton(
tooltip: '返回',
icon: const Icon(
FontAwesomeIcons.arrowLeft,
size: 15,
),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () =>
plPlayerController.triggerFullScreen(status: false),
),
), ),
if (title != null) child,
Expanded( ComBtn(
child: Column( icon: const Icon(
spacing: 5, size: 18,
crossAxisAlignment: CrossAxisAlignment.start, Icons.comment_outlined,
children: [ color: Colors.white,
Text(
title!,
maxLines: 1,
style: const TextStyle(
fontSize: 15,
height: 1,
color: Colors.white,
),
),
if (isFullScreen && upName != null)
Text(
upName!,
maxLines: 1,
style: const TextStyle(
fontSize: 12,
height: 1,
color: Colors.white,
),
),
],
),
)
else
const Spacer(),
SizedBox(
width: 35,
height: 35,
child: IconButton(
tooltip: '发弹幕',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onSendDanmaku,
icon: const Icon(
Icons.comment_outlined,
size: 18,
color: Colors.white,
),
), ),
onTap: onSendDanmaku,
), ),
Obx( Obx(
() { () {
final onlyPlayAudio = plPlayerController.onlyPlayAudio.value; final onlyPlayAudio = plPlayerController.onlyPlayAudio.value;
return SizedBox( return ComBtn(
width: 35, onTap: () {
height: 35, plPlayerController.onlyPlayAudio.value = !onlyPlayAudio;
child: IconButton( onPlayAudio();
onPressed: () { },
plPlayerController.onlyPlayAudio.value = !onlyPlayAudio; icon: onlyPlayAudio
onPlayAudio(); ? const Icon(
}, size: 18,
style: ButtonStyle( MdiIcons.musicCircle,
padding: WidgetStateProperty.all(EdgeInsets.zero), color: Colors.white,
), )
icon: onlyPlayAudio : const Icon(
? const Icon( size: 18,
size: 18, MdiIcons.musicCircleOutline,
MdiIcons.musicCircle, color: Colors.white,
color: Colors.white, ),
)
: const Icon(
size: 18,
MdiIcons.musicCircleOutline,
color: Colors.white,
),
),
); );
}, },
), ),
if (Platform.isAndroid) if (Platform.isAndroid)
SizedBox( ComBtn(
width: 35, onTap: () async {
height: 35, try {
child: IconButton( var floating = Floating();
tooltip: '画中画', if ((await floating.isPipAvailable) == true) {
style: ButtonStyle( plPlayerController.hiddenControls(false);
padding: WidgetStateProperty.all(EdgeInsets.zero), floating.enable(
), plPlayerController.isVertical
onPressed: () async { ? const EnableManual(
try { aspectRatio: Rational.vertical(),
var floating = Floating(); )
if ((await floating.isPipAvailable) == true) { : const EnableManual(),
plPlayerController.hiddenControls(false); );
floating.enable( }
plPlayerController.isVertical } catch (_) {}
? const EnableManual( },
aspectRatio: Rational.vertical(),
)
: const EnableManual(),
);
}
} catch (_) {}
},
icon: const Icon(
Icons.picture_in_picture_outlined,
size: 18,
color: Colors.white,
),
),
),
SizedBox(
width: 35,
height: 35,
child: IconButton(
tooltip: '定时关闭',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => PageUtils.scheduleExit(
context,
plPlayerController.isFullScreen.value,
true,
),
icon: const Icon( icon: const Icon(
size: 18, size: 18,
Icons.schedule, Icons.picture_in_picture_outlined,
color: Colors.white, color: Colors.white,
), ),
), ),
ComBtn(
onTap: () => PageUtils.scheduleExit(
context,
plPlayerController.isFullScreen.value,
true,
),
icon: const Icon(
size: 18,
Icons.schedule,
color: Colors.white,
),
), ),
], ],
), ),

View File

@@ -25,7 +25,6 @@ class MemberController extends CommonDataController<SpaceData, SpaceData?>
MemberController({required this.mid}); MemberController({required this.mid});
int mid; int mid;
String? username; String? username;
RxBool showUname = false.obs;
AccountService accountService = Get.find<AccountService>(); AccountService accountService = Get.find<AccountService>();
@@ -131,7 +130,6 @@ class MemberController extends CommonDataController<SpaceData, SpaceData?>
vsync: this, vsync: this,
length: tabs.length, length: tabs.length,
); );
showUname.value = true;
username = errMsg; username = errMsg;
loadingState.value = const Success(null); loadingState.value = const Success(null);
return true; return true;

View File

@@ -110,7 +110,7 @@ class UserInfoCard extends StatelessWidget {
List<Widget> _buildLeft(BuildContext context, ThemeData theme) => [ List<Widget> _buildLeft(BuildContext context, ThemeData theme) => [
Padding( Padding(
padding: const EdgeInsets.only(left: 20, right: 20, top: 5), padding: const EdgeInsets.only(left: 20, right: 20),
child: Wrap( child: Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
@@ -461,6 +461,7 @@ class UserInfoCard extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 5),
..._buildLeft(context, theme), ..._buildLeft(context, theme),
if (card.prInfo?.content?.isNotEmpty == true) buildPrInfo(theme), if (card.prInfo?.content?.isNotEmpty == true) buildPrInfo(theme),
const SizedBox(height: 5), const SizedBox(height: 5),

View File

@@ -61,27 +61,28 @@ class _MemberVideoState extends State<MemberVideo>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final theme = Theme.of(context); final theme = Theme.of(context);
return Stack( Widget child = refreshIndicator(
clipBehavior: Clip.none, onRefresh: _controller.onRefresh,
children: [ child: CustomScrollView(
refreshIndicator( physics: ReloadScrollPhysics(controller: _controller),
onRefresh: _controller.onRefresh, slivers: [
child: CustomScrollView( SliverPadding(
physics: ReloadScrollPhysics(controller: _controller), padding: EdgeInsets.only(
slivers: [ bottom: MediaQuery.paddingOf(context).bottom + 80,
SliverPadding( ),
padding: EdgeInsets.only( sliver: Obx(
bottom: MediaQuery.paddingOf(context).bottom + 80, () => _buildBody(theme, _controller.loadingState.value),
), ),
sliver: Obx(
() => _buildBody(theme, _controller.loadingState.value),
),
),
],
), ),
), ],
if (widget.type == ContributeType.video && ),
_controller.fromViewAid?.isNotEmpty == true) );
if (widget.type == ContributeType.video &&
_controller.fromViewAid?.isNotEmpty == true) {
return Stack(
clipBehavior: Clip.none,
children: [
child,
Obx( Obx(
() => _controller.isLocating.value != true () => _controller.isLocating.value != true
? Positioned( ? Positioned(
@@ -103,6 +104,7 @@ class _MemberVideoState extends State<MemberVideo>
?.indexWhere((i) => i.param == fromViewAid); ?.indexWhere((i) => i.param == fromViewAid);
if (locatedIndex == null || locatedIndex == -1) { if (locatedIndex == null || locatedIndex == -1) {
_controller _controller
..reload = true
..page = 0 ..page = 0
..loadingState.value = LoadingState.loading() ..loadingState.value = LoadingState.loading()
..queryData(); ..queryData();
@@ -120,8 +122,10 @@ class _MemberVideoState extends State<MemberVideo>
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
], ],
); );
}
return child;
} }
Widget _buildBody( Widget _buildBody(

View File

@@ -527,14 +527,14 @@ List<SettingsModel> get extraSettings => [
title: '超分辨率', title: '超分辨率',
leading: const Icon(Icons.stay_current_landscape_outlined), leading: const Icon(Icons.stay_current_landscape_outlined),
getSubtitle: () => getSubtitle: () =>
'当前:「${SuperResolutionType.values[Pref.superResolutionType].title}\n默认设置对番剧生效, 其他视频默认关闭\n超分辨率需要启用硬件解码, 若启用硬件解码后仍然不生效, 尝试切换硬件解码器为 auto-copy', '当前:「${Pref.superResolutionType.title}\n默认设置对番剧生效, 其他视频默认关闭\n超分辨率需要启用硬件解码, 若启用硬件解码后仍然不生效, 尝试切换硬件解码器为 auto-copy',
onTap: (setState) async { onTap: (setState) async {
SuperResolutionType? result = await showDialog( SuperResolutionType? result = await showDialog(
context: Get.context!, context: Get.context!,
builder: (context) { builder: (context) {
return SelectDialog<SuperResolutionType>( return SelectDialog<SuperResolutionType>(
title: '超分辨率', title: '超分辨率',
value: SuperResolutionType.values[Pref.superResolutionType], value: Pref.superResolutionType,
values: SuperResolutionType.values values: SuperResolutionType.values
.map((e) => (e, e.title)) .map((e) => (e, e.title))
.toList(), .toList(),

View File

@@ -204,8 +204,7 @@ List<SettingsModel> get videoSettings => [
settingsType: SettingsType.normal, settingsType: SettingsType.normal,
title: '直播默认画质', title: '直播默认画质',
leading: const Icon(Icons.video_settings_outlined), leading: const Icon(Icons.video_settings_outlined),
getSubtitle: () => getSubtitle: () => '当前画质:${LiveQuality.fromCode(Pref.liveQuality)?.desc}',
'当前画质:${LiveQualityExt.fromCode(Pref.liveQuality)!.description}',
onTap: (setState) async { onTap: (setState) async {
int? result = await showDialog( int? result = await showDialog(
context: Get.context!, context: Get.context!,
@@ -213,9 +212,7 @@ List<SettingsModel> get videoSettings => [
return SelectDialog<int>( return SelectDialog<int>(
title: '直播默认画质', title: '直播默认画质',
value: Pref.liveQuality, value: Pref.liveQuality,
values: LiveQuality.values values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
.map((e) => (e.code, e.description))
.toList(),
); );
}, },
); );
@@ -230,17 +227,15 @@ List<SettingsModel> get videoSettings => [
title: '蜂窝网络直播默认画质', title: '蜂窝网络直播默认画质',
leading: const Icon(Icons.video_settings_outlined), leading: const Icon(Icons.video_settings_outlined),
getSubtitle: () => getSubtitle: () =>
'当前画质:${LiveQualityExt.fromCode(Pref.liveQualityCellular)!.description}', '当前画质:${LiveQuality.fromCode(Pref.liveQualityCellular)?.desc}',
onTap: (setState) async { onTap: (setState) async {
int? result = await showDialog( int? result = await showDialog(
context: Get.context!, context: Get.context!,
builder: (context) { builder: (context) {
return SelectDialog<int>( return SelectDialog<int>(
title: '直播默认画质', title: '蜂窝网络直播默认画质',
value: Pref.liveQualityCellular, value: Pref.liveQualityCellular,
values: LiveQuality.values values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(),
.map((e) => (e.code, e.description))
.toList(),
); );
}, },
); );

View File

@@ -201,18 +201,18 @@ class HeaderControlState extends TripleState<HeaderControl> {
const SizedBox(width: 10), const SizedBox(width: 10),
Builder( Builder(
builder: (context) => PopupMenuButton( builder: (context) => PopupMenuButton(
initialValue: SuperResolutionType initialValue:
.values[widget.controller.superResolutionType], widget.controller.superResolutionType.value,
child: Padding( child: Padding(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
SuperResolutionType widget
.values[widget .controller
.controller .superResolutionType
.superResolutionType] .value
.title, .title,
strutStyle: const StrutStyle( strutStyle: const StrutStyle(
leading: 0, leading: 0,
@@ -235,7 +235,7 @@ class HeaderControlState extends TripleState<HeaderControl> {
), ),
), ),
onSelected: (value) { onSelected: (value) {
widget.controller.setShader(value.index); widget.controller.setShader(value);
if (context.mounted) { if (context.mounted) {
(context as Element).markNeedsBuild(); (context as Element).markNeedsBuild();
} }

View File

@@ -121,15 +121,15 @@ class _WebDavSettingPageState extends State<WebDavSettingPage> {
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.save), child: const Icon(Icons.save),
onPressed: () async { onPressed: () async {
await GStorage.setting.putAll({
SettingBoxKey.webdavUri: _uriCtr.text,
SettingBoxKey.webdavUsername: _usernameCtr.text,
SettingBoxKey.webdavPassword: _passwordCtr.text,
SettingBoxKey.webdavDirectory: _directoryCtr.text,
});
if (_uriCtr.text.isEmpty) { if (_uriCtr.text.isEmpty) {
SmartDialog.showToast('地址不能为空');
return; return;
} }
final setting = GStorage.setting;
await setting.put(SettingBoxKey.webdavUri, _uriCtr.text);
await setting.put(SettingBoxKey.webdavUsername, _usernameCtr.text);
await setting.put(SettingBoxKey.webdavPassword, _passwordCtr.text);
await setting.put(SettingBoxKey.webdavDirectory, _directoryCtr.text);
try { try {
final res = await WebDav().init(); final res = await WebDav().init();
if (res.first) { if (res.first) {

View File

@@ -9,6 +9,7 @@ import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/audio_normalization.dart'; import 'package:PiliPlus/models/common/audio_normalization.dart';
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; 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/common/video/video_type.dart';
import 'package:PiliPlus/models/user/danmaku_rule.dart'; import 'package:PiliPlus/models/user/danmaku_rule.dart';
import 'package:PiliPlus/models_new/video/video_shot/data.dart'; import 'package:PiliPlus/models_new/video/video_shot/data.dart';
@@ -646,41 +647,43 @@ class PlPlayerController {
} }
late final isAnim = _pgcType == 1 || _pgcType == 4; late final isAnim = _pgcType == 1 || _pgcType == 4;
late int superResolutionType = isAnim ? Pref.superResolutionType : 0; late final Rx<SuperResolutionType> superResolutionType =
Future<void> setShader([int? type, NativePlayer? pp]) async { (isAnim ? Pref.superResolutionType : SuperResolutionType.disable).obs;
Future<void> setShader([SuperResolutionType? type, NativePlayer? pp]) async {
if (type == null) { if (type == null) {
type ??= superResolutionType; type = superResolutionType.value;
} else { } else {
superResolutionType = type; superResolutionType.value = type;
if (isAnim && !tempPlayerConf) { if (isAnim && !tempPlayerConf) {
GStorage.setting.put(SettingBoxKey.superResolutionType, type); setting.put(SettingBoxKey.superResolutionType, type.index);
} }
} }
pp ??= _videoPlayerController?.platform as NativePlayer; pp ??= _videoPlayerController?.platform as NativePlayer;
await pp.waitForPlayerInitialization; await pp.waitForPlayerInitialization;
await pp.waitForVideoControllerInitializationIfAttached; await pp.waitForVideoControllerInitializationIfAttached;
if (type == 1) { switch (type) {
await pp.command([ case SuperResolutionType.disable:
'change-list', return pp.command(['change-list', 'glsl-shaders', 'clr', '']);
'glsl-shaders', case SuperResolutionType.efficiency:
'set', return pp.command([
Utils.buildShadersAbsolutePath( 'change-list',
(await copyShadersToExternalDirectory())?.path ?? '', 'glsl-shaders',
Constants.mpvAnime4KShadersLite, 'set',
), Utils.buildShadersAbsolutePath(
]); (await copyShadersToExternalDirectory())?.path ?? '',
} else if (type == 2) { Constants.mpvAnime4KShadersLite,
await pp.command([ ),
'change-list', ]);
'glsl-shaders', case SuperResolutionType.quality:
'set', return pp.command([
Utils.buildShadersAbsolutePath( 'change-list',
(await copyShadersToExternalDirectory())?.path ?? '', 'glsl-shaders',
Constants.mpvAnime4KShaders, 'set',
), Utils.buildShadersAbsolutePath(
]); (await copyShadersToExternalDirectory())?.path ?? '',
} else { Constants.mpvAnime4KShaders,
await pp.command(['change-list', 'glsl-shaders', 'clr', '']); ),
]);
} }
} }
@@ -711,7 +714,7 @@ class PlPlayerController {
var pp = player.platform as NativePlayer; var pp = player.platform as NativePlayer;
if (_videoPlayerController == null) { if (_videoPlayerController == null) {
if (isAnim) { if (isAnim) {
setShader(superResolutionType, pp); setShader(superResolutionType.value, pp);
} }
String audioNormalization = Pref.audioNormalization; String audioNormalization = Pref.audioNormalization;
audioNormalization = switch (audioNormalization) { audioNormalization = switch (audioNormalization) {
@@ -1500,29 +1503,31 @@ class PlPlayerController {
} }
void putDanmakuSettings() { void putDanmakuSettings() {
setting setting.putAll({
..put(SettingBoxKey.danmakuWeight, danmakuWeight) SettingBoxKey.danmakuWeight: danmakuWeight,
..put(SettingBoxKey.danmakuBlockType, blockTypes.toList()) SettingBoxKey.danmakuBlockType: blockTypes.toList(),
..put(SettingBoxKey.danmakuShowArea, showArea) SettingBoxKey.danmakuShowArea: showArea,
..put(SettingBoxKey.danmakuOpacity, danmakuOpacity) SettingBoxKey.danmakuOpacity: danmakuOpacity,
..put(SettingBoxKey.danmakuFontScale, danmakuFontScale) SettingBoxKey.danmakuFontScale: danmakuFontScale,
..put(SettingBoxKey.danmakuFontScaleFS, danmakuFontScaleFS) SettingBoxKey.danmakuFontScaleFS: danmakuFontScaleFS,
..put(SettingBoxKey.danmakuDuration, danmakuDuration) SettingBoxKey.danmakuDuration: danmakuDuration,
..put(SettingBoxKey.danmakuStaticDuration, danmakuStaticDuration) SettingBoxKey.danmakuStaticDuration: danmakuStaticDuration,
..put(SettingBoxKey.strokeWidth, strokeWidth) SettingBoxKey.strokeWidth: strokeWidth,
..put(SettingBoxKey.fontWeight, fontWeight) SettingBoxKey.fontWeight: fontWeight,
..put(SettingBoxKey.danmakuLineHeight, danmakuLineHeight); SettingBoxKey.danmakuLineHeight: danmakuLineHeight,
});
} }
void putSubtitleSettings() { void putSubtitleSettings() {
setting setting.putAll({
..put(SettingBoxKey.subtitleFontScale, subtitleFontScale) SettingBoxKey.subtitleFontScale: subtitleFontScale,
..put(SettingBoxKey.subtitleFontScaleFS, subtitleFontScaleFS) SettingBoxKey.subtitleFontScaleFS: subtitleFontScaleFS,
..put(SettingBoxKey.subtitlePaddingH, subtitlePaddingH) SettingBoxKey.subtitlePaddingH: subtitlePaddingH,
..put(SettingBoxKey.subtitlePaddingB, subtitlePaddingB) SettingBoxKey.subtitlePaddingB: subtitlePaddingB,
..put(SettingBoxKey.subtitleBgOpaticy, subtitleBgOpaticy) SettingBoxKey.subtitleBgOpaticy: subtitleBgOpaticy,
..put(SettingBoxKey.subtitleStrokeWidth, subtitleStrokeWidth) SettingBoxKey.subtitleStrokeWidth: subtitleStrokeWidth,
..put(SettingBoxKey.subtitleFontWeight, subtitleFontWeight); SettingBoxKey.subtitleFontWeight: subtitleFontWeight,
});
} }
Future<void> dispose() async { Future<void> dispose() async {

View File

@@ -266,49 +266,43 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Widget progressWidget( Widget progressWidget(
BottomControlType bottomControl, BottomControlType bottomControl,
) => switch (bottomControl) { ) => switch (bottomControl) {
/// 上一集
BottomControlType.pre => Container(
width: widgetWidth,
height: 30,
alignment: Alignment.center,
child: ComBtn(
icon: const Icon(
Icons.skip_previous,
semanticLabel: '上一集',
size: 22,
color: Colors.white,
),
onTap: () {
if (!introController.prevPlay()) {
SmartDialog.showToast('已经是第一集了');
}
},
),
),
/// 播放暂停 /// 播放暂停
BottomControlType.playOrPause => PlayOrPauseButton( BottomControlType.playOrPause => PlayOrPauseButton(
plPlayerController: plPlayerController, plPlayerController: plPlayerController,
), ),
/// 一集 /// 一集
BottomControlType.next => Container( BottomControlType.pre => ComBtn(
width: widgetWidth, width: widgetWidth,
height: 30, height: 30,
alignment: Alignment.center, icon: const Icon(
child: ComBtn( Icons.skip_previous,
icon: const Icon( semanticLabel: '上一集',
Icons.skip_next, size: 22,
semanticLabel: '下一集', color: Colors.white,
size: 22,
color: Colors.white,
),
onTap: () {
if (!introController.nextPlay()) {
SmartDialog.showToast('已经是最后一集了');
}
},
), ),
onTap: () {
if (!introController.prevPlay()) {
SmartDialog.showToast('已经是第一集了');
}
},
),
/// 下一集
BottomControlType.next => ComBtn(
width: widgetWidth,
height: 30,
icon: const Icon(
Icons.skip_next,
semanticLabel: '下一集',
size: 22,
color: Colors.white,
),
onTap: () {
if (!introController.nextPlay()) {
SmartDialog.showToast('已经是最后一集了');
}
},
), ),
/// 时间进度 /// 时间进度
@@ -317,8 +311,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
// 播放时间 // 播放时间
Obx(() { Obx(
return Text( () => Text(
DurationUtil.formatDuration( DurationUtil.formatDuration(
plPlayerController.positionSeconds.value, plPlayerController.positionSeconds.value,
), ),
@@ -328,8 +322,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
height: 1.4, height: 1.4,
fontFeatures: [FontFeature.tabularFigures()], fontFeatures: [FontFeature.tabularFigures()],
), ),
); ),
}), ),
Obx( Obx(
() => Text( () => Text(
DurationUtil.formatDuration( DurationUtil.formatDuration(
@@ -350,72 +344,63 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
BottomControlType.dmChart => Obx( BottomControlType.dmChart => Obx(
() => plPlayerController.dmTrend.isEmpty () => plPlayerController.dmTrend.isEmpty
? const SizedBox.shrink() ? const SizedBox.shrink()
: Container( : ComBtn(
width: widgetWidth, width: widgetWidth,
height: 30, height: 30,
alignment: Alignment.center, icon: plPlayerController.showDmTreandChart.value
child: ComBtn( ? const Icon(
icon: plPlayerController.showDmTreandChart.value Icons.show_chart,
? const Icon( size: 22,
Icons.show_chart, color: Colors.white,
size: 22, )
color: Colors.white, : const Stack(
) clipBehavior: Clip.none,
: const Stack( alignment: Alignment.center,
clipBehavior: Clip.none, children: [
alignment: Alignment.center, Icon(
children: [ Icons.show_chart,
Icon( size: 22,
Icons.show_chart, color: Colors.white,
size: 22, ),
color: Colors.white, Icon(
), Icons.hide_source,
Icon( size: 22,
Icons.hide_source, color: Colors.white,
size: 22, ),
color: Colors.white, ],
), ),
], onTap: () => plPlayerController.showDmTreandChart.value =
), !plPlayerController.showDmTreandChart.value,
onTap: () => plPlayerController.showDmTreandChart.value =
!plPlayerController.showDmTreandChart.value,
),
), ),
), ),
/// 超分辨率 /// 超分辨率
BottomControlType.superResolution => Container( BottomControlType.superResolution => Obx(
height: 30, () => PopupMenuButton<SuperResolutionType>(
margin: const EdgeInsets.symmetric(horizontal: 10), initialValue: plPlayerController.superResolutionType.value,
alignment: Alignment.center,
child: PopupMenuButton<SuperResolutionType>(
initialValue: SuperResolutionType
.values[plPlayerController.superResolutionType],
color: Colors.black.withValues(alpha: 0.8), color: Colors.black.withValues(alpha: 0.8),
itemBuilder: (BuildContext context) { itemBuilder: (context) {
return SuperResolutionType.values.map(( return SuperResolutionType.values
SuperResolutionType type, .map(
) { (type) => PopupMenuItem<SuperResolutionType>(
return PopupMenuItem<SuperResolutionType>( height: 35,
height: 35, padding: const EdgeInsets.only(left: 30),
padding: const EdgeInsets.only(left: 30), value: type,
value: type, onTap: () => plPlayerController.setShader(type),
onTap: () => plPlayerController.setShader(type.index), child: Text(
child: Text( type.title,
type.title, style: const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle( ),
color: Colors.white,
fontSize: 13,
), ),
), )
); .toList();
}).toList();
}, },
child: Text( child: Padding(
SuperResolutionType padding: const EdgeInsets.symmetric(horizontal: 10),
.values[plPlayerController.superResolutionType] child: Text(
.title, plPlayerController.superResolutionType.value.title,
style: const TextStyle(color: Colors.white, fontSize: 13), style: const TextStyle(color: Colors.white, fontSize: 13),
),
), ),
), ),
), ),
@@ -424,110 +409,105 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
BottomControlType.viewPoints => Obx( BottomControlType.viewPoints => Obx(
() => plPlayerController.viewPointList.isEmpty () => plPlayerController.viewPointList.isEmpty
? const SizedBox.shrink() ? const SizedBox.shrink()
: Container( : ComBtn(
width: widgetWidth, width: widgetWidth,
height: 30, height: 30,
alignment: Alignment.center, icon: Transform.rotate(
child: ComBtn( angle: pi / 2,
icon: Transform.rotate( child: const Icon(
angle: pi / 2, MdiIcons.viewHeadline,
child: const Icon( semanticLabel: '分段信息',
MdiIcons.viewHeadline, size: 22,
semanticLabel: '分段信息', color: Colors.white,
size: 22,
color: Colors.white,
),
), ),
onTap: widget.showViewPoints,
onLongPress: () {
Feedback.forLongPress(context);
plPlayerController.showVP.value =
!plPlayerController.showVP.value;
},
), ),
onTap: widget.showViewPoints,
onLongPress: () {
Feedback.forLongPress(context);
plPlayerController.showVP.value =
!plPlayerController.showVP.value;
},
), ),
), ),
/// 选集 /// 选集
BottomControlType.episode => Container( BottomControlType.episode => ComBtn(
width: widgetWidth, width: widgetWidth,
height: 30, height: 30,
alignment: Alignment.center, icon: const Icon(
child: ComBtn( Icons.list,
icon: const Icon( semanticLabel: '选集',
Icons.list, size: 22,
semanticLabel: '选集', color: Colors.white,
size: 22, ),
color: Colors.white, onTap: () {
), // part -> playAll -> season(pgc)
onTap: () { if (isPlayAll && !isPart) {
// part -> playAll -> season(pgc) widget.showEpisodes?.call();
if (isPlayAll && !isPart) { return;
widget.showEpisodes?.call(); }
return; int? index;
} int currentCid = plPlayerController.cid;
int? index; String bvid = plPlayerController.bvid;
int currentCid = plPlayerController.cid; List episodes = [];
String bvid = plPlayerController.bvid; if (isSeason) {
List episodes = []; final List<SectionItem> sections = videoDetail.ugcSeason!.sections!;
if (isSeason) { for (int i = 0; i < sections.length; i++) {
final List<SectionItem> sections = final List<EpisodeItem> episodesList = sections[i].episodes!;
videoDetail.ugcSeason!.sections!; for (int j = 0; j < episodesList.length; j++) {
for (int i = 0; i < sections.length; i++) { if (episodesList[j].cid == plPlayerController.cid) {
final List<EpisodeItem> episodesList = sections[i].episodes!; index = i;
for (int j = 0; j < episodesList.length; j++) { episodes = episodesList;
if (episodesList[j].cid == plPlayerController.cid) { break;
index = i;
episodes = episodesList;
break;
}
} }
} }
} else if (isPart) {
episodes = videoDetail.pages!;
} else if (isPgc) {
episodes =
(introController as PgcIntroController).pgcItem.episodes!;
} }
widget.showEpisodes?.call( } else if (isPart) {
index, episodes = videoDetail.pages!;
isSeason ? videoDetail.ugcSeason! : null, } else if (isPgc) {
isSeason ? null : episodes, episodes =
bvid, (introController as PgcIntroController).pgcItem.episodes!;
IdUtils.bv2av(bvid), }
isSeason && isPart widget.showEpisodes?.call(
? widget.videoDetailController?.seasonCid ?? currentCid index,
: currentCid, isSeason ? videoDetail.ugcSeason! : null,
); isSeason ? null : episodes,
}, bvid,
), IdUtils.bv2av(bvid),
isSeason && isPart
? widget.videoDetailController?.seasonCid ?? currentCid
: currentCid,
);
},
), ),
/// 画面比例 /// 画面比例
BottomControlType.fit => Container( BottomControlType.fit => Obx(
height: 30, () => PopupMenuButton<BoxFit>(
margin: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: PopupMenuButton<BoxFit>(
initialValue: plPlayerController.videoFit.value, initialValue: plPlayerController.videoFit.value,
color: Colors.black.withValues(alpha: 0.8), color: Colors.black.withValues(alpha: 0.8),
itemBuilder: (BuildContext context) { itemBuilder: (context) {
return BoxFit.values.map((BoxFit boxFit) { return BoxFit.values
return PopupMenuItem<BoxFit>( .map(
height: 35, (BoxFit boxFit) => PopupMenuItem<BoxFit>(
padding: const EdgeInsets.only(left: 30), height: 35,
value: boxFit, padding: const EdgeInsets.only(left: 30),
onTap: () => plPlayerController.toggleVideoFit(boxFit), value: boxFit,
child: Text( onTap: () => plPlayerController.toggleVideoFit(boxFit),
boxFit.desc, child: Text(
style: const TextStyle(color: Colors.white, fontSize: 13), boxFit.desc,
), style: const TextStyle(color: Colors.white, fontSize: 13),
); ),
}).toList(); ),
)
.toList();
}, },
child: Text( child: Padding(
plPlayerController.videoFit.value.desc, padding: const EdgeInsets.symmetric(horizontal: 10),
style: const TextStyle(color: Colors.white, fontSize: 13), child: Text(
plPlayerController.videoFit.value.desc,
style: const TextStyle(color: Colors.white, fontSize: 13),
),
), ),
), ),
), ),
@@ -536,83 +516,79 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
BottomControlType.subtitle => Obx( BottomControlType.subtitle => Obx(
() => widget.videoDetailController?.subtitles.isEmpty == true () => widget.videoDetailController?.subtitles.isEmpty == true
? const SizedBox.shrink() ? const SizedBox.shrink()
: SizedBox( : PopupMenuButton<int>(
width: widgetWidth, initialValue: widget
height: 30, .videoDetailController!
child: PopupMenuButton<int>( .vttSubtitlesIndex
initialValue: widget .value
.videoDetailController! .clamp(0, widget.videoDetailController!.subtitles.length),
.vttSubtitlesIndex color: Colors.black.withValues(alpha: 0.8),
.value itemBuilder: (context) {
.clamp(0, widget.videoDetailController!.subtitles.length), return [
color: Colors.black.withValues(alpha: 0.8), PopupMenuItem<int>(
itemBuilder: (BuildContext context) { value: 0,
return [ onTap: () => widget.videoDetailController!.setSubtitle(0),
PopupMenuItem<int>( child: const Text(
value: 0, "关闭字幕",
onTap: () => style: TextStyle(color: Colors.white),
widget.videoDetailController!.setSubtitle(0),
child: const Text(
"关闭字幕",
style: TextStyle(color: Colors.white),
),
), ),
...widget.videoDetailController!.subtitles.indexed.map((
e,
) {
return PopupMenuItem<int>(
value: e.$1 + 1,
onTap: () => widget.videoDetailController!
.setSubtitle(e.$1 + 1),
child: Text(
"${e.$2.lanDoc}",
style: const TextStyle(color: Colors.white),
),
);
}),
];
},
child: Container(
width: 35,
height: 30,
alignment: Alignment.center,
child: Icon(
widget.videoDetailController!.vttSubtitlesIndex.value == 0
? Icons.closed_caption_off_outlined
: Icons.closed_caption_off_rounded,
size: 22,
color: Colors.white,
semanticLabel: '字幕',
), ),
), ...widget.videoDetailController!.subtitles.indexed.map((e) {
return PopupMenuItem<int>(
value: e.$1 + 1,
onTap: () =>
widget.videoDetailController!.setSubtitle(e.$1 + 1),
child: Text(
"${e.$2.lanDoc}",
style: const TextStyle(color: Colors.white),
),
);
}),
];
},
child: SizedBox(
width: widgetWidth,
height: 30,
child:
widget.videoDetailController!.vttSubtitlesIndex.value == 0
? const Icon(
Icons.closed_caption_off_outlined,
size: 22,
color: Colors.white,
)
: const Icon(
Icons.closed_caption_off_rounded,
size: 22,
color: Colors.white,
),
), ),
), ),
), ),
/// 播放速度 /// 播放速度
BottomControlType.speed => Obx( BottomControlType.speed => Obx(
() => Container( () => PopupMenuButton<double>(
height: 30, initialValue: plPlayerController.playbackSpeed,
margin: const EdgeInsets.symmetric(horizontal: 10), color: Colors.black.withValues(alpha: 0.8),
alignment: Alignment.center, itemBuilder: (context) {
child: PopupMenuButton<double>( return plPlayerController.speedList
initialValue: plPlayerController.playbackSpeed, .map(
color: Colors.black.withValues(alpha: 0.8), (double speed) => PopupMenuItem<double>(
itemBuilder: (BuildContext context) { height: 35,
return plPlayerController.speedList.map((double speed) { padding: const EdgeInsets.only(left: 30),
return PopupMenuItem<double>( value: speed,
height: 35, onTap: () => plPlayerController.setPlaybackSpeed(speed),
padding: const EdgeInsets.only(left: 30), child: Text(
value: speed, "${speed}X",
onTap: () => plPlayerController.setPlaybackSpeed(speed), style: const TextStyle(color: Colors.white, fontSize: 13),
child: Text( semanticsLabel: "$speed倍速",
"${speed}X", ),
style: const TextStyle(color: Colors.white, fontSize: 13),
semanticsLabel: "$speed倍速",
), ),
); )
}).toList(); .toList();
}, },
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text( child: Text(
"${plPlayerController.playbackSpeed}X", "${plPlayerController.playbackSpeed}X",
style: const TextStyle(color: Colors.white, fontSize: 13), style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -623,20 +599,25 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
), ),
/// 全屏 /// 全屏
BottomControlType.fullscreen => SizedBox( BottomControlType.fullscreen => Obx(
width: widgetWidth, () => ComBtn(
height: 30, width: widgetWidth,
child: Obx( height: 30,
() => ComBtn( icon: isFullScreen
icon: Icon( ? const Icon(
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, Icons.fullscreen_exit,
semanticLabel: isFullScreen ? '退出全屏' : '全屏', semanticLabel: '退出全屏',
size: 24, size: 24,
color: Colors.white, color: Colors.white,
), )
onTap: () => : const Icon(
plPlayerController.triggerFullScreen(status: !isFullScreen), Icons.fullscreen,
), semanticLabel: '全屏',
size: 24,
color: Colors.white,
),
onTap: () =>
plPlayerController.triggerFullScreen(status: !isFullScreen),
), ),
), ),
}; };

View File

@@ -4,19 +4,23 @@ class ComBtn extends StatelessWidget {
final Widget icon; final Widget icon;
final VoidCallback? onTap; final VoidCallback? onTap;
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
final double width;
final double height;
const ComBtn({ const ComBtn({
super.key,
required this.icon, required this.icon,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
super.key, this.width = 34,
this.height = 34,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
width: 34, width: width,
height: 34, height: height,
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: onTap,
onLongPress: onLongPress, onLongPress: onLongPress,

View File

@@ -9,6 +9,7 @@ import 'package:PiliPlus/models/common/member/tab_type.dart';
import 'package:PiliPlus/models/common/msg/msg_unread_type.dart'; import 'package:PiliPlus/models/common/msg/msg_unread_type.dart';
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models/common/theme/theme_type.dart'; import 'package:PiliPlus/models/common/theme/theme_type.dart';
import 'package:PiliPlus/models/common/video/audio_quality.dart'; import 'package:PiliPlus/models/common/video/audio_quality.dart';
import 'package:PiliPlus/models/common/video/cdn_type.dart'; import 'package:PiliPlus/models/common/video/cdn_type.dart';
@@ -22,6 +23,7 @@ import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_key.dart';
@@ -406,8 +408,15 @@ class Pref {
static String get audioNormalization => static String get audioNormalization =>
_setting.get(SettingBoxKey.audioNormalization, defaultValue: '0'); _setting.get(SettingBoxKey.audioNormalization, defaultValue: '0');
static int get superResolutionType => static SuperResolutionType get superResolutionType {
_setting.get(SettingBoxKey.superResolutionType, defaultValue: 0); SuperResolutionType? superResolutionType;
final index = _setting.get(SettingBoxKey.superResolutionType);
if (index != null) {
superResolutionType = SuperResolutionType.values.getOrNull(index);
}
superResolutionType ??= SuperResolutionType.disable;
return superResolutionType;
}
static bool get preInitPlayer => static bool get preInitPlayer =>
_setting.get(SettingBoxKey.preInitPlayer, defaultValue: false); _setting.get(SettingBoxKey.preInitPlayer, defaultValue: false);