Compare commits

...

17 Commits

25 changed files with 231 additions and 186 deletions

View File

@ -0,0 +1,9 @@
## 1.0.21
### 修复
+ 推荐视频全屏问题
+ 番剧全屏播放时灰屏问题
+ 评论回调导致页面卡死问题
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -38,6 +38,10 @@ class VideoCardH extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int aid = videoItem.aid; final int aid = videoItem.aid;
final String bvid = videoItem.bvid; final String bvid = videoItem.bvid;
String type = 'video';
try {
type = videoItem.type;
} catch (_) {}
final String heroTag = Utils.makeHeroTag(aid); final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector( return GestureDetector(
onLongPress: () { onLongPress: () {
@ -53,6 +57,10 @@ class VideoCardH extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
try { try {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
final int cid = final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid', Get.toNamed('/video?bvid=$bvid&cid=$cid',
@ -95,12 +103,20 @@ class VideoCardH extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
if (videoItem.duration != 0)
PBadge( PBadge(
text: Utils.timeFormat(videoItem.duration!), text: Utils.timeFormat(videoItem.duration!),
right: 6.0, right: 6.0,
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null && // if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '') // videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context, // pBadge(videoItem.rcmdReason.content, context,

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const defaultNavigationBars = [ List defaultNavigationBars = [
{ {
'id': 0, 'id': 0,
'icon': Icon( 'icon': const Icon(
Icons.home_outlined, Icons.home_outlined,
size: 21, size: 21,
), ),
'selectIcon': Icon( 'selectIcon': const Icon(
Icons.home, Icons.home,
size: 21, size: 21,
), ),
@ -16,11 +16,11 @@ const defaultNavigationBars = [
}, },
{ {
'id': 1, 'id': 1,
'icon': Icon( 'icon': const Icon(
Icons.motion_photos_on_outlined, Icons.motion_photos_on_outlined,
size: 21, size: 21,
), ),
'selectIcon': Icon( 'selectIcon': const Icon(
Icons.motion_photos_on, Icons.motion_photos_on,
size: 21, size: 21,
), ),
@ -29,11 +29,11 @@ const defaultNavigationBars = [
}, },
{ {
'id': 2, 'id': 2,
'icon': Icon( 'icon': const Icon(
Icons.video_collection_outlined, Icons.video_collection_outlined,
size: 20, size: 20,
), ),
'selectIcon': Icon( 'selectIcon': const Icon(
Icons.video_collection, Icons.video_collection,
size: 21, size: 21,
), ),

View File

@ -85,7 +85,9 @@ class SearchVideoItemModel {
// title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); // title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
title = Em.regTitle(json['title']); title = Em.regTitle(json['title']);
description = json['description']; description = json['description'];
pic = 'https:${json['pic']}'; pic = json['pic'] != null && json['pic'].startsWith('//')
? 'https:${json['pic']}'
: json['pic'] ?? '';
videoReview = json['video_review']; videoReview = json['video_review'];
pubdate = json['pubdate']; pubdate = json['pubdate'];
senddate = json['senddate']; senddate = json['senddate'];

View File

@ -201,7 +201,6 @@ class _AboutPageState extends State<AboutPage> {
var cleanStatus = await CacheManage().clearCacheAll(); var cleanStatus = await CacheManage().clearCacheAll();
if (cleanStatus) { if (cleanStatus) {
getCacheSize(); getCacheSize();
SmartDialog.showToast('清除成功');
} }
}, },
title: const Text('清除缓存'), title: const Text('清除缓存'),
@ -254,12 +253,16 @@ class AboutController extends GetxController {
// 获取远程版本 // 获取远程版本
Future getRemoteApp() async { Future getRemoteApp() async {
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'}); var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
isLoading.value = false;
if (result.data == null || result.data.isEmpty) {
SmartDialog.showToast('获取远程版本失败,请检查网络');
return;
}
data = LatestDataModel.fromJson(result.data); data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data; remoteAppInfo = data;
remoteVersion.value = data.tagName!; remoteVersion.value = data.tagName!;
isUpdate.value = isUpdate.value =
Utils.needUpdate(currentVersion.value, remoteVersion.value); Utils.needUpdate(currentVersion.value, remoteVersion.value);
isLoading.value = false;
} }
// 跳转下载/本地更新 // 跳转下载/本地更新
@ -277,7 +280,7 @@ class AboutController extends GetxController {
githubRelease() { githubRelease() {
launchUrl( launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/release'), Uri.parse('https://github.com/guozhigq/pilipala/releases'),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
} }

View File

@ -34,8 +34,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : ''; userFace.value = userInfo != null ? userInfo.face : '';
// 进行tabs配置
setTabConfig();
hideSearchBar = hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true); setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) { if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
@ -43,6 +41,8 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
} }
enableGradientBg = enableGradientBg =
setting.get(SettingBoxKey.enableGradientBg, defaultValue: true); setting.get(SettingBoxKey.enableGradientBg, defaultValue: true);
// 进行tabs配置
setTabConfig();
} }
void onRefresh() { void onRefresh() {
@ -91,6 +91,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
vsync: this, vsync: this,
); );
// 监听 tabController 切换 // 监听 tabController 切换
if (enableGradientBg) {
tabController.animation!.addListener(() { tabController.animation!.addListener(() {
if (tabController.indexIsChanging) { if (tabController.indexIsChanging) {
if (initialIndex.value != tabController.index) { if (initialIndex.value != tabController.index) {
@ -105,6 +106,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
} }
}); });
} }
}
void searchDefault() async { void searchDefault() async {
var res = await Request().get(Api.searchDefault); var res = await Request().get(Api.searchDefault);

View File

@ -29,7 +29,6 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget {
class _BottomControlState extends State<BottomControl> { class _BottomControlState extends State<BottomControl> {
late PlayUrlModel videoInfo; late PlayUrlModel videoInfo;
List<PlaySpeed> playSpeed = PlaySpeed.values;
TextStyle subTitleStyle = const TextStyle(fontSize: 12); TextStyle subTitleStyle = const TextStyle(fontSize: 12);
TextStyle titleStyle = const TextStyle(fontSize: 14); TextStyle titleStyle = const TextStyle(fontSize: 14);
Size get preferredSize => const Size(double.infinity, kToolbarHeight); Size get preferredSize => const Size(double.infinity, kToolbarHeight);

View File

@ -173,6 +173,12 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.enableAi, setKey: SettingBoxKey.enableAi,
defaultVal: true, defaultVal: true,
), ),
const SetSwitchItem(
title: '相关视频推荐',
subTitle: '视频详情页推荐相关视频',
setKey: SettingBoxKey.enableRelatedVideo,
defaultVal: true,
),
ListTile( ListTile(
dense: false, dense: false,
title: Text('评论展示', style: titleStyle), title: Text('评论展示', style: titleStyle),

View File

@ -17,6 +17,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
Box videoStorage = GStrorage.video; Box videoStorage = GStrorage.video;
Box settingStorage = GStrorage.setting; Box settingStorage = GStrorage.setting;
late double playSpeedDefault; late double playSpeedDefault;
late List<double> playSpeedSystem;
late double longPressSpeedDefault; late double longPressSpeedDefault;
late List customSpeedsList; late List customSpeedsList;
late bool enableAutoLongPressSpeed; late bool enableAutoLongPressSpeed;
@ -53,6 +54,9 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// 系统预设倍速
playSpeedSystem =
videoStorage.get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed);
// 默认倍速 // 默认倍速
playSpeedDefault = playSpeedDefault =
videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
@ -64,6 +68,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []); videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
enableAutoLongPressSpeed = settingStorage enableAutoLongPressSpeed = settingStorage
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
// 开启动态长按倍速时不展示
if (enableAutoLongPressSpeed) { if (enableAutoLongPressSpeed) {
Map newItem = sheetMenu[1]; Map newItem = sheetMenu[1];
newItem['show'] = false; newItem['show'] = false;
@ -123,7 +128,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
} }
// 设定倍速弹窗 // 设定倍速弹窗
void showBottomSheet(type, i) { void showBottomSheet(String type, int i) {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -159,18 +164,11 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
} }
// //
void menuAction(type, index, id) async { void menuAction(type, int index, id) async {
double chooseSpeed = 1.0; double chooseSpeed = 1.0;
if (type == 'system' && id == -1) {
SmartDialog.showToast('系统预设倍速不支持删除');
return;
}
// 获取当前选中的倍速值 // 获取当前选中的倍速值
if (type == 'system') { chooseSpeed =
chooseSpeed = PlaySpeed.values[index].value; type == 'system' ? playSpeedSystem[index] : customSpeedsList[index];
} else {
chooseSpeed = customSpeedsList[index];
}
// 设置 // 设置
if (id == 1) { if (id == 1) {
// 设置默认倍速 // 设置默认倍速
@ -182,17 +180,22 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
videoStorage.put( videoStorage.put(
VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault); VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault);
} else if (id == -1) { } else if (id == -1) {
if (customSpeedsList[index] == playSpeedDefault) { late List speedsList =
playSpeedDefault = 1.0; type == 'system' ? playSpeedSystem : customSpeedsList;
videoStorage.put(VideoBoxKey.playSpeedDefault, playSpeedDefault); if (speedsList[index] == playSpeedDefault) {
SmartDialog.showToast('默认倍速不可删除');
} }
if (customSpeedsList[index] == longPressSpeedDefault) { if (speedsList[index] == longPressSpeedDefault) {
longPressSpeedDefault = 2.0; longPressSpeedDefault = 2.0;
videoStorage.put( videoStorage.put(
VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault); VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault);
} }
customSpeedsList.removeAt(index); speedsList.removeAt(index);
await videoStorage.put(VideoBoxKey.customSpeedsList, customSpeedsList); await videoStorage.put(
type == 'system'
? VideoBoxKey.playSpeedSystem
: VideoBoxKey.customSpeedsList,
speedsList);
} }
setState(() {}); setState(() {});
SmartDialog.showToast('操作成功'); SmartDialog.showToast('操作成功');
@ -249,6 +252,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
subtitle: Text(longPressSpeedDefault.toString()), subtitle: Text(longPressSpeedDefault.toString()),
) )
: const SizedBox(), : const SizedBox(),
if (playSpeedSystem.isNotEmpty) ...[
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 14, left: 14,
@ -272,15 +276,16 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
spacing: 8, spacing: 8,
runSpacing: 2, runSpacing: 2,
children: [ children: [
for (var i in PlaySpeed.values) ...[ for (int i = 0; i < playSpeedSystem.length; i++) ...[
FilledButton.tonal( FilledButton.tonal(
onPressed: () => showBottomSheet('system', i.index), onPressed: () => showBottomSheet('system', i),
child: Text(i.description), child: Text(playSpeedSystem[i].toString()),
), ),
] ]
], ],
), ),
), )
],
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 14, left: 14,

View File

@ -91,6 +91,7 @@ class VideoDetailController extends GetxController
late int cacheAudioQa; late int cacheAudioQa;
PersistentBottomSheetController? replyReplyBottomSheetCtr; PersistentBottomSheetController? replyReplyBottomSheetCtr;
late bool enableRelatedVideo;
@override @override
void onInit() { void onInit() {
@ -113,7 +114,8 @@ class VideoDetailController extends GetxController
autoPlay.value = autoPlay.value =
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true); setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: true); enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: true);
enableRelatedVideo =
setting.get(SettingBoxKey.enableRelatedVideo, defaultValue: true);
if (userInfo == null || if (userInfo == null ||
localCache.get(LocalCacheKey.historyPause) == true) { localCache.get(LocalCacheKey.historyPause) == true) {
enableHeart = false; enableHeart = false;
@ -129,6 +131,7 @@ class VideoDetailController extends GetxController
videoDetailCtr: this, videoDetailCtr: this,
floating: floating, floating: floating,
bvid: bvid, bvid: bvid,
videoType: videoType,
); );
// CDN优化 // CDN优化
enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);

View File

@ -23,7 +23,10 @@ class IntroDetail extends StatelessWidget {
sheetHeight = localCache.get('sheetHeight'); sheetHeight = localCache.get('sheetHeight');
return Container( return Container(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(left: 14, right: 14), padding: EdgeInsets.only(
left: 14,
right: 14,
bottom: MediaQuery.of(context).padding.bottom + 20),
height: sheetHeight, height: sheetHeight,
child: Column( child: Column(
children: [ children: [

View File

@ -280,7 +280,7 @@ class ReplyItem extends StatelessWidget {
// 完成评论,数据添加 // 完成评论,数据添加
if (value != null && value['data'] != null) if (value != null && value['data'] != null)
{ {
addReply!(value['data']) addReply?.call(value['data'])
// replyControl.replies.add(value['data']), // replyControl.replies.add(value['data']),
} }
}); });
@ -531,8 +531,8 @@ InlineSpan buildContent(
spanChilds.add(TextSpan( spanChilds.add(TextSpan(
text: str, text: str,
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = ..onTap = () =>
() => replyReply(replyItem.root == 0 ? replyItem : fReplyItem))); replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem)));
} }
// 分割文本并处理每个部分 // 分割文本并处理每个部分
@ -642,6 +642,11 @@ InlineSpan buildContent(
} else { } else {
final String redirectUrl = final String redirectUrl =
await UrlUtils.parseRedirectUrl(matchStr); await UrlUtils.parseRedirectUrl(matchStr);
if (redirectUrl == matchStr) {
Clipboard.setData(ClipboardData(text: matchStr));
SmartDialog.showToast('地址可能有误');
return;
}
final String pathSegment = Uri.parse(redirectUrl).path; final String pathSegment = Uri.parse(redirectUrl).path;
final String lastPathSegment = final String lastPathSegment =
pathSegment.split('/').last; pathSegment.split('/').last;

View File

@ -30,6 +30,9 @@ class VideoReplyReplyController extends GetxController {
if (type == 'init') { if (type == 'init') {
currentPage = 0; currentPage = 0;
} }
if (isLoadingMore) {
return;
}
isLoadingMore = true; isLoadingMore = true;
final res = await ReplyHttp.replyReplyList( final res = await ReplyHttp.replyReplyList(
oid: aid!, oid: aid!,
@ -41,7 +44,7 @@ class VideoReplyReplyController extends GetxController {
final List<ReplyItemModel> replies = res['data'].replies; final List<ReplyItemModel> replies = res['data'].replies;
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
noMore.value = '加载中...'; noMore.value = '加载中...';
if (replyList.length == res['data'].page.count) { if (replies.length == res['data'].page.count) {
noMore.value = '没有更多了'; noMore.value = '没有更多了';
} }
currentPage++; currentPage++;
@ -50,21 +53,6 @@ class VideoReplyReplyController extends GetxController {
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
} }
if (type == 'init') { if (type == 'init') {
// List<ReplyItemModel> replies = res['data'].replies;
// 添加置顶回复
// if (res['data'].upper.top != null) {
// bool flag = false;
// for (var i = 0; i < res['data'].topReplies.length; i++) {
// if (res['data'].topReplies[i].rpid == res['data'].upper.top.rpid) {
// flag = true;
// }
// }
// if (!flag) {
// replies.insert(0, res['data'].upper.top);
// }
// }
// replies.insertAll(0, res['data'].topReplies);
// res['data'].replies = replies;
replyList.value = replies; replyList.value = replies;
} else { } else {
// 每次回复之后,翻页请求有且只有相同的一条回复数据 // 每次回复之后,翻页请求有且只有相同的一条回复数据

View File

@ -54,7 +54,8 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) { scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { EasyThrottle.throttle('replylist', const Duration(milliseconds: 200),
() {
_videoReplyReplyController.queryReplyList(type: 'onLoad'); _videoReplyReplyController.queryReplyList(type: 'onLoad');
}); });
} }
@ -92,7 +93,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
icon: const Icon(Icons.close, size: 20), icon: const Icon(Icons.close, size: 20),
onPressed: () { onPressed: () {
_videoReplyReplyController.currentPage = 0; _videoReplyReplyController.currentPage = 0;
widget.closePanel!(); widget.closePanel?.call;
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
@ -184,6 +185,8 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
.add(replyItem); .add(replyItem);
}, },
replyType: widget.replyType, replyType: widget.replyType,
replyReply: (replyItem) =>
replyReply(replyItem),
); );
} }
}, },

View File

@ -521,6 +521,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
.withOpacity(0.06), .withOpacity(0.06),
), ),
), ),
if (videoDetailController.videoType ==
SearchType.video &&
videoDetailController.enableRelatedVideo)
const RelatedVideoPanel(), const RelatedVideoPanel(),
], ],
); );
@ -572,6 +575,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
controller: plPlayerController, controller: plPlayerController,
videoDetailCtr: videoDetailController, videoDetailCtr: videoDetailController,
bvid: videoDetailController.bvid, bvid: videoDetailController.bvid,
videoType: videoDetailController.videoType,
), ),
danmuWidget: Obx( danmuWidget: Obx(
() => PlDanmaku( () => PlDanmaku(

View File

@ -19,6 +19,7 @@ import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/http/danmaku.dart'; import 'package:pilipala/http/danmaku.dart';
import 'package:pilipala/services/shutdown_timer_service.dart'; import 'package:pilipala/services/shutdown_timer_service.dart';
import '../../../../models/common/search_type.dart';
import '../../../../models/video_detail_res.dart'; import '../../../../models/video_detail_res.dart';
import '../introduction/index.dart'; import '../introduction/index.dart';
@ -28,12 +29,14 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
this.videoDetailCtr, this.videoDetailCtr,
this.floating, this.floating,
this.bvid, this.bvid,
this.videoType,
super.key, super.key,
}); });
final PlPlayerController? controller; final PlPlayerController? controller;
final VideoDetailController? videoDetailCtr; final VideoDetailController? videoDetailCtr;
final Floating? floating; final Floating? floating;
final String? bvid; final String? bvid;
final SearchType? videoType;
@override @override
State<HeaderControl> createState() => _HeaderControlState(); State<HeaderControl> createState() => _HeaderControlState();
@ -44,7 +47,6 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
class _HeaderControlState extends State<HeaderControl> { class _HeaderControlState extends State<HeaderControl> {
late PlayUrlModel videoInfo; late PlayUrlModel videoInfo;
List<PlaySpeed> playSpeed = PlaySpeed.values;
static const TextStyle subTitleStyle = TextStyle(fontSize: 12); static const TextStyle subTitleStyle = TextStyle(fontSize: 12);
static const TextStyle titleStyle = TextStyle(fontSize: 14); static const TextStyle titleStyle = TextStyle(fontSize: 14);
Size get preferredSize => const Size(double.infinity, kToolbarHeight); Size get preferredSize => const Size(double.infinity, kToolbarHeight);
@ -76,7 +78,11 @@ class _HeaderControlState extends State<HeaderControl> {
} else { } else {
showTitle = false; showTitle = false;
} }
/// TODO setState() called after dispose()
if (mounted) {
setState(() {}); setState(() {});
}
}); });
} }
@ -1107,14 +1113,16 @@ class _HeaderControlState extends State<HeaderControl> {
}, },
), ),
SizedBox(width: buttonSpace), SizedBox(width: buttonSpace),
if (showTitle && isLandscape) ...[ if (showTitle &&
isLandscape &&
widget.videoType == SearchType.video) ...[
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(maxWidth: 200), constraints: const BoxConstraints(maxWidth: 200),
child: Text( child: Text(
videoIntroController.videoDetail.value.title!, videoIntroController.videoDetail.value.title ?? '',
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16, fontSize: 16,

View File

@ -277,8 +277,7 @@ class PlPlayerController {
danmakuDurationVal = danmakuDurationVal =
localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0); localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0);
// 描边粗细 // 描边粗细
strokeWidth = strokeWidth = localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5);
localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5);
playRepeat = PlayRepeat.values.toList().firstWhere( playRepeat = PlayRepeat.values.toList().firstWhere(
(e) => (e) =>
e.value == e.value ==
@ -293,11 +292,19 @@ class PlPlayerController {
_longPressSpeed.value = videoStorage _longPressSpeed.value = videoStorage
.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0); .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0);
} }
// 自定义倍速集合
speedsList = List<double>.from(videoStorage speedsList = List<double>.from(videoStorage
.get(VideoBoxKey.customSpeedsList, defaultValue: <double>[])); .get(VideoBoxKey.customSpeedsList, defaultValue: <double>[]));
for (final PlaySpeed i in PlaySpeed.values) { // 默认倍速
speedsList.add(i.value); speedsList = List<double>.from(videoStorage
} .get(VideoBoxKey.customSpeedsList, defaultValue: <double>[]));
//playSpeedSystem
final List<double> playSpeedSystem =
videoStorage.get(VideoBoxKey.playSpeedSystem, defaultValue: playSpeed);
// for (final PlaySpeed i in PlaySpeed.values) {
speedsList.addAll(playSpeedSystem);
// }
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) { // _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
// if (status == PlayerStatus.playing) { // if (status == PlayerStatus.playing) {
@ -535,8 +542,10 @@ class PlPlayerController {
if (event) { if (event) {
playerStatus.status.value = PlayerStatus.playing; playerStatus.status.value = PlayerStatus.playing;
} else { } else {
// playerStatus.status.value = PlayerStatus.paused; playerStatus.status.value = PlayerStatus.paused;
} }
videoPlayerServiceHandler.onStatusChange(
playerStatus.status.value, isBuffering.value);
/// 触发回调事件 /// 触发回调事件
for (var element in _statusListeners) { for (var element in _statusListeners) {
@ -675,18 +684,6 @@ class PlPlayerController {
_playbackSpeed.value = speed; _playbackSpeed.value = speed;
} }
/// 设置倍速
// Future<void> togglePlaybackSpeed() async {
// List<double> allowedSpeeds =
// PlaySpeed.values.map<double>((e) => e.value).toList();
// int index = allowedSpeeds.indexOf(_playbackSpeed.value);
// if (index < allowedSpeeds.length - 1) {
// setPlaybackSpeed(allowedSpeeds[index + 1]);
// } else {
// setPlaybackSpeed(allowedSpeeds[0]);
// }
// }
/// 播放视频 /// 播放视频
/// TODO _duration.value丢失 /// TODO _duration.value丢失
Future<void> play( Future<void> play(

View File

@ -1,39 +1,15 @@
enum PlaySpeed { List<double> generatePlaySpeedList() {
pointTwoFive, List<double> playSpeed = [];
pointFive, double startSpeed = 0.25;
pointSevenFive, double endSpeed = 2.0;
double increment = 0.25;
one, for (double speed = startSpeed; speed <= endSpeed; speed += increment) {
onePointTwoFive, playSpeed.add(speed);
onePointFive,
onePointSevenFive,
two,
} }
extension PlaySpeedExtension on PlaySpeed { return playSpeed;
static final List<String> _descList = [
'0.25',
'0.5',
'0.75',
'正常',
'1.25',
'1.5',
'1.75',
'2.0',
];
String get description => _descList[index];
static final List<double> _valueList = [
0.25,
0.5,
0.75,
1.0,
1.25,
1.5,
1.75,
2.0,
];
double get value => _valueList[index];
double get defaultValue => _valueList[3];
} }
// 导出 playSpeed 列表
List<double> playSpeed = generatePlaySpeedList();

View File

@ -26,6 +26,7 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
static final List<MediaItem> _item = []; static final List<MediaItem> _item = [];
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
bool enableBackgroundPlay = false; bool enableBackgroundPlay = false;
PlPlayerController player = PlPlayerController.getInstance();
VideoPlayerServiceHandler() { VideoPlayerServiceHandler() {
revalidateSetting(); revalidateSetting();
@ -38,12 +39,12 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
@override @override
Future<void> play() async { Future<void> play() async {
PlPlayerController.getInstance().play(); player.play();
} }
@override @override
Future<void> pause() async { Future<void> pause() async {
PlPlayerController.getInstance().pause(); player.pause();
} }
@override @override
@ -51,7 +52,7 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
playbackState.add(playbackState.value.copyWith( playbackState.add(playbackState.value.copyWith(
updatePosition: position, updatePosition: position,
)); ));
await PlPlayerController.getInstance().seekTo(position); await player.seekTo(position);
} }
Future<void> setMediaItem(MediaItem newMediaItem) async { Future<void> setMediaItem(MediaItem newMediaItem) async {

View File

@ -20,7 +20,7 @@ class AudioSessionHandler {
session.interruptionEventStream.listen((event) { session.interruptionEventStream.listen((event) {
final player = PlPlayerController.getInstance(); final player = PlPlayerController.getInstance();
if (event.begin) { if (event.begin) {
if (player.playerStatus != PlayerStatus.playing) return; if (!player.playerStatus.playing) return;
switch (event.type) { switch (event.type) {
case AudioInterruptionType.duck: case AudioInterruptionType.duck:
player.setVolume(player.volume.value * 0.5); player.setVolume(player.volume.value * 0.5);
@ -52,7 +52,7 @@ class AudioSessionHandler {
// 耳机拔出暂停 // 耳机拔出暂停
session.becomingNoisyEventStream.listen((_) { session.becomingNoisyEventStream.listen((_) {
final player = PlPlayerController.getInstance(); final player = PlPlayerController.getInstance();
if (player.playerStatus == PlayerStatus.playing) { if (player.playerStatus.playing) {
player.pause(); player.pause();
} }
}); });

View File

@ -99,11 +99,9 @@ class CacheManage {
try { try {
// 清除缓存 图片缓存 // 清除缓存 图片缓存
await clearLibraryCache(); await clearLibraryCache();
Timer(const Duration(milliseconds: 500), () {
SmartDialog.dismiss().then((res) { SmartDialog.dismiss().then((res) {
SmartDialog.showToast('清除完成'); SmartDialog.showToast('清除完成');
}); });
});
} catch (err) { } catch (err) {
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast(err.toString()); SmartDialog.showToast(err.toString());

View File

@ -131,7 +131,8 @@ class SettingBoxKey {
enableSearchWord = 'enableSearchWord', enableSearchWord = 'enableSearchWord',
enableSystemProxy = 'enableSystemProxy', enableSystemProxy = 'enableSystemProxy',
enableAi = 'enableAi', enableAi = 'enableAi',
defaultHomePage = 'defaultHomePage'; defaultHomePage = 'defaultHomePage',
enableRelatedVideo = 'enableRelatedVideo';
/// 外观 /// 外观
static const String themeMode = 'themeMode', static const String themeMode = 'themeMode',
@ -181,6 +182,8 @@ class VideoBoxKey {
videoSpeed = 'videoSpeed', videoSpeed = 'videoSpeed',
// 播放顺序 // 播放顺序
playRepeat = 'playRepeat', playRepeat = 'playRepeat',
// 系统预设倍速
playSpeedSystem = 'playSpeedSystem',
// 默认倍速 // 默认倍速
playSpeedDefault = 'playSpeedDefault', playSpeedDefault = 'playSpeedDefault',
// 默认长按倍速 // 默认长按倍速

View File

@ -14,6 +14,7 @@ class UrlUtils {
dio.options.validateStatus = (status) { dio.options.validateStatus = (status) {
return status == 200 || status == 301 || status == 302; return status == 200 || status == 301 || status == 302;
}; };
try {
final response = await dio.get(url); final response = await dio.get(url);
if (response.statusCode == 302) { if (response.statusCode == 302) {
redirectUrl = response.headers['location']?.first as String; redirectUrl = response.headers['location']?.first as String;
@ -27,6 +28,9 @@ class UrlUtils {
return url; return url;
} }
return redirectUrl; return redirectUrl;
} catch (err) {
return url;
}
} }
// 匹配url路由跳转 // 匹配url路由跳转

View File

@ -50,6 +50,9 @@ class Utils {
return time; return time;
} }
if (time < 3600) { if (time < 3600) {
if (time == 0) {
return time;
}
final int minute = time ~/ 60; final int minute = time ~/ 60;
final double res = time / 60; final double res = time / 60;
if (minute != res) { if (minute != res) {
@ -87,6 +90,9 @@ class Utils {
// 时间显示刚刚x分钟前 // 时间显示刚刚x分钟前
static String dateFormat(timeStamp, {formatType = 'list'}) { static String dateFormat(timeStamp, {formatType = 'list'}) {
if (timeStamp == 0 || timeStamp == null || timeStamp == '') {
return '';
}
// 当前时间 // 当前时间
int time = (DateTime.now().millisecondsSinceEpoch / 1000).round(); int time = (DateTime.now().millisecondsSinceEpoch / 1000).round();
// 对比 // 对比
@ -103,6 +109,7 @@ class Utils {
toInt: false, toInt: false,
formatType: formatType); formatType: formatType);
} }
print('distance: $distance');
if (distance <= 60) { if (distance <= 60) {
return '刚刚'; return '刚刚';
} else if (distance <= 3600) { } else if (distance <= 3600) {
@ -236,6 +243,10 @@ class Utils {
SmartDialog.dismiss(); SmartDialog.dismiss();
var currentInfo = await PackageInfo.fromPlatform(); var currentInfo = await PackageInfo.fromPlatform();
var result = await Request().get(Api.latestApp, extra: {'ua': 'mob'}); var result = await Request().get(Api.latestApp, extra: {'ua': 'mob'});
if (result.data == null || result.data.isEmpty) {
SmartDialog.showToast('获取远程版本失败,请检查网络');
return false;
}
LatestDataModel data = LatestDataModel.fromJson(result.data); LatestDataModel data = LatestDataModel.fromJson(result.data);
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!); bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
if (isUpdate) { if (isUpdate) {
@ -344,9 +355,8 @@ class Utils {
} }
static List<int> generateRandomBytes(int minLength, int maxLength) { static List<int> generateRandomBytes(int minLength, int maxLength) {
return List<int>.generate( return List<int>.generate(random.nextInt(maxLength - minLength + 1),
random.nextInt(maxLength-minLength+1), (_) => random.nextInt(0x60) + 0x20 (_) => random.nextInt(0x60) + 0x20);
);
} }
static String base64EncodeRandomString(int minLength, int maxLength) { static String base64EncodeRandomString(int minLength, int maxLength) {

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.20+1020 version: 1.0.21+1021
environment: environment:
sdk: ">=2.19.6 <3.0.0" sdk: ">=2.19.6 <3.0.0"