show fullscreen qa btn

Closes #1081

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-22 15:12:58 +08:00
parent 272cfcb829
commit 4d3a74f2e0
10 changed files with 138 additions and 64 deletions

View File

@@ -1,9 +1,9 @@
enum AudioQuality {
k64(30216, '64K'),
k132(30232, '132K'),
k192(30280, '192K'),
hiRes(30251, 'Hi-Res无损'),
dolby(30250, '杜比全景声'),
hiRes(30251, 'Hi-Res无损');
k192(30280, '192K'),
k132(30232, '132K'),
k64(30216, '64K');
final int code;
final String desc;

View File

@@ -1,21 +1,22 @@
enum VideoQuality {
speed240(6, '240P 极速'),
fluent360(16, '360P 流畅'),
clear480(32, '480P 清晰'),
high720(64, '720P 高清'),
high72060(74, '720P60 高帧率'),
high1080(80, '1080P 高清'),
high1080plus(112, '1080P+码率'),
high108060(116, '1080P60 高帧率'),
super4K(120, '4K 超清'),
hdr(125, 'HDR 真彩色'),
dolbyVision(126, '杜比视界'),
super8k(127, '8K 超高清');
super8k(127, '8K 超高清', '8K'),
dolbyVision(126, '杜比视界', '杜比'),
hdr(125, 'HDR 真彩色', 'HDR'),
super4K(120, '4K 超清', '4K'),
high108060(116, '1080P60 高帧率', '1080P60'),
high1080plus(112, '1080P+ 高码率', '1080P+'),
high1080(80, '1080P 高', '1080P'),
high72060(74, '720P60 高帧率', '720P60'),
high720(64, '720P 高清', '720P'),
clear480(32, '480P 清晰', '480P'),
fluent360(16, '360P 流畅', '360P'),
speed240(6, '240P 极速', '240P');
final int code;
final String desc;
final String shortDesc;
const VideoQuality(this.code, this.desc);
const VideoQuality(this.code, this.desc, this.shortDesc);
static final _codeMap = {for (var i in values) i.code: i};

View File

@@ -107,9 +107,7 @@ List<SettingsModel> get videoSettings => [
return SelectDialog<int>(
title: '默认画质',
value: Pref.defaultVideoQa,
values: VideoQuality.values.reversed
.map((e) => (e.code, e.desc))
.toList(),
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
);
},
);
@@ -132,9 +130,7 @@ List<SettingsModel> get videoSettings => [
return SelectDialog<int>(
title: '蜂窝网络画质',
value: Pref.defaultVideoQaCellular,
values: VideoQuality.values.reversed
.map((e) => (e.code, e.desc))
.toList(),
values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(),
);
},
);
@@ -160,9 +156,7 @@ List<SettingsModel> get videoSettings => [
return SelectDialog<int>(
title: '默认音质',
value: Pref.defaultAudioQa,
values: AudioQuality.values.reversed
.map((e) => (e.code, e.desc))
.toList(),
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
);
},
);
@@ -185,9 +179,7 @@ List<SettingsModel> get videoSettings => [
return SelectDialog<int>(
title: '蜂窝网络音质',
value: Pref.defaultAudioQaCellular,
values: AudioQuality.values.reversed
.map((e) => (e.code, e.desc))
.toList(),
values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(),
);
},
);

View File

@@ -98,7 +98,7 @@ class VideoDetailController extends GetxController
final Rx<LoadingState> videoState = LoadingState.loading().obs;
/// 播放器配置 画质 音质 解码格式
late VideoQuality currentVideoQa;
late Rx<VideoQuality> currentVideoQa;
AudioQuality? currentAudioQa;
late VideoDecodeFormatType currentDecodeFormats;
// 是否开始自动播放 存在多p的情况下第二p需要为true
@@ -1004,7 +1004,7 @@ class VideoDetailController extends GetxController
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
List<VideoItem> videoList = data.dash!.video!
.where((i) => i.id == currentVideoQa.code)
.where((i) => i.id == currentVideoQa.value.code)
.toList();
final List<String> supportDecodeFormats = videoList
@@ -1036,7 +1036,7 @@ class VideoDetailController extends GetxController
orElse: () => videoList.first,
);
} else {
if (currentVideoQa == VideoQuality.dolbyVision) {
if (currentVideoQa.value == VideoQuality.dolbyVision) {
currentDecodeFormats = VideoDecodeFormatTypeExt.fromString(
videoList.first.codecs!,
)!;
@@ -1203,7 +1203,7 @@ class VideoDetailController extends GetxController
);
setVideoHeight();
currentDecodeFormats = VideoDecodeFormatTypeExt.fromString('avc1')!;
currentVideoQa = VideoQuality.fromCode(data.quality!);
currentVideoQa = Rx(VideoQuality.fromCode(data.quality!));
if (autoPlay.value || plPlayerController.preInitPlayer) {
await playerInit();
}
@@ -1236,7 +1236,7 @@ class VideoDetailController extends GetxController
numbers,
);
}
currentVideoQa = VideoQuality.fromCode(resVideoQa);
currentVideoQa = Rx(VideoQuality.fromCode(resVideoQa));
/// 取出符合当前画质的videoList
final List<VideoItem> videosList = allVideosList

View File

@@ -740,17 +740,9 @@ class ReplyItemGrpc extends StatelessWidget {
final ctr = Get.find<VideoDetailController>(
tag: getTag?.call() ?? Get.arguments['heroTag'],
);
int duration = ctr.data.timeLength!;
List<int> split = matchStr
.split(':')
.reversed
.map((item) => int.parse(item))
.toList();
int seek = 0;
for (int i = 0; i < split.length; i++) {
seek += split[i] * pow(60, i).toInt();
}
isValid = seek * 1000 <= duration;
isValid =
ctr.data.timeLength! * 1000 <=
DurationUtil.parseDuration(matchStr);
} catch (e) {
if (kDebugMode) debugPrint('failed to validate: $e');
}

View File

@@ -361,7 +361,7 @@ class HeaderControlState extends TripleState<HeaderControl> {
leading: const Icon(Icons.play_circle_outline, size: 20),
title: const Text('选择画质', style: titleStyle),
subtitle: Text(
'当前画质 ${videoDetailCtr.currentVideoQa.desc}',
'当前画质 ${videoDetailCtr.currentVideoQa.value.desc}',
style: subTitleStyle,
),
),
@@ -604,7 +604,7 @@ class HeaderControlState extends TripleState<HeaderControl> {
return;
}
final List<FormatItem> videoFormat = videoInfo.supportFormats!;
final VideoQuality currentVideoQa = videoDetailCtr.currentVideoQa;
final VideoQuality currentVideoQa = videoDetailCtr.currentVideoQa.value;
/// 总质量分类
final int totalQaSam = videoFormat.length;
@@ -663,10 +663,13 @@ class HeaderControlState extends TripleState<HeaderControl> {
}
Get.back();
final int quality = item.quality!;
final newQa = VideoQuality.fromCode(quality);
videoDetailCtr
..currentVideoQa = VideoQuality.fromCode(quality)
..currentVideoQa.value = newQa
..updatePlayer();
SmartDialog.showToast("画质已变为:${newQa.desc}");
// update
if (!plPlayerController.tempPlayerConf) {
final res = await Connectivity().checkConnectivity();
@@ -682,9 +685,6 @@ class HeaderControlState extends TripleState<HeaderControl> {
);
}
}
SmartDialog.showToast(
"画质已变为:${VideoQuality.fromCode(quality).desc}",
);
},
// 可能包含会员解锁画质
enabled: index >= totalQaSam - userfulQaSam,
@@ -740,10 +740,13 @@ class HeaderControlState extends TripleState<HeaderControl> {
}
Get.back();
final int quality = i.id!;
final newQa = AudioQuality.fromCode(quality);
videoDetailCtr
..currentAudioQa = AudioQuality.fromCode(quality)
..currentAudioQa = newQa
..updatePlayer();
SmartDialog.showToast("音质已变为:${newQa.desc}");
// update
if (!plPlayerController.tempPlayerConf) {
final res = await Connectivity().checkConnectivity();
@@ -759,9 +762,6 @@ class HeaderControlState extends TripleState<HeaderControl> {
);
}
}
SmartDialog.showToast(
"音质已变为:${AudioQuality.fromCode(quality).desc}",
);
},
contentPadding: const EdgeInsets.only(left: 20, right: 20),
title: Text(i.quality),

View File

@@ -1,6 +1,6 @@
enum BottomControlType {
pre,
playOrPause,
pre,
next,
time,
episode,
@@ -11,4 +11,5 @@ enum BottomControlType {
viewPoints,
superResolution,
dmChart,
qa,
}

View File

@@ -9,6 +9,8 @@ import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
import 'package:PiliPlus/models_new/video/video_detail/section.dart';
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
@@ -31,6 +33,9 @@ import 'package:PiliPlus/plugin/pl_player/widgets/forward_seek.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
import 'package:PiliPlus/utils/duration_util.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:fl_chart/fl_chart.dart';
@@ -400,7 +405,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
.toList();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
plPlayerController.superResolutionType.value.title,
style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -508,7 +513,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
.toList();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
plPlayerController.videoFit.value.desc,
style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -595,7 +600,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
.toList();
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
"${plPlayerController.playbackSpeed}X",
style: const TextStyle(color: Colors.white, fontSize: 13),
@@ -605,6 +610,88 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
),
),
BottomControlType.qa => Obx(
() {
final videoDetailCtr = widget.videoDetailController!;
final VideoQuality currentVideoQa =
videoDetailCtr.currentVideoQa.value;
final PlayUrlModel videoInfo = videoDetailCtr.data;
final List<FormatItem> videoFormat = videoInfo.supportFormats!;
final int totalQaSam = videoFormat.length;
int userfulQaSam = 0;
final List<VideoItem> video = videoInfo.dash!.video!;
final Set<int> idSet = {};
for (final VideoItem item in video) {
final int id = item.id!;
if (!idSet.contains(id)) {
idSet.add(id);
userfulQaSam++;
}
}
return PopupMenuButton<int>(
requestFocus: false,
initialValue: currentVideoQa.code,
color: Colors.black.withValues(alpha: 0.8),
itemBuilder: (context) {
return List.generate(
totalQaSam,
(index) {
final item = videoFormat[index];
final enabled = index >= totalQaSam - userfulQaSam;
return PopupMenuItem<int>(
enabled: enabled,
height: 35,
padding: const EdgeInsets.only(left: 20),
value: item.quality,
onTap: () async {
if (currentVideoQa.code == item.quality) {
return;
}
final int quality = item.quality!;
final newQa = VideoQuality.fromCode(quality);
videoDetailCtr
..currentVideoQa.value = newQa
..updatePlayer();
SmartDialog.showToast("画质已变为:${newQa.desc}");
// update
if (!plPlayerController.tempPlayerConf) {
final res = await Connectivity().checkConnectivity();
if (res.contains(ConnectivityResult.wifi)) {
GStorage.setting.put(
SettingBoxKey.defaultVideoQa,
quality,
);
} else {
GStorage.setting.put(
SettingBoxKey.defaultVideoQaCellular,
quality,
);
}
}
},
child: Text(
item.newDesc ?? '',
style: enabled
? const TextStyle(color: Colors.white, fontSize: 13)
: null,
),
);
},
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
currentVideoQa.shortDesc,
style: const TextStyle(color: Colors.white, fontSize: 13),
),
),
);
},
),
/// 全屏
BottomControlType.fullscreen => ComBtn(
width: widgetWidth,
@@ -644,6 +731,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (isFullScreen) BottomControlType.fit,
BottomControlType.subtitle,
BottomControlType.speed,
if (isFullScreen) BottomControlType.qa,
BottomControlType.fullscreen,
];

View File

@@ -372,9 +372,9 @@ class PageUtils {
EnableManual(
aspectRatio: aspectRatio.fitsInAndroidRequirements
? aspectRatio
: width > height
? const Rational.landscape()
: const Rational.vertical(),
: height > width
? const Rational.vertical()
: const Rational.landscape(),
),
);
} else {

View File

@@ -198,7 +198,7 @@ class Pref {
static int get defaultVideoQa => _setting.get(
SettingBoxKey.defaultVideoQa,
defaultValue: VideoQuality.values.last.code,
defaultValue: VideoQuality.super8k.code,
);
static int get defaultVideoQaCellular => _setting.get(
@@ -208,7 +208,7 @@ class Pref {
static int get defaultAudioQa => _setting.get(
SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.values.last.code,
defaultValue: AudioQuality.hiRes.code,
);
static int get defaultAudioQaCellular => _setting.get(