mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-28 22:40:13 +08:00
feat: ai translate
Closes #1285 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] AI 原声翻译
|
||||||
- [x] SuperChat
|
- [x] SuperChat
|
||||||
- [x] 播放课堂视频
|
- [x] 播放课堂视频
|
||||||
- [x] 发起投票
|
- [x] 发起投票
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ class VideoHttp {
|
|||||||
dynamic seasonId,
|
dynamic seasonId,
|
||||||
required bool tryLook,
|
required bool tryLook,
|
||||||
required VideoType videoType,
|
required VideoType videoType,
|
||||||
|
String? language,
|
||||||
}) async {
|
}) async {
|
||||||
final params = await WbiSign.makSign({
|
final params = await WbiSign.makSign({
|
||||||
'avid': ?avid,
|
'avid': ?avid,
|
||||||
@@ -214,6 +215,7 @@ class VideoHttp {
|
|||||||
'web_location': 1315873,
|
'web_location': 1315873,
|
||||||
// 免登录查看1080p
|
// 免登录查看1080p
|
||||||
if (tryLook) 'try_look': 1,
|
if (tryLook) 'try_look': 1,
|
||||||
|
'cur_language': ?language,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class PlayUrlModel {
|
|||||||
List<FormatItem>? supportFormats;
|
List<FormatItem>? supportFormats;
|
||||||
int? lastPlayTime;
|
int? lastPlayTime;
|
||||||
int? lastPlayCid;
|
int? lastPlayCid;
|
||||||
|
String? curLanguage;
|
||||||
|
Language? language;
|
||||||
|
|
||||||
PlayUrlModel.fromJson(Map<String, dynamic> json) {
|
PlayUrlModel.fromJson(Map<String, dynamic> json) {
|
||||||
from = json['from'];
|
from = json['from'];
|
||||||
@@ -62,6 +64,51 @@ class PlayUrlModel {
|
|||||||
.toList();
|
.toList();
|
||||||
lastPlayTime = json['last_play_time'];
|
lastPlayTime = json['last_play_time'];
|
||||||
lastPlayCid = json['last_play_cid'];
|
lastPlayCid = json['last_play_cid'];
|
||||||
|
curLanguage = json['cur_language'];
|
||||||
|
language = json['language'] == null
|
||||||
|
? null
|
||||||
|
: Language.fromJson(json['language']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Language {
|
||||||
|
Language({
|
||||||
|
this.support,
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? support;
|
||||||
|
List<LanguageItem>? items;
|
||||||
|
|
||||||
|
Language.fromJson(Map<String, dynamic> json) {
|
||||||
|
support = json['support'];
|
||||||
|
items = (json['items'] as List?)
|
||||||
|
?.map((e) => LanguageItem.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LanguageItem {
|
||||||
|
LanguageItem({
|
||||||
|
this.lang,
|
||||||
|
this.title,
|
||||||
|
this.subtitleLang,
|
||||||
|
this.videoDetext,
|
||||||
|
this.videoMouthShapeChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? lang;
|
||||||
|
String? title;
|
||||||
|
String? subtitleLang;
|
||||||
|
bool? videoDetext;
|
||||||
|
bool? videoMouthShapeChange;
|
||||||
|
|
||||||
|
LanguageItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
lang = json['lang'];
|
||||||
|
title = json['title'];
|
||||||
|
subtitleLang = json['subtitle_lang'];
|
||||||
|
videoDetext = json['video_detext'];
|
||||||
|
videoMouthShapeChange = json['video_mouth_shape_change'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isFullScreen = plPlayerController.isFullScreen.value;
|
||||||
return AppBar(
|
return AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
@@ -40,6 +41,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
PlayOrPauseButton(plPlayerController: plPlayerController),
|
PlayOrPauseButton(plPlayerController: plPlayerController),
|
||||||
ComBtn(
|
ComBtn(
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '刷新',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.refresh,
|
Icons.refresh,
|
||||||
size: 18,
|
size: 18,
|
||||||
@@ -50,6 +52,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
ComBtn(
|
ComBtn(
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '屏蔽',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
size: 18,
|
size: 18,
|
||||||
Icons.block,
|
Icons.block,
|
||||||
@@ -74,6 +77,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
final enableShowLiveDanmaku =
|
final enableShowLiveDanmaku =
|
||||||
plPlayerController.enableShowLiveDanmaku.value;
|
plPlayerController.enableShowLiveDanmaku.value;
|
||||||
return ComBtn(
|
return ComBtn(
|
||||||
|
tooltip: "${enableShowLiveDanmaku ? '关闭' : '开启'}弹幕",
|
||||||
icon: enableShowLiveDanmaku
|
icon: enableShowLiveDanmaku
|
||||||
? const Icon(
|
? const Icon(
|
||||||
size: 18,
|
size: 18,
|
||||||
@@ -100,6 +104,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => PopupMenuButton<VideoFitType>(
|
() => PopupMenuButton<VideoFitType>(
|
||||||
|
tooltip: '画面比例',
|
||||||
initialValue: plPlayerController.videoFit.value,
|
initialValue: plPlayerController.videoFit.value,
|
||||||
color: Colors.black.withValues(alpha: 0.8),
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
@@ -132,6 +137,7 @@ class BottomControl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => PopupMenuButton<int>(
|
() => PopupMenuButton<int>(
|
||||||
|
tooltip: '画质',
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
initialValue: liveRoomCtr.currentQn,
|
initialValue: liveRoomCtr.currentQn,
|
||||||
color: Colors.black.withValues(alpha: 0.8),
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
@@ -165,22 +171,20 @@ class BottomControl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ComBtn(
|
ComBtn(
|
||||||
height: 30,
|
height: 30,
|
||||||
icon: plPlayerController.isFullScreen.value
|
tooltip: isFullScreen ? '退出全屏' : '全屏',
|
||||||
|
icon: isFullScreen
|
||||||
? const Icon(
|
? const Icon(
|
||||||
Icons.fullscreen_exit,
|
Icons.fullscreen_exit,
|
||||||
semanticLabel: '退出全屏',
|
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)
|
)
|
||||||
: const Icon(
|
: const Icon(
|
||||||
Icons.fullscreen,
|
Icons.fullscreen,
|
||||||
semanticLabel: '全屏',
|
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onTap: () => plPlayerController.triggerFullScreen(
|
onTap: () =>
|
||||||
status: !plPlayerController.isFullScreen.value,
|
plPlayerController.triggerFullScreen(status: !isFullScreen),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -72,11 +72,13 @@ class LiveHeaderControl extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (isFullScreen)
|
if (isFullScreen)
|
||||||
ComBtn(
|
ComBtn(
|
||||||
|
tooltip: '返回',
|
||||||
icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
|
icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
|
||||||
onTap: () => plPlayerController.triggerFullScreen(status: false),
|
onTap: () => plPlayerController.triggerFullScreen(status: false),
|
||||||
),
|
),
|
||||||
child,
|
child,
|
||||||
ComBtn(
|
ComBtn(
|
||||||
|
tooltip: '发弹幕',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
size: 18,
|
size: 18,
|
||||||
Icons.comment_outlined,
|
Icons.comment_outlined,
|
||||||
@@ -88,6 +90,7 @@ class LiveHeaderControl extends StatelessWidget {
|
|||||||
() {
|
() {
|
||||||
final onlyPlayAudio = plPlayerController.onlyPlayAudio.value;
|
final onlyPlayAudio = plPlayerController.onlyPlayAudio.value;
|
||||||
return ComBtn(
|
return ComBtn(
|
||||||
|
tooltip: '仅播放音频',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
plPlayerController.onlyPlayAudio.value = !onlyPlayAudio;
|
plPlayerController.onlyPlayAudio.value = !onlyPlayAudio;
|
||||||
onPlayAudio();
|
onPlayAudio();
|
||||||
@@ -108,6 +111,7 @@ class LiveHeaderControl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
ComBtn(
|
ComBtn(
|
||||||
|
tooltip: '画中画',
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
var floating = Floating();
|
var floating = Floating();
|
||||||
@@ -130,6 +134,7 @@ class LiveHeaderControl extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ComBtn(
|
ComBtn(
|
||||||
|
tooltip: '定时关闭',
|
||||||
onTap: () => PageUtils.scheduleExit(
|
onTap: () => PageUtils.scheduleExit(
|
||||||
context,
|
context,
|
||||||
plPlayerController.isFullScreen.value,
|
plPlayerController.isFullScreen.value,
|
||||||
|
|||||||
@@ -247,13 +247,15 @@ class VideoDetailController extends GetxController
|
|||||||
imageStatus = false;
|
imageStatus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isLoginVideo = Accounts.get(AccountType.video).isLogin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
args = Get.arguments;
|
args = Get.arguments;
|
||||||
videoType = args['videoType'];
|
videoType = args['videoType'];
|
||||||
if (videoType == VideoType.pgc) {
|
if (videoType == VideoType.pgc) {
|
||||||
if (!Accounts.get(AccountType.video).isLogin) {
|
if (!isLoginVideo) {
|
||||||
_actualVideoType = VideoType.ugc;
|
_actualVideoType = VideoType.ugc;
|
||||||
}
|
}
|
||||||
} else if (args['pgcApi'] == true) {
|
} else if (args['pgcApi'] == true) {
|
||||||
@@ -1112,6 +1114,17 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
bool isQuerying = false;
|
bool isQuerying = false;
|
||||||
|
|
||||||
|
final Rx<List<LanguageItem>?> languages = Rx<List<LanguageItem>?>(null);
|
||||||
|
final Rx<String?> currLang = Rx(null);
|
||||||
|
void setLanguage(String language) {
|
||||||
|
if (!isLoginVideo) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currLang.value = language;
|
||||||
|
queryVideoUrl(defaultST: playedTime);
|
||||||
|
}
|
||||||
|
|
||||||
// 视频链接
|
// 视频链接
|
||||||
Future<void> queryVideoUrl({
|
Future<void> queryVideoUrl({
|
||||||
Duration? defaultST,
|
Duration? defaultST,
|
||||||
@@ -1142,11 +1155,15 @@ class VideoDetailController extends GetxController
|
|||||||
seasonId: seasonId,
|
seasonId: seasonId,
|
||||||
tryLook: plPlayerController.tryLook,
|
tryLook: plPlayerController.tryLook,
|
||||||
videoType: _actualVideoType ?? videoType,
|
videoType: _actualVideoType ?? videoType,
|
||||||
|
language: currLang.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
data = result.data;
|
data = result.data;
|
||||||
|
|
||||||
|
languages.value = data.language?.items;
|
||||||
|
currLang.value = data.curLanguage;
|
||||||
|
|
||||||
if (data.acceptDesc?.contains('试看') == true) {
|
if (data.acceptDesc?.contains('试看') == true) {
|
||||||
SmartDialog.showToast(
|
SmartDialog.showToast(
|
||||||
'该视频为专属视频,仅提供试看',
|
'该视频为专属视频,仅提供试看',
|
||||||
@@ -1564,6 +1581,10 @@ class VideoDetailController extends GetxController
|
|||||||
videoUrl = null;
|
videoUrl = null;
|
||||||
audioUrl = null;
|
audioUrl = null;
|
||||||
|
|
||||||
|
// language
|
||||||
|
languages.value = null;
|
||||||
|
currLang.value = null;
|
||||||
|
|
||||||
if (scrollRatio.value != 0) {
|
if (scrollRatio.value != 0) {
|
||||||
scrollRatio.refresh();
|
scrollRatio.refresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ enum BottomControlType {
|
|||||||
superResolution,
|
superResolution,
|
||||||
dmChart,
|
dmChart,
|
||||||
qa,
|
qa,
|
||||||
|
aiTranslate,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,9 +256,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
BottomControlType.pre => ComBtn(
|
BottomControlType.pre => ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '上一集',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.skip_previous,
|
Icons.skip_previous,
|
||||||
semanticLabel: '上一集',
|
|
||||||
size: 22,
|
size: 22,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -273,9 +273,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
BottomControlType.next => ComBtn(
|
BottomControlType.next => ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '下一集',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.skip_next,
|
Icons.skip_next,
|
||||||
semanticLabel: '下一集',
|
|
||||||
size: 22,
|
size: 22,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -329,6 +329,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return ComBtn(
|
return ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '高能进度条',
|
||||||
icon: videoDetailController.showDmTreandChart.value
|
icon: videoDetailController.showDmTreandChart.value
|
||||||
? const Icon(
|
? const Icon(
|
||||||
Icons.show_chart,
|
Icons.show_chart,
|
||||||
@@ -399,11 +400,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
: ComBtn(
|
: ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '分段信息',
|
||||||
icon: Transform.rotate(
|
icon: Transform.rotate(
|
||||||
angle: math.pi / 2,
|
angle: math.pi / 2,
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
MdiIcons.viewHeadline,
|
MdiIcons.viewHeadline,
|
||||||
semanticLabel: '分段信息',
|
|
||||||
size: 22,
|
size: 22,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -421,9 +422,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
BottomControlType.episode => ComBtn(
|
BottomControlType.episode => ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: '选集',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.list,
|
Icons.list,
|
||||||
semanticLabel: '选集',
|
|
||||||
size: 22,
|
size: 22,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -501,12 +502,58 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
BottomControlType.aiTranslate => Obx(
|
||||||
|
() {
|
||||||
|
final list = videoDetailController.languages.value;
|
||||||
|
if (list != null && list.isNotEmpty) {
|
||||||
|
return PopupMenuButton<String>(
|
||||||
|
tooltip: '原声翻译',
|
||||||
|
requestFocus: false,
|
||||||
|
initialValue: videoDetailController.currLang.value,
|
||||||
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: '',
|
||||||
|
onTap: () => videoDetailController.setLanguage(''),
|
||||||
|
child: const Text(
|
||||||
|
"关闭翻译",
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...list.map((e) {
|
||||||
|
return PopupMenuItem<String>(
|
||||||
|
value: e.lang,
|
||||||
|
onTap: () => videoDetailController.setLanguage(e.lang!),
|
||||||
|
child: Text(
|
||||||
|
e.title!,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: widgetWidth,
|
||||||
|
height: 30,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.translate,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
/// 字幕
|
/// 字幕
|
||||||
BottomControlType.subtitle => Obx(
|
BottomControlType.subtitle => Obx(
|
||||||
() => videoDetailController.subtitles.isEmpty == true
|
() => videoDetailController.subtitles.isEmpty == true
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: PopupMenuButton<int>(
|
: PopupMenuButton<int>(
|
||||||
tooltip: '选择字幕',
|
tooltip: '字幕',
|
||||||
requestFocus: false,
|
requestFocus: false,
|
||||||
initialValue: videoDetailController.vttSubtitlesIndex.value
|
initialValue: videoDetailController.vttSubtitlesIndex.value
|
||||||
.clamp(
|
.clamp(
|
||||||
@@ -676,16 +723,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
BottomControlType.fullscreen => ComBtn(
|
BottomControlType.fullscreen => ComBtn(
|
||||||
width: widgetWidth,
|
width: widgetWidth,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
tooltip: isFullScreen ? '退出全屏' : '全屏',
|
||||||
icon: isFullScreen
|
icon: isFullScreen
|
||||||
? const Icon(
|
? const Icon(
|
||||||
Icons.fullscreen_exit,
|
Icons.fullscreen_exit,
|
||||||
semanticLabel: '退出全屏',
|
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)
|
)
|
||||||
: const Icon(
|
: const Icon(
|
||||||
Icons.fullscreen,
|
Icons.fullscreen,
|
||||||
semanticLabel: '全屏',
|
|
||||||
size: 24,
|
size: 24,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -709,6 +755,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
if (plPlayerController.showViewPoints) BottomControlType.viewPoints,
|
if (plPlayerController.showViewPoints) BottomControlType.viewPoints,
|
||||||
if (anySeason) BottomControlType.episode,
|
if (anySeason) BottomControlType.episode,
|
||||||
if (isFullScreen) BottomControlType.fit,
|
if (isFullScreen) BottomControlType.fit,
|
||||||
|
BottomControlType.aiTranslate,
|
||||||
BottomControlType.subtitle,
|
BottomControlType.subtitle,
|
||||||
BottomControlType.speed,
|
BottomControlType.speed,
|
||||||
if (isFullScreen) BottomControlType.qa,
|
if (isFullScreen) BottomControlType.qa,
|
||||||
@@ -1251,6 +1298,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
|
|
||||||
// 头部、底部控制条
|
// 头部、底部控制条
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
|
top: -1,
|
||||||
|
bottom: -1,
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -1489,16 +1538,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
final controlsLock =
|
final controlsLock =
|
||||||
plPlayerController.controlsLock.value;
|
plPlayerController.controlsLock.value;
|
||||||
return ComBtn(
|
return ComBtn(
|
||||||
|
tooltip: controlsLock ? '解锁' : '锁定',
|
||||||
icon: controlsLock
|
icon: controlsLock
|
||||||
? const Icon(
|
? const Icon(
|
||||||
FontAwesomeIcons.lock,
|
FontAwesomeIcons.lock,
|
||||||
semanticLabel: '解锁',
|
|
||||||
size: 15,
|
size: 15,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
)
|
)
|
||||||
: const Icon(
|
: const Icon(
|
||||||
FontAwesomeIcons.lockOpen,
|
FontAwesomeIcons.lockOpen,
|
||||||
semanticLabel: '锁定',
|
|
||||||
size: 15,
|
size: 15,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -1530,9 +1578,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
child: ComBtn(
|
child: ComBtn(
|
||||||
|
tooltip: '截图',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.photo_camera,
|
Icons.photo_camera,
|
||||||
semanticLabel: '截图',
|
|
||||||
size: 20,
|
size: 20,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AppBarAni extends StatelessWidget {
|
|||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
colors: <Color>[
|
colors: <Color>[
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
Colors.black54,
|
Color(0xBF000000),
|
||||||
],
|
],
|
||||||
tileMode: TileMode.mirror,
|
tileMode: TileMode.mirror,
|
||||||
)
|
)
|
||||||
@@ -45,7 +45,7 @@ class AppBarAni extends StatelessWidget {
|
|||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: <Color>[
|
colors: <Color>[
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
Colors.black54,
|
Color(0xBF000000),
|
||||||
],
|
],
|
||||||
tileMode: TileMode.mirror,
|
tileMode: TileMode.mirror,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class ComBtn extends StatelessWidget {
|
|||||||
final VoidCallback? onLongPress;
|
final VoidCallback? onLongPress;
|
||||||
final double width;
|
final double width;
|
||||||
final double height;
|
final double height;
|
||||||
|
final String? tooltip;
|
||||||
|
|
||||||
const ComBtn({
|
const ComBtn({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -14,11 +15,12 @@ class ComBtn extends StatelessWidget {
|
|||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.width = 34,
|
this.width = 34,
|
||||||
this.height = 34,
|
this.height = 34,
|
||||||
|
this.tooltip,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
final child = SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@@ -28,5 +30,9 @@ class ComBtn extends StatelessWidget {
|
|||||||
child: icon,
|
child: icon,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (tooltip != null) {
|
||||||
|
return Tooltip(message: tooltip, child: child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user