Skip to content

Instantly share code, notes, and snippets.

@Clon1998
Created July 23, 2023 16:39
Show Gist options
  • Save Clon1998/d912327938cdeaafa50df0c7e8105b44 to your computer and use it in GitHub Desktop.
Save Clon1998/d912327938cdeaafa50df0c7e8105b44 to your computer and use it in GitHub Desktop.
WebRtcManager Mobileraker
@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