fix: 弹幕停留

This commit is contained in:
guozhigq
2023-08-30 13:58:54 +08:00
14 changed files with 259 additions and 94 deletions

View File

@ -179,14 +179,16 @@ class UserHttp {
}
// 移除已观看
static Future toViewDel() async {
static Future toViewDel({int? aid}) async {
final Map<String, dynamic> params = {
'jsonp': 'jsonp',
'csrf': await Request.getCsrf(),
};
params[aid != null ? 'aid' : 'viewed'] = aid ?? true;
var res = await Request().post(
Api.toViewDel,
queryParameters: {
'jsonp': 'jsonp',
'viewed': true,
'csrf': await Request.getCsrf(),
},
queryParameters: params,
);
if (res.data['code'] == 0) {
return {'status': true, 'msg': 'yeah成功移除'};

View File

@ -112,7 +112,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
duration: const Duration(milliseconds: 100),
child: DanmakuView(
createdController: (DanmakuController e) async {
_controller = e;
widget.playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: 15,

View File

@ -23,29 +23,38 @@ class LaterController extends GetxController {
return res;
}
Future toViewDel() async {
Future toViewDel({int? aid}) async {
SmartDialog.show(
useSystem: true,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
content: Text(
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: const Text('取消')),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await UserHttp.toViewDel();
var res = await UserHttp.toViewDel(aid: aid);
if (res['status']) {
if (aid != null) {
laterList.removeWhere((e) => e.aid == aid);
} else {
laterList.clear();
queryLaterList();
}
}
SmartDialog.dismiss();
SmartDialog.showToast(res['msg']);
},
child: const Text('确认删除'),
child: Text(aid != null ? '确认移除' : '确认删除'),
)
],
);
@ -65,7 +74,11 @@ class LaterController extends GetxController {
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: const Text('取消')),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await UserHttp.toViewClear();

View File

@ -80,10 +80,12 @@ class _LaterPageState extends State<LaterPage> {
? SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
var videoItem = _laterController.laterList[index];
return VideoCardH(
videoItem: _laterController.laterList[index],
videoItem: videoItem,
source: 'later',
);
longPress: () => _laterController.toViewDel(
aid: videoItem.aid));
}, childCount: _laterController.laterList.length),
)
: _laterController.isLoading.value

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
@ -53,6 +54,7 @@ class MainController extends GetxController {
final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast();
Box setting = GStrorage.setting;
DateTime? _lastPressedAt;
@override
void onInit() {
@ -61,4 +63,16 @@ class MainController extends GetxController {
Utils.checkUpdata();
}
}
Future<bool> onBackPressed(BuildContext context) {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) >
const Duration(seconds: 2)) {
// 两次点击时间间隔超过2秒重新记录时间戳
_lastPressedAt = DateTime.now();
SmartDialog.showToast("再按一次退出Pili");
return Future.value(false); // 不退出应用
}
return Future.value(true); // 退出应用
}
}

View File

@ -110,7 +110,9 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
MediaQuery.of(context).size.width * 9 / 16;
localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight);
return Scaffold(
return WillPopScope(
onWillPop: () => _mainController.onBackPressed(context),
child: Scaffold(
extendBody: true,
body: FadeTransition(
opacity: _fadeAnimation!,
@ -160,6 +162,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
);
},
),
),
);
}
}

View File

@ -55,6 +55,12 @@ class _ExtraSettingState extends State<ExtraSetting> {
defaultVal: true,
callFn: (val) => {SmartDialog.showToast('下次启动时生效')},
),
const SetSwitchItem(
title: '快速收藏',
subTitle: '点按收藏至默认,长按选择文件夹',
setKey: SettingBoxKey.enableQuickFav,
defaultVal: false,
),
ListTile(
dense: false,
title: Text('评论展示', style: titleStyle),

View File

@ -60,6 +60,18 @@ class _PlaySettingState extends State<PlaySetting> {
setKey: SettingBoxKey.autoPlayEnable,
defaultVal: true,
),
const SetSwitchItem(
title: '自动全屏',
subTitle: '视频开始播放时进入全屏',
setKey: SettingBoxKey.enableAutoEnter,
defaultVal: false,
),
const SetSwitchItem(
title: '自动退出',
subTitle: '视频结束播放时退出全屏',
setKey: SettingBoxKey.enableAutoExit,
defaultVal: false,
),
const SetSwitchItem(
title: '开启硬解',
subTitle: '以较低功耗播放视频',
@ -90,6 +102,12 @@ class _PlaySettingState extends State<PlaySetting> {
setKey: SettingBoxKey.enableAutoExit,
defaultVal: false,
),
const SetSwitchItem(
title: '双击快退/快进',
subTitle: '左侧双击快退,右侧双击快进',
setKey: SettingBoxKey.enableQuickDouble,
defaultVal: true,
),
ListTile(
dense: false,
title: Text('默认画质', style: titleStyle),

View File

@ -144,6 +144,8 @@ class VideoIntroController extends GetxController {
// 获取收藏状态
Future queryHasFavVideo() async {
/// fix 延迟查询
await Future.delayed(const Duration(milliseconds: 200));
var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
if (result['status']) {
hasFav.value = result["data"]['favoured'];
@ -275,7 +277,27 @@ class VideoIntroController extends GetxController {
}
// (取消)收藏
Future actionFavVideo() async {
Future actionFavVideo({type = 'choose'}) async {
// 收藏至默认文件夹
if (type == 'default') {
await queryVideoInFolder();
int defaultFolderId = favFolderData.value.list!.first.id!;
int favStatus = favFolderData.value.list!.first.favState!;
print('favStatus: $favStatus');
var result = await VideoHttp.favVideo(
aid: IdUtils.bv2av(bvid),
addIds: favStatus == 0 ? '$defaultFolderId' : '',
delIds: favStatus == 1 ? '$defaultFolderId' : '',
);
if (result['status']) {
if (result['data']['prompt']) {
// 重新获取收藏状态
await queryHasFavVideo();
SmartDialog.showToast('✅ 操作成功');
}
}
return;
}
try {
for (var i in favFolderData.value.list!.toList()) {
if (i.favState == 1) {
@ -288,17 +310,19 @@ class VideoIntroController extends GetxController {
// ignore: avoid_print
print(e);
}
SmartDialog.showLoading(msg: '请求中');
var result = await VideoHttp.favVideo(
aid: IdUtils.bv2av(bvid),
addIds: addMediaIdsNew.join(','),
delIds: delMediaIdsNew.join(','));
SmartDialog.dismiss();
if (result['status']) {
if (result['data']['prompt']) {
addMediaIdsNew = [];
delMediaIdsNew = [];
Get.back();
// 重新获取收藏状态
queryHasFavVideo();
await queryHasFavVideo();
SmartDialog.showToast('✅ 操作成功');
}
}

View File

@ -122,6 +122,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late final Map<dynamic, dynamic> videoItem;
Box localCache = GStrorage.localCache;
Box setting = GStrorage.setting;
late double sheetHeight;
late final bool loadingStatus; // 加载状态
@ -150,11 +151,21 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
// 收藏
showFavBottomSheet() {
showFavBottomSheet({type = 'tap'}) {
if (videoIntroController.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
bool enableDragQuickFav =
setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
// 快速收藏 &
// 点按 收藏至默认文件夹
// 长按选择文件夹
if (enableDragQuickFav) {
if (type == 'tap') {
if (!videoIntroController.hasFav.value) {
videoIntroController.actionFavVideo(type: 'default');
} else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
@ -164,6 +175,27 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
},
);
}
} else {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return FavPanel(ctr: videoIntroController);
},
);
}
} else if (type != 'longPress') {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return FavPanel(ctr: videoIntroController);
},
);
}
}
// 视频介绍
showIntroDetail() {
@ -510,6 +542,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: loadingStatus,
text: !loadingStatus

View File

@ -8,6 +8,7 @@ class ActionRowItem extends StatelessWidget {
final bool? loadingStatus;
final String? text;
final bool selectStatus;
final Function? onLongPress;
const ActionRowItem({
Key? key,
@ -17,6 +18,7 @@ class ActionRowItem extends StatelessWidget {
this.loadingStatus,
this.text,
this.selectStatus = false,
this.onLongPress,
}) : super(key: key);
@override
@ -32,6 +34,12 @@ class ActionRowItem extends StatelessWidget {
feedBack(),
onTap!(),
},
onLongPress: () {
feedBack();
if (onLongPress != null) {
onLongPress!();
}
},
child: Padding(
padding: const EdgeInsets.fromLTRB(15, 7, 15, 7),
child: Row(

View File

@ -10,6 +10,7 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:ns_danmaku/ns_danmaku.dart';
import 'package:pilipala/http/video.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/feed_back.dart';
@ -196,6 +197,8 @@ class PlPlayerController {
/// 弹幕开关
Rx<bool> isOpenDanmu = true.obs;
// 关联弹幕控制器
DanmakuController? danmakuController;
// 添加一个私有构造函数
PlPlayerController._() {
@ -312,7 +315,10 @@ class PlPlayerController {
buffered.value = Duration.zero;
_heartDuration = 0;
_position.value = Duration.zero;
// 初始化时清空弹幕,防止上次重叠
if (danmakuController != null) {
danmakuController!.clear();
}
Player player = _videoPlayerController ??
Player(
configuration: const PlayerConfiguration(
@ -778,8 +784,6 @@ class PlPlayerController {
}
toggleFullScreen(true);
print(headerControl);
print(danmuWidget);
var result = await showDialog(
context: Get.context!,
useSafeArea: false,

View File

@ -69,6 +69,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Box setting = GStrorage.setting;
late FullScreenMode mode;
late int defaultBtmProgressBehavior;
late bool enableQuickDouble;
void onDoubleTapSeekBackward() {
setState(() {
@ -82,6 +83,36 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
});
}
// 双击播放、暂停
void onDoubleTapCenter() {
final _ = widget.controller;
if (_.playerStatus.status.value == PlayerStatus.playing) {
_.togglePlay();
} else {
_.play();
}
}
doubleTapFuc(String type) {
if (!enableQuickDouble) {
onDoubleTapCenter();
return;
}
switch (type) {
case 'left':
// 双击左边区域 👈
onDoubleTapSeekBackward();
break;
case 'center':
onDoubleTapCenter();
break;
case 'right':
// 双击右边区域 👈
onDoubleTapSeekForward();
break;
}
}
@override
void initState() {
super.initState();
@ -92,6 +123,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
widget.controller.danmuWidget = widget.danmuWidget;
defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior,
defaultValue: BtmProgresBehavior.values.first.code);
enableQuickDouble =
setting.get(SettingBoxKey.enableQuickDouble, defaultValue: true);
Future.microtask(() async {
try {
@ -427,26 +460,22 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
_.controls = !_.showControls.value;
},
onDoubleTapDown: (details) {
// live模式下禁用
if (_.videoType.value == 'live') {
// live模式下禁用 锁定时🔒禁用
if (_.videoType.value == 'live' || _.controlsLock.value) {
return;
}
final totalWidth = MediaQuery.of(context).size.width;
final tapPosition = details.localPosition.dx;
final sectionWidth = totalWidth / 3;
String type = 'left';
if (tapPosition < sectionWidth) {
// 双击左边区域 👈
onDoubleTapSeekBackward();
type = 'left';
} else if (tapPosition < sectionWidth * 2) {
if (_.playerStatus.status.value == PlayerStatus.playing) {
_.togglePlay();
type = 'center';
} else {
_.play();
}
} else {
// 双击右边区域 👈
onDoubleTapSeekForward();
type = 'right';
}
doubleTapFuc(type);
},
onLongPressStart: (detail) {
feedBack();
@ -458,7 +487,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
/// 水平位置 快进 live模式下禁用
onHorizontalDragUpdate: (DragUpdateDetails details) {
if (_.videoType.value == 'live') {
// live模式下禁用 锁定时🔒禁用
if (_.videoType.value == 'live' || _.controlsLock.value) {
return;
}
final tapPosition = details.localPosition.dx;
@ -479,7 +509,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
_initTapPositoin = tapPosition;
},
onHorizontalDragEnd: (DragEndDetails details) {
if (_.videoType.value == 'live') {
if (_.videoType.value == 'live' || _.controlsLock.value) {
return;
}
_.onChangedSliderEnd();
@ -491,6 +521,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final tapPosition = details.localPosition.dx;
final sectionWidth = totalWidth / 3;
final delta = details.delta.dy;
/// 锁定时禁用
if (_.controlsLock.value) {
return;
}
if (tapPosition < sectionWidth) {
// 左边区域 👈
final brightness = _brightnessValue - delta / 100.0;
@ -626,7 +661,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child: Align(
alignment: Alignment.centerLeft,
child: FractionalTranslation(
translation: const Offset(0.5, 0.0),
translation: const Offset(1, 0.0),
child: Visibility(
visible: _.showControls.value,
child: ComBtn(

View File

@ -99,6 +99,8 @@ class SettingBoxKey {
static const String enableAutoBrightness = 'enableAutoBrightness';
static const String enableAutoEnter = 'enableAutoEnter';
static const String enableAutoExit = 'enableAutoExit';
// youtube 双击快进快退
static const String enableQuickDouble = 'enableQuickDouble';
/// 隐私
static const String blackMidsList = 'blackMidsList';
@ -108,6 +110,7 @@ class SettingBoxKey {
static const String replySortType = 'replySortType';
static const String defaultDynamicType = 'defaultDynamicType';
static const String enableHotKey = 'enableHotKey';
static const String enableQuickFav = 'enableQuickFav';
/// 外观
static const String themeMode = 'themeMode';