Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-07-23 16:47:11 +08:00
parent 148e0872b4
commit 418a1e8d39
821 changed files with 29467 additions and 25520 deletions

View File

@@ -35,8 +35,9 @@ class LiveRoomController extends GetxController {
double volume = 0.0;
// 静音状态
RxBool volumeOff = false.obs;
PlPlayerController plPlayerController =
PlPlayerController.getInstance(videoType: 'live');
PlPlayerController plPlayerController = PlPlayerController.getInstance(
videoType: 'live',
);
Rx<RoomInfoH5Data?> roomInfoH5 = Rx<RoomInfoH5Data?>(null);
RxList<dynamic> messages = [].obs;
@@ -75,7 +76,7 @@ class LiveRoomController extends GetxController {
httpHeaders: {
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15',
'referer': HttpString.baseUrl
'referer': HttpString.baseUrl,
},
),
autoplay: true,
@@ -193,8 +194,10 @@ class LiveRoomController extends GetxController {
if (v['status']) {
if ((v['data'] as List?)?.isNotEmpty == true) {
try {
messages.addAll((v['data'] as List)
.map((obj) => {
messages.addAll(
(v['data'] as List)
.map(
(obj) => {
'name': obj['user']['base']['name'],
'uid': obj['user']['uid'],
'text': obj['text'],
@@ -202,8 +205,10 @@ class LiveRoomController extends GetxController {
'uemote': obj['emoticon']['emoticon_unique'] != ""
? obj['emoticon']
: null,
})
.toList());
},
)
.toList(),
);
WidgetsBinding.instance.addPostFrameCallback(
(_) => scrollToBottom(),
);
@@ -266,46 +271,48 @@ class LiveRoomController extends GetxController {
if (info.hostList.isNullOrEmpty) {
return;
}
msgStream = LiveMessageStream(
streamToken: info.token!,
roomId: roomId,
uid: Accounts.main.mid,
servers: info.hostList!
.map((host) => 'wss://${host.host}:${host.wssPort}/sub')
.toList(),
)
..addEventListener((obj) {
try {
if (obj['cmd'] == 'DANMU_MSG') {
// logger.i(' 原始弹幕消息 ======> ${jsonEncode(obj)}');
final info = obj['info'];
final first = info[0];
final content = first[15];
final extra = jsonDecode(content['extra']);
final user = content['user'];
final uid = user['uid'];
messages.add({
'name': user['base']['name'],
'uid': uid,
'text': info[1],
'emots': extra['emots'],
'uemote': first[13],
});
if (showDanmaku) {
controller?.addDanmaku(
DanmakuContentItem(
extra['content'],
color: DmUtils.decimalToColor(extra['color']),
type: DmUtils.getPosition(extra['mode']),
selfSend: isLogin && uid == accountService.mid,
),
);
WidgetsBinding.instance
.addPostFrameCallback((_) => scrollToBottom());
}
}
} catch (_) {}
})
..init();
msgStream =
LiveMessageStream(
streamToken: info.token!,
roomId: roomId,
uid: Accounts.main.mid,
servers: info.hostList!
.map((host) => 'wss://${host.host}:${host.wssPort}/sub')
.toList(),
)
..addEventListener((obj) {
try {
if (obj['cmd'] == 'DANMU_MSG') {
// logger.i(' 原始弹幕消息 ======> ${jsonEncode(obj)}');
final info = obj['info'];
final first = info[0];
final content = first[15];
final extra = jsonDecode(content['extra']);
final user = content['user'];
final uid = user['uid'];
messages.add({
'name': user['base']['name'],
'uid': uid,
'text': info[1],
'emots': extra['emots'],
'uemote': first[13],
});
if (showDanmaku) {
controller?.addDanmaku(
DanmakuContentItem(
extra['content'],
color: DmUtils.decimalToColor(extra['color']),
type: DmUtils.getPosition(extra['mode']),
selfSend: isLogin && uid == accountService.mid,
),
);
WidgetsBinding.instance.addPostFrameCallback(
(_) => scrollToBottom(),
);
}
}
} catch (_) {}
})
..init();
}
}

View File

@@ -41,7 +41,8 @@ class _ReplyPageState extends CommonRichTextPubPageState<LiveSendDmPanel> {
@override
void dispose() {
Get.delete<LiveEmotePanelController>(
tag: liveRoomController.roomId.toString());
tag: liveRoomController.roomId.toString(),
);
super.dispose();
}
@@ -75,22 +76,26 @@ class _ReplyPageState extends CommonRichTextPubPageState<LiveSendDmPanel> {
@override
Widget? get customPanel => LiveEmotePanel(
onChoose: onChooseEmote,
roomId: liveRoomController.roomId,
onSendEmoticonUnique: (emote) {
onCustomPublish(
message: emote.emoticonUnique!,
dmType: 1,
emoticonOptions: '[object Object]',
);
},
onChoose: onChooseEmote,
roomId: liveRoomController.roomId,
onSendEmoticonUnique: (emote) {
onCustomPublish(
message: emote.emoticonUnique!,
dmType: 1,
emoticonOptions: '[object Object]',
);
},
);
List<Widget> buildInputView(ThemeData theme) {
return [
Container(
padding:
const EdgeInsets.only(top: 12, right: 15, left: 15, bottom: 10),
padding: const EdgeInsets.only(
top: 12,
right: 15,
left: 15,
bottom: 10,
),
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Listener(
@@ -137,8 +142,10 @@ class _ReplyPageState extends CommonRichTextPubPageState<LiveSendDmPanel> {
() => FilledButton.tonal(
onPressed: enablePublish.value ? onPublish : null,
style: FilledButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
visualDensity: VisualDensity.compact,
),
child: const Text('发送'),

View File

@@ -96,8 +96,9 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Future<void> _updateFontSize() async {
if (Platform.isAndroid) {
_isPipMode =
await const MethodChannel("floating").invokeMethod('inPipAlready');
_isPipMode = await const MethodChannel(
"floating",
).invokeMethod('inPipAlready');
}
if (_liveRoomController.controller != null) {
_liveRoomController.controller!.updateOption(
@@ -197,9 +198,11 @@ class _LiveRoomPageState extends State<LiveRoomPage>
hideTop: plPlayerController.blockTypes.contains(5),
hideScroll: plPlayerController.blockTypes.contains(2),
hideBottom: plPlayerController.blockTypes.contains(4),
duration: plPlayerController.danmakuDuration /
duration:
plPlayerController.danmakuDuration /
plPlayerController.playbackSpeed,
staticDuration: plPlayerController.danmakuStaticDuration /
staticDuration:
plPlayerController.danmakuStaticDuration /
plPlayerController.playbackSpeed,
strokeWidth: plPlayerController.strokeWidth,
lineHeight: plPlayerController.danmakuLineHeight,
@@ -249,7 +252,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
return const SizedBox.shrink();
}
final appBackground = _liveRoomController
.roomInfoH5.value?.roomInfo?.appBackground;
.roomInfoH5
.value
?.roomInfo
?.appBackground;
return Positioned.fill(
child: Opacity(
opacity: 0.6,
@@ -296,55 +302,54 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
Widget get _buildPH => Column(
children: [
_buildAppBar,
..._buildBodyP,
],
);
children: [
_buildAppBar,
..._buildBodyP,
],
);
Widget get _buildPP => Stack(
clipBehavior: Clip.none,
clipBehavior: Clip.none,
children: [
_buildAppBar,
Column(
children: [
_buildAppBar,
Column(
children: [
Expanded(
child: Stack(
clipBehavior: Clip.none,
children: [
Obx(
() => Container(
margin: isFullScreen
? null
: const EdgeInsets.only(top: 56),
color: Colors.black,
child: videoPlayerPanel(
alignment: isFullScreen ? null : Alignment.topCenter,
),
),
Expanded(
child: Stack(
clipBehavior: Clip.none,
children: [
Obx(
() => Container(
margin: isFullScreen
? null
: const EdgeInsets.only(top: 56),
color: Colors.black,
child: videoPlayerPanel(
alignment: isFullScreen ? null : Alignment.topCenter,
),
Obx(
() => isFullScreen
? const SizedBox.shrink()
: Positioned(
left: 0,
right: 0,
bottom: 55,
child: SizedBox(
height: 125,
child: _buildChatWidget(true),
),
),
),
],
),
),
),
Obx(() =>
isFullScreen ? const SizedBox.shrink() : _buildInputWidget),
],
Obx(
() => isFullScreen
? const SizedBox.shrink()
: Positioned(
left: 0,
right: 0,
bottom: 55,
child: SizedBox(
height: 125,
child: _buildChatWidget(true),
),
),
),
],
),
),
Obx(() => isFullScreen ? const SizedBox.shrink() : _buildInputWidget),
],
);
),
],
);
@override
Widget build(BuildContext context) {
@@ -487,14 +492,15 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Widget get _buildBodyH {
double videoWidth =
clampDouble(context.height / context.width * 1.08, 0.58, 0.75) *
context.width;
context.width;
return Expanded(
child: Row(
children: [
Obx(
() => Container(
margin:
EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom),
margin: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
),
color: isFullScreen ? Colors.black : null,
width: isFullScreen ? Get.size.width : videoWidth,
height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16,
@@ -513,93 +519,95 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
List<Widget> get _buildBodyP => [
Obx(
() => Container(
color: Colors.black,
width: Get.size.width,
height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16,
child: videoPlayerPanel(),
),
),
..._buildBottomWidget,
];
Obx(
() => Container(
color: Colors.black,
width: Get.size.width,
height: isFullScreen ? Get.size.height : Get.size.width * 9 / 16,
child: videoPlayerPanel(),
),
),
..._buildBottomWidget,
];
final GlobalKey chatKey = GlobalKey();
List<Widget> get _buildBottomWidget => [
Expanded(child: _buildChatWidget()),
_buildInputWidget,
];
Expanded(child: _buildChatWidget()),
_buildInputWidget,
];
Widget _buildChatWidget([bool? isPP]) => Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: LiveRoomChat(
key: chatKey,
isPP: isPP,
roomId: _roomId,
liveRoomController: _liveRoomController,
),
);
padding: const EdgeInsets.symmetric(vertical: 16),
child: LiveRoomChat(
key: chatKey,
isPP: isPP,
roomId: _roomId,
liveRoomController: _liveRoomController,
),
);
Widget get _buildInputWidget => Container(
padding: EdgeInsets.only(
top: 5,
left: 10,
right: 10,
bottom: 15 + MediaQuery.paddingOf(context).bottom,
),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
border: Border(
top: BorderSide(color: Color(0x1AFFFFFF)),
),
color: Color(0x1AFFFFFF),
),
child: GestureDetector(
onTap: onSendDanmaku,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.only(top: 5, bottom: 10),
child: Row(
children: [
Obx(
() {
final enableShowDanmaku =
plPlayerController.enableShowDanmaku.value;
return IconButton(
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
GStorage.setting
.put(SettingBoxKey.enableShowDanmaku, newVal);
},
icon: Icon(
enableShowDanmaku
? Icons.subtitles_outlined
: Icons.subtitles_off_outlined,
color: _color,
),
padding: EdgeInsets.only(
top: 5,
left: 10,
right: 10,
bottom: 15 + MediaQuery.paddingOf(context).bottom,
),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
border: Border(
top: BorderSide(color: Color(0x1AFFFFFF)),
),
color: Color(0x1AFFFFFF),
),
child: GestureDetector(
onTap: onSendDanmaku,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.only(top: 5, bottom: 10),
child: Row(
children: [
Obx(
() {
final enableShowDanmaku =
plPlayerController.enableShowDanmaku.value;
return IconButton(
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
GStorage.setting.put(
SettingBoxKey.enableShowDanmaku,
newVal,
);
},
),
Expanded(
child: Text(
'发送弹幕',
style: TextStyle(color: _color),
icon: Icon(
enableShowDanmaku
? Icons.subtitles_outlined
: Icons.subtitles_off_outlined,
color: _color,
),
),
IconButton(
onPressed: () => onSendDanmaku(true),
icon: Icon(Icons.emoji_emotions_outlined, color: _color),
),
],
);
},
),
),
Expanded(
child: Text(
'发送弹幕',
style: TextStyle(color: _color),
),
),
IconButton(
onPressed: () => onSendDanmaku(true),
icon: Icon(Icons.emoji_emotions_outlined, color: _color),
),
],
),
);
),
),
);
void onSendDanmaku([bool fromEmote = false]) {
if (!_liveRoomController.accountService.isLogin.value) {
@@ -627,8 +635,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
},
transitionDuration: const Duration(milliseconds: 500),
transitionBuilder: (context, animation, secondaryAnimation, child) {
var tween = Tween(begin: const Offset(0.0, 1.0), end: Offset.zero)
.chain(CurveTween(curve: Curves.linear));
var tween = Tween(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).chain(CurveTween(curve: Curves.linear));
return SlideTransition(
position: animation.drive(tween),
child: child,

View File

@@ -91,8 +91,10 @@ class BottomControl extends StatelessWidget {
onPressed: () {
final newVal = !enableShowDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
GStorage.setting
.put(SettingBoxKey.enableShowDanmaku, newVal);
GStorage.setting.put(
SettingBoxKey.enableShowDanmaku,
newVal,
);
},
icon: Icon(
size: 18,
@@ -122,8 +124,10 @@ class BottomControl extends StatelessWidget {
onTap: () => plPlayerController.toggleVideoFit(boxFit),
child: Text(
boxFit.desc,
style:
const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(
color: Colors.white,
fontSize: 13,
),
),
);
}).toList();
@@ -156,8 +160,10 @@ class BottomControl extends StatelessWidget {
onTap: () => liveRoomCtr.changeQn(e.code),
child: Text(
e.desc,
style:
const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(
color: Colors.white,
fontSize: 13,
),
),
);
}).toList();
@@ -174,7 +180,8 @@ class BottomControl extends StatelessWidget {
color: Colors.white,
),
onTap: () => plPlayerController.triggerFullScreen(
status: !plPlayerController.isFullScreen.value),
status: !plPlayerController.isFullScreen.value,
),
),
],
),

View File

@@ -37,8 +37,10 @@ class LiveRoomChat extends StatelessWidget {
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: isPP == true
? Colors.black.withValues(alpha: 0.3)
@@ -89,7 +91,7 @@ class LiveRoomChat extends StatelessWidget {
),
)
: const SizedBox.shrink(),
)
),
],
);
}
@@ -99,7 +101,7 @@ class LiveRoomChat extends StatelessWidget {
dynamic uemote = obj['uemote'];
List<String> list = [
if (emots != null) ...emots.keys,
if (uemote is Map) uemote['emoticon_unique'].replaceFirst('upower_', '')
if (uemote is Map) uemote['emoticon_unique'].replaceFirst('upower_', ''),
];
if (list.isNotEmpty) {
RegExp regExp = RegExp(list.map(RegExp.escape).join('|'));
@@ -109,16 +111,19 @@ class LiveRoomChat extends StatelessWidget {
onMatch: (Match match) {
String key = match[0]!;
dynamic emote = emots?[key] ?? uemote;
spanChildren.add(WidgetSpan(
child: ExcludeSemantics(
spanChildren.add(
WidgetSpan(
child: ExcludeSemantics(
child: NetworkImgLayer(
src: emote['url'],
type: ImageType.emote,
width: emote['width'].toDouble(),
height: emote['height'].toDouble(),
semanticsLabel: key,
)),
));
src: emote['url'],
type: ImageType.emote,
width: emote['width'].toDouble(),
height: emote['height'].toDouble(),
semanticsLabel: key,
),
),
),
);
return '';
},
onNonMatch: (String nonMatchStr) {

View File

@@ -63,14 +63,20 @@ class LiveHeaderControl extends StatelessWidget {
title!,
maxLines: 1,
style: const TextStyle(
fontSize: 15, height: 1, color: Colors.white),
fontSize: 15,
height: 1,
color: Colors.white,
),
),
if (isFullScreen && upName != null)
Text(
upName!,
maxLines: 1,
style: const TextStyle(
fontSize: 12, height: 1, color: Colors.white),
fontSize: 12,
height: 1,
color: Colors.white,
),
),
],
),
@@ -139,7 +145,8 @@ class LiveHeaderControl extends StatelessWidget {
floating.enable(
plPlayerController.direction.value == 'vertical'
? const EnableManual(
aspectRatio: Rational.vertical())
aspectRatio: Rational.vertical(),
)
: const EnableManual(),
);
}