205 lines
6.3 KiB
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();
|
|
}
|
|
}
|