add: basic webrtc is working
This commit is contained in:
+145
-25
@@ -1,22 +1,60 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:ui/gen/signaler_service.pb.dart';
|
||||
import 'package:ui/gen/signaler_service.pbgrpc.dart' as pb;
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:ui/session_service.dart';
|
||||
|
||||
class Call extends StatefulWidget {
|
||||
final String host;
|
||||
const Call({required this.host, super.key});
|
||||
final pb.SignalerServiceClient client;
|
||||
final SessionService sessionService;
|
||||
final pb.Camera_Identifier cameraID;
|
||||
final String home;
|
||||
const Call(this.client, this.sessionService,
|
||||
{required this.cameraID, required this.home, super.key});
|
||||
|
||||
@override
|
||||
_CallState createState() => _CallState();
|
||||
CallState createState() => CallState();
|
||||
}
|
||||
|
||||
class _CallState extends State<Call> {
|
||||
class CallState extends State<Call> {
|
||||
Logger logger = Logger();
|
||||
RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
|
||||
final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
|
||||
bool _ready = false;
|
||||
String statusLine = "Building...";
|
||||
|
||||
void _connect(BuildContext context) async {}
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_connect();
|
||||
}
|
||||
|
||||
Future<Session> _createSesson() async {
|
||||
_connect() async {
|
||||
_ready = false;
|
||||
logger.i("Init remote renderer");
|
||||
await _remoteRenderer.initialize();
|
||||
logger.i("Creating session");
|
||||
await _createSesson();
|
||||
}
|
||||
|
||||
_createSesson() async {
|
||||
var callOptions = CallOptions(metadata: {
|
||||
'Authorization': await widget.sessionService.getAuthToken(widget.home)
|
||||
});
|
||||
|
||||
var cancelCreate = Completer();
|
||||
|
||||
var clientSession = await widget.client.createSession(
|
||||
pb.CreateSessionRequest(
|
||||
session: pb.Session(
|
||||
camera: widget.cameraID,
|
||||
),
|
||||
),
|
||||
options: callOptions);
|
||||
RTCPeerConnection peerConnection = await createPeerConnection({
|
||||
// Ice servers; just use the Google one for now
|
||||
'iceServers': [
|
||||
@@ -28,47 +66,129 @@ class _CallState extends State<Call> {
|
||||
|
||||
peerConnection.onAddStream = (stream) {
|
||||
// Stream has been added; connect it to our renderer
|
||||
logger.i("Got stream from remote; connecting it");
|
||||
_remoteRenderer.srcObject = stream;
|
||||
_ready = true;
|
||||
setState(() {});
|
||||
};
|
||||
|
||||
peerConnection.onIceCandidate = (candidate) {
|
||||
peerConnection.onIceCandidate = (candidate) async {
|
||||
if (candidate.candidate == null) {
|
||||
logger.i("Out of candidates");
|
||||
await widget.client.createIceMessage(
|
||||
CreateIceMessageRequest(
|
||||
sessionIdentifier: clientSession.id,
|
||||
iceMessage: IceMessage(
|
||||
noMoreCandidates: NoMoreCandidates(),
|
||||
)),
|
||||
options: callOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the candidate on to the signaling server
|
||||
await widget.client.createIceMessage(
|
||||
pb.CreateIceMessageRequest(
|
||||
sessionIdentifier: clientSession.id,
|
||||
iceMessage: pb.IceMessage(
|
||||
candidate: pb.IceCandidate(
|
||||
candidate: candidate.candidate,
|
||||
sdpMid: candidate.sdpMid,
|
||||
sdpLineIndex: candidate.sdpMLineIndex,
|
||||
),
|
||||
),
|
||||
),
|
||||
options: callOptions);
|
||||
};
|
||||
|
||||
peerConnection.onIceConnectionState = (state) {};
|
||||
peerConnection.onIceConnectionState = (state) {
|
||||
statusLine = "Ice state now $state";
|
||||
setState(() {});
|
||||
logger.i("Ice state now $state");
|
||||
|
||||
switch (state) {
|
||||
case RTCIceConnectionState.RTCIceConnectionStateClosed:
|
||||
case RTCIceConnectionState.RTCIceConnectionStateDisconnected:
|
||||
case RTCIceConnectionState.RTCIceConnectionStateFailed:
|
||||
cancelCreate.complete(CallCancelled());
|
||||
_connect();
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
peerConnection.onIceGatheringState = (state) async {
|
||||
logger.i("ICE gathering state $state");
|
||||
if (state == RTCIceGatheringState.RTCIceGatheringStateComplete) {
|
||||
await widget.client.createIceMessage(
|
||||
CreateIceMessageRequest(
|
||||
sessionIdentifier: clientSession.id,
|
||||
iceMessage: IceMessage(
|
||||
noMoreCandidates: NoMoreCandidates(),
|
||||
)),
|
||||
options: callOptions);
|
||||
}
|
||||
};
|
||||
|
||||
peerConnection.onRemoveStream = (stream) {};
|
||||
|
||||
peerConnection.onDataChannel = (channel) {};
|
||||
|
||||
// Get list of candidates from signaling server
|
||||
for (final remoteCandidate in []) {
|
||||
peerConnection.addCandidate(remoteCandidate);
|
||||
}
|
||||
// This will find the intersection of my candidates and the remote,
|
||||
// then propose one to use
|
||||
var offer = peerConnection.createOffer();
|
||||
var offer = await peerConnection.createOffer();
|
||||
await peerConnection.setLocalDescription(offer);
|
||||
// Send offer through signaling server
|
||||
logger.i("Offer is $offer");
|
||||
await widget.client.createIceMessage(
|
||||
pb.CreateIceMessageRequest(
|
||||
sessionIdentifier: clientSession.id,
|
||||
iceMessage: pb.IceMessage(
|
||||
session: pb.IceSessionDescription(
|
||||
sdp: offer.sdp,
|
||||
sdpType: Int64(1), // offer
|
||||
),
|
||||
),
|
||||
),
|
||||
options: callOptions);
|
||||
|
||||
var session = Session(peerConnection);
|
||||
return session;
|
||||
// Get candidates from remote
|
||||
while (true) {
|
||||
var someResponse = await Future.any([
|
||||
widget.client.popIceMessage(
|
||||
pb.PopIceMessageRequest(sessionIdentifier: clientSession.id),
|
||||
options: callOptions),
|
||||
cancelCreate.future,
|
||||
]);
|
||||
if (someResponse is CallCancelled) {
|
||||
break;
|
||||
}
|
||||
var resp = someResponse as pb.IceMessage;
|
||||
if (resp.hasCandidate()) {
|
||||
await peerConnection.addCandidate(RTCIceCandidate(
|
||||
resp.candidate.candidate,
|
||||
resp.candidate.sdpMid,
|
||||
resp.candidate.sdpLineIndex));
|
||||
} else if (resp.hasNoMoreCandidates()) {
|
||||
logger.i("No more candidates from remote");
|
||||
} else if (resp.hasSession()) {
|
||||
var session = resp.session;
|
||||
await peerConnection
|
||||
.setRemoteDescription(RTCSessionDescription(session.sdp, "answer"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RTCVideoView(_remoteRenderer);
|
||||
return Column(children: [
|
||||
Text(widget.cameraID.id),
|
||||
Text(statusLine),
|
||||
SizedBox(
|
||||
height: 480,
|
||||
child: _ready
|
||||
? RTCVideoView(_remoteRenderer)
|
||||
: const Text("Loading...")),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class Session {
|
||||
RTCPeerConnection peerConnection;
|
||||
|
||||
Session(this.peerConnection);
|
||||
List<RTCIceCandidate> remoteCandidates = [];
|
||||
}
|
||||
class CallCancelled {}
|
||||
|
||||
Reference in New Issue
Block a user