-
-
Save Clon1998/d912327938cdeaafa50df0c7e8105b44 to your computer and use it in GitHub Desktop.
WebRtcManager Mobileraker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@freezed | |
abstract class WebRtcConnectionEvent with _$WebRtcConnectionEvent { | |
const factory WebRtcConnectionEvent.closed() = RtcClosedEvent; | |
const factory WebRtcConnectionEvent.failed() = RtcFailedEvent; | |
const factory WebRtcConnectionEvent.disconnected() = RtcDisconnectedEvent; | |
const factory WebRtcConnectionEvent.new() = RtcNewEvent; | |
const factory WebRtcConnectionEvent.connecting() = RtcConnectingEvent; | |
const factory WebRtcConnectionEvent.connected( | |
RTCVideoRenderer videoRenderer) = RtcConnectedEvent; | |
} | |
class WebRtcManager { | |
final Map<String, dynamic> offerSdpConstraints = { | |
"mandatory": { | |
"OfferToReceiveAudio": false, | |
"OfferToReceiveVideo": true, | |
}, | |
"optional": [], | |
}; | |
WebRtcManager({ | |
required this.camUri, | |
Map<String, String> httpHeader = const {}, | |
}) : httpHeader = {'Content-Type': 'application/json', ...httpHeader}; | |
final Uri camUri; | |
final Map<String, String> httpHeader; | |
final RTCVideoRenderer _localRenderer = RTCVideoRenderer(); | |
String? _remoteId; | |
RTCPeerConnection? _pc; | |
final StreamController<WebRtcConnectionEvent> _streamController = | |
StreamController(); | |
Stream<WebRtcConnectionEvent> get stream => _streamController.stream; | |
RTCPeerConnectionState? __connectionState; | |
set _connectionState(RTCPeerConnectionState state) { | |
logger.i('RTC _onConnectionState: $__connectionState -> $state'); | |
__connectionState = state; | |
WebRtcConnectionEvent event = switch (state) { | |
RTCPeerConnectionState.RTCPeerConnectionStateClosed => | |
const WebRtcConnectionEvent.closed(), | |
RTCPeerConnectionState.RTCPeerConnectionStateFailed => | |
const WebRtcConnectionEvent.failed(), | |
RTCPeerConnectionState.RTCPeerConnectionStateDisconnected => | |
const WebRtcConnectionEvent.disconnected(), | |
RTCPeerConnectionState.RTCPeerConnectionStateNew => | |
const WebRtcConnectionEvent(), | |
RTCPeerConnectionState.RTCPeerConnectionStateConnecting => | |
const WebRtcConnectionEvent.connecting(), | |
RTCPeerConnectionState.RTCPeerConnectionStateConnected => | |
WebRtcConnectionEvent.connected(_localRenderer), | |
}; | |
if (!_streamController.isClosed) _streamController.add(event); | |
} | |
Future<void> startCam() async { | |
try { | |
await _pc?.dispose(); | |
await _localRenderer.initialize(); | |
var response = await _requestOffer(); | |
logger.i('Received offer from cam'); | |
_remoteId = response['id']; // I dont think I need that | |
_pc = await _setupPeerConnection(response['sdp']); | |
await _sendAnswer(_pc!); | |
logger.i('Completed webrtc offer and answer sequence'); | |
} catch (e, s) { | |
logger.w('Error while trying to start WebRTC cam', e); | |
_streamController.addError(e, s); | |
} | |
} | |
Future<void> stopCam() async { | |
logger.i('Stopping PeerConnection'); | |
return _pc?.close(); | |
} | |
Future<Map<String, dynamic>> _requestOffer() async { | |
logger.i('Sending webRTC request'); | |
var response = await http.post( | |
camUri, | |
headers: httpHeader, | |
body: jsonEncode(<String, String>{ | |
'type': 'request', | |
}), | |
); | |
if (response.statusCode != 200) { | |
throw HttpException('${response.statusCode} - ${response.reasonPhrase}'); | |
} | |
return jsonDecode(response.body) as Map<String, dynamic>; | |
} | |
Future<RTCPeerConnection> _setupPeerConnection(String offerSdp) async { | |
return await createPeerConnection({ | |
'sdpSemantics': 'unified-plan', | |
// 'iceServers': [ | |
// {'url': 'stun:stun.l.google.com:19302'} | |
// ] | |
}, offerSdpConstraints) | |
..onTrack = _onTrack | |
..onIceCandidate = _onIceCandidate | |
..onConnectionState = _onConnectionState | |
..addTransceiver( | |
kind: RTCRtpMediaType.RTCRtpMediaTypeVideo, | |
init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)) | |
..setRemoteDescription(RTCSessionDescription(offerSdp, 'offer')); | |
} | |
Future<void> _sendAnswer(RTCPeerConnection pc) async { | |
var answer = await pc.createAnswer(); | |
pc.setLocalDescription(answer); | |
var response = await http.post(camUri, | |
headers: httpHeader, | |
body: jsonEncode(<String, String?>{ | |
'id': _remoteId, | |
'type': 'answer', | |
'sdp': answer.sdp, | |
})); | |
if (response.statusCode != 200) { | |
throw HttpException('${response.statusCode} - ${response.reasonPhrase}'); | |
} | |
} | |
void _onTrack(RTCTrackEvent event) { | |
logger.i('Received RTCTrackEvent'); | |
if (_streamController.isClosed) return; | |
if (event.track.kind == 'video' && event.streams.isNotEmpty) { | |
_localRenderer.srcObject?.dispose(); | |
_localRenderer.srcObject = event.streams.first; | |
} | |
} | |
void _onIceCandidate(RTCIceCandidate candidate) { | |
logger.i('Received a new ICE candidate'); | |
if (_streamController.isClosed) return; | |
http | |
.post(camUri, | |
headers: httpHeader, | |
body: jsonEncode(<String, dynamic>{ | |
'type': 'remote_candidate', | |
'id': _remoteId, | |
'candidates': [candidate.candidate], | |
})) | |
.catchError((e, _) => logger.w( | |
'Caught exception while sending ICECandidate to server', e)); | |
} | |
void _onConnectionState(RTCPeerConnectionState event) { | |
_connectionState = event; | |
} | |
dispose() { | |
_pc?.close(); | |
_localRenderer.dispose(); | |
_streamController.close(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment