mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-24 11:06:51 +08:00
@@ -9,6 +9,7 @@ import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/common/account_type.dart';
|
||||
import 'package:PiliPlus/models/common/audio_normalization.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/user/danmaku_rule.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 int superResolutionType = isAnim ? Pref.superResolutionType : 0;
|
||||
Future<void> setShader([int? type, NativePlayer? pp]) async {
|
||||
late final Rx<SuperResolutionType> superResolutionType =
|
||||
(isAnim ? Pref.superResolutionType : SuperResolutionType.disable).obs;
|
||||
Future<void> setShader([SuperResolutionType? type, NativePlayer? pp]) async {
|
||||
if (type == null) {
|
||||
type ??= superResolutionType;
|
||||
type = superResolutionType.value;
|
||||
} else {
|
||||
superResolutionType = type;
|
||||
superResolutionType.value = type;
|
||||
if (isAnim && !tempPlayerConf) {
|
||||
GStorage.setting.put(SettingBoxKey.superResolutionType, type);
|
||||
setting.put(SettingBoxKey.superResolutionType, type.index);
|
||||
}
|
||||
}
|
||||
pp ??= _videoPlayerController?.platform as NativePlayer;
|
||||
await pp.waitForPlayerInitialization;
|
||||
await pp.waitForVideoControllerInitializationIfAttached;
|
||||
if (type == 1) {
|
||||
await pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShadersLite,
|
||||
),
|
||||
]);
|
||||
} else if (type == 2) {
|
||||
await pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShaders,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
await pp.command(['change-list', 'glsl-shaders', 'clr', '']);
|
||||
switch (type) {
|
||||
case SuperResolutionType.disable:
|
||||
return pp.command(['change-list', 'glsl-shaders', 'clr', '']);
|
||||
case SuperResolutionType.efficiency:
|
||||
return pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShadersLite,
|
||||
),
|
||||
]);
|
||||
case SuperResolutionType.quality:
|
||||
return pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShaders,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +714,7 @@ class PlPlayerController {
|
||||
var pp = player.platform as NativePlayer;
|
||||
if (_videoPlayerController == null) {
|
||||
if (isAnim) {
|
||||
setShader(superResolutionType, pp);
|
||||
setShader(superResolutionType.value, pp);
|
||||
}
|
||||
String audioNormalization = Pref.audioNormalization;
|
||||
audioNormalization = switch (audioNormalization) {
|
||||
@@ -1500,29 +1503,31 @@ class PlPlayerController {
|
||||
}
|
||||
|
||||
void putDanmakuSettings() {
|
||||
setting
|
||||
..put(SettingBoxKey.danmakuWeight, danmakuWeight)
|
||||
..put(SettingBoxKey.danmakuBlockType, blockTypes.toList())
|
||||
..put(SettingBoxKey.danmakuShowArea, showArea)
|
||||
..put(SettingBoxKey.danmakuOpacity, danmakuOpacity)
|
||||
..put(SettingBoxKey.danmakuFontScale, danmakuFontScale)
|
||||
..put(SettingBoxKey.danmakuFontScaleFS, danmakuFontScaleFS)
|
||||
..put(SettingBoxKey.danmakuDuration, danmakuDuration)
|
||||
..put(SettingBoxKey.danmakuStaticDuration, danmakuStaticDuration)
|
||||
..put(SettingBoxKey.strokeWidth, strokeWidth)
|
||||
..put(SettingBoxKey.fontWeight, fontWeight)
|
||||
..put(SettingBoxKey.danmakuLineHeight, danmakuLineHeight);
|
||||
setting.putAll({
|
||||
SettingBoxKey.danmakuWeight: danmakuWeight,
|
||||
SettingBoxKey.danmakuBlockType: blockTypes.toList(),
|
||||
SettingBoxKey.danmakuShowArea: showArea,
|
||||
SettingBoxKey.danmakuOpacity: danmakuOpacity,
|
||||
SettingBoxKey.danmakuFontScale: danmakuFontScale,
|
||||
SettingBoxKey.danmakuFontScaleFS: danmakuFontScaleFS,
|
||||
SettingBoxKey.danmakuDuration: danmakuDuration,
|
||||
SettingBoxKey.danmakuStaticDuration: danmakuStaticDuration,
|
||||
SettingBoxKey.strokeWidth: strokeWidth,
|
||||
SettingBoxKey.fontWeight: fontWeight,
|
||||
SettingBoxKey.danmakuLineHeight: danmakuLineHeight,
|
||||
});
|
||||
}
|
||||
|
||||
void putSubtitleSettings() {
|
||||
setting
|
||||
..put(SettingBoxKey.subtitleFontScale, subtitleFontScale)
|
||||
..put(SettingBoxKey.subtitleFontScaleFS, subtitleFontScaleFS)
|
||||
..put(SettingBoxKey.subtitlePaddingH, subtitlePaddingH)
|
||||
..put(SettingBoxKey.subtitlePaddingB, subtitlePaddingB)
|
||||
..put(SettingBoxKey.subtitleBgOpaticy, subtitleBgOpaticy)
|
||||
..put(SettingBoxKey.subtitleStrokeWidth, subtitleStrokeWidth)
|
||||
..put(SettingBoxKey.subtitleFontWeight, subtitleFontWeight);
|
||||
setting.putAll({
|
||||
SettingBoxKey.subtitleFontScale: subtitleFontScale,
|
||||
SettingBoxKey.subtitleFontScaleFS: subtitleFontScaleFS,
|
||||
SettingBoxKey.subtitlePaddingH: subtitlePaddingH,
|
||||
SettingBoxKey.subtitlePaddingB: subtitlePaddingB,
|
||||
SettingBoxKey.subtitleBgOpaticy: subtitleBgOpaticy,
|
||||
SettingBoxKey.subtitleStrokeWidth: subtitleStrokeWidth,
|
||||
SettingBoxKey.subtitleFontWeight: subtitleFontWeight,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
|
||||
@@ -266,49 +266,43 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
Widget progressWidget(
|
||||
BottomControlType 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(
|
||||
plPlayerController: plPlayerController,
|
||||
),
|
||||
|
||||
/// 下一集
|
||||
BottomControlType.next => Container(
|
||||
/// 上一集
|
||||
BottomControlType.pre => ComBtn(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.skip_next,
|
||||
semanticLabel: '下一集',
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () {
|
||||
if (!introController.nextPlay()) {
|
||||
SmartDialog.showToast('已经是最后一集了');
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.skip_previous,
|
||||
semanticLabel: '上一集',
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
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,
|
||||
children: [
|
||||
// 播放时间
|
||||
Obx(() {
|
||||
return Text(
|
||||
Obx(
|
||||
() => Text(
|
||||
DurationUtil.formatDuration(
|
||||
plPlayerController.positionSeconds.value,
|
||||
),
|
||||
@@ -328,8 +322,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
height: 1.4,
|
||||
fontFeatures: [FontFeature.tabularFigures()],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Text(
|
||||
DurationUtil.formatDuration(
|
||||
@@ -350,72 +344,63 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
BottomControlType.dmChart => Obx(
|
||||
() => plPlayerController.dmTrend.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: Container(
|
||||
: ComBtn(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: ComBtn(
|
||||
icon: plPlayerController.showDmTreandChart.value
|
||||
? const Icon(
|
||||
Icons.show_chart,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.show_chart,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
Icon(
|
||||
Icons.hide_source,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => plPlayerController.showDmTreandChart.value =
|
||||
!plPlayerController.showDmTreandChart.value,
|
||||
),
|
||||
icon: plPlayerController.showDmTreandChart.value
|
||||
? const Icon(
|
||||
Icons.show_chart,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.show_chart,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
Icon(
|
||||
Icons.hide_source,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => plPlayerController.showDmTreandChart.value =
|
||||
!plPlayerController.showDmTreandChart.value,
|
||||
),
|
||||
),
|
||||
|
||||
/// 超分辨率
|
||||
BottomControlType.superResolution => Container(
|
||||
height: 30,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
child: PopupMenuButton<SuperResolutionType>(
|
||||
initialValue: SuperResolutionType
|
||||
.values[plPlayerController.superResolutionType],
|
||||
BottomControlType.superResolution => Obx(
|
||||
() => PopupMenuButton<SuperResolutionType>(
|
||||
initialValue: plPlayerController.superResolutionType.value,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return SuperResolutionType.values.map((
|
||||
SuperResolutionType type,
|
||||
) {
|
||||
return PopupMenuItem<SuperResolutionType>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: type,
|
||||
onTap: () => plPlayerController.setShader(type.index),
|
||||
child: Text(
|
||||
type.title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
itemBuilder: (context) {
|
||||
return SuperResolutionType.values
|
||||
.map(
|
||||
(type) => PopupMenuItem<SuperResolutionType>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: type,
|
||||
onTap: () => plPlayerController.setShader(type),
|
||||
child: Text(
|
||||
type.title,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
child: Text(
|
||||
SuperResolutionType
|
||||
.values[plPlayerController.superResolutionType]
|
||||
.title,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
plPlayerController.superResolutionType.value.title,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -424,110 +409,105 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
BottomControlType.viewPoints => Obx(
|
||||
() => plPlayerController.viewPointList.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: Container(
|
||||
: ComBtn(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: ComBtn(
|
||||
icon: Transform.rotate(
|
||||
angle: pi / 2,
|
||||
child: const Icon(
|
||||
MdiIcons.viewHeadline,
|
||||
semanticLabel: '分段信息',
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
icon: Transform.rotate(
|
||||
angle: pi / 2,
|
||||
child: const Icon(
|
||||
MdiIcons.viewHeadline,
|
||||
semanticLabel: '分段信息',
|
||||
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,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
child: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.list,
|
||||
semanticLabel: '选集',
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () {
|
||||
// part -> playAll -> season(pgc)
|
||||
if (isPlayAll && !isPart) {
|
||||
widget.showEpisodes?.call();
|
||||
return;
|
||||
}
|
||||
int? index;
|
||||
int currentCid = plPlayerController.cid;
|
||||
String bvid = plPlayerController.bvid;
|
||||
List episodes = [];
|
||||
if (isSeason) {
|
||||
final List<SectionItem> sections =
|
||||
videoDetail.ugcSeason!.sections!;
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
final List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||
for (int j = 0; j < episodesList.length; j++) {
|
||||
if (episodesList[j].cid == plPlayerController.cid) {
|
||||
index = i;
|
||||
episodes = episodesList;
|
||||
break;
|
||||
}
|
||||
icon: const Icon(
|
||||
Icons.list,
|
||||
semanticLabel: '选集',
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () {
|
||||
// part -> playAll -> season(pgc)
|
||||
if (isPlayAll && !isPart) {
|
||||
widget.showEpisodes?.call();
|
||||
return;
|
||||
}
|
||||
int? index;
|
||||
int currentCid = plPlayerController.cid;
|
||||
String bvid = plPlayerController.bvid;
|
||||
List episodes = [];
|
||||
if (isSeason) {
|
||||
final List<SectionItem> sections = videoDetail.ugcSeason!.sections!;
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
final List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||
for (int j = 0; j < episodesList.length; j++) {
|
||||
if (episodesList[j].cid == plPlayerController.cid) {
|
||||
index = i;
|
||||
episodes = episodesList;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (isPart) {
|
||||
episodes = videoDetail.pages!;
|
||||
} else if (isPgc) {
|
||||
episodes =
|
||||
(introController as PgcIntroController).pgcItem.episodes!;
|
||||
}
|
||||
widget.showEpisodes?.call(
|
||||
index,
|
||||
isSeason ? videoDetail.ugcSeason! : null,
|
||||
isSeason ? null : episodes,
|
||||
bvid,
|
||||
IdUtils.bv2av(bvid),
|
||||
isSeason && isPart
|
||||
? widget.videoDetailController?.seasonCid ?? currentCid
|
||||
: currentCid,
|
||||
);
|
||||
},
|
||||
),
|
||||
} else if (isPart) {
|
||||
episodes = videoDetail.pages!;
|
||||
} else if (isPgc) {
|
||||
episodes =
|
||||
(introController as PgcIntroController).pgcItem.episodes!;
|
||||
}
|
||||
widget.showEpisodes?.call(
|
||||
index,
|
||||
isSeason ? videoDetail.ugcSeason! : null,
|
||||
isSeason ? null : episodes,
|
||||
bvid,
|
||||
IdUtils.bv2av(bvid),
|
||||
isSeason && isPart
|
||||
? widget.videoDetailController?.seasonCid ?? currentCid
|
||||
: currentCid,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
/// 画面比例
|
||||
BottomControlType.fit => Container(
|
||||
height: 30,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
child: PopupMenuButton<BoxFit>(
|
||||
BottomControlType.fit => Obx(
|
||||
() => PopupMenuButton<BoxFit>(
|
||||
initialValue: plPlayerController.videoFit.value,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return BoxFit.values.map((BoxFit boxFit) {
|
||||
return PopupMenuItem<BoxFit>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: boxFit,
|
||||
onTap: () => plPlayerController.toggleVideoFit(boxFit),
|
||||
child: Text(
|
||||
boxFit.desc,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
itemBuilder: (context) {
|
||||
return BoxFit.values
|
||||
.map(
|
||||
(BoxFit boxFit) => PopupMenuItem<BoxFit>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: boxFit,
|
||||
onTap: () => plPlayerController.toggleVideoFit(boxFit),
|
||||
child: Text(
|
||||
boxFit.desc,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
child: Text(
|
||||
plPlayerController.videoFit.value.desc,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
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(
|
||||
() => widget.videoDetailController?.subtitles.isEmpty == true
|
||||
? const SizedBox.shrink()
|
||||
: SizedBox(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
child: PopupMenuButton<int>(
|
||||
initialValue: widget
|
||||
.videoDetailController!
|
||||
.vttSubtitlesIndex
|
||||
.value
|
||||
.clamp(0, widget.videoDetailController!.subtitles.length),
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
onTap: () =>
|
||||
widget.videoDetailController!.setSubtitle(0),
|
||||
child: const Text(
|
||||
"关闭字幕",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
: PopupMenuButton<int>(
|
||||
initialValue: widget
|
||||
.videoDetailController!
|
||||
.vttSubtitlesIndex
|
||||
.value
|
||||
.clamp(0, widget.videoDetailController!.subtitles.length),
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem<int>(
|
||||
value: 0,
|
||||
onTap: () => 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(
|
||||
() => Container(
|
||||
height: 30,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
child: PopupMenuButton<double>(
|
||||
initialValue: plPlayerController.playbackSpeed,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return plPlayerController.speedList.map((double speed) {
|
||||
return PopupMenuItem<double>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: speed,
|
||||
onTap: () => plPlayerController.setPlaybackSpeed(speed),
|
||||
child: Text(
|
||||
"${speed}X",
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
semanticsLabel: "$speed倍速",
|
||||
() => PopupMenuButton<double>(
|
||||
initialValue: plPlayerController.playbackSpeed,
|
||||
color: Colors.black.withValues(alpha: 0.8),
|
||||
itemBuilder: (context) {
|
||||
return plPlayerController.speedList
|
||||
.map(
|
||||
(double speed) => PopupMenuItem<double>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: speed,
|
||||
onTap: () => plPlayerController.setPlaybackSpeed(speed),
|
||||
child: Text(
|
||||
"${speed}X",
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
semanticsLabel: "$speed倍速",
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
"${plPlayerController.playbackSpeed}X",
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
@@ -623,20 +599,25 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
|
||||
/// 全屏
|
||||
BottomControlType.fullscreen => SizedBox(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
child: Obx(
|
||||
() => ComBtn(
|
||||
icon: Icon(
|
||||
isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
semanticLabel: isFullScreen ? '退出全屏' : '全屏',
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () =>
|
||||
plPlayerController.triggerFullScreen(status: !isFullScreen),
|
||||
),
|
||||
BottomControlType.fullscreen => Obx(
|
||||
() => ComBtn(
|
||||
width: widgetWidth,
|
||||
height: 30,
|
||||
icon: isFullScreen
|
||||
? const Icon(
|
||||
Icons.fullscreen_exit,
|
||||
semanticLabel: '退出全屏',
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.fullscreen,
|
||||
semanticLabel: '全屏',
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () =>
|
||||
plPlayerController.triggerFullScreen(status: !isFullScreen),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -4,19 +4,23 @@ class ComBtn extends StatelessWidget {
|
||||
final Widget icon;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const ComBtn({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
super.key,
|
||||
this.width = 34,
|
||||
this.height = 34,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
width: width,
|
||||
height: height,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
|
||||
Reference in New Issue
Block a user