add: basic webrtc is working

This commit is contained in:
Charles Hathaway
2023-09-28 20:35:50 -07:00
parent 7fbd4fff69
commit 19bb6c49b4
22 changed files with 2615 additions and 330 deletions
+145 -25
View File
@@ -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 {}
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: signaler_service.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+159
View File
@@ -0,0 +1,159 @@
//
// Generated code. Do not modify.
// source: signaler_service.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:async' as $async;
import 'dart:core' as $core;
import 'package:grpc/service_api.dart' as $grpc;
import 'package:protobuf/protobuf.dart' as $pb;
import 'signaler_service.pb.dart' as $0;
export 'signaler_service.pb.dart';
@$pb.GrpcServiceName('signaler.SignalerService')
class SignalerServiceClient extends $grpc.Client {
static final _$createAuthToken = $grpc.ClientMethod<$0.CreateAuthTokenRequest, $0.AuthToken>(
'/signaler.SignalerService/CreateAuthToken',
($0.CreateAuthTokenRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.AuthToken.fromBuffer(value));
static final _$listCameras = $grpc.ClientMethod<$0.ListCamerasRequest, $0.ListCamerasResponse>(
'/signaler.SignalerService/ListCameras',
($0.ListCamerasRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.ListCamerasResponse.fromBuffer(value));
static final _$createSession = $grpc.ClientMethod<$0.CreateSessionRequest, $0.Session>(
'/signaler.SignalerService/CreateSession',
($0.CreateSessionRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Session.fromBuffer(value));
static final _$popSession = $grpc.ClientMethod<$0.PopSessionRequest, $0.Session>(
'/signaler.SignalerService/PopSession',
($0.PopSessionRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Session.fromBuffer(value));
static final _$createIceMessage = $grpc.ClientMethod<$0.CreateIceMessageRequest, $0.IceMessage>(
'/signaler.SignalerService/CreateIceMessage',
($0.CreateIceMessageRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.IceMessage.fromBuffer(value));
static final _$popIceMessage = $grpc.ClientMethod<$0.PopIceMessageRequest, $0.IceMessage>(
'/signaler.SignalerService/PopIceMessage',
($0.PopIceMessageRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.IceMessage.fromBuffer(value));
SignalerServiceClient($grpc.ClientChannel channel,
{$grpc.CallOptions? options,
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
: super(channel, options: options,
interceptors: interceptors);
$grpc.ResponseFuture<$0.AuthToken> createAuthToken($0.CreateAuthTokenRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$createAuthToken, request, options: options);
}
$grpc.ResponseFuture<$0.ListCamerasResponse> listCameras($0.ListCamerasRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$listCameras, request, options: options);
}
$grpc.ResponseFuture<$0.Session> createSession($0.CreateSessionRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$createSession, request, options: options);
}
$grpc.ResponseFuture<$0.Session> popSession($0.PopSessionRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$popSession, request, options: options);
}
$grpc.ResponseFuture<$0.IceMessage> createIceMessage($0.CreateIceMessageRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$createIceMessage, request, options: options);
}
$grpc.ResponseFuture<$0.IceMessage> popIceMessage($0.PopIceMessageRequest request, {$grpc.CallOptions? options}) {
return $createUnaryCall(_$popIceMessage, request, options: options);
}
}
@$pb.GrpcServiceName('signaler.SignalerService')
abstract class SignalerServiceBase extends $grpc.Service {
$core.String get $name => 'signaler.SignalerService';
SignalerServiceBase() {
$addMethod($grpc.ServiceMethod<$0.CreateAuthTokenRequest, $0.AuthToken>(
'CreateAuthToken',
createAuthToken_Pre,
false,
false,
($core.List<$core.int> value) => $0.CreateAuthTokenRequest.fromBuffer(value),
($0.AuthToken value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.ListCamerasRequest, $0.ListCamerasResponse>(
'ListCameras',
listCameras_Pre,
false,
false,
($core.List<$core.int> value) => $0.ListCamerasRequest.fromBuffer(value),
($0.ListCamerasResponse value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.CreateSessionRequest, $0.Session>(
'CreateSession',
createSession_Pre,
false,
false,
($core.List<$core.int> value) => $0.CreateSessionRequest.fromBuffer(value),
($0.Session value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.PopSessionRequest, $0.Session>(
'PopSession',
popSession_Pre,
false,
false,
($core.List<$core.int> value) => $0.PopSessionRequest.fromBuffer(value),
($0.Session value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.CreateIceMessageRequest, $0.IceMessage>(
'CreateIceMessage',
createIceMessage_Pre,
false,
false,
($core.List<$core.int> value) => $0.CreateIceMessageRequest.fromBuffer(value),
($0.IceMessage value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.PopIceMessageRequest, $0.IceMessage>(
'PopIceMessage',
popIceMessage_Pre,
false,
false,
($core.List<$core.int> value) => $0.PopIceMessageRequest.fromBuffer(value),
($0.IceMessage value) => value.writeToBuffer()));
}
$async.Future<$0.AuthToken> createAuthToken_Pre($grpc.ServiceCall call, $async.Future<$0.CreateAuthTokenRequest> request) async {
return createAuthToken(call, await request);
}
$async.Future<$0.ListCamerasResponse> listCameras_Pre($grpc.ServiceCall call, $async.Future<$0.ListCamerasRequest> request) async {
return listCameras(call, await request);
}
$async.Future<$0.Session> createSession_Pre($grpc.ServiceCall call, $async.Future<$0.CreateSessionRequest> request) async {
return createSession(call, await request);
}
$async.Future<$0.Session> popSession_Pre($grpc.ServiceCall call, $async.Future<$0.PopSessionRequest> request) async {
return popSession(call, await request);
}
$async.Future<$0.IceMessage> createIceMessage_Pre($grpc.ServiceCall call, $async.Future<$0.CreateIceMessageRequest> request) async {
return createIceMessage(call, await request);
}
$async.Future<$0.IceMessage> popIceMessage_Pre($grpc.ServiceCall call, $async.Future<$0.PopIceMessageRequest> request) async {
return popIceMessage(call, await request);
}
$async.Future<$0.AuthToken> createAuthToken($grpc.ServiceCall call, $0.CreateAuthTokenRequest request);
$async.Future<$0.ListCamerasResponse> listCameras($grpc.ServiceCall call, $0.ListCamerasRequest request);
$async.Future<$0.Session> createSession($grpc.ServiceCall call, $0.CreateSessionRequest request);
$async.Future<$0.Session> popSession($grpc.ServiceCall call, $0.PopSessionRequest request);
$async.Future<$0.IceMessage> createIceMessage($grpc.ServiceCall call, $0.CreateIceMessageRequest request);
$async.Future<$0.IceMessage> popIceMessage($grpc.ServiceCall call, $0.PopIceMessageRequest request);
}
+286
View File
@@ -0,0 +1,286 @@
//
// Generated code. Do not modify.
// source: signaler_service.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use createAuthTokenRequestDescriptor instead')
const CreateAuthTokenRequest$json = {
'1': 'CreateAuthTokenRequest',
'2': [
{'1': 'home', '3': 1, '4': 1, '5': 9, '10': 'home'},
{'1': 'camera', '3': 2, '4': 1, '5': 11, '6': '.signaler.CreateAuthTokenRequest.Camera', '9': 0, '10': 'camera'},
{'1': 'client', '3': 3, '4': 1, '5': 11, '6': '.signaler.CreateAuthTokenRequest.Client', '9': 0, '10': 'client'},
],
'3': [CreateAuthTokenRequest_Camera$json, CreateAuthTokenRequest_Client$json],
'8': [
{'1': 'type'},
],
};
@$core.Deprecated('Use createAuthTokenRequestDescriptor instead')
const CreateAuthTokenRequest_Camera$json = {
'1': 'Camera',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
],
};
@$core.Deprecated('Use createAuthTokenRequestDescriptor instead')
const CreateAuthTokenRequest_Client$json = {
'1': 'Client',
};
/// Descriptor for `CreateAuthTokenRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createAuthTokenRequestDescriptor = $convert.base64Decode(
'ChZDcmVhdGVBdXRoVG9rZW5SZXF1ZXN0EhIKBGhvbWUYASABKAlSBGhvbWUSQQoGY2FtZXJhGA'
'IgASgLMicuc2lnbmFsZXIuQ3JlYXRlQXV0aFRva2VuUmVxdWVzdC5DYW1lcmFIAFIGY2FtZXJh'
'EkEKBmNsaWVudBgDIAEoCzInLnNpZ25hbGVyLkNyZWF0ZUF1dGhUb2tlblJlcXVlc3QuQ2xpZW'
'50SABSBmNsaWVudBoYCgZDYW1lcmESDgoCaWQYASABKAlSAmlkGggKBkNsaWVudEIGCgR0eXBl');
@$core.Deprecated('Use listCamerasRequestDescriptor instead')
const ListCamerasRequest$json = {
'1': 'ListCamerasRequest',
};
/// Descriptor for `ListCamerasRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listCamerasRequestDescriptor = $convert.base64Decode(
'ChJMaXN0Q2FtZXJhc1JlcXVlc3Q=');
@$core.Deprecated('Use listCamerasResponseDescriptor instead')
const ListCamerasResponse$json = {
'1': 'ListCamerasResponse',
'2': [
{'1': 'cameras', '3': 1, '4': 3, '5': 11, '6': '.signaler.Camera', '10': 'cameras'},
],
};
/// Descriptor for `ListCamerasResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listCamerasResponseDescriptor = $convert.base64Decode(
'ChNMaXN0Q2FtZXJhc1Jlc3BvbnNlEioKB2NhbWVyYXMYASADKAsyEC5zaWduYWxlci5DYW1lcm'
'FSB2NhbWVyYXM=');
@$core.Deprecated('Use createSessionRequestDescriptor instead')
const CreateSessionRequest$json = {
'1': 'CreateSessionRequest',
'2': [
{'1': 'session', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session', '10': 'session'},
{'1': 'wait_for_update', '3': 2, '4': 1, '5': 8, '10': 'waitForUpdate'},
],
};
/// Descriptor for `CreateSessionRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createSessionRequestDescriptor = $convert.base64Decode(
'ChRDcmVhdGVTZXNzaW9uUmVxdWVzdBIrCgdzZXNzaW9uGAEgASgLMhEuc2lnbmFsZXIuU2Vzc2'
'lvblIHc2Vzc2lvbhImCg93YWl0X2Zvcl91cGRhdGUYAiABKAhSDXdhaXRGb3JVcGRhdGU=');
@$core.Deprecated('Use popSessionRequestDescriptor instead')
const PopSessionRequest$json = {
'1': 'PopSessionRequest',
'2': [
{'1': 'session', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session', '10': 'session'},
],
};
/// Descriptor for `PopSessionRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List popSessionRequestDescriptor = $convert.base64Decode(
'ChFQb3BTZXNzaW9uUmVxdWVzdBIrCgdzZXNzaW9uGAEgASgLMhEuc2lnbmFsZXIuU2Vzc2lvbl'
'IHc2Vzc2lvbg==');
@$core.Deprecated('Use updateSessionRequestDescriptor instead')
const UpdateSessionRequest$json = {
'1': 'UpdateSessionRequest',
'2': [
{'1': 'session', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session', '10': 'session'},
{'1': 'wait_for_update', '3': 2, '4': 1, '5': 8, '10': 'waitForUpdate'},
],
};
/// Descriptor for `UpdateSessionRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List updateSessionRequestDescriptor = $convert.base64Decode(
'ChRVcGRhdGVTZXNzaW9uUmVxdWVzdBIrCgdzZXNzaW9uGAEgASgLMhEuc2lnbmFsZXIuU2Vzc2'
'lvblIHc2Vzc2lvbhImCg93YWl0X2Zvcl91cGRhdGUYAiABKAhSDXdhaXRGb3JVcGRhdGU=');
@$core.Deprecated('Use listSessionsRequestDescriptor instead')
const ListSessionsRequest$json = {
'1': 'ListSessionsRequest',
};
/// Descriptor for `ListSessionsRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listSessionsRequestDescriptor = $convert.base64Decode(
'ChNMaXN0U2Vzc2lvbnNSZXF1ZXN0');
@$core.Deprecated('Use listSessionsResponseDescriptor instead')
const ListSessionsResponse$json = {
'1': 'ListSessionsResponse',
'2': [
{'1': 'sessions', '3': 1, '4': 3, '5': 11, '6': '.signaler.Session', '10': 'sessions'},
],
};
/// Descriptor for `ListSessionsResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listSessionsResponseDescriptor = $convert.base64Decode(
'ChRMaXN0U2Vzc2lvbnNSZXNwb25zZRItCghzZXNzaW9ucxgBIAMoCzIRLnNpZ25hbGVyLlNlc3'
'Npb25SCHNlc3Npb25z');
@$core.Deprecated('Use createIceMessageRequestDescriptor instead')
const CreateIceMessageRequest$json = {
'1': 'CreateIceMessageRequest',
'2': [
{'1': 'session_identifier', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session.Identifier', '10': 'sessionIdentifier'},
{'1': 'ice_message', '3': 2, '4': 1, '5': 11, '6': '.signaler.IceMessage', '10': 'iceMessage'},
],
};
/// Descriptor for `CreateIceMessageRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createIceMessageRequestDescriptor = $convert.base64Decode(
'ChdDcmVhdGVJY2VNZXNzYWdlUmVxdWVzdBJLChJzZXNzaW9uX2lkZW50aWZpZXIYASABKAsyHC'
'5zaWduYWxlci5TZXNzaW9uLklkZW50aWZpZXJSEXNlc3Npb25JZGVudGlmaWVyEjUKC2ljZV9t'
'ZXNzYWdlGAIgASgLMhQuc2lnbmFsZXIuSWNlTWVzc2FnZVIKaWNlTWVzc2FnZQ==');
@$core.Deprecated('Use popIceMessageRequestDescriptor instead')
const PopIceMessageRequest$json = {
'1': 'PopIceMessageRequest',
'2': [
{'1': 'session_identifier', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session.Identifier', '10': 'sessionIdentifier'},
],
};
/// Descriptor for `PopIceMessageRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List popIceMessageRequestDescriptor = $convert.base64Decode(
'ChRQb3BJY2VNZXNzYWdlUmVxdWVzdBJLChJzZXNzaW9uX2lkZW50aWZpZXIYASABKAsyHC5zaW'
'duYWxlci5TZXNzaW9uLklkZW50aWZpZXJSEXNlc3Npb25JZGVudGlmaWVy');
@$core.Deprecated('Use cameraDescriptor instead')
const Camera$json = {
'1': 'Camera',
'2': [
{'1': 'identifier', '3': 1, '4': 1, '5': 11, '6': '.signaler.Camera.Identifier', '10': 'identifier'},
],
'3': [Camera_Identifier$json],
};
@$core.Deprecated('Use cameraDescriptor instead')
const Camera_Identifier$json = {
'1': 'Identifier',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
],
};
/// Descriptor for `Camera`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List cameraDescriptor = $convert.base64Decode(
'CgZDYW1lcmESOwoKaWRlbnRpZmllchgBIAEoCzIbLnNpZ25hbGVyLkNhbWVyYS5JZGVudGlmaW'
'VyUgppZGVudGlmaWVyGhwKCklkZW50aWZpZXISDgoCaWQYASABKAlSAmlk');
@$core.Deprecated('Use iceMessageDescriptor instead')
const IceMessage$json = {
'1': 'IceMessage',
'2': [
{'1': 'candidate', '3': 1, '4': 1, '5': 11, '6': '.signaler.IceCandidate', '9': 0, '10': 'candidate'},
{'1': 'session', '3': 2, '4': 1, '5': 11, '6': '.signaler.IceSessionDescription', '9': 0, '10': 'session'},
{'1': 'no_more_candidates', '3': 3, '4': 1, '5': 11, '6': '.signaler.NoMoreCandidates', '9': 0, '10': 'noMoreCandidates'},
],
'8': [
{'1': 'type'},
],
};
/// Descriptor for `IceMessage`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List iceMessageDescriptor = $convert.base64Decode(
'CgpJY2VNZXNzYWdlEjYKCWNhbmRpZGF0ZRgBIAEoCzIWLnNpZ25hbGVyLkljZUNhbmRpZGF0ZU'
'gAUgljYW5kaWRhdGUSOwoHc2Vzc2lvbhgCIAEoCzIfLnNpZ25hbGVyLkljZVNlc3Npb25EZXNj'
'cmlwdGlvbkgAUgdzZXNzaW9uEkoKEm5vX21vcmVfY2FuZGlkYXRlcxgDIAEoCzIaLnNpZ25hbG'
'VyLk5vTW9yZUNhbmRpZGF0ZXNIAFIQbm9Nb3JlQ2FuZGlkYXRlc0IGCgR0eXBl');
@$core.Deprecated('Use noMoreCandidatesDescriptor instead')
const NoMoreCandidates$json = {
'1': 'NoMoreCandidates',
};
/// Descriptor for `NoMoreCandidates`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List noMoreCandidatesDescriptor = $convert.base64Decode(
'ChBOb01vcmVDYW5kaWRhdGVz');
@$core.Deprecated('Use iceCandidateDescriptor instead')
const IceCandidate$json = {
'1': 'IceCandidate',
'2': [
{'1': 'candidate', '3': 1, '4': 1, '5': 9, '10': 'candidate'},
{'1': 'sdp_mid', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'sdpMid', '17': true},
{'1': 'sdp_line_index', '3': 3, '4': 1, '5': 5, '9': 1, '10': 'sdpLineIndex', '17': true},
{'1': 'username_fragment', '3': 4, '4': 1, '5': 9, '9': 2, '10': 'usernameFragment', '17': true},
],
'8': [
{'1': '_sdp_mid'},
{'1': '_sdp_line_index'},
{'1': '_username_fragment'},
],
};
/// Descriptor for `IceCandidate`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List iceCandidateDescriptor = $convert.base64Decode(
'CgxJY2VDYW5kaWRhdGUSHAoJY2FuZGlkYXRlGAEgASgJUgljYW5kaWRhdGUSHAoHc2RwX21pZB'
'gCIAEoCUgAUgZzZHBNaWSIAQESKQoOc2RwX2xpbmVfaW5kZXgYAyABKAVIAVIMc2RwTGluZUlu'
'ZGV4iAEBEjAKEXVzZXJuYW1lX2ZyYWdtZW50GAQgASgJSAJSEHVzZXJuYW1lRnJhZ21lbnSIAQ'
'FCCgoIX3NkcF9taWRCEQoPX3NkcF9saW5lX2luZGV4QhQKEl91c2VybmFtZV9mcmFnbWVudA==');
@$core.Deprecated('Use iceSessionDescriptionDescriptor instead')
const IceSessionDescription$json = {
'1': 'IceSessionDescription',
'2': [
{'1': 'sdp_type', '3': 1, '4': 1, '5': 3, '10': 'sdpType'},
{'1': 'sdp', '3': 2, '4': 1, '5': 9, '10': 'sdp'},
],
};
/// Descriptor for `IceSessionDescription`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List iceSessionDescriptionDescriptor = $convert.base64Decode(
'ChVJY2VTZXNzaW9uRGVzY3JpcHRpb24SGQoIc2RwX3R5cGUYASABKANSB3NkcFR5cGUSEAoDc2'
'RwGAIgASgJUgNzZHA=');
@$core.Deprecated('Use sessionDescriptor instead')
const Session$json = {
'1': 'Session',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 11, '6': '.signaler.Session.Identifier', '10': 'id'},
{'1': 'camera', '3': 2, '4': 1, '5': 11, '6': '.signaler.Camera.Identifier', '10': 'camera'},
],
'3': [Session_Identifier$json],
};
@$core.Deprecated('Use sessionDescriptor instead')
const Session_Identifier$json = {
'1': 'Identifier',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
],
};
/// Descriptor for `Session`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sessionDescriptor = $convert.base64Decode(
'CgdTZXNzaW9uEiwKAmlkGAEgASgLMhwuc2lnbmFsZXIuU2Vzc2lvbi5JZGVudGlmaWVyUgJpZB'
'IzCgZjYW1lcmEYAiABKAsyGy5zaWduYWxlci5DYW1lcmEuSWRlbnRpZmllclIGY2FtZXJhGhwK'
'CklkZW50aWZpZXISDgoCaWQYASABKAlSAmlk');
@$core.Deprecated('Use authTokenDescriptor instead')
const AuthToken$json = {
'1': 'AuthToken',
'2': [
{'1': 'token', '3': 1, '4': 1, '5': 9, '10': 'token'},
],
};
/// Descriptor for `AuthToken`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List authTokenDescriptor = $convert.base64Decode(
'CglBdXRoVG9rZW4SFAoFdG9rZW4YASABKAlSBXRva2Vu');
+82
View File
@@ -0,0 +1,82 @@
//
// Generated code. Do not modify.
// source: token/token.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class AuthToken extends $pb.GeneratedMessage {
factory AuthToken({
$core.String? uid,
$core.String? home,
}) {
final $result = create();
if (uid != null) {
$result.uid = uid;
}
if (home != null) {
$result.home = home;
}
return $result;
}
AuthToken._() : super();
factory AuthToken.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory AuthToken.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AuthToken', package: const $pb.PackageName(_omitMessageNames ? '' : 'token'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'uid')
..aOS(2, _omitFieldNames ? '' : 'home')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
AuthToken clone() => AuthToken()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
AuthToken copyWith(void Function(AuthToken) updates) => super.copyWith((message) => updates(message as AuthToken)) as AuthToken;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static AuthToken create() => AuthToken._();
AuthToken createEmptyInstance() => create();
static $pb.PbList<AuthToken> createRepeated() => $pb.PbList<AuthToken>();
@$core.pragma('dart2js:noInline')
static AuthToken getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AuthToken>(create);
static AuthToken? _defaultInstance;
@$pb.TagNumber(1)
$core.String get uid => $_getSZ(0);
@$pb.TagNumber(1)
set uid($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasUid() => $_has(0);
@$pb.TagNumber(1)
void clearUid() => clearField(1);
@$pb.TagNumber(2)
$core.String get home => $_getSZ(1);
@$pb.TagNumber(2)
set home($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasHome() => $_has(1);
@$pb.TagNumber(2)
void clearHome() => clearField(2);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
+11
View File
@@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: token/token.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+28
View File
@@ -0,0 +1,28 @@
//
// Generated code. Do not modify.
// source: token/token.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use authTokenDescriptor instead')
const AuthToken$json = {
'1': 'AuthToken',
'2': [
{'1': 'uid', '3': 1, '4': 1, '5': 9, '10': 'uid'},
{'1': 'home', '3': 2, '4': 1, '5': 9, '10': 'home'},
],
};
/// Descriptor for `AuthToken`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List authTokenDescriptor = $convert.base64Decode(
'CglBdXRoVG9rZW4SEAoDdWlkGAEgASgJUgN1aWQSEgoEaG9tZRgCIAEoCVIEaG9tZQ==');
+68 -23
View File
@@ -1,12 +1,30 @@
import 'package:flutter/material.dart';
//import 'package:grpc/grpc_web.dart';
import 'package:grpc/grpc.dart';
import 'package:logger/logger.dart';
import 'package:ui/call.dart';
import 'package:ui/gen/signaler_service.pbgrpc.dart';
import 'package:ui/session_service.dart';
void main() {
runApp(const MyApp());
void main() async {
Logger logger = Logger();
logger.i("Establishing connection...");
final channel = ClientChannel(
'192.168.0.65',
port: 8080,
options: const ChannelOptions(credentials: ChannelCredentials.insecure()),
channelShutdownHandler: () {
logger.e("Channel shutdown unexpectedly");
},
);
final stub = SignalerServiceClient(channel);
runApp(MyApp(stub, SessionService(stub)));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
final SignalerServiceClient client;
final SessionService sessionService;
const MyApp(this.client, this.sessionService, {super.key});
// This widget is the root of your application.
@override
@@ -32,13 +50,22 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
home: MyHomePage(
client,
sessionService,
title: 'Home Sensors',
home: "home1234",
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final SignalerServiceClient client;
final SessionService sessionService;
final String home;
const MyHomePage(this.client, this.sessionService,
{super.key, required this.title, required this.home});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
@@ -56,17 +83,38 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String topMessage = "Creating session...";
List<Call> camerasToRender = [];
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
@override
void initState() {
super.initState();
_getSession();
_listCameras();
}
_getSession() async {
var token = await widget.sessionService.getAuthToken(widget.home);
topMessage = "Created session $token";
setState(() {});
}
_listCameras() async {
var callOptions = CallOptions(metadata: {
'Authorization': await widget.sessionService.getAuthToken(widget.home)
});
var cameras = await widget.client
.listCameras(ListCamerasRequest(), options: callOptions);
for (var camera in cameras.cameras) {
camerasToRender.add(Call(
widget.client,
widget.sessionService,
cameraID: camera.identifier,
home: widget.home,
));
}
setState(() {});
}
@override
@@ -87,18 +135,15 @@ class _MyHomePageState extends State<MyHomePage> {
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: const Center(
body: Column(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Call(
host: '',
),
children: <Widget>[
Text(topMessage),
] +
camerasToRender,
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
+15
View File
@@ -0,0 +1,15 @@
import 'package:grpc/grpc_or_grpcweb.dart';
import 'package:ui/gen/signaler_service.pbgrpc.dart';
class SessionService {
final SignalerServiceClient _stub;
final Map<String, ResponseFuture<AuthToken>> _authTokens = {};
SessionService(this._stub);
Future<String> getAuthToken(String cameraID) async {
var val = await _authTokens.putIfAbsent(cameraID,
() => _stub.createAuthToken(CreateAuthTokenRequest(home: cameraID)));
return "Bearer ${val.token}";
}
}