fix: 完善直播结束通知与 WebSocket 连接
This commit is contained in:
@@ -18,7 +18,7 @@ const (
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"` // "chat", "system", "danmaku"
|
||||
Type string `json:"type"` // "chat", "system", "danmaku", "stream_end"
|
||||
Username string `json:"username"`
|
||||
Content string `json:"content"`
|
||||
RoomID string `json:"room_id"`
|
||||
@@ -190,7 +190,15 @@ func (r *roomHub) handleBroadcast(message Message) {
|
||||
delete(r.clients, client)
|
||||
}
|
||||
}
|
||||
shouldDeleteIfIdle := message.Type == "stream_end"
|
||||
if shouldDeleteIfIdle {
|
||||
r.history = nil
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
|
||||
if shouldDeleteIfIdle {
|
||||
r.manager.deleteRoomIfIdle(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roomHub) handleClearHistory() {
|
||||
@@ -223,6 +231,15 @@ func (h *Hub) BroadcastToRoom(msg Message) {
|
||||
h.getOrCreateRoom(msg.RoomID).broadcast <- msg
|
||||
}
|
||||
|
||||
func (h *Hub) NotifyStreamEnded(roomID string) {
|
||||
h.BroadcastToRoom(Message{
|
||||
Type: "stream_end",
|
||||
Username: "System",
|
||||
Content: "The host has ended the live stream.",
|
||||
RoomID: roomID,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) ReadPump() {
|
||||
defer func() {
|
||||
c.Hub.UnregisterClient(c)
|
||||
|
||||
@@ -172,7 +172,7 @@ func NewRTMPServer(rtmpPort string) *RTMPServer {
|
||||
monitor.Warnf("Failed to mark room inactive room_id=%s: %v", roomID, err)
|
||||
}
|
||||
}
|
||||
chat.MainHub.ClearRoomHistory(roomID)
|
||||
chat.MainHub.NotifyStreamEnded(roomID)
|
||||
monitor.Infof("Publishing ended for room_id=%s", roomID)
|
||||
} else {
|
||||
monitor.Infof("Variant publishing ended for room_id=%s path=%s", roomID, channelPath)
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"availableNow": "Available now",
|
||||
"waitingForTranscoding": "Waiting for backend transcoding output",
|
||||
"sendMessage": "Send a message...",
|
||||
"liveStreamEnded": "The host has ended the live stream.",
|
||||
"liveStreamEndedShort": "Live stream ended",
|
||||
"liveChat": "Live Chat",
|
||||
"refresh": "Refresh",
|
||||
"volume": "Volume",
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"availableNow": "利用可能",
|
||||
"waitingForTranscoding": "バックエンドのトランスコード出力を待機中",
|
||||
"sendMessage": "メッセージを送信...",
|
||||
"liveStreamEnded": "配信者が退出したため、ライブ配信は終了しました。",
|
||||
"liveStreamEndedShort": "ライブ配信は終了しました",
|
||||
"liveChat": "ライブチャット",
|
||||
"refresh": "更新",
|
||||
"volume": "音量",
|
||||
|
||||
@@ -401,6 +401,18 @@ abstract class AppLocalizations {
|
||||
/// **'Send a message...'**
|
||||
String get sendMessage;
|
||||
|
||||
/// No description provided for @liveStreamEnded.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The host has ended the live stream.'**
|
||||
String get liveStreamEnded;
|
||||
|
||||
/// No description provided for @liveStreamEndedShort.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Live stream ended'**
|
||||
String get liveStreamEndedShort;
|
||||
|
||||
/// No description provided for @liveChat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -159,6 +159,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get sendMessage => 'Send a message...';
|
||||
|
||||
@override
|
||||
String get liveStreamEnded => 'The host has ended the live stream.';
|
||||
|
||||
@override
|
||||
String get liveStreamEndedShort => 'Live stream ended';
|
||||
|
||||
@override
|
||||
String get liveChat => 'Live Chat';
|
||||
|
||||
|
||||
@@ -159,6 +159,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get sendMessage => 'メッセージを送信...';
|
||||
|
||||
@override
|
||||
String get liveStreamEnded => '配信者が退出したため、ライブ配信は終了しました。';
|
||||
|
||||
@override
|
||||
String get liveStreamEndedShort => 'ライブ配信は終了しました';
|
||||
|
||||
@override
|
||||
String get liveChat => 'ライブチャット';
|
||||
|
||||
|
||||
@@ -158,6 +158,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get sendMessage => '发送消息...';
|
||||
|
||||
@override
|
||||
String get liveStreamEnded => '主播已退出,直播已结束。';
|
||||
|
||||
@override
|
||||
String get liveStreamEndedShort => '直播已结束';
|
||||
|
||||
@override
|
||||
String get liveChat => '实时聊天';
|
||||
|
||||
@@ -364,6 +370,12 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
|
||||
@override
|
||||
String get sendMessage => '發送訊息...';
|
||||
|
||||
@override
|
||||
String get liveStreamEnded => '主播已退出,直播已結束。';
|
||||
|
||||
@override
|
||||
String get liveStreamEndedShort => '直播已結束';
|
||||
|
||||
@override
|
||||
String get liveChat => '即時聊天';
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"availableNow": "当前可用",
|
||||
"waitingForTranscoding": "正在等待后端转码输出",
|
||||
"sendMessage": "发送消息...",
|
||||
"liveStreamEnded": "主播已退出,直播已结束。",
|
||||
"liveStreamEndedShort": "直播已结束",
|
||||
"liveChat": "实时聊天",
|
||||
"refresh": "刷新",
|
||||
"volume": "音量",
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"availableNow": "目前可用",
|
||||
"waitingForTranscoding": "正在等待後端轉碼輸出",
|
||||
"sendMessage": "發送訊息...",
|
||||
"liveStreamEnded": "主播已退出,直播已結束。",
|
||||
"liveStreamEndedShort": "直播已結束",
|
||||
"liveChat": "即時聊天",
|
||||
"refresh": "重新整理",
|
||||
"volume": "音量",
|
||||
|
||||
@@ -43,6 +43,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
bool _isRefreshing = false;
|
||||
bool _isFullscreen = false;
|
||||
bool _controlsVisible = true;
|
||||
bool _streamEnded = false;
|
||||
double _volume = kIsWeb ? 0.0 : 1.0;
|
||||
int _playerVersion = 0;
|
||||
String _selectedResolution = 'Source';
|
||||
@@ -146,6 +147,10 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
|
||||
_chatService.messages.listen((msg) {
|
||||
if (mounted) {
|
||||
if (msg.type == "stream_end") {
|
||||
_handleStreamEnded(msg.content);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_messages.insert(0, msg);
|
||||
if (!msg.isHistory && (msg.type == "chat" || msg.type == "danmaku")) {
|
||||
@@ -158,6 +163,44 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleStreamEnded(String message) {
|
||||
if (_streamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final streamEndedMessage = l10n.liveStreamEnded;
|
||||
|
||||
setState(() {
|
||||
_streamEnded = true;
|
||||
_isRefreshing = false;
|
||||
_danmakus.clear();
|
||||
_messages.insert(
|
||||
0,
|
||||
ChatMessage(
|
||||
type: "system",
|
||||
username: "System",
|
||||
content: streamEndedMessage,
|
||||
roomId: widget.roomId,
|
||||
),
|
||||
);
|
||||
if (!kIsWeb) {
|
||||
_isError = true;
|
||||
_errorMessage = streamEndedMessage;
|
||||
}
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(streamEndedMessage)));
|
||||
|
||||
Future<void>.delayed(const Duration(seconds: 2), () {
|
||||
if (mounted && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _addDanmaku(String text) {
|
||||
final key = UniqueKey();
|
||||
final lane = DateTime.now().millisecondsSinceEpoch % 8;
|
||||
@@ -180,7 +223,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
}
|
||||
|
||||
void _sendMsg() {
|
||||
if (_msgController.text.isNotEmpty) {
|
||||
if (!_streamEnded && _msgController.text.isNotEmpty) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
_chatService.sendMessage(
|
||||
_msgController.text,
|
||||
@@ -195,6 +238,9 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
if (_isRefreshing) {
|
||||
return;
|
||||
}
|
||||
if (_streamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _loadPlaybackOptions();
|
||||
|
||||
@@ -625,7 +671,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
_buildControlButton(
|
||||
icon: Icons.refresh,
|
||||
label: l10n.refresh,
|
||||
onPressed: _refreshPlayer,
|
||||
onPressed: _streamEnded ? null : _refreshPlayer,
|
||||
),
|
||||
_buildControlButton(
|
||||
icon: _volume == 0
|
||||
@@ -645,7 +691,9 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
icon: _isFullscreen
|
||||
? Icons.fullscreen_exit
|
||||
: Icons.fullscreen,
|
||||
label: _isFullscreen ? l10n.exitFullscreen : l10n.fullscreen,
|
||||
label: _isFullscreen
|
||||
? l10n.exitFullscreen
|
||||
: l10n.fullscreen,
|
||||
onPressed: _toggleFullscreen,
|
||||
),
|
||||
_buildControlButton(
|
||||
@@ -665,13 +713,15 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
Widget _buildControlButton({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required FutureOr<void> Function() onPressed,
|
||||
required FutureOr<void> Function()? onPressed,
|
||||
}) {
|
||||
return FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
_showControls();
|
||||
await onPressed();
|
||||
},
|
||||
onPressed: onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
_showControls();
|
||||
await onPressed();
|
||||
},
|
||||
icon: Icon(icon, size: 18),
|
||||
label: Text(label),
|
||||
style: FilledButton.styleFrom(
|
||||
@@ -693,7 +743,10 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
children: [
|
||||
const Icon(Icons.chat_bubble_outline, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(l10n.liveChat, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
l10n.liveChat,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -716,8 +769,11 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _msgController,
|
||||
enabled: !_streamEnded,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.sendMessage,
|
||||
hintText: _streamEnded
|
||||
? l10n.liveStreamEndedShort
|
||||
: l10n.sendMessage,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
@@ -734,7 +790,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
Icons.send,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: _sendMsg,
|
||||
onPressed: _streamEnded ? null : _sendMsg,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -46,7 +46,6 @@ class ChatService {
|
||||
|
||||
void connect(String baseUrl, String roomId, String username) {
|
||||
final wsUri = _webSocketUri(baseUrl).replace(
|
||||
scheme: 'ws',
|
||||
path: '/api/ws/room/$roomId',
|
||||
queryParameters: {'username': username},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user