mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
mod: live schedule
Closes #581 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -145,24 +145,26 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
|
||||
void liveMsg() {
|
||||
LiveHttp.liveRoomDanmaPrefetch(roomId: roomId).then((v) {
|
||||
if (v['status']) {
|
||||
messages.addAll((v['data'] as List)
|
||||
.map((obj) => {
|
||||
'name': obj['user']['base']['name'],
|
||||
'uid': obj['user']['uid'],
|
||||
'text': obj['text'],
|
||||
'emots': obj['emots'],
|
||||
'uemote': obj['emoticon']['emoticon_unique'] != ""
|
||||
? obj['emoticon']
|
||||
: null,
|
||||
})
|
||||
.toList());
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => scrollToBottom(),
|
||||
);
|
||||
}
|
||||
});
|
||||
if (messages.isEmpty) {
|
||||
LiveHttp.liveRoomDanmaPrefetch(roomId: roomId).then((v) {
|
||||
if (v['status']) {
|
||||
messages.addAll((v['data'] as List)
|
||||
.map((obj) => {
|
||||
'name': obj['user']['base']['name'],
|
||||
'uid': obj['user']['uid'],
|
||||
'text': obj['text'],
|
||||
'emots': obj['emots'],
|
||||
'uemote': obj['emoticon']['emoticon_unique'] != ""
|
||||
? obj['emoticon']
|
||||
: null,
|
||||
})
|
||||
.toList());
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => scrollToBottom(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
LiveHttp.liveRoomGetDanmakuToken(roomId: roomId).then((v) {
|
||||
if (v['status']) {
|
||||
LiveDanmakuInfo info = v['data'];
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:PiliPlus/http/live.dart';
|
||||
import 'package:PiliPlus/pages/live_room/widgets/chat.dart';
|
||||
import 'package:PiliPlus/pages/live_room/widgets/header_control.dart';
|
||||
import 'package:PiliPlus/services/service_locator.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
@@ -75,7 +76,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
videoSourceInit();
|
||||
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
|
||||
plPlayerController.autoEnterFullscreen();
|
||||
_liveRoomController.liveMsg();
|
||||
plPlayerController.addStatusLister(playerListener);
|
||||
_listener = plPlayerController.isFullScreen.listen((isFullScreen) {
|
||||
if (isFullScreen != _isFullScreen) {
|
||||
_isFullScreen = isFullScreen;
|
||||
@@ -84,6 +85,16 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
});
|
||||
}
|
||||
|
||||
void playerListener(PlayerStatus? status) {
|
||||
if (status != PlayerStatus.playing) {
|
||||
plPlayerController.danmakuController?.pause();
|
||||
_liveRoomController.msgStream?.close();
|
||||
} else {
|
||||
plPlayerController.danmakuController?.resume();
|
||||
_liveRoomController.liveMsg();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateFontSize() async {
|
||||
if (Platform.isAndroid) {
|
||||
_isPipMode =
|
||||
@@ -119,6 +130,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
_liveRoomController.msgStream?.close();
|
||||
// floating?.dispose();
|
||||
_node.dispose();
|
||||
plPlayerController.removeStatusLister(playerListener);
|
||||
plPlayerController.dispose();
|
||||
_ctr.dispose();
|
||||
super.dispose();
|
||||
@@ -160,10 +172,13 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
fill: fill,
|
||||
alignment: alignment,
|
||||
plPlayerController: plPlayerController,
|
||||
headerControl: LiveHeaderControl(
|
||||
plPlayerController: plPlayerController,
|
||||
floating: floating,
|
||||
),
|
||||
bottomControl: BottomControl(
|
||||
plPlayerController: plPlayerController,
|
||||
liveRoomCtr: _liveRoomController,
|
||||
floating: floating,
|
||||
onRefresh: () {
|
||||
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
|
||||
},
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/pages/live_room/index.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
const BottomControl({
|
||||
required this.plPlayerController,
|
||||
required this.liveRoomCtr,
|
||||
this.floating,
|
||||
required this.onRefresh,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final PlPlayerController plPlayerController;
|
||||
final LiveRoomController liveRoomCtr;
|
||||
final Floating? floating;
|
||||
final VoidCallback onRefresh;
|
||||
|
||||
final TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||
@@ -39,6 +34,10 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
titleSpacing: 14,
|
||||
title: Row(
|
||||
children: [
|
||||
PlayOrPauseButton(
|
||||
plPlayerController: plPlayerController,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
@@ -78,18 +77,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
Obx(
|
||||
() => IconButton(
|
||||
onPressed: plPlayerController.setOnlyPlayAudio,
|
||||
icon: Icon(
|
||||
size: 18,
|
||||
plPlayerController.onlyPlayAudio.value
|
||||
? MdiIcons.musicCircle
|
||||
: MdiIcons.musicCircleOutline,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Obx(
|
||||
() => IconButton(
|
||||
onPressed: () {
|
||||
@@ -140,32 +128,6 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (Platform.isAndroid) ...[
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '画中画',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () async {
|
||||
try {
|
||||
if ((await floating?.isPipAvailable) == true) {
|
||||
plPlayerController.hiddenControls(false);
|
||||
floating!.enable(const EnableManual());
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.picture_in_picture_outlined,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
Obx(
|
||||
() => SizedBox(
|
||||
width: 30,
|
||||
|
||||
89
lib/pages/live_room/widgets/header_control.dart
Normal file
89
lib/pages/live_room/widgets/header_control.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
class LiveHeaderControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
const LiveHeaderControl({
|
||||
required this.plPlayerController,
|
||||
this.floating,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Floating? floating;
|
||||
final PlPlayerController plPlayerController;
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
primary: false,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 14,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Obx(
|
||||
() => IconButton(
|
||||
onPressed: plPlayerController.setOnlyPlayAudio,
|
||||
icon: Icon(
|
||||
size: 18,
|
||||
plPlayerController.onlyPlayAudio.value
|
||||
? MdiIcons.musicCircle
|
||||
: MdiIcons.musicCircleOutline,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (Platform.isAndroid) ...[
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '画中画',
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () async {
|
||||
try {
|
||||
if ((await floating?.isPipAvailable) == true) {
|
||||
plPlayerController.hiddenControls(false);
|
||||
floating!.enable(const EnableManual());
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.picture_in_picture_outlined,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
IconButton(
|
||||
onPressed: () => Utils.scheduleExit(
|
||||
context,
|
||||
plPlayerController.isFullScreen.value,
|
||||
true,
|
||||
),
|
||||
icon: Icon(
|
||||
size: 18,
|
||||
Icons.schedule,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import 'package:PiliPlus/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/services/shutdown_timer_service.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import '../../../../models/video/play/CDN.dart';
|
||||
import '../../../setting/widgets/select_dialog.dart';
|
||||
@@ -179,7 +178,8 @@ class HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () => {Get.back(), scheduleExit()},
|
||||
onTap: () =>
|
||||
{Get.back(), Utils.scheduleExit(context, isFullScreen)},
|
||||
leading: const Icon(Icons.hourglass_top_outlined, size: 20),
|
||||
title: const Text('定时关闭', style: titleStyle),
|
||||
),
|
||||
@@ -592,190 +592,6 @@ class HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 定时关闭
|
||||
void scheduleExit() {
|
||||
const List<int> scheduleTimeChoices = [0, 15, 30, 45, 60];
|
||||
Utils.showFSSheet(
|
||||
context,
|
||||
isFullScreen: () => isFullScreen,
|
||||
child: StatefulBuilder(
|
||||
builder: (_, setState) {
|
||||
return Theme(
|
||||
data: Theme.of(context),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: ListView(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
const Center(child: Text('定时关闭', style: titleStyle)),
|
||||
const SizedBox(height: 10),
|
||||
...[
|
||||
...[
|
||||
...scheduleTimeChoices,
|
||||
if (scheduleTimeChoices
|
||||
.contains(
|
||||
shutdownTimerService.scheduledExitInMinutes)
|
||||
.not)
|
||||
shutdownTimerService.scheduledExitInMinutes,
|
||||
]..sort(),
|
||||
-1,
|
||||
].map(
|
||||
(choice) => ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
if (choice == -1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String duration = '';
|
||||
return AlertDialog(
|
||||
title: const Text('自定义时长'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
onChanged: (value) => duration = value,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'\d+')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
suffixText: 'min'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
int choice =
|
||||
int.tryParse(duration) ?? 0;
|
||||
shutdownTimerService
|
||||
.scheduledExitInMinutes = choice;
|
||||
shutdownTimerService
|
||||
.startShutdownTimer();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
Get.back();
|
||||
shutdownTimerService.scheduledExitInMinutes =
|
||||
choice;
|
||||
shutdownTimerService.startShutdownTimer();
|
||||
}
|
||||
},
|
||||
contentPadding: const EdgeInsets.only(),
|
||||
title: Text(choice == -1
|
||||
? '自定义'
|
||||
: choice == 0
|
||||
? "禁用"
|
||||
: "$choice分钟后"),
|
||||
trailing: shutdownTimerService.scheduledExitInMinutes ==
|
||||
choice
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
width: 125,
|
||||
child: Divider(height: 1),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
!shutdownTimerService.waitForPlayingCompleted;
|
||||
setState(() {});
|
||||
},
|
||||
contentPadding: const EdgeInsets.only(),
|
||||
title: const Text("额外等待视频播放完毕", style: titleStyle),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment
|
||||
.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: shutdownTimerService.waitForPlayingCompleted,
|
||||
onChanged: (value) => setState(() =>
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
value),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const Text('倒计时结束:', style: titleStyle),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = false;
|
||||
setState(() {});
|
||||
// Get.back();
|
||||
},
|
||||
text: " 暂停视频 ",
|
||||
selectStatus: !shutdownTimerService.exitApp,
|
||||
),
|
||||
const Spacer(),
|
||||
// const SizedBox(width: 10),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = true;
|
||||
setState(() {});
|
||||
// Get.back();
|
||||
},
|
||||
text: " 退出APP ",
|
||||
selectStatus: shutdownTimerService.exitApp,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 选择画质
|
||||
void showSetVideoQa() {
|
||||
if (videoInfo.dash == null) {
|
||||
|
||||
Reference in New Issue
Block a user