Files
App/lib/pods/chat/webrtc_signaling.dart

205 lines
6.3 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/account.dart';
import 'package:island/models/chat.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/pods/websocket.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:island/talker.dart';
part 'webrtc_signaling.freezed.dart';
part 'webrtc_signaling.g.dart';
@freezed
sealed class SignalingMessage with _$SignalingMessage {
const factory SignalingMessage({
required String type,
// CHANGED: Added 'to' field for directed messaging
String? to,
required String accountId,
required SnAccount account,
required Map<String, dynamic> data,
}) = _SignalingMessage;
factory SignalingMessage.fromJson(Map<String, dynamic> json) =>
_$SignalingMessageFromJson(json);
}
@freezed
sealed class WebRTCWelcomeMessage with _$WebRTCWelcomeMessage {
const factory WebRTCWelcomeMessage({
required String userId,
required String roomId,
required String message,
required String timestamp,
// CHANGED: Added participants list
@Default([]) List<CallParticipant> participants,
}) = _WebRTCWelcomeMessage;
factory WebRTCWelcomeMessage.fromJson(Map<String, dynamic> json) =>
_$WebRTCWelcomeMessageFromJson(json);
}
class WebRTCSignaling {
final String roomId;
late final String userId;
late final String userName;
late final SnAccount user;
final StreamController<SignalingMessage> _messageController =
StreamController<SignalingMessage>.broadcast();
final StreamController<WebRTCWelcomeMessage> _welcomeController =
StreamController<WebRTCWelcomeMessage>.broadcast();
WebSocketChannel? _channel;
Stream<SignalingMessage> get messages => _messageController.stream;
Stream<WebRTCWelcomeMessage> get welcomeMessages => _welcomeController.stream;
WebRTCSignaling({required this.roomId});
Future<void> connect(Ref ref) async {
user = ref.watch(userInfoProvider).value!;
final baseUrl = ref.watch(serverUrlProvider);
final token = await getToken(ref.watch(tokenProvider));
userId = user.id;
userName = user.name;
final url = '$baseUrl/sphere/chat/realtime/$roomId'.replaceFirst(
'http',
'ws',
);
talker.info('[WebRTC Signaling] Trying connecting to $url');
try {
if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse('$url?tk=$token'));
} else {
_channel = IOWebSocketChannel.connect(
Uri.parse(url),
headers: {'Authorization': 'AtField $token'},
);
}
await _channel!.ready;
_channel!.stream.listen(
(data) {
final dataStr =
data is Uint8List ? utf8.decode(data) : data.toString();
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
talker.info(
'[WebRTC Signaling] Recieved a singal message with packet type: ${packet.type}',
);
if (packet.type == 'webrtc') {
try {
final welcomeMessage = WebRTCWelcomeMessage.fromJson(
packet.data!,
);
_welcomeController.add(welcomeMessage);
talker.info(
'[WebRTC Signaling] Welcome message received: ${welcomeMessage.message}',
);
} catch (e) {
talker.error(
'[WebRTC Signaling] Failed to parse welcome message: $e',
);
}
} else if (packet.type == 'webrtc.signal') {
try {
final signalingMessage = SignalingMessage.fromJson(packet.data!);
// CHANGED: Ensure we only process messages intended for us if the 'to' field is present
if (signalingMessage.to == null ||
signalingMessage.to == userId) {
_messageController.add(signalingMessage);
}
} catch (e) {
talker.error(
'[WebRTC Signaling] Failed to parse signaling message: $e',
);
}
}
},
onError: (error) {
talker.error('[WebRTC Signaling] WebSocket error: $error');
_messageController.addError(error);
_welcomeController.addError(error);
},
onDone: () {
talker.info('[WebRTC Signaling] WebSocket connection closed');
_messageController.close();
_welcomeController.close();
},
);
} catch (err) {
talker.error('[WebRTC Signaling] Failed to connect: $err');
_messageController.addError(err);
_welcomeController.addError(err);
}
}
void sendMessage(SignalingMessage message) {
if (_channel == null) return;
talker.info(
'[WebRTC Signaling] Sending a message with message type: ${message.type} to ${message.to}',
);
final packet = WebSocketPacket(
type: 'webrtc.signal',
data: message.toJson(),
);
_channel!.sink.add(jsonEncode(packet.toJson()));
}
// CHANGED: All send methods now correctly use the `to` parameter
void sendOffer(String to, RTCSessionDescription offer) {
sendMessage(
SignalingMessage(
type: 'offer',
to: to,
accountId: userId,
account: user,
data: {'sdp': offer.sdp, 'type': offer.type},
),
);
}
void sendAnswer(String to, RTCSessionDescription answer) {
sendMessage(
SignalingMessage(
type: 'answer',
to: to,
accountId: userId,
account: user,
data: {'sdp': answer.sdp, 'type': answer.type},
),
);
}
void sendIceCandidate(String to, RTCIceCandidate candidate) {
sendMessage(
SignalingMessage(
type: 'ice-candidate',
to: to,
accountId: userId,
account: user,
data: {
'candidate': candidate.candidate,
'sdpMid': candidate.sdpMid,
'sdpMLineIndex': candidate.sdpMLineIndex,
},
),
);
}
void disconnect() {
_channel?.sink.close();
_messageController.close();
_welcomeController.close();
}
}