diff --git a/assets/images/live/default_bg.webp b/assets/images/live/default_bg.webp new file mode 100644 index 00000000..a58259de Binary files /dev/null and b/assets/images/live/default_bg.webp differ diff --git a/lib/http/api.dart b/lib/http/api.dart index 532ca341..84bbf548 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -230,6 +230,10 @@ class Api { static const String liveRoomInfo = '${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo'; + // 直播间详情 H5 + static const String liveRoomInfoH5 = + '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getH5InfoByRoom'; + // 用户信息 需要Wbi签名 // https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482 static const String memberInfo = '/x/space/wbi/acc/info'; diff --git a/lib/http/live.dart b/lib/http/live.dart index c62fb6bd..e624120e 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -1,5 +1,6 @@ import '../models/live/item.dart'; import '../models/live/room_info.dart'; +import '../models/live/room_info_h5.dart'; import 'api.dart'; import 'init.dart'; @@ -46,4 +47,22 @@ class LiveHttp { }; } } + + static Future liveRoomInfoH5({roomId, qn}) async { + var res = await Request().get(Api.liveRoomInfoH5, data: { + 'room_id': roomId, + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': RoomInfoH5Model.fromJson(res.data['data']) + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/main.dart b/lib/main.dart index 5c467722..5849f8a3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,7 +25,6 @@ import 'package:pilipala/utils/recommend_filter.dart'; import 'package:catcher_2/catcher_2.dart'; import './services/loggeer.dart'; - void main() async { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); @@ -63,7 +62,6 @@ void main() async { }, ); - // 小白条、导航栏沉浸 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( diff --git a/lib/models/live/room_info_h5.dart b/lib/models/live/room_info_h5.dart new file mode 100644 index 00000000..a0c19621 --- /dev/null +++ b/lib/models/live/room_info_h5.dart @@ -0,0 +1,130 @@ +class RoomInfoH5Model { + RoomInfoH5Model({ + this.roomInfo, + this.anchorInfo, + this.isRoomFeed, + this.watchedShow, + this.likeInfoV3, + this.blockInfo, + }); + + RoomInfo? roomInfo; + AnchorInfo? anchorInfo; + int? isRoomFeed; + Map? watchedShow; + LikeInfoV3? likeInfoV3; + Map? blockInfo; + + RoomInfoH5Model.fromJson(Map json) { + roomInfo = RoomInfo.fromJson(json['room_info']); + anchorInfo = AnchorInfo.fromJson(json['anchor_info']); + isRoomFeed = json['is_room_feed']; + watchedShow = json['watched_show']; + likeInfoV3 = LikeInfoV3.fromJson(json['like_info_v3']); + blockInfo = json['block_info']; + } +} + +class RoomInfo { + RoomInfo({ + this.uid, + this.roomId, + this.title, + this.cover, + this.description, + this.liveStatus, + this.liveStartTime, + this.areaId, + this.areaName, + this.parentAreaId, + this.parentAreaName, + this.online, + this.background, + this.appBackground, + this.liveId, + }); + + int? uid; + int? roomId; + String? title; + String? cover; + String? description; + int? liveStatus; + int? liveStartTime; + int? areaId; + String? areaName; + int? parentAreaId; + String? parentAreaName; + int? online; + String? background; + String? appBackground; + String? liveId; + + RoomInfo.fromJson(Map json) { + uid = json['uid']; + roomId = json['room_id']; + title = json['title']; + cover = json['cover']; + description = json['description']; + liveStatus = json['liveS_satus']; + liveStartTime = json['live_start_time']; + areaId = json['area_id']; + areaName = json['area_name']; + parentAreaId = json['parent_area_id']; + parentAreaName = json['parent_area_name']; + online = json['online']; + background = json['background']; + appBackground = json['app_background']; + liveId = json['live_id']; + } +} + +class AnchorInfo { + AnchorInfo({ + this.baseInfo, + this.relationInfo, + }); + + BaseInfo? baseInfo; + RelationInfo? relationInfo; + + AnchorInfo.fromJson(Map json) { + baseInfo = BaseInfo.fromJson(json['base_info']); + relationInfo = RelationInfo.fromJson(json['relation_info']); + } +} + +class BaseInfo { + BaseInfo({ + this.uname, + this.face, + }); + + String? uname; + String? face; + + BaseInfo.fromJson(Map json) { + uname = json['uname']; + face = json['face']; + } +} + +class RelationInfo { + RelationInfo({this.attention}); + + int? attention; + + RelationInfo.fromJson(Map json) { + attention = json['attention']; + } +} + +class LikeInfoV3 { + LikeInfoV3({this.totalLikes}); + + int? totalLikes; + + LikeInfoV3.fromJson(Map json) { + totalLikes = json['total_likes']; + } +} diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 2f489fec..56da0a78 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -4,6 +4,8 @@ import 'package:pilipala/http/live.dart'; import 'package:pilipala/models/live/room_info.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; +import '../../models/live/room_info_h5.dart'; + class LiveRoomController extends GetxController { String cover = ''; late int roomId; @@ -21,6 +23,7 @@ class LiveRoomController extends GetxController { // controlsStyle: ControlsStyle.live, // enabledButtons: const EnabledButtons(pip: true), // ); + Rx roomInfoH5 = RoomInfoH5Model().obs; @override void onInit() { @@ -37,10 +40,11 @@ class LiveRoomController extends GetxController { } } queryLiveInfo(); + queryLiveInfoH5(); } - playerInit(source) { - plPlayerController.setDataSource( + playerInit(source) async { + await plPlayerController.setDataSource( DataSource( videoSource: source, audioSource: null, @@ -66,7 +70,8 @@ class LiveRoomController extends GetxController { String videoUrl = (item.urlInfo?.first.host)! + item.baseUrl! + item.urlInfo!.first.extra!; - playerInit(videoUrl); + await playerInit(videoUrl); + return res; } } @@ -80,4 +85,12 @@ class LiveRoomController extends GetxController { volumeOff.value = true; } } + + Future queryLiveInfoH5() async { + var res = await LiveHttp.liveRoomInfoH5(roomId: roomId); + if (res['status']) { + roomInfoH5.value = res['data']; + } + return res; + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 5ac382e6..f727bfd0 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -19,6 +19,8 @@ class LiveRoomPage extends StatefulWidget { class _LiveRoomPageState extends State { final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); PlPlayerController? plPlayerController; + late Future? _futureBuilder; + late Future? _futureBuilderFuture; bool isShowCover = true; bool isPlay = true; @@ -39,6 +41,8 @@ class _LiveRoomPageState extends State { if (Platform.isAndroid) { floating = Floating(); } + _futureBuilder = _liveRoomController.queryLiveInfoH5(); + _futureBuilderFuture = _liveRoomController.queryLiveInfo(); } @override @@ -52,57 +56,110 @@ class _LiveRoomPageState extends State { @override Widget build(BuildContext context) { + Widget videoPlayerPanel = FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && snapshot.data['status']) { + return PLVideoPlayer( + controller: plPlayerController!, + bottomControl: BottomControl( + controller: plPlayerController, + liveRoomCtr: _liveRoomController, + floating: floating, + ), + ); + } else { + return const SizedBox(); + } + }, + ); + Widget childWhenDisabled = Scaffold( primary: true, - appBar: PreferredSize( - preferredSize: Size.fromHeight( - MediaQuery.of(context).orientation == Orientation.portrait ? 56 : 0, - ), - child: AppBar( - centerTitle: false, - titleSpacing: 0, - title: _liveRoomController.liveItem != null - ? Row( - children: [ - NetworkImgLayer( - width: 34, - height: 34, - type: 'avatar', - src: _liveRoomController.liveItem.face, - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _liveRoomController.liveItem.uname, - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 1), - if (_liveRoomController.liveItem.watchedShow != null) - Text( - _liveRoomController - .liveItem.watchedShow['text_large'] ?? - '', - style: const TextStyle(fontSize: 12)), - ], - ), - ], - ) - : const SizedBox(), - // actions: [ - // SizedBox( - // height: 34, - // child: ElevatedButton(onPressed: () {}, child: const Text('关注')), - // ), - // const SizedBox(width: 12), - // ], - ), - ), - body: Column( + backgroundColor: Colors.black, + body: Stack( children: [ - Stack( + Obx( + () => + _liveRoomController.roomInfoH5.value.roomInfo?.appBackground != + '' + ? Positioned.fill( + child: Opacity( + opacity: 0.8, + child: NetworkImgLayer( + width: Get.width, + height: Get.height, + src: _liveRoomController + .roomInfoH5.value.roomInfo?.appBackground ?? + '', + ), + ), + ) + : Image.asset( + 'assets/images/live/default_bg.webp', + width: Get.width, + height: Get.height, + ), + ), + Column( children: [ + AppBar( + centerTitle: false, + titleSpacing: 0, + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + toolbarHeight: + MediaQuery.of(context).orientation == Orientation.portrait + ? 56 + : 0, + title: FutureBuilder( + future: _futureBuilder, + builder: (context, snapshot) { + if (snapshot.data == null) { + return const SizedBox(); + } + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => Row( + children: [ + NetworkImgLayer( + width: 34, + height: 34, + type: 'avatar', + src: _liveRoomController + .roomInfoH5.value.anchorInfo!.baseInfo!.face, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _liveRoomController.roomInfoH5.value + .anchorInfo!.baseInfo!.uname!, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 1), + if (_liveRoomController + .roomInfoH5.value.watchedShow != + null) + Text( + _liveRoomController.roomInfoH5.value + .watchedShow!['text_large'] ?? + '', + style: const TextStyle(fontSize: 12), + ), + ], + ), + ], + ), + ); + } else { + return const SizedBox(); + } + }, + ), + ), PopScope( canPop: plPlayerController?.isFullScreen.value != true, onPopInvoked: (bool didPop) { @@ -120,55 +177,19 @@ class _LiveRoomPageState extends State { Orientation.landscape ? Get.size.height : Get.size.width * 9 / 16, - child: plPlayerController!.videoPlayerController != null - ? PLVideoPlayer( - controller: plPlayerController!, - bottomControl: BottomControl( - controller: plPlayerController, - liveRoomCtr: _liveRoomController, - floating: floating, - ), - ) - : const SizedBox(), + child: videoPlayerPanel, ), ), - // if (_liveRoomController.liveItem != null && - // _liveRoomController.liveItem.cover != null) - // Visibility( - // visible: isShowCover, - // child: Positioned( - // top: 0, - // left: 0, - // right: 0, - // child: NetworkImgLayer( - // type: 'emote', - // src: _liveRoomController.liveItem.cover, - // width: Get.size.width, - // height: videoHeight, - // ), - // ), - // ), ], ), ], ), ); - Widget childWhenEnabled = AspectRatio( - aspectRatio: 16 / 9, - child: plPlayerController!.videoPlayerController != null - ? PLVideoPlayer( - controller: plPlayerController!, - bottomControl: BottomControl( - controller: plPlayerController, - liveRoomCtr: _liveRoomController, - ), - ) - : const SizedBox(), - ); if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, - childWhenEnabled: childWhenEnabled, + childWhenEnabled: videoPlayerPanel, + floating: floating, ); } else { return childWhenDisabled; diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 3980453b..d073945b 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -586,6 +586,7 @@ class _PLVideoPlayerState extends State ), /// 进度条 live模式下禁用 + Obx( () { final int value = _.sliderPositionSeconds.value; @@ -609,7 +610,7 @@ class _PLVideoPlayerState extends State } if (_.videoType.value == 'live') { - return nil; + return const SizedBox(); } if (value > max || max <= 0) { return nil; diff --git a/pubspec.yaml b/pubspec.yaml index 16df4ad7..9597eab7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -188,6 +188,7 @@ flutter: - assets/images/ - assets/images/lv/ - assets/images/logo/ + - assets/images/live/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware