opt live room

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-01 16:41:24 +08:00
parent dd5c2229b3
commit 53ef4219eb
11 changed files with 175 additions and 201 deletions

View File

@@ -30,45 +30,46 @@ import 'package:get/get.dart';
class LiveRoomController extends GetxController {
LiveRoomController(this.heroTag);
final String heroTag;
late int roomId;
dynamic liveItem;
double volume = 0.0;
// 静音状态
RxBool volumeOff = false.obs;
int roomId = int.parse(Get.parameters['roomid']!);
PlPlayerController plPlayerController = PlPlayerController.getInstance(
isLive: true,
);
RxBool isLoaded = false.obs;
Rx<RoomInfoH5Data?> roomInfoH5 = Rx<RoomInfoH5Data?>(null);
// dm
LiveDmInfoData? dmInfo;
bool showDanmaku = true;
DanmakuController? controller;
List<RichTextItem>? savedDanmaku;
RxList<dynamic> messages = [].obs;
RxBool disableAutoScroll = false.obs;
double? brightness;
DanmakuController? controller;
bool showDanmaku = true;
LiveMessageStream? msgStream;
late final ScrollController scrollController = ScrollController()
..addListener(listener);
int? currentQn;
late List<({int code, String desc})> acceptQnList = [];
RxString currentQnDesc = ''.obs;
final RxBool isPortrait = false.obs;
late List<({int code, String desc})> acceptQnList = [];
List<RichTextItem>? savedDanmaku;
AccountService accountService = Get.find<AccountService>();
late final isLogin = accountService.isLogin.value;
LiveDmInfoData? dmInfo;
AccountService accountService = Get.find<AccountService>();
@override
void onInit() {
super.onInit();
roomId = int.parse(Get.parameters['roomid']!);
queryLiveUrl();
queryLiveInfoH5();
if (Accounts.get(AccountType.heartbeat).isLogin && !Pref.historyPause) {
VideoHttp.roomEntryAction(roomId: roomId);
}
}
Future<void> playerInit(String source) async {
await plPlayerController.setDataSource(
Future<void> playerInit(String source) {
return plPlayerController.setDataSource(
DataSource(
videoSource: source,
audioSource: null,
@@ -84,9 +85,7 @@ class LiveRoomController extends GetxController {
);
}
final RxBool isPortrait = false.obs;
Future<void> queryLiveInfo() async {
Future<void> queryLiveUrl() async {
if (currentQn == null) {
await Connectivity().checkConnectivity().then((res) {
currentQn = res.contains(ConnectivityResult.wifi)
@@ -102,7 +101,7 @@ class LiveRoomController extends GetxController {
if (res['status']) {
RoomPlayInfoData data = res['data'];
if (data.liveStatus != 1) {
_dialog(title: '当前直播间未开播');
_showDialog('当前直播间未开播');
return;
}
if (data.roomId != null) {
@@ -127,7 +126,7 @@ class LiveRoomController extends GetxController {
.description;
String videoUrl = VideoUtils.getCdnUrl(item);
await playerInit(videoUrl);
return res;
isLoaded.value = true;
}
}
@@ -143,14 +142,12 @@ class LiveRoomController extends GetxController {
);
} else {
if (res['msg'] != null) {
_dialog(title: res['msg']);
_showDialog(res['msg']);
}
}
}
void _dialog({
required String title,
}) {
void _showDialog(String title) {
Get.dialog(
AlertDialog(
title: Text(title),
@@ -173,10 +170,6 @@ class LiveRoomController extends GetxController {
);
}
LiveMessageStream? msgStream;
late final ScrollController scrollController = ScrollController()
..addListener(listener);
void scrollToBottom() {
if (disableAutoScroll.value) return;
if (scrollController.hasClients) {
@@ -264,7 +257,7 @@ class LiveRoomController extends GetxController {
currentQnDesc.value = LiveQuality.values
.firstWhere((element) => element.code == currentQn)
.description;
return queryLiveInfo();
return queryLiveUrl();
}
void initDm(LiveDmInfoData info) {

View File

@@ -37,26 +37,22 @@ class LiveRoomPage extends StatefulWidget {
class _LiveRoomPageState extends State<LiveRoomPage>
with WidgetsBindingObserver {
late final String heroTag;
late final int _roomId;
late final LiveRoomController _liveRoomController;
late final PlPlayerController plPlayerController;
late Future? _futureBuilderFuture;
bool get isFullScreen => plPlayerController.isFullScreen.value;
bool isShowCover = true;
bool isPlay = true;
StreamSubscription? _listener;
int latestAddedPosition = -1;
bool? _isFullScreen;
bool? _isPipMode;
void playCallBack() {
plPlayerController.play();
}
final GlobalKey chatKey = GlobalKey();
final GlobalKey playerKey = GlobalKey();
final GlobalKey videoPlayerKey = GlobalKey();
late final String heroTag;
final Color _color = const Color(0xFFEEEEEE);
@override
void initState() {
@@ -68,9 +64,8 @@ class _LiveRoomPageState extends State<LiveRoomPage>
LiveRoomController(heroTag),
tag: heroTag,
);
PlPlayerController.setPlayCallBack(playCallBack);
videoSourceInit();
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
plPlayerController = _liveRoomController.plPlayerController;
PlPlayerController.setPlayCallBack(plPlayerController.play);
plPlayerController
..autoEnterFullscreen()
..addStatusLister(playerListener);
@@ -115,10 +110,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
: 15 * plPlayerController.danmakuFontScaleFS;
}
void videoSourceInit() {
plPlayerController = _liveRoomController.plPlayerController;
}
@override
void dispose() {
videoPlayerServiceHandler.onVideoDetailDispose(heroTag);
@@ -145,8 +136,21 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
}
final GlobalKey videoPlayerKey = GlobalKey();
final GlobalKey playerKey = GlobalKey();
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateFontSize();
});
final isPortrait = context.isPortrait;
if (Platform.isAndroid) {
return Floating().isPipMode
? videoPlayerPanel()
: childWhenDisabled(isPortrait);
} else {
return childWhenDisabled(isPortrait);
}
}
Widget videoPlayerPanel({Color? fill, Alignment? alignment}) {
return PopScope(
@@ -156,66 +160,59 @@ class _LiveRoomPageState extends State<LiveRoomPage>
plPlayerController.triggerFullScreen(status: false);
}
},
child: FutureBuilder(
key: videoPlayerKey,
future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data['status']) {
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
return PLVideoPlayer(
key: playerKey,
fill: fill,
alignment: alignment,
child: Obx(() {
if (_liveRoomController.isLoaded.value) {
final roomInfoH5 = _liveRoomController.roomInfoH5.value;
return PLVideoPlayer(
key: playerKey,
fill: fill,
alignment: alignment,
plPlayerController: plPlayerController,
headerControl: LiveHeaderControl(
title: roomInfoH5?.roomInfo?.title,
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
plPlayerController: plPlayerController,
headerControl: LiveHeaderControl(
title: roomInfoH5?.roomInfo?.title,
upName: roomInfoH5?.anchorInfo?.baseInfo?.uname,
plPlayerController: plPlayerController,
onSendDanmaku: onSendDanmaku,
onPlayAudio: _liveRoomController.queryLiveInfo,
),
bottomControl: BottomControl(
plPlayerController: plPlayerController,
liveRoomCtr: _liveRoomController,
onRefresh: () {
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
},
),
danmuWidget: Obx(
() => AnimatedOpacity(
opacity: plPlayerController.enableShowDanmaku.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuScreen(
createdController: (DanmakuController e) {
plPlayerController.danmakuController =
_liveRoomController.controller = e;
},
option: DanmakuOption(
fontSize: _getFontSize(isFullScreen),
fontWeight: plPlayerController.fontWeight,
area: plPlayerController.showArea,
opacity: plPlayerController.danmakuOpacity,
hideTop: plPlayerController.blockTypes.contains(5),
hideScroll: plPlayerController.blockTypes.contains(2),
hideBottom: plPlayerController.blockTypes.contains(4),
duration:
plPlayerController.danmakuDuration /
plPlayerController.playbackSpeed,
staticDuration:
plPlayerController.danmakuStaticDuration /
plPlayerController.playbackSpeed,
strokeWidth: plPlayerController.strokeWidth,
lineHeight: plPlayerController.danmakuLineHeight,
),
onSendDanmaku: onSendDanmaku,
onPlayAudio: _liveRoomController.queryLiveUrl,
),
bottomControl: BottomControl(
plPlayerController: plPlayerController,
liveRoomCtr: _liveRoomController,
onRefresh: _liveRoomController.queryLiveUrl,
),
danmuWidget: Obx(
() => AnimatedOpacity(
opacity: plPlayerController.enableShowDanmaku.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuScreen(
createdController: (DanmakuController e) {
plPlayerController.danmakuController =
_liveRoomController.controller = e;
},
option: DanmakuOption(
fontSize: _getFontSize(isFullScreen),
fontWeight: plPlayerController.fontWeight,
area: plPlayerController.showArea,
opacity: plPlayerController.danmakuOpacity,
hideTop: plPlayerController.blockTypes.contains(5),
hideScroll: plPlayerController.blockTypes.contains(2),
hideBottom: plPlayerController.blockTypes.contains(4),
duration:
plPlayerController.danmakuDuration /
plPlayerController.playbackSpeed,
staticDuration:
plPlayerController.danmakuStaticDuration /
plPlayerController.playbackSpeed,
strokeWidth: plPlayerController.strokeWidth,
lineHeight: plPlayerController.danmakuLineHeight,
),
),
),
);
} else {
return const SizedBox.shrink();
}
},
),
),
);
}
return const SizedBox.shrink();
}),
);
}
@@ -256,21 +253,23 @@ class _LiveRoomPageState extends State<LiveRoomPage>
.value
?.roomInfo
?.appBackground;
Widget child;
if (appBackground?.isNotEmpty == true) {
final size = Get.size;
child = CachedNetworkImage(
fit: BoxFit.cover,
width: size.width,
height: size.height,
imageUrl: appBackground!.http2https,
);
} else {
child = Image.asset(
'assets/images/live/default_bg.webp',
fit: BoxFit.cover,
);
}
return Positioned.fill(
child: Opacity(
opacity: 0.6,
child: appBackground?.isNotEmpty == true
? CachedNetworkImage(
fit: BoxFit.cover,
width: Get.width,
height: Get.height,
imageUrl: appBackground!.http2https,
)
: Image.asset(
'assets/images/live/default_bg.webp',
fit: BoxFit.cover,
),
),
child: Opacity(opacity: 0.6, child: child),
);
},
),
@@ -285,7 +284,25 @@ class _LiveRoomPageState extends State<LiveRoomPage>
if (_liveRoomController.isPortrait.value) {
return _buildPP;
}
return _buildPH;
return Column(
children: [
_buildAppBar,
Obx(
() {
final size = Get.size;
return Container(
color: Colors.black,
width: size.width,
height: isFullScreen
? size.height
: size.width * 9 / 16,
child: videoPlayerPanel(),
);
},
),
..._buildBottomWidget,
],
);
},
)
: Column(
@@ -301,13 +318,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
);
}
Widget get _buildPH => Column(
children: [
_buildAppBar,
..._buildBodyP,
],
);
Widget get _buildPP => Stack(
clipBehavior: Clip.none,
children: [
@@ -351,26 +361,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
],
);
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateFontSize();
});
return OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
if (Platform.isAndroid) {
return Floating().isPipMode
? videoPlayerPanel()
: childWhenDisabled(orientation == Orientation.portrait);
} else {
return childWhenDisabled(orientation == Orientation.portrait);
}
},
);
}
final Color _color = const Color(0xFFEEEEEE);
PreferredSizeWidget get _buildAppBar {
final color = Theme.of(context).colorScheme.onSurfaceVariant;
return AppBar(
@@ -421,8 +411,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
actions: [
IconButton(
tooltip: '刷新',
onPressed: () =>
_futureBuilderFuture = _liveRoomController.queryLiveInfo(),
onPressed: _liveRoomController.queryLiveUrl,
icon: const Icon(Icons.refresh, size: 20),
),
PopupMenuButton(
@@ -497,15 +486,18 @@ class _LiveRoomPageState extends State<LiveRoomPage>
child: Row(
children: [
Obx(
() => Container(
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,
child: videoPlayerPanel(fill: Colors.transparent),
),
() {
final size = Get.size;
return Container(
margin: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
),
color: isFullScreen ? Colors.black : null,
width: isFullScreen ? size.width : videoWidth,
height: isFullScreen ? size.height : size.width * 9 / 16,
child: videoPlayerPanel(fill: Colors.transparent),
);
},
),
Expanded(
child: Column(
@@ -518,20 +510,6 @@ 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,
];
final GlobalKey chatKey = GlobalKey();
List<Widget> get _buildBottomWidget => [
Expanded(child: _buildChatWidget()),
_buildInputWidget,

View File

@@ -252,9 +252,7 @@ class _SavePanelState extends State<SavePanel> {
mimeType: 'image/png',
),
],
sharePositionOrigin: await Utils.isIpad()
? Rect.fromLTWH(0, 0, Get.width, Get.height / 2)
: null,
sharePositionOrigin: await Utils.sharePositionOrigin,
),
);
} else {

View File

@@ -53,6 +53,7 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
TextStyle subTitleStyle = theme.textTheme.labelMedium!.copyWith(
color: theme.colorScheme.outline,
);
final size = Get.size;
return Scaffold(
appBar: AppBar(title: const Text('选择应用主题')),
body: SafeArea(
@@ -240,8 +241,8 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
...[
IgnorePointer(
child: Container(
height: Get.height / 2,
width: Get.width,
height: size.height / 2,
width: size.width,
color: theme.colorScheme.surface,
child: const HomePage(),
),

View File

@@ -136,10 +136,10 @@ class VideoDetailController extends GetxController
duration: const Duration(milliseconds: 200),
);
late final double minVideoHeight = Get.mediaQuery.size.shortestSide * 9 / 16;
late final double maxVideoHeight = max(
Get.mediaQuery.size.longestSide * 0.65,
Get.mediaQuery.size.shortestSide,
);
late final double maxVideoHeight = () {
final size = Get.mediaQuery.size;
return max(size.longestSide * 0.65, size.shortestSide);
}();
late double videoHeight = minVideoHeight;
void animToTop() {
@@ -1304,7 +1304,7 @@ class VideoDetailController extends GetxController
videoUrl = VideoUtils.getCdnUrl(firstVideo);
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
late AudioItem? firstAudio;
AudioItem? firstAudio;
final List<AudioItem> audiosList = data.dash!.audio ?? <AudioItem>[];
if (data.dash!.dolby?.audio != null &&
data.dash!.dolby!.audio!.isNotEmpty) {

View File

@@ -190,10 +190,11 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
final isVertical = videoDetailController.isVertical.value;
final mode = plPlayerController?.mode;
late final size = Get.size;
if (!(mode == FullScreenMode.vertical ||
(mode == FullScreenMode.auto && isVertical) ||
(mode == FullScreenMode.ratio &&
(Get.height / Get.width < 1.25 || isVertical)))) {
(isVertical || size.height / size.width < 1.25)))) {
landScape();
}
});

View File

@@ -895,14 +895,8 @@ class HeaderControlState extends State<HeaderControl> {
mimeType: Headers.jsonContentType,
),
],
sharePositionOrigin: await Utils.isIpad()
? Rect.fromLTWH(
0,
0,
Get.width,
Get.height / 2,
)
: null,
sharePositionOrigin:
await Utils.sharePositionOrigin,
),
);
}