import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'datachannel_mock.dart'; class MockPeerConnection extends RTCPeerConnection { static const _offerType = 'offer'; static const _answerType = 'answer'; bool closed = false; RTCSessionDescription? _localDescription; RTCSessionDescription? _remoteDescription; RTCPeerConnectionState _connectionState = RTCPeerConnectionState.RTCPeerConnectionStateNew; RTCIceConnectionState _iceConnectionState = RTCIceConnectionState.RTCIceConnectionStateNew; RTCIceGatheringState _iceGatheringState = RTCIceGatheringState.RTCIceGatheringStateNew; @override Future getLocalDescription() async => _localDescription; @override Future setLocalDescription(RTCSessionDescription description) async { _localDescription = description; _handleIceConnection(); } @override Future getRemoteDescription() async => _remoteDescription; @override Future setRemoteDescription(RTCSessionDescription description) async { _remoteDescription = description; _handleIceConnection(); } void _handleIceConnection() { if ((_localDescription?.type == _offerType && _remoteDescription?.type == _answerType) || (_localDescription?.type == _answerType && _remoteDescription?.type == _offerType)) { iceConnectionState = RTCIceConnectionState.RTCIceConnectionStateCompleted; } } @override RTCPeerConnectionState get connectionState => _connectionState; set connectionState(RTCPeerConnectionState newState) { if (newState != _connectionState) { _connectionState = newState; onConnectionState?.call(newState); } } @override RTCIceConnectionState get iceConnectionState => _iceConnectionState; set iceConnectionState(RTCIceConnectionState newState) { if (newState != _iceConnectionState) { _iceConnectionState = newState; onIceConnectionState?.call(newState); switch (newState) { case RTCIceConnectionState.RTCIceConnectionStateNew: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateNew; break; case RTCIceConnectionState.RTCIceConnectionStateChecking: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateConnecting; break; case RTCIceConnectionState.RTCIceConnectionStateConnected: case RTCIceConnectionState.RTCIceConnectionStateCompleted: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateConnected; break; case RTCIceConnectionState.RTCIceConnectionStateFailed: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateFailed; break; case RTCIceConnectionState.RTCIceConnectionStateDisconnected: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateDisconnected; break; case RTCIceConnectionState.RTCIceConnectionStateClosed: connectionState = RTCPeerConnectionState.RTCPeerConnectionStateClosed; break; case RTCIceConnectionState.RTCIceConnectionStateCount: throw Exception("This state shouldn't exist (not in RFC)."); } } } @override RTCIceGatheringState get iceGatheringState => _iceGatheringState; set iceGatheringState(RTCIceGatheringState newState) { if (newState != _iceGatheringState) { _iceGatheringState = newState; onIceGatheringState?.call(newState); } } @override Future addCandidate(RTCIceCandidate candidate) async {} @override Future addStream(MediaStream stream) async {} @override Future addTrack(MediaStreamTrack track, [MediaStream? stream]) async { // TODO: implement addTrack throw UnimplementedError(); } @override Future addTransceiver( {MediaStreamTrack? track, RTCRtpMediaType? kind, RTCRtpTransceiverInit? init}) { // TODO: implement addTransceiver throw UnimplementedError(); } @override Future close() async { closed = true; } @override Future createAnswer( [Map? constraints]) async { return RTCSessionDescription('local_answer', 'answer'); } @override Future createOffer( [Map? constraints]) async { return RTCSessionDescription('local_offer', 'offer'); } @override RTCSignalingState? get signalingState { if (closed) { return RTCSignalingState.RTCSignalingStateClosed; } if ((_localDescription?.type == null && _remoteDescription?.type == null) || (_localDescription?.type == _offerType && _remoteDescription?.type == _answerType) || (_localDescription?.type == _answerType && _remoteDescription?.type == _offerType)) { return RTCSignalingState.RTCSignalingStateStable; } if (_localDescription?.type == _offerType && _remoteDescription?.type == null) { return RTCSignalingState.RTCSignalingStateHaveLocalOffer; } if (_remoteDescription?.type == _offerType && _localDescription?.type == null) { return RTCSignalingState.RTCSignalingStateHaveRemoteOffer; } throw Exception( 'Illegal signalling state? localDesc: $_localDescription, remoteDesc: $_remoteDescription'); } @override Future createDataChannel( String label, RTCDataChannelInit dataChannelDict) async { return MockDataChannel(dataChannelDict.id, label); } @override RTCDTMFSender createDtmfSender(MediaStreamTrack track) { // TODO: implement createDtmfSender throw UnimplementedError(); } @override Future dispose() async { await close(); } @override // TODO: implement getConfiguration Map get getConfiguration => throw UnimplementedError(); @override List getLocalStreams() => List.empty(); @override Future> getReceivers() async => List.empty(); @override List getRemoteStreams() => List.empty(); @override Future> getSenders() async => List.empty(); @override Future> getStats([MediaStreamTrack? track]) async => List.empty(); @override Future> getTransceivers() async => List.empty(); @override Future removeStream(MediaStream stream) async {} @override Future removeTrack(RTCRtpSender sender) async => true; @override Future setConfiguration(Map configuration) { // TODO: implement setConfiguration throw UnimplementedError(); } static Future create(Map configuration, [Map? constraints]) async => MockPeerConnection(); @override // TODO: implement restartIce Future restartIce() => throw UnimplementedError(); }