mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
* feat: use wss instead of tcp connect * feat: fallback to next server when live room danmu server connect failed
198 lines
6.2 KiB
Dart
198 lines
6.2 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:PiliPalaX/models/live/danmu_info.dart';
|
|
import 'package:PiliPalaX/tcp/live.dart';
|
|
import 'package:PiliPalaX/utils/danmaku.dart';
|
|
import 'package:PiliPalaX/utils/storage.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:PiliPalaX/http/constants.dart';
|
|
import 'package:PiliPalaX/http/live.dart';
|
|
import 'package:PiliPalaX/models/live/room_info.dart';
|
|
import 'package:PiliPalaX/plugin/pl_player/index.dart';
|
|
import 'package:ns_danmaku/danmaku_controller.dart';
|
|
import 'package:ns_danmaku/models/danmaku_item.dart';
|
|
import '../../models/live/room_info_h5.dart';
|
|
import '../../utils/video_utils.dart';
|
|
|
|
class LiveRoomController extends GetxController {
|
|
String cover = '';
|
|
late int roomId;
|
|
dynamic liveItem;
|
|
late String heroTag;
|
|
double volume = 0.0;
|
|
// 静音状态
|
|
RxBool volumeOff = false.obs;
|
|
PlPlayerController plPlayerController =
|
|
PlPlayerController.getInstance(videoType: 'live');
|
|
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
|
|
// late bool enableCDN;
|
|
|
|
RxList<dynamic> messages = [].obs;
|
|
RxBool disableAutoScroll = false.obs;
|
|
double? brightness;
|
|
DanmakuController? controller;
|
|
bool showDanmaku = true;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
roomId = int.parse(Get.parameters['roomid']!);
|
|
if (Get.arguments != null) {
|
|
liveItem = Get.arguments['liveItem'];
|
|
heroTag = Get.arguments['heroTag'] ?? '';
|
|
if (liveItem != null && liveItem.pic != null && liveItem.pic != '') {
|
|
cover = liveItem.pic;
|
|
}
|
|
if (liveItem != null && liveItem.cover != null && liveItem.cover != '') {
|
|
cover = liveItem.cover;
|
|
}
|
|
}
|
|
// CDN优化
|
|
// enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);
|
|
}
|
|
|
|
playerInit(source) async {
|
|
await plPlayerController.setDataSource(
|
|
DataSource(
|
|
videoSource: source,
|
|
audioSource: null,
|
|
type: DataSourceType.network,
|
|
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
|
|
},
|
|
),
|
|
// 硬解
|
|
enableHA: true,
|
|
autoplay: true,
|
|
);
|
|
}
|
|
|
|
bool? isPortrait;
|
|
|
|
Future queryLiveInfo() async {
|
|
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: 10000);
|
|
if (res['status']) {
|
|
isPortrait = res['data'].isPortrait;
|
|
List<CodecItem> codec =
|
|
res['data'].playurlInfo.playurl.stream.first.format.first.codec;
|
|
CodecItem item = codec.first;
|
|
String videoUrl = VideoUtils.getCdnUrl(item);
|
|
await playerInit(videoUrl);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
void setVolume(value) {
|
|
if (value == 0) {
|
|
// 设置音量
|
|
volumeOff.value = false;
|
|
} else {
|
|
// 取消音量
|
|
volume = value;
|
|
volumeOff.value = true;
|
|
}
|
|
}
|
|
|
|
Future queryLiveInfoH5() async {
|
|
var res = await LiveHttp.liveRoomInfoH5(roomId: roomId);
|
|
if (res['status']) {
|
|
roomInfoH5.value = res['data'];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
LiveMessageStream? msgStream;
|
|
final ScrollController scrollController = ScrollController();
|
|
|
|
void scrollToBottom() {
|
|
if (disableAutoScroll.value) return;
|
|
if (scrollController.hasClients) {
|
|
scrollController.animateTo(
|
|
scrollController.position.maxScrollExtent,
|
|
duration: const Duration(milliseconds: 200),
|
|
curve: Curves.linearToEaseOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
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(),
|
|
);
|
|
}
|
|
});
|
|
LiveHttp.liveRoomGetDanmakuToken(roomId: roomId).then((v) {
|
|
if (v['status']) {
|
|
LiveDanmakuInfo info = v['data'];
|
|
// logger.d("info => $info");
|
|
List<String> servers = [];
|
|
for (final host in info.data.hostList) {
|
|
servers.add('wss://${host.host}:${host.wssPort}/sub');
|
|
}
|
|
msgStream = LiveMessageStream(
|
|
streamToken: info.data.token,
|
|
roomId: roomId,
|
|
uid: GStorage.userInfo.get('userInfoCache')?.mid ?? 0,
|
|
servers: servers,
|
|
);
|
|
msgStream?.addEventListener((obj) {
|
|
if (obj['cmd'] == 'DANMU_MSG') {
|
|
// logger.i(' 原始弹幕消息 ======> ${jsonEncode(obj)}');
|
|
messages.add({
|
|
'name': obj['info'][0][15]['user']['base']['name'],
|
|
'uid': obj['info'][0][15]['user']['uid'],
|
|
'text': obj['info'][1],
|
|
'emots': jsonDecode(obj['info'][0][15]['extra'])['emots'],
|
|
'uemote': obj['info'][0][13],
|
|
});
|
|
Map json = jsonDecode(obj['info'][0][15]['extra']);
|
|
if (showDanmaku) {
|
|
controller?.addItems([
|
|
DanmakuItem(
|
|
json['content'],
|
|
color: DmUtils.decimalToColor(json['color']),
|
|
// time: e.progress,
|
|
type: DmUtils.getPosition(json['mode']),
|
|
)
|
|
]);
|
|
WidgetsBinding.instance.addPostFrameCallback(
|
|
(_) => scrollToBottom(),
|
|
);
|
|
}
|
|
}
|
|
});
|
|
msgStream?.init();
|
|
scrollController.addListener(() {
|
|
if (scrollController.position.userScrollDirection ==
|
|
ScrollDirection.forward) {
|
|
disableAutoScroll.value = true;
|
|
} else if (scrollController.position.userScrollDirection ==
|
|
ScrollDirection.reverse) {
|
|
final pos = scrollController.position;
|
|
if (pos.maxScrollExtent - pos.pixels <= 100) {
|
|
disableAutoScroll.value = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|