Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-05-11 10:06:11 +08:00
parent 49c6cd0ab8
commit 32eeef7866
15 changed files with 253 additions and 320 deletions

View File

@@ -50,9 +50,7 @@ class LiveRoomController extends GetxController {
int roomId = Get.arguments;
int? ruid;
DanmakuController<DanmakuExtra>? danmakuController;
final plPlayerController = PlPlayerController.getInstance(
isLive: true,
);
final plPlayerController = PlPlayerController.getInstance(isLive: true);
final isLoaded = false.obs;
final roomInfoH5 = Rxn<RoomInfoH5Data>();
@@ -89,10 +87,7 @@ class LiveRoomController extends GetxController {
}
return Text(
text,
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
style: const TextStyle(fontSize: 12, color: Colors.white),
);
});
@@ -107,6 +102,7 @@ class LiveRoomController extends GetxController {
final disableAutoScroll = false.obs;
bool autoScroll = true;
LiveMessageStream? _msgStream;
LiveMessageStream? get msgStream => _msgStream;
late final ScrollController scrollController;
late final RxInt pageIndex = 0.obs;
late final PageController pageController;
@@ -150,7 +146,7 @@ class LiveRoomController extends GetxController {
final account = Accounts.main;
isLogin = account.isLogin;
mid = account.mid;
queryLiveUrl(autoFullScreenFlag: true);
queryLiveUrl();
queryLiveInfoH5();
if (Accounts.heartbeat.isLogin && !Pref.historyPause) {
VideoHttp.roomEntryAction(roomId: roomId);
@@ -158,10 +154,7 @@ class LiveRoomController extends GetxController {
pageController = PageController();
}
Future<void>? playerInit({
bool autoplay = true,
bool autoFullScreenFlag = false,
}) {
Future<void>? playerInit({bool autoplay = true}) {
if (videoUrl == null) {
return null;
}
@@ -173,7 +166,7 @@ class LiveRoomController extends GetxController {
);
}
Future<void> queryLiveUrl({bool autoFullScreenFlag = false}) async {
Future<void> queryLiveUrl() async {
currentQn ??= await ConnectivityUtils.isWiFi
? Pref.liveQuality
: Pref.liveQualityCellular;
@@ -195,7 +188,12 @@ class LiveRoomController extends GetxController {
if (response.roomId != null) {
roomId = response.roomId!;
}
liveTime.value = response.liveTime;
final liveTime = response.liveTime;
if (liveTime != null &&
DateTime.now().millisecondsSinceEpoch ~/ 1000 - liveTime <
Duration.secondsPerDay * 2) {
this.liveTime.value = liveTime;
}
startLiveTimer();
isPortrait.value = response.isPortrait ?? false;
List<CodecItem> codec =
@@ -212,7 +210,7 @@ class LiveRoomController extends GetxController {
currentQnDesc.value =
LiveQuality.fromCode(currentQn)?.desc ?? currentQn.toString();
videoUrl = VideoUtils.getLiveCdnUrl(item);
await playerInit(autoFullScreenFlag: autoFullScreenFlag);
await playerInit();
isLoaded.value = true;
} else {
_showDialog(res.toString());
@@ -323,10 +321,6 @@ class LiveRoomController extends GetxController {
}
}
void clearSC() {
superChatMsg.removeWhere((e) => e.expired);
}
void startLiveMsg() {
if (messages.isEmpty) {
prefetch();
@@ -530,7 +524,7 @@ class LiveRoomController extends GetxController {
void onLikeTapDown([_]) {
cancelLikeTimer();
likeClickTime.value++;
likeClickTime.value += 2;
}
void onLikeTapUp([_]) {
@@ -568,19 +562,22 @@ class LiveRoomController extends GetxController {
PublishRoute(
barrierColor: Colors.transparent,
pageBuilder: (context, animation, secondaryAnimation) {
return LiveSendDmPanel(
fromEmote: fromEmote,
liveRoomController: this,
items: savedDanmaku,
autofocus: !fromEmote,
onSave: (msg) {
if (msg.isEmpty) {
savedDanmaku?.clear();
savedDanmaku = null;
} else {
savedDanmaku = msg.toList();
}
},
return Theme(
data: ThemeUtils.darkTheme,
child: LiveSendDmPanel(
fromEmote: fromEmote,
liveRoomController: this,
items: savedDanmaku,
autofocus: !fromEmote,
onSave: (msg) {
if (msg.isEmpty) {
savedDanmaku?.clear();
savedDanmaku = null;
} else {
savedDanmaku = msg.toList();
}
},
),
);
},
transitionDuration: fromEmote

View File

@@ -44,6 +44,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/share_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart';
@@ -53,6 +54,8 @@ import 'package:flutter/material.dart' hide PageView;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
const baseWhite = Color(0xFFEEEEEE);
class LiveRoomPage extends StatefulWidget {
const LiveRoomPage({super.key});
@@ -66,6 +69,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
late final LiveRoomController _liveRoomController;
late final PlPlayerController plPlayerController;
bool get isFullScreen => plPlayerController.isFullScreen.value;
final colorScheme = ThemeUtils.darkTheme.colorScheme;
late final GlobalKey pageKey = GlobalKey();
late final GlobalKey chatKey = GlobalKey();
@@ -212,7 +216,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
child: child,
);
}
return child;
return Theme(data: ThemeUtils.darkTheme, child: child);
}
Widget videoPlayerPanel(
@@ -518,28 +522,24 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
PreferredSizeWidget _buildAppBar(bool isFullScreen) {
final color = Theme.of(context).colorScheme.onSurfaceVariant;
return AppBar(
toolbarHeight: isFullScreen ? 0 : null,
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
titleTextStyle: const TextStyle(color: Colors.white),
title: isFullScreen || plPlayerController.isDesktopPip
? null
: Obx(
() {
RoomInfoH5Data? roomInfoH5 =
_liveRoomController.roomInfoH5.value;
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
if (roomInfoH5 == null) {
return const SizedBox.shrink();
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
behavior: .opaque,
onTap: () =>
Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'),
child: Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
NetworkImgLayer(
width: 34,
@@ -550,12 +550,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Flexible(
child: Column(
spacing: 1,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Row(
spacing: 10,
mainAxisSize: .min,
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: .end,
children: [
Flexible(
child: Text(
@@ -586,11 +586,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
},
),
actions: [
// IconButton(
// tooltip: '刷新',
// onPressed: _liveRoomController.queryLiveUrl,
// icon: const Icon(Icons.refresh, size: 20),
// ),
PopupMenuButton(
icon: const Icon(Icons.more_vert, size: 20),
itemBuilder: (BuildContext context) {
@@ -599,32 +594,24 @@ class _LiveRoomPageState extends State<LiveRoomPage>
return <PopupMenuEntry>[
PopupMenuItem(
onTap: () => Utils.copyText(liveUrl),
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.copy,
size: 19,
color: color,
),
const Text('复制链接'),
Icon(Icons.copy, size: 19),
Text('复制链接'),
],
),
),
if (PlatformUtils.isMobile)
PopupMenuItem(
onTap: () => ShareUtils.shareText(liveUrl),
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.share,
size: 19,
color: color,
),
const Text('分享直播间'),
Icon(Icons.share, size: 19),
Text('分享直播间'),
],
),
),
@@ -651,16 +638,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
SmartDialog.showToast(e.toString());
}
},
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.forward_to_inbox,
size: 19,
color: color,
),
const Text('分享至消息'),
Icon(Icons.forward_to_inbox, size: 19),
Text('分享至消息'),
],
),
),
@@ -683,14 +666,14 @@ class _LiveRoomPageState extends State<LiveRoomPage>
: videoHeight;
return Padding(
padding: isFullScreen
? EdgeInsets.zero
: EdgeInsets.only(left: padding.left, right: padding.right),
? .zero
: .only(left: padding.left, right: padding.right),
child: Row(
children: [
Container(
width: width,
height: height,
margin: EdgeInsets.only(bottom: padding.bottom),
margin: .only(bottom: padding.bottom),
child: videoPlayerPanel(
isFullScreen,
fill: Colors.transparent,
@@ -712,7 +695,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
Widget get _buildBottomWidget => Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Expanded(child: _buildChatWidget()),
_buildInputWidget,
@@ -737,7 +720,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
..onSendDanmaku(),
);
return Padding(
padding: EdgeInsets.only(bottom: 12, top: isPortrait ? 12 : 0),
padding: .only(bottom: 12, top: isPortrait ? 12 : 0),
child: PageView<CustomHorizontalDragGestureRecognizer>(
key: pageKey,
controller: _liveRoomController.pageController,
@@ -758,7 +741,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Widget get _buildInputWidget {
final child = Container(
padding: EdgeInsets.only(
padding: .only(
top: 5,
left: 10,
right: 10,
@@ -766,15 +749,15 @@ class _LiveRoomPageState extends State<LiveRoomPage>
),
height: 70 + padding.bottom,
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
borderRadius: .vertical(top: .circular(20)),
border: Border(top: BorderSide(color: Color(0x1AFFFFFF))),
color: Color(0x1AFFFFFF),
),
child: GestureDetector(
onTap: _liveRoomController.onSendDanmaku,
behavior: HitTestBehavior.opaque,
behavior: .opaque,
child: Padding(
padding: const EdgeInsets.only(top: 5, bottom: 10),
padding: const .only(top: 5, bottom: 10),
child: Align(
alignment: Alignment.topCenter,
child: Row(
@@ -788,8 +771,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
width: 34,
height: 34,
child: IconButton(
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
style: const ButtonStyle(
padding: WidgetStatePropertyAll(.zero),
),
onPressed: () {
final newVal = !enableShowLiveDanmaku;
@@ -803,80 +786,74 @@ class _LiveRoomPageState extends State<LiveRoomPage>
? const Icon(
size: 22,
CustomIcons.dm_on,
color: Color(0xFFEEEEEE),
color: baseWhite,
)
: const Icon(
size: 22,
CustomIcons.dm_off,
color: Color(0xFFEEEEEE),
color: baseWhite,
),
),
);
},
),
const Expanded(
child: Text(
'发送弹幕',
style: TextStyle(color: Color(0xFFEEEEEE)),
),
child: Text('发送弹幕', style: TextStyle(color: baseWhite)),
),
Builder(
builder: (context) {
final colorScheme = Theme.of(context).colorScheme;
return Material(
type: MaterialType.transparency,
child: Stack(
clipBehavior: Clip.none,
children: [
InkWell(
overlayColor: overlayColor(colorScheme),
customBorder: const CircleBorder(),
onTapDown: _liveRoomController.onLikeTapDown,
onTapUp: _liveRoomController.onLikeTapUp,
onTapCancel: _liveRoomController.onLikeTapUp,
child: const SizedBox.square(
dimension: 34,
child: Icon(
size: 22,
color: Color(0xFFEEEEEE),
Icons.thumb_up_off_alt,
),
),
Material(
type: .transparency,
child: Stack(
clipBehavior: .none,
children: [
InkWell(
overlayColor: overlayColor(colorScheme),
customBorder: const CircleBorder(),
onTapDown: _liveRoomController.onLikeTapDown,
onTapUp: _liveRoomController.onLikeTapUp,
onTapCancel: _liveRoomController.onLikeTapUp,
child: const SizedBox.square(
dimension: 34,
child: Icon(
size: 22,
color: baseWhite,
Icons.thumb_up_off_alt,
),
Positioned(
left: 30,
top: -12,
child: Obx(() {
final likeClickTime =
_liveRoomController.likeClickTime.value;
if (likeClickTime == 0) {
return const SizedBox.shrink();
}
return Text(
'x$likeClickTime',
style: TextStyle(
fontSize: 16,
color: colorScheme.isDark
? colorScheme.primary
: colorScheme.inversePrimary,
),
);
}),
),
],
),
),
);
},
Positioned(
left: 30,
top: -12,
child: Obx(() {
final likeClickTime =
_liveRoomController.likeClickTime.value;
if (likeClickTime == 0) {
return const SizedBox.shrink();
}
return Text(
'x$likeClickTime',
style: TextStyle(
fontSize: 16,
color: colorScheme.isDark
? colorScheme.primary
: colorScheme.inversePrimary,
),
);
}),
),
],
),
),
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: IconButton.styleFrom(padding: EdgeInsets.zero),
style: const ButtonStyle(
padding: WidgetStatePropertyAll(.zero),
),
onPressed: () => _liveRoomController.onSendDanmaku(true),
icon: const Icon(
size: 22,
color: Color(0xFFEEEEEE),
color: baseWhite,
Icons.emoji_emotions_outlined,
),
),
@@ -888,6 +865,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
),
);
return Stack(
clipBehavior: .none,
children: [
child,
Positioned(
@@ -896,11 +874,21 @@ class _LiveRoomPageState extends State<LiveRoomPage>
right: 0,
child: Obx(
() => _BorderIndicator(
radius: const Radius.circular(20),
radius: const .circular(20),
isLeft: _liveRoomController.pageIndex.value == 0,
),
),
),
Positioned(
top: -6,
right: 6,
child: Obx(
() => Badge.count(
isLabelVisible: _liveRoomController.superChatMsg.isNotEmpty,
count: _liveRoomController.superChatMsg.length,
),
),
),
],
);
}
@@ -1001,7 +989,7 @@ class _RenderBorderIndicator extends RenderBox {
width,
size.height,
),
borderRadius: BorderRadius.only(
borderRadius: .only(
topLeft: _isLeft ? _radius : .zero,
topRight: _isLeft ? .zero : _radius,
),

View File

@@ -164,53 +164,6 @@ class LiveRoomChatPanel extends StatelessWidget {
),
),
],
Positioned(
top: 12,
right: 12,
child: Obx(() {
final isEmpty = liveRoomController.superChatMsg.isEmpty;
return AnimatedOpacity(
opacity: isEmpty ? 0 : 1,
duration: const Duration(milliseconds: 120),
child: GestureDetector(
onTap: isEmpty
? null
: () => liveRoomController.pageController.animateToPage(
1,
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: const Color(0x2FFFFFFF),
border: Border.all(color: Colors.white24, width: 0.7),
),
padding: const EdgeInsets.fromLTRB(10, 4, 4, 4),
child: Text.rich(
style: const TextStyle(color: Colors.white, height: 1),
strutStyle: const StrutStyle(height: 1, leading: 0),
TextSpan(
children: [
TextSpan(
text: 'SC(${liveRoomController.superChatMsg.length})',
),
const WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
size: 18,
Icons.keyboard_arrow_right,
color: Colors.white,
),
),
],
),
),
),
),
);
}),
),
Obx(
() => liveRoomController.disableAutoScroll.value
? Positioned(
@@ -225,6 +178,38 @@ class LiveRoomChatPanel extends StatelessWidget {
)
: const SizedBox.shrink(),
),
Positioned(
top: 0,
right: 12,
width: 32,
height: 32,
child: PopupMenuButton(
iconSize: 19,
padding: .zero,
itemBuilder: (context) => [
if (liveRoomController.msgStream != null)
PopupMenuItem(
height: 35,
onTap: liveRoomController.closeLiveMsg,
child: const Text('Pause'),
)
else
PopupMenuItem(
height: 35,
onTap: liveRoomController.startLiveMsg,
child: const Text('Resume'),
),
PopupMenuItem(
height: 35,
onTap: () => liveRoomController
..danmakuController?.clear()
..messages.clear()
..disableAutoScroll.value = false,
child: const Text('Clear'),
),
],
),
),
],
);
}
@@ -274,10 +259,7 @@ class LiveRoomChatPanel extends StatelessWidget {
spanChildren.add(
TextSpan(
text: nonMatchStr,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
style: const TextStyle(color: Colors.white, fontSize: 14),
),
);
return '';
@@ -287,10 +269,7 @@ class LiveRoomChatPanel extends StatelessWidget {
} else {
return TextSpan(
text: obj.text,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
style: const TextStyle(color: Colors.white, fontSize: 14),
);
}
}