import 'dart:convert'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:signalr_netcore/http_connection_options.dart'; import 'package:signalr_netcore/hub_connection.dart'; import 'package:signalr_netcore/hub_connection_builder.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import '../chat_provider.dart'; class CallProvider with ChangeNotifier, DiagnosticableTreeMixin { ///////////////////// Web RTC Video Calling ////////////////////// // Video Call late RTCPeerConnection _peerConnection; RTCVideoRenderer _localVideoRenderer = RTCVideoRenderer(); final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer(); MediaStream? _localStream; MediaStream? _remoteStream; bool isMuted = false; bool isSpeakerOn = false; bool isCameraOn = true; bool isPeerMuted = false; bool isPeerCameraOn = true; Future _disposeConnection() async { try { if (chatHubConnection != null) { await chatHubConnection!.stop(); if (kDebugMode) { print('🔌 SignalR connection closed successfully'); } } } catch (e) { if (kDebugMode) { print('⚠️ Error closing SignalR connection: $e'); } } finally { chatHubConnection = null; } } @override void dispose() { _disposeConnection().then((_) { if (kDebugMode) { print('✅ ChatProvider disposed'); } }).catchError((error) { if (kDebugMode) { print('⚠️ Error during ChatProvider disposal: $error'); } }); super.dispose(); } Future getHubConnection() async { if (kDebugMode) { print('🔧 Creating new SignalR hub connection...'); } HubConnection hub; HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?accessTokenFactory=${URLs.chatApiKey}", options: httpOp).withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); if (kDebugMode) { print('✅ SignalR hub connection created'); } return hub; } Future buildHubConnection() async { try { // Dispose existing connection if any await _disposeConnection(); chatHubConnection = await getHubConnection(); await chatHubConnection!.start(); if (kDebugMode) { print("🔌 SignalR Hub Connection: Started"); } return true; //group On message // chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); } catch (e) { if (kDebugMode) { print('⚠️ Error building SignalR connection: $e'); } // Clean up on error await _disposeConnection(); return false; rethrow; // Rethrow so caller knows about the error } } void initAudioCallListeners() { chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); chatHubConnection!.on("OnOfferAsync", onOfferAsync); chatHubConnection!.on("OnAnswerOffer", onAnswerOffer); chatHubConnection!.on("OnHangUpAsync", onHangUpAsync); chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); chatHubConnection!.on('OnAudioToggle', onAudioToggle); } void initCallListeners() { chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync); chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync); chatHubConnection!.on("OnOfferAsync", onOfferAsync); chatHubConnection!.on("OnAnswerOffer", onAnswerOffer); chatHubConnection!.on("OnHangUpAsync", onHangUpAsync); chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync); chatHubConnection!.on('OnAudioToggle', onAudioToggle); chatHubConnection!.on('OnCameraToggle', onCameraToggle); // chatHubConnection!.on('OnIncomingCallAsync', onHandleIncomingCall); // chatHubConnection!.on('OnCallAcceptedAsync', _handleCallAccepted); // chatHubConnection!.on('OnCallDeclinedAsync', _handleCallDeclined); // chatHubConnection!.on('OnHangUpAsync', onHangUpAsync); // chatHubConnection!.on('OnOfferAsync', _handleOffer); // chatHubConnection!.on('OnAnswerOfferAsync', onAnswerOffer); // chatHubConnection!.on('OnIceCandidateAsync', _handleIceCandidate); // chatHubConnection!.on('OnAudioToggle', _handleAudioToggle); // chatHubConnection!.on('OnCameraToggle', _handleCameraToggle); // chatHubConnection!.on('OnUserOnlineAsync', _handleUserOnline); // chatHubConnection!.on('OnUserOfflineAsync', _handleUserOffline); // chatHubConnection!.on('OnCallHistoryUpdated', _handleCallHistoryUpdated); // chatHubConnection!.on('OnError', _handleError); } //Video Constraints var videoConstraints = { "video": { "mandatory": { "width": {"min": 320}, "height": {"min": 180} }, "optional": [ { "width": {"max": 1280} }, {"frameRate": 25}, {"facingMode": "user"} ] }, "frameRate": 25, "width": 420, //420,//640,//1280, "height": 240 //240//480//720 }; // Audio Constraints var audioConstraints = { "sampleRate": 8000, "sampleSize": 16, "channelCount": 2, "echoCancellation": true, "audio": true, }; Future _createPeerConnection() async { // {"url": "stun:stun.l.google.com:19302"}, Map configuration = { "iceServers": [ {"urls": 'stun:15.185.116.59:3478'}, {"urls": "turn:15.185.116.59:3479", "username": "admin", "credential": "admin"} ] }; Map offerSdpConstraints = { "mandatory": { "OfferToReceiveAudio": true, "OfferToReceiveVideo": true, }, "optional": [], }; RTCPeerConnection pc = await createPeerConnection(configuration, offerSdpConstraints); // if (pc != null) print(pc); //pc.addStream(widget.localStream); pc.onIceCandidate = (e) { if (e.candidate != null) { print(json.encode({ 'candidate': e.candidate.toString(), 'sdpMid': e.sdpMid.toString(), 'sdpMlineIndex': e.sdpMLineIndex, })); } }; pc.onIceConnectionState = (e) { print(e); }; pc.onAddStream = (stream) { print('addStream: ' + stream.id); _remoteRenderer.srcObject = stream; }; return pc; } void init() { initRenderers(); _createPeerConnection().then((pc) { _peerConnection = pc; // _setRemoteDescription(widget.info); }); } void initRenderers() { _localVideoRenderer.initialize(); _remoteRenderer.initialize(); initLocalCamera(); } void initLocalCamera() async { _localStream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true}); _localVideoRenderer.srcObject = _localStream; // _localVideoRenderer.srcObject = await navigator.mediaDevices // .getUserMedia({'video': true, 'audio': true}); print('this source Object'); print('this suarce ${_localVideoRenderer.srcObject != null}'); notifyListeners(); } late String senderN; late String recipientN; late bool isVideo; int? moduleId; int? referenceID; void startCall(String senderN, String recipientN, bool isVideo, int? moduleId, int? referenceID) async { this.senderN = senderN; this.recipientN = recipientN; this.isVideo = isVideo; this.moduleId = moduleId; this.referenceID = referenceID; invoke(invokeMethod: "CallUserAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); } void acceptCall(String senderN, String recipientN, bool isVideo, int moduleId, int? referenceID) async { invoke(invokeMethod: "AnswerCallAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); } void declineCall(String senderN, String recipientN, bool isVideo, int moduleId, int? referenceID) async { invoke(invokeMethod: "CallDeclinedAsync", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); } void endCall() { invoke(invokeMethod: "HangUpAsync", currentUserID: senderN, targetUserID: recipientN, data: moduleId); } void toggleMute() { invoke(invokeMethod: "AudioToggle", currentUserID: senderN, targetUserID: recipientN, isVideoCall: isVideo); } void checkCall(Map message) { switch (message["callStatus"]) { case 'connected': {} break; case 'offer': {} break; case 'accept': {} break; case 'candidate': {} break; case 'bye': {} break; case 'leave': {} break; } } //// Listeners Methods //// void onCallAcceptedAsync(List? params) { print("onCallAcceptedAsync: $params"); } void onIceCandidateAsync(List? params) { print("onCallAcceptedAsync: $params"); } void onOfferAsync(List? params) { print("onCallAcceptedAsync: $params"); } void onAnswerOffer(List? params) { print("onAnswerOffer: $params"); } void onHangUpAsync(List? params) { print("onHangUpAsync: $params"); } void onCallDeclinedAsync(List? params) { print("onCallDeclinedAsync: $params"); } void onAudioToggle(List? params) { print("onAudioToggle: $params"); isMuted = !isMuted; notifyListeners(); } void onCameraToggle(List? params) { print("onCameraToggle: $params"); } //// Invoke Methods Future invoke({required String invokeMethod, required String currentUserID, required String targetUserID, bool isVideoCall = false, var data}) async { List args = []; if (invokeMethod == "answerCallAsync") { args = [currentUserID, targetUserID]; } else if (invokeMethod == "CallUserAsync") { args = [currentUserID, targetUserID, isVideoCall]; } else if (invokeMethod == "HangUpAsync ") { args = [currentUserID, targetUserID, data]; } else if (invokeMethod == "AudioToggle") { args = [currentUserID, targetUserID, isVideoCall]; } else if (invokeMethod == "IceCandidateAsync") { args = [targetUserID, data]; } else if (invokeMethod == "OfferAsync") { args = [targetUserID, data]; } else if (invokeMethod == "AnswerOfferAsync") { args = [targetUserID, data]; } await chatHubConnection!.invoke(invokeMethod, args: args); } void stopListeners() async { chatHubConnection!.off('OnCallDeclinedAsync'); chatHubConnection!.off('OnCallAcceptedAsync'); chatHubConnection!.off('OnIceCandidateAsync'); chatHubConnection!.off('OnAnswerOffer'); chatHubConnection!.off('OnOfferAsync'); chatHubConnection!.off('OnHangUpAsync'); chatHubConnection!.off('OnAudioToggle'); } }