Simulcast, Screen sharing & Various improvements (#4)

* Respect `RTCIceTransportPolicy` enum and organize

* Simplify syntax where possible etc.

* Combine `VideoPreset` and `VideoPresets`

* Default values for `ConnectOptions`

* Build URI instead of String manipulation

* Slight modifications to Exception

* `LiveKitTheme` for example

* `VideoEncoding` class

* Organize imports

* First simulcast implementation

* Remove unnecessary try-catches

* Update Android settings

* Remember uri and token

* example improvements

* `fit` parameter for VideoTrackRenderer

* Simulcast option for example

* Pass `defaultPublishOptions`

* Show only `VideoQuality`

* Pass tests

* Better buildUri logic

* Named parameter to positional

* Explicit imports

* `VideoParameter` instead of `VideoPreset`

* Use `mediaTrack.getSettings` when possible

* Safer dispose logic

* Safer `PCTransport`

Update transport.dart

* Synchronized events for `SignalClient`

* Use logger instead of print

* Make example compile for iOS

* First screen share implementation

* Make example work with screen share

* Example improvement

* Code optimization

* Don't depend on web_socket_channel

* Fix: Unpublish track bug

* Show participant mute state & identity

* Update protos

* Remote mute/unmute

* iOS Background mode

* Separate `createCameraTrack` and `createScreenTrack`

* Clean up

* PB fix

* format

* Fix analyzer warning

* Android clean up

* Update README.md

* Clean up
This commit is contained in:
Hiroshi Horie
2021-09-11 03:14:14 +09:00
committed by GitHub
parent 8e66e324f6
commit 50f56c5e1e
69 changed files with 2653 additions and 2351 deletions
-5
View File
@@ -1,5 +0,0 @@
import 'package:web_socket_channel/web_socket_channel.dart';
Future<WebSocketChannel> connectToWebSocket(Uri uri) {
throw UnsupportedError('no implementations found');
}
-19
View File
@@ -1,19 +0,0 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'package:web_socket_channel/html.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
Future<WebSocketChannel> connectToWebSocket(Uri uri) {
final ws = WebSocket(uri.toString());
ws.binaryType = 'arraybuffer';
final completer = Completer<WebSocketChannel>();
ws.onOpen.first.then((_) {
completer.complete(HtmlWebSocketChannel(ws));
});
ws.onError.first.then((e) {
completer.completeError('could not connect');
});
return completer.future;
}
-14
View File
@@ -1,14 +0,0 @@
import 'dart:io';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
Future<WebSocketChannel> connectToWebSocket(Uri uri) async {
try {
// ignore: close_sinks
final ws = await WebSocket.connect(uri.toString());
return IOWebSocketChannel(ws);
} catch (e) {
return Future.error(e);
}
}
+19 -17
View File
@@ -1,30 +1,32 @@
class LiveKitError extends Error {
String message;
LiveKitError(this.message);
//
// `Exception` implies runtime errors while, an `Error` object
// represents a program failure that the programmer
// should have avoided.
//
class LiveKitException implements Exception {
final String message;
const LiveKitException._(this.message);
@override
String toString() {
return message;
}
String toString() => 'LiveKitException $runtimeType $message';
}
class ConnectError extends LiveKitError {
ConnectError([String msg = 'Failed to connect to server']) : super(msg);
class ConnectError extends LiveKitException {
ConnectError([String msg = 'Failed to connect to server']) : super._(msg);
}
class UnexpectedConnectionState extends LiveKitError {
UnexpectedConnectionState([String msg = 'Unexpected connection state']) : super(msg);
class UnexpectedConnectionState extends LiveKitException {
UnexpectedConnectionState([String msg = 'Unexpected connection state']) : super._(msg);
}
class TrackCreateError extends LiveKitError {
TrackCreateError([String msg = 'Failed to create track']) : super(msg);
class TrackCreateError extends LiveKitException {
TrackCreateError([String msg = 'Failed to create track']) : super._(msg);
}
class TrackPublishError extends LiveKitError {
TrackPublishError([String msg = 'Failed to publish track']) : super(msg);
class TrackPublishError extends LiveKitException {
TrackPublishError([String msg = 'Failed to publish track']) : super._(msg);
}
class DataPublishError extends LiveKitError {
DataPublishError([String msg = 'Failed to publish data']) : super(msg);
class DataPublishError extends LiveKitException {
DataPublishError([String msg = 'Failed to publish data']) : super._(msg);
}
+39 -36
View File
@@ -1,39 +1,3 @@
class RTCConfiguration {
int? iceCandidatePoolSize;
List<RTCIceServer>? iceServers;
String? iceTransportPolicy;
Map<String, dynamic> toMap() {
final iceServersMap = <Map<String, dynamic>>[];
for (final element in (iceServers ?? <RTCIceServer>[])) {
iceServersMap.add(element.toMap());
}
return <String, dynamic>{
// only supports unified plan
'sdpSemantics': 'unified-plan',
if (iceCandidatePoolSize != null) 'iceCandidatePoolSize': iceCandidatePoolSize,
'iceServers': iceServersMap,
if (iceTransportPolicy != null) 'iceTransportPolicy': iceTransportPolicy,
};
}
}
class RTCIceServer {
List<String> urls;
String? username;
String? credential;
RTCIceServer({required this.urls, this.username, this.credential});
Map<String, dynamic> toMap() {
return <String, dynamic>{
'urls': urls,
if (username != null) 'username': username,
if (credential != null) 'credential': credential,
};
}
}
enum RTCIceTransportPolicy {
all,
relay,
@@ -45,3 +9,42 @@ extension RTCIceTransportPolicyExt on RTCIceTransportPolicy {
RTCIceTransportPolicy.relay: 'relay',
}[this]!;
}
class RTCConfiguration {
int? iceCandidatePoolSize;
List<RTCIceServer>? iceServers;
RTCIceTransportPolicy? iceTransportPolicy;
Map<String, dynamic> toMap() {
final iceServersMap = <Map<String, dynamic>>[
if (iceServers != null)
for (final element in iceServers!) element.toMap()
];
return <String, dynamic>{
// only supports unified plan
'sdpSemantics': 'unified-plan',
if (iceServersMap.isNotEmpty) 'iceServers': iceServersMap,
if (iceCandidatePoolSize != null) 'iceCandidatePoolSize': iceCandidatePoolSize,
if (iceTransportPolicy != null) 'iceTransportPolicy': iceTransportPolicy!.toStringValue(),
};
}
}
class RTCIceServer {
List<String> urls;
String? username;
String? credential;
RTCIceServer({
required this.urls,
this.username,
this.credential,
});
Map<String, dynamic> toMap() => <String, dynamic>{
'urls': urls,
if (username != null) 'username': username,
if (credential != null) 'credential': credential,
};
}
+13 -3
View File
@@ -1,12 +1,22 @@
import 'room.dart';
import 'options.dart';
import 'room.dart';
/// Main entry point to connect to a room.
/// {@category Room}
class LiveKitClient {
static const version = '0.4.0';
/// Connects to a LiveKit room
static Future<Room> connect(String url, String token, [JoinOptions? options]) {
static Future<Room> connect(
String url,
String token, {
ConnectOptions? options,
}) {
final room = Room();
return room.connect(url, token, options);
return room.connect(
url,
token,
options: options,
);
}
}
+26 -3
View File
@@ -1,7 +1,30 @@
import 'track/options.dart';
/// Options when joining a room.
/// {@category Room}
class JoinOptions {
final bool? autoSubscribe;
class ConnectOptions {
/// Auto-subscribe to room tracks upon connect, defaults to true.
final bool autoSubscribe;
final TrackPublishOptions defaultPublishOptions;
const JoinOptions({this.autoSubscribe});
const ConnectOptions({
this.autoSubscribe = true,
this.defaultPublishOptions = const TrackPublishOptions(),
});
}
class TrackPublishOptions {
///
final VideoEncoding? videoEncoding;
///
final bool simulcast;
const TrackPublishOptions({
this.videoEncoding,
this.simulcast = false,
});
@override
String toString() => '${runtimeType}(videoEncoding: ${videoEncoding}, simulcast: ${simulcast})';
}
+108 -66
View File
@@ -1,23 +1,28 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../errors.dart';
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_rtc.pbserver.dart';
import '../logger.dart';
import '../options.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../rtc_engine.dart';
import '../track/local_audio_track.dart';
import '../track/local_track_publication.dart';
import '../track/local_video_track.dart';
import '../track/track.dart';
import '../track/track_publication.dart';
import '../utils.dart';
import 'participant.dart';
/// Represents the current participant in the room.
class LocalParticipant extends Participant {
final RTCEngine _engine;
final TrackPublishOptions? defaultPublishOptions;
LocalParticipant({
required RTCEngine engine,
required ParticipantInfo info,
required lk_models.ParticipantInfo info,
this.defaultPublishOptions,
}) : _engine = engine,
super(info.sid, info.identity) {
updateFromInfo(info);
@@ -29,97 +34,134 @@ class LocalParticipant extends Participant {
/// publish an audio track to the room
Future<TrackPublication> publishAudioTrack(LocalAudioTrack track) async {
if (audioTracks.values.any((element) => element.track?.mediaTrack.id == track.mediaTrack.id)) {
return Future.error(TrackPublishError('track already exists'));
if (audioTracks.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishError('track already exists');
}
try {
final trackInfo =
await _engine.addTrack(cid: track.getCid(), name: track.name, kind: track.kind);
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
);
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaTrack,
init: transceiverInit,
);
// try {
final trackInfo = await _engine.addTrack(
cid: track.getCid(),
name: track.name,
kind: track.kind,
);
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
);
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
init: transceiverInit,
);
return pub;
} catch (e) {
return Future.error(e);
}
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
return pub;
}
/// Publish a video track to the room
Future<TrackPublication> publishVideoTrack(LocalVideoTrack track) async {
if (videoTracks.values.any((element) => element.track?.mediaTrack.id == track.mediaTrack.id)) {
return Future.error(TrackPublishError('track already exists'));
Future<TrackPublication> publishVideoTrack(
LocalVideoTrack track, {
TrackPublishOptions? options,
}) async {
if (videoTracks.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishError('track already exists');
}
try {
final trackInfo =
await _engine.addTrack(cid: track.getCid(), name: track.name, kind: track.kind);
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
);
// TODO: video encodings and simulcasts
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaTrack,
init: transceiverInit,
);
// Use default options from `ConnectOptions` if options is null
options = options ?? defaultPublishOptions;
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
final trackInfo = await _engine.addTrack(
cid: track.getCid(),
name: track.name,
kind: track.kind,
);
return pub;
} catch (e) {
return Future.error(e);
//
// Video encodings and simulcasts
//
// use constraints passed to getUserMedia by default
int? width = track.currentOptions.params.width;
int? height = track.currentOptions.params.height;
if (kIsWeb) {
// getSettings() is only implemented for Web
try {
// try to use getSettings for more accurate resolution
final settings = track.mediaStreamTrack.getSettings();
width = settings['width'] as int?;
height = settings['height'] as int?;
// TODO: Get actual video dimensions to compute more accurately
// mediaTrack.getConsstraints() is not implemented for mobile
} catch (_) {
logger.warning('Failed to call `mediaStreamTrack.getSettings()`');
}
}
logger.fine('Compute encodings with resolution: ${width}x${height}, options: ${options}');
final encodings = Utils.computeVideoEncodings(
width: width,
height: height,
options: options,
);
logger.fine('Using encodings: ${encodings?.map((e) => e.toMap())}');
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
sendEncodings: encodings,
streams: [track.mediaStream],
);
//
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
//
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
init: transceiverInit,
);
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
notifyListeners();
return pub;
}
/// Unpublish a track that's already published
void unpublishTrack(Track track) {
Future<void> unpublishTrack(Track track) async {
final existing = tracks.values.where((element) => element.track == track);
if (existing.isEmpty) {
return;
}
if (existing.isEmpty) return;
final pub = existing.first;
track.stop();
await track.stop();
final sender = track.transceiver?.sender;
if (sender != null) {
engine.publisher?.pc.removeTrack(sender);
await engine.publisher?.pc.removeTrack(sender);
}
tracks.remove(pub.sid);
switch (pub.kind) {
case TrackType.AUDIO:
audioTracks.remove(pub.sid);
break;
case TrackType.VIDEO:
videoTracks.remove(pub.sid);
break;
default:
break;
}
}
/// Publish a new data payload to the room.
/// @param destinationSids When empty, data will be forwarded to each participant in the room.
void publishData(List<int> data, DataPacket_Kind reliability, {List<String>? destinationSids}) {
void publishData(
List<int> data,
lk_models.DataPacket_Kind reliability, {
List<String>? destinationSids,
}) {
RTCDataChannel? channel;
switch (reliability) {
case DataPacket_Kind.RELIABLE:
case lk_models.DataPacket_Kind.RELIABLE:
channel = engine.reliableDC;
break;
case DataPacket_Kind.LOSSY:
case lk_models.DataPacket_Kind.LOSSY:
channel = engine.lossyDC;
break;
}
@@ -127,9 +169,9 @@ class LocalParticipant extends Participant {
return;
}
final packet = DataPacket(
final packet = lk_models.DataPacket(
kind: reliability,
user: UserPacket(
user: lk_models.UserPacket(
payload: data,
participantSid: sid,
destinationSids: destinationSids,
@@ -143,7 +185,7 @@ class LocalParticipant extends Participant {
/// for internal use
/// {@nodoc}
@override
void updateFromInfo(ParticipantInfo info) {
void updateFromInfo(lk_models.ParticipantInfo info) {
super.updateFromInfo(info);
}
}
+16 -30
View File
@@ -1,10 +1,10 @@
import 'package:flutter/foundation.dart';
import 'remote_participant.dart';
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../track/remote_track_publication.dart';
import '../track/track.dart';
import '../track/track_publication.dart';
import 'remote_participant.dart';
/// Callbacks for participant changes
mixin ParticipantDelegate {
@@ -51,9 +51,6 @@ mixin ParticipantDelegate {
/// - added/removed subscribed tracks
/// - metadata changed
class Participant extends ChangeNotifier {
Map<String, TrackPublication> audioTracks = {};
Map<String, TrackPublication> videoTracks = {};
/// map of track sid => published track
Map<String, TrackPublication> tracks = {};
@@ -77,7 +74,7 @@ class Participant extends ChangeNotifier {
/// delegate to receive participant callbacks
ParticipantDelegate? delegate;
ParticipantInfo? _participantInfo;
lk_models.ParticipantInfo? _participantInfo;
bool _isSpeaking = false;
/// when the participant joined the room
@@ -94,10 +91,8 @@ class Participant extends ChangeNotifier {
/// true if participant is publishing an audio track and is muted
bool get isMuted {
if (audioTracks.values.isEmpty) {
return false;
}
return audioTracks.values.first.muted;
if (audioTracks.isEmpty) return false;
return audioTracks.first.muted;
}
bool get hasAudio => audioTracks.isNotEmpty;
@@ -105,15 +100,7 @@ class Participant extends ChangeNotifier {
bool get hasVideo => videoTracks.isNotEmpty;
/// tracks that are subscribed to
List<TrackPublication> get subscribedTracks {
List<TrackPublication> result = [];
for (final track in tracks.values) {
if (track.subscribed) {
result.add(track);
}
}
return result;
}
List<TrackPublication> get subscribedTracks => tracks.values.where((e) => e.subscribed).toList();
/// for internal use
/// {@nodoc}
@@ -148,7 +135,7 @@ class Participant extends ChangeNotifier {
/// for internal use
/// {@nodoc}
void updateFromInfo(ParticipantInfo info) {
void updateFromInfo(lk_models.ParticipantInfo info) {
identity = info.identity;
sid = info.sid;
if (info.metadata.isNotEmpty) {
@@ -168,15 +155,14 @@ class Participant extends ChangeNotifier {
void addTrackPublication(TrackPublication pub) {
pub.track?.sid = pub.sid;
tracks[pub.sid] = pub;
switch (pub.kind) {
case TrackType.AUDIO:
audioTracks[pub.sid] = pub;
break;
case TrackType.VIDEO:
videoTracks[pub.sid] = pub;
break;
default:
// nothing
}
}
}
// Convenience extension
extension LKParticipantExt on Participant {
List<TrackPublication> get videoTracks =>
tracks.values.where((e) => e.kind == lk_models.TrackType.VIDEO).toList();
List<TrackPublication> get audioTracks =>
tracks.values.where((e) => e.kind == lk_models.TrackType.AUDIO).toList();
}
+30 -26
View File
@@ -1,7 +1,9 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/src/track/audio_track.dart';
import '../proto/livekit_models.pb.dart';
import '../logger.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../signal_client.dart';
import '../track/audio_track.dart';
import '../track/remote_track_publication.dart';
import '../track/track.dart';
import '../track/video_track.dart';
@@ -13,17 +15,22 @@ class RemoteParticipant extends Participant {
SignalClient get client => _client;
RemoteParticipant(this._client, String sid, String identity) : super(sid, identity);
RemoteParticipant(
this._client,
String sid,
String identity,
) : super(sid, identity);
RemoteParticipant.fromInfo(this._client, ParticipantInfo info) : super(info.sid, info.identity) {
RemoteParticipant.fromInfo(
this._client,
lk_models.ParticipantInfo info,
) : super(info.sid, info.identity) {
updateFromInfo(info);
}
RemoteTrackPublication? getTrackPublication(String sid) {
final pub = tracks[sid];
if (pub is RemoteTrackPublication) {
return pub;
}
if (pub is RemoteTrackPublication) return pub;
}
/// for internal use
@@ -49,11 +56,11 @@ class RemoteParticipant extends Participant {
}
Track? track;
if (pub.kind == TrackType.AUDIO) {
if (pub.kind == lk_models.TrackType.AUDIO) {
final audioTrack = AudioTrack(pub.name, mediaTrack, stream);
audioTrack.start();
track = audioTrack;
} else if (pub.kind == TrackType.VIDEO) {
} else if (pub.kind == lk_models.TrackType.VIDEO) {
track = VideoTrack(pub.name, mediaTrack, stream);
} else {
final msg = 'unsupported track type ${pub.kind}';
@@ -73,7 +80,7 @@ class RemoteParticipant extends Participant {
/// for internal use
/// {@nodoc}
@override
void updateFromInfo(ParticipantInfo info) {
void updateFromInfo(lk_models.ParticipantInfo info) async {
final hadInfo = hasInfo;
super.updateFromInfo(info);
@@ -105,30 +112,28 @@ class RemoteParticipant extends Participant {
}
// remove tracks
for (final pub in tracks.values) {
if (!validPubs.containsKey(pub.sid)) {
unpublishTrack(sid, true);
}
final removeTrackSids =
tracks.values.where((e) => !validPubs.containsKey(e.sid)).map((e) => e.sid).toList();
for (final sid in removeTrackSids) {
await unpublishTrack(sid, true);
}
}
void unpublishTrack(String sid, [bool sendUnpublish = false]) {
Future<void> unpublishTrack(String sid, [bool notify = false]) async {
logger.finer('Unpublish track sid: $sid, notify: $notify');
final pub = tracks.remove(sid);
if (pub == null || pub is! RemoteTrackPublication) {
return;
}
audioTracks.remove(sid);
videoTracks.remove(sid);
if (pub == null || pub is! RemoteTrackPublication) return;
final track = pub.track;
if (track != null) {
track.stop();
await track.stop();
delegate?.onTrackUnsubscribed(this, track, pub);
roomDelegate?.onTrackUnsubscribed(this, track, pub);
notifyListeners();
}
if (sendUnpublish) {
if (notify) {
delegate?.onTrackUnpublished(this, pub);
roomDelegate?.onTrackUnpublished(this, pub);
}
@@ -141,9 +146,8 @@ class RemoteParticipant extends Participant {
await Future<RemoteTrackPublication?>.delayed(const Duration(milliseconds: 100), () {
return getTrackPublication(sid);
});
if (pub != null) {
return pub;
}
if (pub != null) return pub;
}
}
}
+245 -551
View File
@@ -3,7 +3,7 @@
// source: livekit_models.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
import 'dart:core' as $core;
@@ -572,659 +572,353 @@ class TrackInfo extends $pb.GeneratedMessage {
void clearSimulcast() => clearField(7);
}
enum DataMessage_Value { text, binary, notSet }
enum DataPacket_Value { user, speaker, notSet }
class DataMessage extends $pb.GeneratedMessage {
static const $core.Map<$core.int, DataMessage_Value> _DataMessage_ValueByTag = {
1: DataMessage_Value.text,
2: DataMessage_Value.binary,
0: DataMessage_Value.notSet
class DataPacket extends $pb.GeneratedMessage {
static const $core.Map<$core.int, DataPacket_Value> _DataPacket_ValueByTag = {
2: DataPacket_Value.user,
3: DataPacket_Value.speaker,
0: DataPacket_Value.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DataMessage',
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DataPacket',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..oo(0, [1, 2])
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
..a<$core.List<$core.int>>(
2,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'binary',
$pb.PbFieldType.OY)
..oo(0, [2, 3])
..e<DataPacket_Kind>(
1,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'kind',
$pb.PbFieldType.OE,
defaultOrMaker: DataPacket_Kind.RELIABLE,
valueOf: DataPacket_Kind.valueOf,
enumValues: DataPacket_Kind.values)
..aOM<UserPacket>(
2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'user',
subBuilder: UserPacket.create)
..aOM<ActiveSpeakerUpdate>(
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speaker',
subBuilder: ActiveSpeakerUpdate.create)
..hasRequiredFields = false;
DataMessage._() : super();
factory DataMessage({
$core.String? text,
$core.List<$core.int>? binary,
DataPacket._() : super();
factory DataPacket({
DataPacket_Kind? kind,
UserPacket? user,
ActiveSpeakerUpdate? speaker,
}) {
final _result = create();
if (text != null) {
_result.text = text;
if (kind != null) {
_result.kind = kind;
}
if (binary != null) {
_result.binary = binary;
if (user != null) {
_result.user = user;
}
if (speaker != null) {
_result.speaker = speaker;
}
return _result;
}
factory DataMessage.fromBuffer($core.List<$core.int> i,
factory DataPacket.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory DataMessage.fromJson($core.String i,
factory DataPacket.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DataMessage clone() => DataMessage()..mergeFromMessage(this);
DataPacket clone() => DataPacket()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DataMessage copyWith(void Function(DataMessage) updates) =>
super.copyWith((message) => updates(message as DataMessage))
as DataMessage; // ignore: deprecated_member_use
DataPacket copyWith(void Function(DataPacket) updates) =>
super.copyWith((message) => updates(message as DataPacket))
as DataPacket; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DataMessage create() => DataMessage._();
DataMessage createEmptyInstance() => create();
static $pb.PbList<DataMessage> createRepeated() => $pb.PbList<DataMessage>();
static DataPacket create() => DataPacket._();
DataPacket createEmptyInstance() => create();
static $pb.PbList<DataPacket> createRepeated() => $pb.PbList<DataPacket>();
@$core.pragma('dart2js:noInline')
static DataMessage getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DataMessage>(create);
static DataMessage? _defaultInstance;
static DataPacket getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DataPacket>(create);
static DataPacket? _defaultInstance;
DataMessage_Value whichValue() => _DataMessage_ValueByTag[$_whichOneof(0)]!;
DataPacket_Value whichValue() => _DataPacket_ValueByTag[$_whichOneof(0)]!;
void clearValue() => clearField($_whichOneof(0));
@$pb.TagNumber(1)
$core.String get text => $_getSZ(0);
DataPacket_Kind get kind => $_getN(0);
@$pb.TagNumber(1)
set text($core.String v) {
$_setString(0, v);
set kind(DataPacket_Kind v) {
setField(1, v);
}
@$pb.TagNumber(1)
$core.bool hasText() => $_has(0);
$core.bool hasKind() => $_has(0);
@$pb.TagNumber(1)
void clearText() => clearField(1);
void clearKind() => clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get binary => $_getN(1);
UserPacket get user => $_getN(1);
@$pb.TagNumber(2)
set binary($core.List<$core.int> v) {
$_setBytes(1, v);
}
@$pb.TagNumber(2)
$core.bool hasBinary() => $_has(1);
@$pb.TagNumber(2)
void clearBinary() => clearField(2);
}
class RecordingInput extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RecordingInput',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url')
..aOM<RecordingTemplate>(
2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'template',
subBuilder: RecordingTemplate.create)
..a<$core.int>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width',
$pb.PbFieldType.O3)
..a<$core.int>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height',
$pb.PbFieldType.O3)
..a<$core.int>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'depth',
$pb.PbFieldType.O3)
..a<$core.int>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'framerate', $pb.PbFieldType.O3)
..hasRequiredFields = false;
RecordingInput._() : super();
factory RecordingInput({
$core.String? url,
RecordingTemplate? template,
$core.int? width,
$core.int? height,
$core.int? depth,
$core.int? framerate,
}) {
final _result = create();
if (url != null) {
_result.url = url;
}
if (template != null) {
_result.template = template;
}
if (width != null) {
_result.width = width;
}
if (height != null) {
_result.height = height;
}
if (depth != null) {
_result.depth = depth;
}
if (framerate != null) {
_result.framerate = framerate;
}
return _result;
}
factory RecordingInput.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory RecordingInput.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
RecordingInput clone() => RecordingInput()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
RecordingInput copyWith(void Function(RecordingInput) updates) =>
super.copyWith((message) => updates(message as RecordingInput))
as RecordingInput; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecordingInput create() => RecordingInput._();
RecordingInput createEmptyInstance() => create();
static $pb.PbList<RecordingInput> createRepeated() => $pb.PbList<RecordingInput>();
@$core.pragma('dart2js:noInline')
static RecordingInput getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecordingInput>(create);
static RecordingInput? _defaultInstance;
@$pb.TagNumber(1)
$core.String get url => $_getSZ(0);
@$pb.TagNumber(1)
set url($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasUrl() => $_has(0);
@$pb.TagNumber(1)
void clearUrl() => clearField(1);
@$pb.TagNumber(2)
RecordingTemplate get template => $_getN(1);
@$pb.TagNumber(2)
set template(RecordingTemplate v) {
set user(UserPacket v) {
setField(2, v);
}
@$pb.TagNumber(2)
$core.bool hasTemplate() => $_has(1);
$core.bool hasUser() => $_has(1);
@$pb.TagNumber(2)
void clearTemplate() => clearField(2);
void clearUser() => clearField(2);
@$pb.TagNumber(2)
RecordingTemplate ensureTemplate() => $_ensure(1);
UserPacket ensureUser() => $_ensure(1);
@$pb.TagNumber(3)
$core.int get width => $_getIZ(2);
ActiveSpeakerUpdate get speaker => $_getN(2);
@$pb.TagNumber(3)
set width($core.int v) {
$_setSignedInt32(2, v);
}
@$pb.TagNumber(3)
$core.bool hasWidth() => $_has(2);
@$pb.TagNumber(3)
void clearWidth() => clearField(3);
@$pb.TagNumber(4)
$core.int get height => $_getIZ(3);
@$pb.TagNumber(4)
set height($core.int v) {
$_setSignedInt32(3, v);
}
@$pb.TagNumber(4)
$core.bool hasHeight() => $_has(3);
@$pb.TagNumber(4)
void clearHeight() => clearField(4);
@$pb.TagNumber(5)
$core.int get depth => $_getIZ(4);
@$pb.TagNumber(5)
set depth($core.int v) {
$_setSignedInt32(4, v);
}
@$pb.TagNumber(5)
$core.bool hasDepth() => $_has(4);
@$pb.TagNumber(5)
void clearDepth() => clearField(5);
@$pb.TagNumber(6)
$core.int get framerate => $_getIZ(5);
@$pb.TagNumber(6)
set framerate($core.int v) {
$_setSignedInt32(5, v);
}
@$pb.TagNumber(6)
$core.bool hasFramerate() => $_has(5);
@$pb.TagNumber(6)
void clearFramerate() => clearField(6);
}
class RecordingTemplate extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RecordingTemplate',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'type')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'wsUrl')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'token')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'roomName')
..hasRequiredFields = false;
RecordingTemplate._() : super();
factory RecordingTemplate({
$core.String? type,
$core.String? wsUrl,
$core.String? token,
$core.String? roomName,
}) {
final _result = create();
if (type != null) {
_result.type = type;
}
if (wsUrl != null) {
_result.wsUrl = wsUrl;
}
if (token != null) {
_result.token = token;
}
if (roomName != null) {
_result.roomName = roomName;
}
return _result;
}
factory RecordingTemplate.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory RecordingTemplate.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
RecordingTemplate clone() => RecordingTemplate()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
RecordingTemplate copyWith(void Function(RecordingTemplate) updates) =>
super.copyWith((message) => updates(message as RecordingTemplate))
as RecordingTemplate; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecordingTemplate create() => RecordingTemplate._();
RecordingTemplate createEmptyInstance() => create();
static $pb.PbList<RecordingTemplate> createRepeated() => $pb.PbList<RecordingTemplate>();
@$core.pragma('dart2js:noInline')
static RecordingTemplate getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecordingTemplate>(create);
static RecordingTemplate? _defaultInstance;
@$pb.TagNumber(1)
$core.String get type => $_getSZ(0);
@$pb.TagNumber(1)
set type($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasType() => $_has(0);
@$pb.TagNumber(1)
void clearType() => clearField(1);
@$pb.TagNumber(2)
$core.String get wsUrl => $_getSZ(1);
@$pb.TagNumber(2)
set wsUrl($core.String v) {
$_setString(1, v);
}
@$pb.TagNumber(2)
$core.bool hasWsUrl() => $_has(1);
@$pb.TagNumber(2)
void clearWsUrl() => clearField(2);
@$pb.TagNumber(3)
$core.String get token => $_getSZ(2);
@$pb.TagNumber(3)
set token($core.String v) {
$_setString(2, v);
}
@$pb.TagNumber(3)
$core.bool hasToken() => $_has(2);
@$pb.TagNumber(3)
void clearToken() => clearField(3);
@$pb.TagNumber(4)
$core.String get roomName => $_getSZ(3);
@$pb.TagNumber(4)
set roomName($core.String v) {
$_setString(3, v);
}
@$pb.TagNumber(4)
$core.bool hasRoomName() => $_has(3);
@$pb.TagNumber(4)
void clearRoomName() => clearField(4);
}
class RecordingOutput extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RecordingOutput',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'file')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rtmp')
..aOM<RecordingS3Output>(
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 's3',
subBuilder: RecordingS3Output.create)
..a<$core.int>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width',
$pb.PbFieldType.O3)
..a<$core.int>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height',
$pb.PbFieldType.O3)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'audioBitrate')
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'audioFrequency')
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'videoBitrate')
..aOS(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'videoBuffer')
..hasRequiredFields = false;
RecordingOutput._() : super();
factory RecordingOutput({
$core.String? file,
$core.String? rtmp,
RecordingS3Output? s3,
$core.int? width,
$core.int? height,
$core.String? audioBitrate,
$core.String? audioFrequency,
$core.String? videoBitrate,
$core.String? videoBuffer,
}) {
final _result = create();
if (file != null) {
_result.file = file;
}
if (rtmp != null) {
_result.rtmp = rtmp;
}
if (s3 != null) {
_result.s3 = s3;
}
if (width != null) {
_result.width = width;
}
if (height != null) {
_result.height = height;
}
if (audioBitrate != null) {
_result.audioBitrate = audioBitrate;
}
if (audioFrequency != null) {
_result.audioFrequency = audioFrequency;
}
if (videoBitrate != null) {
_result.videoBitrate = videoBitrate;
}
if (videoBuffer != null) {
_result.videoBuffer = videoBuffer;
}
return _result;
}
factory RecordingOutput.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory RecordingOutput.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
RecordingOutput clone() => RecordingOutput()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
RecordingOutput copyWith(void Function(RecordingOutput) updates) =>
super.copyWith((message) => updates(message as RecordingOutput))
as RecordingOutput; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecordingOutput create() => RecordingOutput._();
RecordingOutput createEmptyInstance() => create();
static $pb.PbList<RecordingOutput> createRepeated() => $pb.PbList<RecordingOutput>();
@$core.pragma('dart2js:noInline')
static RecordingOutput getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecordingOutput>(create);
static RecordingOutput? _defaultInstance;
@$pb.TagNumber(1)
$core.String get file => $_getSZ(0);
@$pb.TagNumber(1)
set file($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasFile() => $_has(0);
@$pb.TagNumber(1)
void clearFile() => clearField(1);
@$pb.TagNumber(2)
$core.String get rtmp => $_getSZ(1);
@$pb.TagNumber(2)
set rtmp($core.String v) {
$_setString(1, v);
}
@$pb.TagNumber(2)
$core.bool hasRtmp() => $_has(1);
@$pb.TagNumber(2)
void clearRtmp() => clearField(2);
@$pb.TagNumber(3)
RecordingS3Output get s3 => $_getN(2);
@$pb.TagNumber(3)
set s3(RecordingS3Output v) {
set speaker(ActiveSpeakerUpdate v) {
setField(3, v);
}
@$pb.TagNumber(3)
$core.bool hasS3() => $_has(2);
$core.bool hasSpeaker() => $_has(2);
@$pb.TagNumber(3)
void clearS3() => clearField(3);
void clearSpeaker() => clearField(3);
@$pb.TagNumber(3)
RecordingS3Output ensureS3() => $_ensure(2);
@$pb.TagNumber(4)
$core.int get width => $_getIZ(3);
@$pb.TagNumber(4)
set width($core.int v) {
$_setSignedInt32(3, v);
}
@$pb.TagNumber(4)
$core.bool hasWidth() => $_has(3);
@$pb.TagNumber(4)
void clearWidth() => clearField(4);
@$pb.TagNumber(5)
$core.int get height => $_getIZ(4);
@$pb.TagNumber(5)
set height($core.int v) {
$_setSignedInt32(4, v);
}
@$pb.TagNumber(5)
$core.bool hasHeight() => $_has(4);
@$pb.TagNumber(5)
void clearHeight() => clearField(5);
@$pb.TagNumber(6)
$core.String get audioBitrate => $_getSZ(5);
@$pb.TagNumber(6)
set audioBitrate($core.String v) {
$_setString(5, v);
}
@$pb.TagNumber(6)
$core.bool hasAudioBitrate() => $_has(5);
@$pb.TagNumber(6)
void clearAudioBitrate() => clearField(6);
@$pb.TagNumber(7)
$core.String get audioFrequency => $_getSZ(6);
@$pb.TagNumber(7)
set audioFrequency($core.String v) {
$_setString(6, v);
}
@$pb.TagNumber(7)
$core.bool hasAudioFrequency() => $_has(6);
@$pb.TagNumber(7)
void clearAudioFrequency() => clearField(7);
@$pb.TagNumber(8)
$core.String get videoBitrate => $_getSZ(7);
@$pb.TagNumber(8)
set videoBitrate($core.String v) {
$_setString(7, v);
}
@$pb.TagNumber(8)
$core.bool hasVideoBitrate() => $_has(7);
@$pb.TagNumber(8)
void clearVideoBitrate() => clearField(8);
@$pb.TagNumber(9)
$core.String get videoBuffer => $_getSZ(8);
@$pb.TagNumber(9)
set videoBuffer($core.String v) {
$_setString(8, v);
}
@$pb.TagNumber(9)
$core.bool hasVideoBuffer() => $_has(8);
@$pb.TagNumber(9)
void clearVideoBuffer() => clearField(9);
ActiveSpeakerUpdate ensureSpeaker() => $_ensure(2);
}
class RecordingS3Output extends $pb.GeneratedMessage {
class ActiveSpeakerUpdate extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RecordingS3Output',
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ActiveSpeakerUpdate',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'bucket')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'key')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'accessKey')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'secret')
..pc<SpeakerInfo>(
1,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speakers',
$pb.PbFieldType.PM,
subBuilder: SpeakerInfo.create)
..hasRequiredFields = false;
RecordingS3Output._() : super();
factory RecordingS3Output({
$core.String? bucket,
$core.String? key,
$core.String? accessKey,
$core.String? secret,
ActiveSpeakerUpdate._() : super();
factory ActiveSpeakerUpdate({
$core.Iterable<SpeakerInfo>? speakers,
}) {
final _result = create();
if (bucket != null) {
_result.bucket = bucket;
}
if (key != null) {
_result.key = key;
}
if (accessKey != null) {
_result.accessKey = accessKey;
}
if (secret != null) {
_result.secret = secret;
if (speakers != null) {
_result.speakers.addAll(speakers);
}
return _result;
}
factory RecordingS3Output.fromBuffer($core.List<$core.int> i,
factory ActiveSpeakerUpdate.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory RecordingS3Output.fromJson($core.String i,
factory ActiveSpeakerUpdate.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
RecordingS3Output clone() => RecordingS3Output()..mergeFromMessage(this);
ActiveSpeakerUpdate clone() => ActiveSpeakerUpdate()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
RecordingS3Output copyWith(void Function(RecordingS3Output) updates) =>
super.copyWith((message) => updates(message as RecordingS3Output))
as RecordingS3Output; // ignore: deprecated_member_use
ActiveSpeakerUpdate copyWith(void Function(ActiveSpeakerUpdate) updates) =>
super.copyWith((message) => updates(message as ActiveSpeakerUpdate))
as ActiveSpeakerUpdate; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecordingS3Output create() => RecordingS3Output._();
RecordingS3Output createEmptyInstance() => create();
static $pb.PbList<RecordingS3Output> createRepeated() => $pb.PbList<RecordingS3Output>();
static ActiveSpeakerUpdate create() => ActiveSpeakerUpdate._();
ActiveSpeakerUpdate createEmptyInstance() => create();
static $pb.PbList<ActiveSpeakerUpdate> createRepeated() => $pb.PbList<ActiveSpeakerUpdate>();
@$core.pragma('dart2js:noInline')
static RecordingS3Output getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RecordingS3Output>(create);
static RecordingS3Output? _defaultInstance;
static ActiveSpeakerUpdate getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ActiveSpeakerUpdate>(create);
static ActiveSpeakerUpdate? _defaultInstance;
@$pb.TagNumber(1)
$core.String get bucket => $_getSZ(0);
$core.List<SpeakerInfo> get speakers => $_getList(0);
}
class SpeakerInfo extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SpeakerInfo',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'sid')
..a<$core.double>(
2,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'level',
$pb.PbFieldType.OF)
..aOB(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'active')
..hasRequiredFields = false;
SpeakerInfo._() : super();
factory SpeakerInfo({
$core.String? sid,
$core.double? level,
$core.bool? active,
}) {
final _result = create();
if (sid != null) {
_result.sid = sid;
}
if (level != null) {
_result.level = level;
}
if (active != null) {
_result.active = active;
}
return _result;
}
factory SpeakerInfo.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory SpeakerInfo.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
SpeakerInfo clone() => SpeakerInfo()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
SpeakerInfo copyWith(void Function(SpeakerInfo) updates) =>
super.copyWith((message) => updates(message as SpeakerInfo))
as SpeakerInfo; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SpeakerInfo create() => SpeakerInfo._();
SpeakerInfo createEmptyInstance() => create();
static $pb.PbList<SpeakerInfo> createRepeated() => $pb.PbList<SpeakerInfo>();
@$core.pragma('dart2js:noInline')
static SpeakerInfo getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpeakerInfo>(create);
static SpeakerInfo? _defaultInstance;
@$pb.TagNumber(1)
set bucket($core.String v) {
$core.String get sid => $_getSZ(0);
@$pb.TagNumber(1)
set sid($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasBucket() => $_has(0);
$core.bool hasSid() => $_has(0);
@$pb.TagNumber(1)
void clearBucket() => clearField(1);
void clearSid() => clearField(1);
@$pb.TagNumber(2)
$core.String get key => $_getSZ(1);
$core.double get level => $_getN(1);
@$pb.TagNumber(2)
set key($core.String v) {
$_setString(1, v);
set level($core.double v) {
$_setFloat(1, v);
}
@$pb.TagNumber(2)
$core.bool hasKey() => $_has(1);
$core.bool hasLevel() => $_has(1);
@$pb.TagNumber(2)
void clearKey() => clearField(2);
void clearLevel() => clearField(2);
@$pb.TagNumber(3)
$core.String get accessKey => $_getSZ(2);
$core.bool get active => $_getBF(2);
@$pb.TagNumber(3)
set accessKey($core.String v) {
$_setString(2, v);
set active($core.bool v) {
$_setBool(2, v);
}
@$pb.TagNumber(3)
$core.bool hasAccessKey() => $_has(2);
$core.bool hasActive() => $_has(2);
@$pb.TagNumber(3)
void clearAccessKey() => clearField(3);
void clearActive() => clearField(3);
}
@$pb.TagNumber(4)
$core.String get secret => $_getSZ(3);
@$pb.TagNumber(4)
set secret($core.String v) {
$_setString(3, v);
class UserPacket extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserPacket',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'participantSid')
..a<$core.List<$core.int>>(
2,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'payload',
$pb.PbFieldType.OY)
..pPS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'destinationSids')
..hasRequiredFields = false;
UserPacket._() : super();
factory UserPacket({
$core.String? participantSid,
$core.List<$core.int>? payload,
$core.Iterable<$core.String>? destinationSids,
}) {
final _result = create();
if (participantSid != null) {
_result.participantSid = participantSid;
}
if (payload != null) {
_result.payload = payload;
}
if (destinationSids != null) {
_result.destinationSids.addAll(destinationSids);
}
return _result;
}
factory UserPacket.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory UserPacket.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
UserPacket clone() => UserPacket()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
UserPacket copyWith(void Function(UserPacket) updates) =>
super.copyWith((message) => updates(message as UserPacket))
as UserPacket; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UserPacket create() => UserPacket._();
UserPacket createEmptyInstance() => create();
static $pb.PbList<UserPacket> createRepeated() => $pb.PbList<UserPacket>();
@$core.pragma('dart2js:noInline')
static UserPacket getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserPacket>(create);
static UserPacket? _defaultInstance;
@$pb.TagNumber(1)
$core.String get participantSid => $_getSZ(0);
@$pb.TagNumber(1)
set participantSid($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasParticipantSid() => $_has(0);
@$pb.TagNumber(1)
void clearParticipantSid() => clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get payload => $_getN(1);
@$pb.TagNumber(2)
set payload($core.List<$core.int> v) {
$_setBytes(1, v);
}
@$pb.TagNumber(2)
$core.bool hasPayload() => $_has(1);
@$pb.TagNumber(2)
void clearPayload() => clearField(2);
@$pb.TagNumber(4)
$core.bool hasSecret() => $_has(3);
@$pb.TagNumber(4)
void clearSecret() => clearField(4);
@$pb.TagNumber(3)
$core.List<$core.String> get destinationSids => $_getList(2);
}
+19 -1
View File
@@ -3,7 +3,7 @@
// source: livekit_models.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
// ignore_for_file: UNDEFINED_SHOWN_NAME
import 'dart:core' as $core;
@@ -52,3 +52,21 @@ class ParticipantInfo_State extends $pb.ProtobufEnum {
const ParticipantInfo_State._($core.int v, $core.String n) : super(v, n);
}
class DataPacket_Kind extends $pb.ProtobufEnum {
static const DataPacket_Kind RELIABLE = DataPacket_Kind._(
0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RELIABLE');
static const DataPacket_Kind LOSSY = DataPacket_Kind._(
1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'LOSSY');
static const $core.List<DataPacket_Kind> values = <DataPacket_Kind>[
RELIABLE,
LOSSY,
];
static final $core.Map<$core.int, DataPacket_Kind> _byValue =
$pb.ProtobufEnum.initByValue(values);
static DataPacket_Kind? valueOf($core.int value) => _byValue[value];
const DataPacket_Kind._($core.int v, $core.String n) : super(v, n);
}
+49 -63
View File
@@ -3,7 +3,7 @@
// source: livekit_models.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
import 'dart:core' as $core;
import 'dart:convert' as $convert;
@@ -111,88 +111,74 @@ const TrackInfo$json = const {
/// Descriptor for `TrackInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List trackInfoDescriptor = $convert.base64Decode(
'CglUcmFja0luZm8SEAoDc2lkGAEgASgJUgNzaWQSJgoEdHlwZRgCIAEoDjISLmxpdmVraXQuVHJhY2tUeXBlUgR0eXBlEhIKBG5hbWUYAyABKAlSBG5hbWUSFAoFbXV0ZWQYBCABKAhSBW11dGVkEhQKBXdpZHRoGAUgASgNUgV3aWR0aBIWCgZoZWlnaHQYBiABKA1SBmhlaWdodBIcCglzaW11bGNhc3QYByABKAhSCXNpbXVsY2FzdA==');
@$core.Deprecated('Use dataMessageDescriptor instead')
const DataMessage$json = const {
'1': 'DataMessage',
@$core.Deprecated('Use dataPacketDescriptor instead')
const DataPacket$json = const {
'1': 'DataPacket',
'2': const [
const {'1': 'text', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'text'},
const {'1': 'binary', '3': 2, '4': 1, '5': 12, '9': 0, '10': 'binary'},
const {'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.livekit.DataPacket.Kind', '10': 'kind'},
const {'1': 'user', '3': 2, '4': 1, '5': 11, '6': '.livekit.UserPacket', '9': 0, '10': 'user'},
const {
'1': 'speaker',
'3': 3,
'4': 1,
'5': 11,
'6': '.livekit.ActiveSpeakerUpdate',
'9': 0,
'10': 'speaker'
},
],
'4': const [DataPacket_Kind$json],
'8': const [
const {'1': 'value'},
],
};
/// Descriptor for `DataMessage`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dataMessageDescriptor = $convert.base64Decode(
'CgtEYXRhTWVzc2FnZRIUCgR0ZXh0GAEgASgJSABSBHRleHQSGAoGYmluYXJ5GAIgASgMSABSBmJpbmFyeUIHCgV2YWx1ZQ==');
@$core.Deprecated('Use recordingInputDescriptor instead')
const RecordingInput$json = const {
'1': 'RecordingInput',
@$core.Deprecated('Use dataPacketDescriptor instead')
const DataPacket_Kind$json = const {
'1': 'Kind',
'2': const [
const {'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'},
const {
'1': 'template',
'3': 2,
'4': 1,
'5': 11,
'6': '.livekit.RecordingTemplate',
'10': 'template'
},
const {'1': 'width', '3': 3, '4': 1, '5': 5, '10': 'width'},
const {'1': 'height', '3': 4, '4': 1, '5': 5, '10': 'height'},
const {'1': 'depth', '3': 5, '4': 1, '5': 5, '10': 'depth'},
const {'1': 'framerate', '3': 6, '4': 1, '5': 5, '10': 'framerate'},
const {'1': 'RELIABLE', '2': 0},
const {'1': 'LOSSY', '2': 1},
],
};
/// Descriptor for `RecordingInput`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recordingInputDescriptor = $convert.base64Decode(
'Cg5SZWNvcmRpbmdJbnB1dBIQCgN1cmwYASABKAlSA3VybBI2Cgh0ZW1wbGF0ZRgCIAEoCzIaLmxpdmVraXQuUmVjb3JkaW5nVGVtcGxhdGVSCHRlbXBsYXRlEhQKBXdpZHRoGAMgASgFUgV3aWR0aBIWCgZoZWlnaHQYBCABKAVSBmhlaWdodBIUCgVkZXB0aBgFIAEoBVIFZGVwdGgSHAoJZnJhbWVyYXRlGAYgASgFUglmcmFtZXJhdGU=');
@$core.Deprecated('Use recordingTemplateDescriptor instead')
const RecordingTemplate$json = const {
'1': 'RecordingTemplate',
/// Descriptor for `DataPacket`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dataPacketDescriptor = $convert.base64Decode(
'CgpEYXRhUGFja2V0EiwKBGtpbmQYASABKA4yGC5saXZla2l0LkRhdGFQYWNrZXQuS2luZFIEa2luZBIpCgR1c2VyGAIgASgLMhMubGl2ZWtpdC5Vc2VyUGFja2V0SABSBHVzZXISOAoHc3BlYWtlchgDIAEoCzIcLmxpdmVraXQuQWN0aXZlU3BlYWtlclVwZGF0ZUgAUgdzcGVha2VyIh8KBEtpbmQSDAoIUkVMSUFCTEUQABIJCgVMT1NTWRABQgcKBXZhbHVl');
@$core.Deprecated('Use activeSpeakerUpdateDescriptor instead')
const ActiveSpeakerUpdate$json = const {
'1': 'ActiveSpeakerUpdate',
'2': const [
const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'},
const {'1': 'ws_url', '3': 2, '4': 1, '5': 9, '10': 'wsUrl'},
const {'1': 'token', '3': 3, '4': 1, '5': 9, '10': 'token'},
const {'1': 'room_name', '3': 4, '4': 1, '5': 9, '10': 'roomName'},
const {'1': 'speakers', '3': 1, '4': 3, '5': 11, '6': '.livekit.SpeakerInfo', '10': 'speakers'},
],
};
/// Descriptor for `RecordingTemplate`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recordingTemplateDescriptor = $convert.base64Decode(
'ChFSZWNvcmRpbmdUZW1wbGF0ZRISCgR0eXBlGAEgASgJUgR0eXBlEhUKBndzX3VybBgCIAEoCVIFd3NVcmwSFAoFdG9rZW4YAyABKAlSBXRva2VuEhsKCXJvb21fbmFtZRgEIAEoCVIIcm9vbU5hbWU=');
@$core.Deprecated('Use recordingOutputDescriptor instead')
const RecordingOutput$json = const {
'1': 'RecordingOutput',
/// Descriptor for `ActiveSpeakerUpdate`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List activeSpeakerUpdateDescriptor = $convert.base64Decode(
'ChNBY3RpdmVTcGVha2VyVXBkYXRlEjAKCHNwZWFrZXJzGAEgAygLMhQubGl2ZWtpdC5TcGVha2VySW5mb1IIc3BlYWtlcnM=');
@$core.Deprecated('Use speakerInfoDescriptor instead')
const SpeakerInfo$json = const {
'1': 'SpeakerInfo',
'2': const [
const {'1': 'file', '3': 1, '4': 1, '5': 9, '10': 'file'},
const {'1': 'rtmp', '3': 2, '4': 1, '5': 9, '10': 'rtmp'},
const {'1': 's3', '3': 3, '4': 1, '5': 11, '6': '.livekit.RecordingS3Output', '10': 's3'},
const {'1': 'width', '3': 4, '4': 1, '5': 5, '10': 'width'},
const {'1': 'height', '3': 5, '4': 1, '5': 5, '10': 'height'},
const {'1': 'audio_bitrate', '3': 6, '4': 1, '5': 9, '10': 'audioBitrate'},
const {'1': 'audio_frequency', '3': 7, '4': 1, '5': 9, '10': 'audioFrequency'},
const {'1': 'video_bitrate', '3': 8, '4': 1, '5': 9, '10': 'videoBitrate'},
const {'1': 'video_buffer', '3': 9, '4': 1, '5': 9, '10': 'videoBuffer'},
const {'1': 'sid', '3': 1, '4': 1, '5': 9, '10': 'sid'},
const {'1': 'level', '3': 2, '4': 1, '5': 2, '10': 'level'},
const {'1': 'active', '3': 3, '4': 1, '5': 8, '10': 'active'},
],
};
/// Descriptor for `RecordingOutput`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recordingOutputDescriptor = $convert.base64Decode(
'Cg9SZWNvcmRpbmdPdXRwdXQSEgoEZmlsZRgBIAEoCVIEZmlsZRISCgRydG1wGAIgASgJUgRydG1wEioKAnMzGAMgASgLMhoubGl2ZWtpdC5SZWNvcmRpbmdTM091dHB1dFICczMSFAoFd2lkdGgYBCABKAVSBXdpZHRoEhYKBmhlaWdodBgFIAEoBVIGaGVpZ2h0EiMKDWF1ZGlvX2JpdHJhdGUYBiABKAlSDGF1ZGlvQml0cmF0ZRInCg9hdWRpb19mcmVxdWVuY3kYByABKAlSDmF1ZGlvRnJlcXVlbmN5EiMKDXZpZGVvX2JpdHJhdGUYCCABKAlSDHZpZGVvQml0cmF0ZRIhCgx2aWRlb19idWZmZXIYCSABKAlSC3ZpZGVvQnVmZmVy');
@$core.Deprecated('Use recordingS3OutputDescriptor instead')
const RecordingS3Output$json = const {
'1': 'RecordingS3Output',
/// Descriptor for `SpeakerInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List speakerInfoDescriptor = $convert.base64Decode(
'CgtTcGVha2VySW5mbxIQCgNzaWQYASABKAlSA3NpZBIUCgVsZXZlbBgCIAEoAlIFbGV2ZWwSFgoGYWN0aXZlGAMgASgIUgZhY3RpdmU=');
@$core.Deprecated('Use userPacketDescriptor instead')
const UserPacket$json = const {
'1': 'UserPacket',
'2': const [
const {'1': 'bucket', '3': 1, '4': 1, '5': 9, '10': 'bucket'},
const {'1': 'key', '3': 2, '4': 1, '5': 9, '10': 'key'},
const {'1': 'access_key', '3': 3, '4': 1, '5': 9, '10': 'accessKey'},
const {'1': 'secret', '3': 4, '4': 1, '5': 9, '10': 'secret'},
const {'1': 'participant_sid', '3': 1, '4': 1, '5': 9, '10': 'participantSid'},
const {'1': 'payload', '3': 2, '4': 1, '5': 12, '10': 'payload'},
const {'1': 'destination_sids', '3': 3, '4': 3, '5': 9, '10': 'destinationSids'},
],
};
/// Descriptor for `RecordingS3Output`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recordingS3OutputDescriptor = $convert.base64Decode(
'ChFSZWNvcmRpbmdTM091dHB1dBIWCgZidWNrZXQYASABKAlSBmJ1Y2tldBIQCgNrZXkYAiABKAlSA2tleRIdCgphY2Nlc3Nfa2V5GAMgASgJUglhY2Nlc3NLZXkSFgoGc2VjcmV0GAQgASgJUgZzZWNyZXQ=');
/// Descriptor for `UserPacket`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List userPacketDescriptor = $convert.base64Decode(
'CgpVc2VyUGFja2V0EicKD3BhcnRpY2lwYW50X3NpZBgBIAEoCVIOcGFydGljaXBhbnRTaWQSGAoHcGF5bG9hZBgCIAEoDFIHcGF5bG9hZBIpChBkZXN0aW5hdGlvbl9zaWRzGAMgAygJUg9kZXN0aW5hdGlvblNpZHM=');
+1 -1
View File
@@ -3,6 +3,6 @@
// source: livekit_models.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
export 'livekit_models.pb.dart';
+82 -371
View File
@@ -3,7 +3,7 @@
// source: livekit_rtc.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
import 'dart:core' as $core;
@@ -48,22 +48,33 @@ class SignalRequest extends $pb.GeneratedMessage {
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9])
..aOM<SessionDescription>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'offer',
..aOM<SessionDescription>(
1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'offer',
subBuilder: SessionDescription.create)
..aOM<SessionDescription>(
2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'answer',
subBuilder: SessionDescription.create)
..aOM<TrickleRequest>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trickle',
..aOM<TrickleRequest>(
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trickle',
subBuilder: TrickleRequest.create)
..aOM<AddTrackRequest>(
4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'addTrack',
subBuilder: AddTrackRequest.create)
..aOM<MuteTrackRequest>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'mute',
..aOM<MuteTrackRequest>(
5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'mute',
subBuilder: MuteTrackRequest.create)
..aOM<UpdateSubscription>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'subscription', subBuilder: UpdateSubscription.create)
..aOM<UpdateTrackSettings>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trackSetting', subBuilder: UpdateTrackSettings.create)
..aOM<LeaveRequest>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'leave', subBuilder: LeaveRequest.create)
..aOM<SetSimulcastLayers>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'simulcast', subBuilder: SetSimulcastLayers.create)
..aOM<UpdateSubscription>(
6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'subscription',
subBuilder: UpdateSubscription.create)
..aOM<UpdateTrackSettings>(
7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trackSetting',
subBuilder: UpdateTrackSettings.create)
..aOM<LeaveRequest>(
8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'leave',
subBuilder: LeaveRequest.create)
..aOM<SetSimulcastLayers>(
9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'simulcast',
subBuilder: SetSimulcastLayers.create)
..hasRequiredFields = false;
SignalRequest._() : super();
@@ -273,6 +284,7 @@ enum SignalResponse_Message {
trackPublished,
speaker,
leave,
mute,
notSet
}
@@ -286,6 +298,7 @@ class SignalResponse extends $pb.GeneratedMessage {
6: SignalResponse_Message.trackPublished,
7: SignalResponse_Message.speaker,
8: SignalResponse_Message.leave,
9: SignalResponse_Message.mute,
0: SignalResponse_Message.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@@ -293,22 +306,34 @@ class SignalResponse extends $pb.GeneratedMessage {
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8])
..aOM<JoinResponse>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'join',
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9])
..aOM<JoinResponse>(
1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'join',
subBuilder: JoinResponse.create)
..aOM<SessionDescription>(
2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'answer',
subBuilder: SessionDescription.create)
..aOM<SessionDescription>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'offer',
..aOM<SessionDescription>(
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'offer',
subBuilder: SessionDescription.create)
..aOM<TrickleRequest>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trickle',
..aOM<TrickleRequest>(
4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trickle',
subBuilder: TrickleRequest.create)
..aOM<ParticipantUpdate>(
5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'update',
subBuilder: ParticipantUpdate.create)
..aOM<TrackPublishedResponse>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trackPublished', subBuilder: TrackPublishedResponse.create)
..aOM<ActiveSpeakerUpdate>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speaker', subBuilder: ActiveSpeakerUpdate.create)
..aOM<LeaveRequest>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'leave', subBuilder: LeaveRequest.create)
..aOM<TrackPublishedResponse>(
6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trackPublished',
subBuilder: TrackPublishedResponse.create)
..aOM<$0.ActiveSpeakerUpdate>(
7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speaker',
subBuilder: $0.ActiveSpeakerUpdate.create)
..aOM<LeaveRequest>(
8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'leave',
subBuilder: LeaveRequest.create)
..aOM<MuteTrackRequest>(
9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'mute',
subBuilder: MuteTrackRequest.create)
..hasRequiredFields = false;
SignalResponse._() : super();
@@ -319,8 +344,9 @@ class SignalResponse extends $pb.GeneratedMessage {
TrickleRequest? trickle,
ParticipantUpdate? update,
TrackPublishedResponse? trackPublished,
ActiveSpeakerUpdate? speaker,
$0.ActiveSpeakerUpdate? speaker,
LeaveRequest? leave,
MuteTrackRequest? mute,
}) {
final _result = create();
if (join != null) {
@@ -347,6 +373,9 @@ class SignalResponse extends $pb.GeneratedMessage {
if (leave != null) {
_result.leave = leave;
}
if (mute != null) {
_result.mute = mute;
}
return _result;
}
factory SignalResponse.fromBuffer($core.List<$core.int> i,
@@ -463,9 +492,9 @@ class SignalResponse extends $pb.GeneratedMessage {
TrackPublishedResponse ensureTrackPublished() => $_ensure(5);
@$pb.TagNumber(7)
ActiveSpeakerUpdate get speaker => $_getN(6);
$0.ActiveSpeakerUpdate get speaker => $_getN(6);
@$pb.TagNumber(7)
set speaker(ActiveSpeakerUpdate v) {
set speaker($0.ActiveSpeakerUpdate v) {
setField(7, v);
}
@@ -474,7 +503,7 @@ class SignalResponse extends $pb.GeneratedMessage {
@$pb.TagNumber(7)
void clearSpeaker() => clearField(7);
@$pb.TagNumber(7)
ActiveSpeakerUpdate ensureSpeaker() => $_ensure(6);
$0.ActiveSpeakerUpdate ensureSpeaker() => $_ensure(6);
@$pb.TagNumber(8)
LeaveRequest get leave => $_getN(7);
@@ -489,6 +518,20 @@ class SignalResponse extends $pb.GeneratedMessage {
void clearLeave() => clearField(8);
@$pb.TagNumber(8)
LeaveRequest ensureLeave() => $_ensure(7);
@$pb.TagNumber(9)
MuteTrackRequest get mute => $_getN(8);
@$pb.TagNumber(9)
set mute(MuteTrackRequest v) {
setField(9, v);
}
@$pb.TagNumber(9)
$core.bool hasMute() => $_has(8);
@$pb.TagNumber(9)
void clearMute() => clearField(9);
@$pb.TagNumber(9)
MuteTrackRequest ensureMute() => $_ensure(8);
}
class AddTrackRequest extends $pb.GeneratedMessage {
@@ -510,6 +553,7 @@ class AddTrackRequest extends $pb.GeneratedMessage {
$pb.PbFieldType.OU3)
..a<$core.int>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'height',
$pb.PbFieldType.OU3)
..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'muted')
..hasRequiredFields = false;
AddTrackRequest._() : super();
@@ -519,6 +563,7 @@ class AddTrackRequest extends $pb.GeneratedMessage {
$0.TrackType? type,
$core.int? width,
$core.int? height,
$core.bool? muted,
}) {
final _result = create();
if (cid != null) {
@@ -536,6 +581,9 @@ class AddTrackRequest extends $pb.GeneratedMessage {
if (height != null) {
_result.height = height;
}
if (muted != null) {
_result.muted = muted;
}
return _result;
}
factory AddTrackRequest.fromBuffer($core.List<$core.int> i,
@@ -623,6 +671,18 @@ class AddTrackRequest extends $pb.GeneratedMessage {
$core.bool hasHeight() => $_has(4);
@$pb.TagNumber(5)
void clearHeight() => clearField(5);
@$pb.TagNumber(6)
$core.bool get muted => $_getBF(5);
@$pb.TagNumber(6)
set muted($core.bool v) {
$_setBool(5, v);
}
@$pb.TagNumber(6)
$core.bool hasMuted() => $_has(5);
@$pb.TagNumber(6)
void clearMuted() => clearField(6);
}
class TrickleRequest extends $pb.GeneratedMessage {
@@ -871,7 +931,9 @@ class JoinResponse extends $pb.GeneratedMessage {
subBuilder: $0.ParticipantInfo.create)
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'serverVersion')
..pc<ICEServer>(
5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'iceServers', $pb.PbFieldType.PM,
5,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'iceServers',
$pb.PbFieldType.PM,
subBuilder: ICEServer.create)
..hasRequiredFields = false;
@@ -1184,154 +1246,6 @@ class ParticipantUpdate extends $pb.GeneratedMessage {
$core.List<$0.ParticipantInfo> get participants => $_getList(0);
}
class ActiveSpeakerUpdate extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ActiveSpeakerUpdate',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..pc<SpeakerInfo>(
1,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speakers',
$pb.PbFieldType.PM,
subBuilder: SpeakerInfo.create)
..hasRequiredFields = false;
ActiveSpeakerUpdate._() : super();
factory ActiveSpeakerUpdate({
$core.Iterable<SpeakerInfo>? speakers,
}) {
final _result = create();
if (speakers != null) {
_result.speakers.addAll(speakers);
}
return _result;
}
factory ActiveSpeakerUpdate.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory ActiveSpeakerUpdate.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
ActiveSpeakerUpdate clone() => ActiveSpeakerUpdate()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
ActiveSpeakerUpdate copyWith(void Function(ActiveSpeakerUpdate) updates) =>
super.copyWith((message) => updates(message as ActiveSpeakerUpdate))
as ActiveSpeakerUpdate; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ActiveSpeakerUpdate create() => ActiveSpeakerUpdate._();
ActiveSpeakerUpdate createEmptyInstance() => create();
static $pb.PbList<ActiveSpeakerUpdate> createRepeated() => $pb.PbList<ActiveSpeakerUpdate>();
@$core.pragma('dart2js:noInline')
static ActiveSpeakerUpdate getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ActiveSpeakerUpdate>(create);
static ActiveSpeakerUpdate? _defaultInstance;
@$pb.TagNumber(1)
$core.List<SpeakerInfo> get speakers => $_getList(0);
}
class SpeakerInfo extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SpeakerInfo',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'sid')
..a<$core.double>(
2,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'level',
$pb.PbFieldType.OF)
..aOB(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'active')
..hasRequiredFields = false;
SpeakerInfo._() : super();
factory SpeakerInfo({
$core.String? sid,
$core.double? level,
$core.bool? active,
}) {
final _result = create();
if (sid != null) {
_result.sid = sid;
}
if (level != null) {
_result.level = level;
}
if (active != null) {
_result.active = active;
}
return _result;
}
factory SpeakerInfo.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory SpeakerInfo.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
SpeakerInfo clone() => SpeakerInfo()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
SpeakerInfo copyWith(void Function(SpeakerInfo) updates) =>
super.copyWith((message) => updates(message as SpeakerInfo))
as SpeakerInfo; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SpeakerInfo create() => SpeakerInfo._();
SpeakerInfo createEmptyInstance() => create();
static $pb.PbList<SpeakerInfo> createRepeated() => $pb.PbList<SpeakerInfo>();
@$core.pragma('dart2js:noInline')
static SpeakerInfo getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpeakerInfo>(create);
static SpeakerInfo? _defaultInstance;
@$pb.TagNumber(1)
$core.String get sid => $_getSZ(0);
@$pb.TagNumber(1)
set sid($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasSid() => $_has(0);
@$pb.TagNumber(1)
void clearSid() => clearField(1);
@$pb.TagNumber(2)
$core.double get level => $_getN(1);
@$pb.TagNumber(2)
set level($core.double v) {
$_setFloat(1, v);
}
@$pb.TagNumber(2)
$core.bool hasLevel() => $_has(1);
@$pb.TagNumber(2)
void clearLevel() => clearField(2);
@$pb.TagNumber(3)
$core.bool get active => $_getBF(2);
@$pb.TagNumber(3)
set active($core.bool v) {
$_setBool(2, v);
}
@$pb.TagNumber(3)
$core.bool hasActive() => $_has(2);
@$pb.TagNumber(3)
void clearActive() => clearField(3);
}
class UpdateSubscription extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateSubscription',
@@ -1627,206 +1541,3 @@ class ICEServer extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
void clearCredential() => clearField(3);
}
enum DataPacket_Value { user, speaker, notSet }
class DataPacket extends $pb.GeneratedMessage {
static const $core.Map<$core.int, DataPacket_Value> _DataPacket_ValueByTag = {
2: DataPacket_Value.user,
3: DataPacket_Value.speaker,
0: DataPacket_Value.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DataPacket',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..oo(0, [2, 3])
..e<DataPacket_Kind>(
1,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'kind',
$pb.PbFieldType.OE,
defaultOrMaker: DataPacket_Kind.RELIABLE,
valueOf: DataPacket_Kind.valueOf,
enumValues: DataPacket_Kind.values)
..aOM<UserPacket>(
2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'user',
subBuilder: UserPacket.create)
..aOM<ActiveSpeakerUpdate>(
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'speaker',
subBuilder: ActiveSpeakerUpdate.create)
..hasRequiredFields = false;
DataPacket._() : super();
factory DataPacket({
DataPacket_Kind? kind,
UserPacket? user,
ActiveSpeakerUpdate? speaker,
}) {
final _result = create();
if (kind != null) {
_result.kind = kind;
}
if (user != null) {
_result.user = user;
}
if (speaker != null) {
_result.speaker = speaker;
}
return _result;
}
factory DataPacket.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory DataPacket.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DataPacket clone() => DataPacket()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DataPacket copyWith(void Function(DataPacket) updates) =>
super.copyWith((message) => updates(message as DataPacket))
as DataPacket; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DataPacket create() => DataPacket._();
DataPacket createEmptyInstance() => create();
static $pb.PbList<DataPacket> createRepeated() => $pb.PbList<DataPacket>();
@$core.pragma('dart2js:noInline')
static DataPacket getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DataPacket>(create);
static DataPacket? _defaultInstance;
DataPacket_Value whichValue() => _DataPacket_ValueByTag[$_whichOneof(0)]!;
void clearValue() => clearField($_whichOneof(0));
@$pb.TagNumber(1)
DataPacket_Kind get kind => $_getN(0);
@$pb.TagNumber(1)
set kind(DataPacket_Kind v) {
setField(1, v);
}
@$pb.TagNumber(1)
$core.bool hasKind() => $_has(0);
@$pb.TagNumber(1)
void clearKind() => clearField(1);
@$pb.TagNumber(2)
UserPacket get user => $_getN(1);
@$pb.TagNumber(2)
set user(UserPacket v) {
setField(2, v);
}
@$pb.TagNumber(2)
$core.bool hasUser() => $_has(1);
@$pb.TagNumber(2)
void clearUser() => clearField(2);
@$pb.TagNumber(2)
UserPacket ensureUser() => $_ensure(1);
@$pb.TagNumber(3)
ActiveSpeakerUpdate get speaker => $_getN(2);
@$pb.TagNumber(3)
set speaker(ActiveSpeakerUpdate v) {
setField(3, v);
}
@$pb.TagNumber(3)
$core.bool hasSpeaker() => $_has(2);
@$pb.TagNumber(3)
void clearSpeaker() => clearField(3);
@$pb.TagNumber(3)
ActiveSpeakerUpdate ensureSpeaker() => $_ensure(2);
}
class UserPacket extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserPacket',
package: const $pb.PackageName(
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'livekit'),
createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'participantSid')
..a<$core.List<$core.int>>(
2,
const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'payload',
$pb.PbFieldType.OY)
..pPS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'destinationSids')
..hasRequiredFields = false;
UserPacket._() : super();
factory UserPacket({
$core.String? participantSid,
$core.List<$core.int>? payload,
$core.Iterable<$core.String>? destinationSids,
}) {
final _result = create();
if (participantSid != null) {
_result.participantSid = participantSid;
}
if (payload != null) {
_result.payload = payload;
}
if (destinationSids != null) {
_result.destinationSids.addAll(destinationSids);
}
return _result;
}
factory UserPacket.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory UserPacket.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
UserPacket clone() => UserPacket()..mergeFromMessage(this);
@$core.Deprecated('Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
UserPacket copyWith(void Function(UserPacket) updates) =>
super.copyWith((message) => updates(message as UserPacket))
as UserPacket; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UserPacket create() => UserPacket._();
UserPacket createEmptyInstance() => create();
static $pb.PbList<UserPacket> createRepeated() => $pb.PbList<UserPacket>();
@$core.pragma('dart2js:noInline')
static UserPacket getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserPacket>(create);
static UserPacket? _defaultInstance;
@$pb.TagNumber(1)
$core.String get participantSid => $_getSZ(0);
@$pb.TagNumber(1)
set participantSid($core.String v) {
$_setString(0, v);
}
@$pb.TagNumber(1)
$core.bool hasParticipantSid() => $_has(0);
@$pb.TagNumber(1)
void clearParticipantSid() => clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get payload => $_getN(1);
@$pb.TagNumber(2)
set payload($core.List<$core.int> v) {
$_setBytes(1, v);
}
@$pb.TagNumber(2)
$core.bool hasPayload() => $_has(1);
@$pb.TagNumber(2)
void clearPayload() => clearField(2);
@$pb.TagNumber(3)
$core.List<$core.String> get destinationSids => $_getList(2);
}
+1 -19
View File
@@ -3,7 +3,7 @@
// source: livekit_rtc.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
// ignore_for_file: UNDEFINED_SHOWN_NAME
import 'dart:core' as $core;
@@ -45,21 +45,3 @@ class VideoQuality extends $pb.ProtobufEnum {
const VideoQuality._($core.int v, $core.String n) : super(v, n);
}
class DataPacket_Kind extends $pb.ProtobufEnum {
static const DataPacket_Kind RELIABLE = DataPacket_Kind._(
0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RELIABLE');
static const DataPacket_Kind LOSSY = DataPacket_Kind._(
1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'LOSSY');
static const $core.List<DataPacket_Kind> values = <DataPacket_Kind>[
RELIABLE,
LOSSY,
];
static final $core.Map<$core.int, DataPacket_Kind> _byValue =
$pb.ProtobufEnum.initByValue(values);
static DataPacket_Kind? valueOf($core.int value) => _byValue[value];
const DataPacket_Kind._($core.int v, $core.String n) : super(v, n);
}
+13 -74
View File
@@ -3,7 +3,7 @@
// source: livekit_rtc.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
import 'dart:core' as $core;
import 'dart:convert' as $convert;
@@ -204,6 +204,15 @@ const SignalResponse$json = const {
'9': 0,
'10': 'leave'
},
const {
'1': 'mute',
'3': 9,
'4': 1,
'5': 11,
'6': '.livekit.MuteTrackRequest',
'9': 0,
'10': 'mute'
},
],
'8': const [
const {'1': 'message'},
@@ -212,7 +221,7 @@ const SignalResponse$json = const {
/// Descriptor for `SignalResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List signalResponseDescriptor = $convert.base64Decode(
'Cg5TaWduYWxSZXNwb25zZRIrCgRqb2luGAEgASgLMhUubGl2ZWtpdC5Kb2luUmVzcG9uc2VIAFIEam9pbhI1CgZhbnN3ZXIYAiABKAsyGy5saXZla2l0LlNlc3Npb25EZXNjcmlwdGlvbkgAUgZhbnN3ZXISMwoFb2ZmZXIYAyABKAsyGy5saXZla2l0LlNlc3Npb25EZXNjcmlwdGlvbkgAUgVvZmZlchIzCgd0cmlja2xlGAQgASgLMhcubGl2ZWtpdC5Ucmlja2xlUmVxdWVzdEgAUgd0cmlja2xlEjQKBnVwZGF0ZRgFIAEoCzIaLmxpdmVraXQuUGFydGljaXBhbnRVcGRhdGVIAFIGdXBkYXRlEkoKD3RyYWNrX3B1Ymxpc2hlZBgGIAEoCzIfLmxpdmVraXQuVHJhY2tQdWJsaXNoZWRSZXNwb25zZUgAUg50cmFja1B1Ymxpc2hlZBI4CgdzcGVha2VyGAcgASgLMhwubGl2ZWtpdC5BY3RpdmVTcGVha2VyVXBkYXRlSABSB3NwZWFrZXISLQoFbGVhdmUYCCABKAsyFS5saXZla2l0LkxlYXZlUmVxdWVzdEgAUgVsZWF2ZUIJCgdtZXNzYWdl');
'Cg5TaWduYWxSZXNwb25zZRIrCgRqb2luGAEgASgLMhUubGl2ZWtpdC5Kb2luUmVzcG9uc2VIAFIEam9pbhI1CgZhbnN3ZXIYAiABKAsyGy5saXZla2l0LlNlc3Npb25EZXNjcmlwdGlvbkgAUgZhbnN3ZXISMwoFb2ZmZXIYAyABKAsyGy5saXZla2l0LlNlc3Npb25EZXNjcmlwdGlvbkgAUgVvZmZlchIzCgd0cmlja2xlGAQgASgLMhcubGl2ZWtpdC5Ucmlja2xlUmVxdWVzdEgAUgd0cmlja2xlEjQKBnVwZGF0ZRgFIAEoCzIaLmxpdmVraXQuUGFydGljaXBhbnRVcGRhdGVIAFIGdXBkYXRlEkoKD3RyYWNrX3B1Ymxpc2hlZBgGIAEoCzIfLmxpdmVraXQuVHJhY2tQdWJsaXNoZWRSZXNwb25zZUgAUg50cmFja1B1Ymxpc2hlZBI4CgdzcGVha2VyGAcgASgLMhwubGl2ZWtpdC5BY3RpdmVTcGVha2VyVXBkYXRlSABSB3NwZWFrZXISLQoFbGVhdmUYCCABKAsyFS5saXZla2l0LkxlYXZlUmVxdWVzdEgAUgVsZWF2ZRIvCgRtdXRlGAkgASgLMhkubGl2ZWtpdC5NdXRlVHJhY2tSZXF1ZXN0SABSBG11dGVCCQoHbWVzc2FnZQ==');
@$core.Deprecated('Use addTrackRequestDescriptor instead')
const AddTrackRequest$json = const {
'1': 'AddTrackRequest',
@@ -222,12 +231,13 @@ const AddTrackRequest$json = const {
const {'1': 'type', '3': 3, '4': 1, '5': 14, '6': '.livekit.TrackType', '10': 'type'},
const {'1': 'width', '3': 4, '4': 1, '5': 13, '10': 'width'},
const {'1': 'height', '3': 5, '4': 1, '5': 13, '10': 'height'},
const {'1': 'muted', '3': 6, '4': 1, '5': 8, '10': 'muted'},
],
};
/// Descriptor for `AddTrackRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List addTrackRequestDescriptor = $convert.base64Decode(
'Cg9BZGRUcmFja1JlcXVlc3QSEAoDY2lkGAEgASgJUgNjaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRImCgR0eXBlGAMgASgOMhIubGl2ZWtpdC5UcmFja1R5cGVSBHR5cGUSFAoFd2lkdGgYBCABKA1SBXdpZHRoEhYKBmhlaWdodBgFIAEoDVIGaGVpZ2h0');
'Cg9BZGRUcmFja1JlcXVlc3QSEAoDY2lkGAEgASgJUgNjaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRImCgR0eXBlGAMgASgOMhIubGl2ZWtpdC5UcmFja1R5cGVSBHR5cGUSFAoFd2lkdGgYBCABKA1SBXdpZHRoEhYKBmhlaWdodBgFIAEoDVIGaGVpZ2h0EhQKBW11dGVkGAYgASgIUgVtdXRlZA==');
@$core.Deprecated('Use trickleRequestDescriptor instead')
const TrickleRequest$json = const {
'1': 'TrickleRequest',
@@ -342,30 +352,6 @@ const ParticipantUpdate$json = const {
/// Descriptor for `ParticipantUpdate`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List participantUpdateDescriptor = $convert.base64Decode(
'ChFQYXJ0aWNpcGFudFVwZGF0ZRI8CgxwYXJ0aWNpcGFudHMYASADKAsyGC5saXZla2l0LlBhcnRpY2lwYW50SW5mb1IMcGFydGljaXBhbnRz');
@$core.Deprecated('Use activeSpeakerUpdateDescriptor instead')
const ActiveSpeakerUpdate$json = const {
'1': 'ActiveSpeakerUpdate',
'2': const [
const {'1': 'speakers', '3': 1, '4': 3, '5': 11, '6': '.livekit.SpeakerInfo', '10': 'speakers'},
],
};
/// Descriptor for `ActiveSpeakerUpdate`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List activeSpeakerUpdateDescriptor = $convert.base64Decode(
'ChNBY3RpdmVTcGVha2VyVXBkYXRlEjAKCHNwZWFrZXJzGAEgAygLMhQubGl2ZWtpdC5TcGVha2VySW5mb1IIc3BlYWtlcnM=');
@$core.Deprecated('Use speakerInfoDescriptor instead')
const SpeakerInfo$json = const {
'1': 'SpeakerInfo',
'2': const [
const {'1': 'sid', '3': 1, '4': 1, '5': 9, '10': 'sid'},
const {'1': 'level', '3': 2, '4': 1, '5': 2, '10': 'level'},
const {'1': 'active', '3': 3, '4': 1, '5': 8, '10': 'active'},
],
};
/// Descriptor for `SpeakerInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List speakerInfoDescriptor = $convert.base64Decode(
'CgtTcGVha2VySW5mbxIQCgNzaWQYASABKAlSA3NpZBIUCgVsZXZlbBgCIAEoAlIFbGV2ZWwSFgoGYWN0aXZlGAMgASgIUgZhY3RpdmU=');
@$core.Deprecated('Use updateSubscriptionDescriptor instead')
const UpdateSubscription$json = const {
'1': 'UpdateSubscription',
@@ -415,50 +401,3 @@ const ICEServer$json = const {
/// Descriptor for `ICEServer`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List iCEServerDescriptor = $convert.base64Decode(
'CglJQ0VTZXJ2ZXISEgoEdXJscxgBIAMoCVIEdXJscxIaCgh1c2VybmFtZRgCIAEoCVIIdXNlcm5hbWUSHgoKY3JlZGVudGlhbBgDIAEoCVIKY3JlZGVudGlhbA==');
@$core.Deprecated('Use dataPacketDescriptor instead')
const DataPacket$json = const {
'1': 'DataPacket',
'2': const [
const {'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.livekit.DataPacket.Kind', '10': 'kind'},
const {'1': 'user', '3': 2, '4': 1, '5': 11, '6': '.livekit.UserPacket', '9': 0, '10': 'user'},
const {
'1': 'speaker',
'3': 3,
'4': 1,
'5': 11,
'6': '.livekit.ActiveSpeakerUpdate',
'9': 0,
'10': 'speaker'
},
],
'4': const [DataPacket_Kind$json],
'8': const [
const {'1': 'value'},
],
};
@$core.Deprecated('Use dataPacketDescriptor instead')
const DataPacket_Kind$json = const {
'1': 'Kind',
'2': const [
const {'1': 'RELIABLE', '2': 0},
const {'1': 'LOSSY', '2': 1},
],
};
/// Descriptor for `DataPacket`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dataPacketDescriptor = $convert.base64Decode(
'CgpEYXRhUGFja2V0EiwKBGtpbmQYASABKA4yGC5saXZla2l0LkRhdGFQYWNrZXQuS2luZFIEa2luZBIpCgR1c2VyGAIgASgLMhMubGl2ZWtpdC5Vc2VyUGFja2V0SABSBHVzZXISOAoHc3BlYWtlchgDIAEoCzIcLmxpdmVraXQuQWN0aXZlU3BlYWtlclVwZGF0ZUgAUgdzcGVha2VyIh8KBEtpbmQSDAoIUkVMSUFCTEUQABIJCgVMT1NTWRABQgcKBXZhbHVl');
@$core.Deprecated('Use userPacketDescriptor instead')
const UserPacket$json = const {
'1': 'UserPacket',
'2': const [
const {'1': 'participant_sid', '3': 1, '4': 1, '5': 9, '10': 'participantSid'},
const {'1': 'payload', '3': 2, '4': 1, '5': 12, '10': 'payload'},
const {'1': 'destination_sids', '3': 3, '4': 3, '5': 9, '10': 'destinationSids'},
],
};
/// Descriptor for `UserPacket`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List userPacketDescriptor = $convert.base64Decode(
'CgpVc2VyUGFja2V0EicKD3BhcnRpY2lwYW50X3NpZBgBIAEoCVIOcGFydGljaXBhbnRTaWQSGAoHcGF5bG9hZBgCIAEoDFIHcGF5bG9hZBIpChBkZXN0aW5hdGlvbl9zaWRzGAMgAygJUg9kZXN0aW5hdGlvblNpZHM=');
+1 -1
View File
@@ -3,6 +3,6 @@
// source: livekit_rtc.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_this,unused_import,unused_shown_name
export 'livekit_rtc.pb.dart';
+39 -21
View File
@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:collection';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:tuple/tuple.dart';
@@ -12,8 +12,7 @@ import 'options.dart';
import 'participant/local_participant.dart';
import 'participant/participant.dart';
import 'participant/remote_participant.dart';
import 'proto/livekit_models.pb.dart';
import 'proto/livekit_rtc.pb.dart';
import 'proto/livekit_models.pb.dart' as lk_models;
import 'rtc_engine.dart';
import 'signal_client.dart';
import 'track/remote_track_publication.dart';
@@ -140,9 +139,10 @@ class Room extends ChangeNotifier with ParticipantDelegate {
_engine.onTrack = _onTrackAdded;
_engine.onICEConnected = _handleICEConnected;
_engine.onDisconnected = _handleDisconnect;
_engine.onParticipantUpdateCallback = _handleParticipantUpdate;
_engine.onActiveSpeakerchangedCallback = _handleSpeakerUpdate;
_engine.onDataMessageCallback = _handleDataPacket;
_engine.onParticipantUpdated = _handleParticipantUpdate;
_engine.onActiveSpeakerUpdated = _handleSpeakerUpdate;
_engine.onDataMessage = _handleDataPacket;
_engine.onRemoteMute = _onRemoteMuteChanged;
_engine.onReconnected = () {
_state = RoomState.connected;
delegate?.onReconnected();
@@ -155,16 +155,26 @@ class Room extends ChangeNotifier with ParticipantDelegate {
};
}
Future<Room> connect(String url, String token, [JoinOptions? opts]) async {
Future<Room> connect(
String url,
String token, {
ConnectOptions? options,
}) async {
final completer = Completer<Room>();
_connectCompleter = completer;
final joinResponse = await _engine.join(url, token, opts);
final joinResponse = await _engine.join(
url,
token,
options: options,
);
logger.fine('connected to LiveKit server, version: ${joinResponse.serverVersion}');
localParticipant = LocalParticipant(
engine: _engine,
info: joinResponse.participant,
defaultPublishOptions: options?.defaultPublishOptions,
);
localParticipant.roomDelegate = this;
@@ -191,12 +201,12 @@ class Room extends ChangeNotifier with ParticipantDelegate {
}
/// Disconnects from the room, notifying server of disconnection.
void disconnect() {
Future<void> disconnect() async {
_engine.client.sendLeave();
_handleDisconnect();
await _handleDisconnect();
}
RemoteParticipant _getOrCreateRemoteParticipant(String sid, ParticipantInfo? info) {
RemoteParticipant _getOrCreateRemoteParticipant(String sid, lk_models.ParticipantInfo? info) {
var participant = _participants[sid];
if (participant != null) {
return participant;
@@ -220,7 +230,7 @@ class Room extends ChangeNotifier with ParticipantDelegate {
notifyListeners();
}
void _handleDisconnect() {
Future<void> _handleDisconnect() async {
if (_state == RoomState.disconnected) {
return;
}
@@ -228,14 +238,14 @@ class Room extends ChangeNotifier with ParticipantDelegate {
for (final p in _participants.values) {
final tracks = List<TrackPublication>.from(p.tracks.values);
for (final pub in tracks) {
p.unpublishTrack(pub.sid);
await p.unpublishTrack(pub.sid);
}
}
for (final pub in localParticipant.tracks.values) {
pub.track?.stop();
await pub.track?.stop();
}
_engine.close();
await _engine.close();
_participants.clear();
_activeSpeakers.clear();
_state = RoomState.disconnected;
@@ -243,7 +253,7 @@ class Room extends ChangeNotifier with ParticipantDelegate {
delegate?.onDisconnected();
}
void _handleParticipantUpdate(List<ParticipantInfo> updates) {
void _handleParticipantUpdate(List<lk_models.ParticipantInfo> updates) {
// trigger change notifier only if list of participants membership is changed
var hasChanged = false;
for (final info in updates) {
@@ -252,7 +262,7 @@ class Room extends ChangeNotifier with ParticipantDelegate {
continue;
}
if (info.state == ParticipantInfo_State.DISCONNECTED) {
if (info.state == lk_models.ParticipantInfo_State.DISCONNECTED) {
hasChanged = true;
_handleParticipantDisconnect(info.sid);
continue;
@@ -274,7 +284,7 @@ class Room extends ChangeNotifier with ParticipantDelegate {
}
}
void _handleSpeakerUpdate(List<SpeakerInfo> speakers) {
void _handleSpeakerUpdate(List<lk_models.SpeakerInfo> speakers) {
final seenSids = <String>{};
List<Participant> newSpeakers = [];
for (final info in speakers) {
@@ -312,7 +322,7 @@ class Room extends ChangeNotifier with ParticipantDelegate {
notifyListeners();
}
void _handleDataPacket(UserPacket packet, DataPacket_Kind kind) {
void _handleDataPacket(lk_models.UserPacket packet, lk_models.DataPacket_Kind kind) {
final participant = participants[packet.participantSid];
if (participant == null) {
return;
@@ -322,6 +332,14 @@ class Room extends ChangeNotifier with ParticipantDelegate {
delegate?.onDataReceived(participant, packet.payload);
}
void _onRemoteMuteChanged(String sid, bool mute) {
final track = localParticipant.tracks[sid];
//
// This will trigger signalClient.sendMuteTrack(sid, mute);
//
track?.muted = mute;
}
void _onTrackAdded(MediaStreamTrack track, MediaStream? stream, RTCRtpReceiver? receiver) {
if (stream == null) {
// we need the stream to get the track's id
@@ -329,8 +347,8 @@ class Room extends ChangeNotifier with ParticipantDelegate {
return;
}
var parsed = _unpackStreamId(stream.id);
var trackSid = parsed.item2 ?? track.id;
final parsed = _unpackStreamId(stream.id);
final trackSid = parsed.item2 ?? track.id;
final participant = _getOrCreateRemoteParticipant(parsed.item1, null);
participant.addSubscribedMediaTrack(track, stream, trackSid);
+91 -86
View File
@@ -1,12 +1,13 @@
import 'dart:async';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'errors.dart';
import 'extensions.dart';
import 'logger.dart';
import 'options.dart';
import 'proto/livekit_rtc.pb.dart';
import 'proto/livekit_models.pb.dart';
import 'proto/livekit_models.pb.dart' as lk_models;
import 'proto/livekit_rtc.pb.dart' as lk_rtc;
import 'signal_client.dart';
import 'track/track.dart';
import 'transport.dart';
@@ -19,10 +20,15 @@ const iceRestartTimeout = Duration(seconds: 10);
typedef GenericCallback = void Function();
typedef TrackCallback = void Function(
MediaStreamTrack track, MediaStream? stream, RTCRtpReceiver? receiver);
typedef ParticipantUpdateCallback = void Function(List<ParticipantInfo> participants);
typedef ActiveSpeakerChangedCallback = void Function(List<SpeakerInfo> speakers);
typedef DataPacketCallback = void Function(UserPacket packet, DataPacket_Kind kind);
MediaStreamTrack track,
MediaStream? stream,
RTCRtpReceiver? receiver,
);
typedef ParticipantUpdateCallback = void Function(List<lk_models.ParticipantInfo> participants);
typedef ActiveSpeakerChangedCallback = void Function(List<lk_models.SpeakerInfo> speakers);
typedef DataPacketCallback = void Function(
lk_models.UserPacket packet, lk_models.DataPacket_Kind kind);
typedef RemoteMuteCallback = void Function(String sid, bool mute);
class RTCEngine with SignalClientDelegate {
PCTransport? publisher;
@@ -36,10 +42,10 @@ class RTCEngine with SignalClientDelegate {
bool iceConnected = false;
bool isReconnecting = false;
bool isClosed = true;
Map<String, Completer<TrackInfo>> pendingTrackResolvers = {};
Map<String, Completer<lk_models.TrackInfo>> pendingTrackResolvers = {};
int reconnectAttempts = 0;
// to complete join request
Completer<JoinResponse>? joinCompleter;
Completer<lk_rtc.JoinResponse>? joinCompleter;
// remember url and token for reconnect
String? url;
String? token;
@@ -47,9 +53,10 @@ class RTCEngine with SignalClientDelegate {
// delegate methods
GenericCallback? onICEConnected;
TrackCallback? onTrack;
ParticipantUpdateCallback? onParticipantUpdateCallback;
ActiveSpeakerChangedCallback? onActiveSpeakerchangedCallback;
DataPacketCallback? onDataMessageCallback;
ParticipantUpdateCallback? onParticipantUpdated;
ActiveSpeakerChangedCallback? onActiveSpeakerUpdated;
DataPacketCallback? onDataMessage;
RemoteMuteCallback? onRemoteMute;
GenericCallback? onReconnecting;
GenericCallback? onReconnected;
GenericCallback? onDisconnected;
@@ -62,18 +69,18 @@ class RTCEngine with SignalClientDelegate {
client.delegate = this;
}
Future<JoinResponse> join(String url, String token, JoinOptions? opts) async {
Future<lk_rtc.JoinResponse> join(
String url,
String token, {
ConnectOptions? options,
}) async {
this.url = url;
this.token = token;
final completer = Completer<JoinResponse>();
final completer = Completer<lk_rtc.JoinResponse>();
joinCompleter = completer;
try {
await client.join(url, token, opts);
} catch (e) {
return Future.error(e);
}
await client.join(url, token, options: options);
// if it's not complete after 5 seconds, fail
Timer(connectionTimeout, () {
@@ -84,35 +91,30 @@ class RTCEngine with SignalClientDelegate {
return completer.future;
}
void close() async {
Future<void> close() async {
isClosed = true;
if (publisher != null) {
final senders = await publisher?.pc.getSenders();
for (final element in (senders ?? <RTCRtpSender>[])) {
await publisher?.pc.removeTrack(element);
}
// PCTransport is responsible for disposing RTCPeerConnection
await publisher?.dispose();
publisher = null;
await subscriber?.dispose();
subscriber = null;
publisher?.pc.close();
publisher = null;
}
if (subscriber != null) {
subscriber?.pc.close();
subscriber = null;
}
client.close();
}
Future<TrackInfo> addTrack(
{required String cid,
required String name,
required TrackType kind,
TrackDimension? dimension}) async {
Future<lk_models.TrackInfo> addTrack({
required String cid,
required String name,
required lk_models.TrackType kind,
TrackDimension? dimension,
}) async {
if (pendingTrackResolvers[cid] != null) {
throw TrackPublishError('a track with the same CID has already been published');
}
final completer = Completer<TrackInfo>();
final completer = Completer<lk_models.TrackInfo>();
pendingTrackResolvers[cid] = completer;
client.sendAddTrack(cid: cid, name: name, type: kind, dimension: dimension);
@@ -122,9 +124,7 @@ class RTCEngine with SignalClientDelegate {
Future<void> negotiate({bool? iceRestart}) async {
final pub = publisher;
if (pub == null) {
return;
}
if (pub == null) return;
final remoteDesc = await pub.getRemoteDescription();
@@ -142,14 +142,15 @@ class RTCEngine with SignalClientDelegate {
};
}
final offer = await pub.pc.createOffer(constraints);
logger.fine('Created offer');
logger.finer('sdp: ${offer.sdp}');
await pub.pc.setLocalDescription(offer);
client.sendOffer(offer);
}
Future<void> reconnect() async {
if (isClosed) {
return;
}
if (isClosed) return;
final url = this.url;
final token = this.token;
if (url == null || token == null) {
@@ -174,9 +175,9 @@ class RTCEngine with SignalClientDelegate {
sub.restartingIce = true;
await negotiate(iceRestart: true);
} catch (e) {
} catch (error) {
isReconnecting = false;
return Future.error(e);
return Future.error(error);
}
// wait for connectivity to change
@@ -190,7 +191,7 @@ class RTCEngine with SignalClientDelegate {
}
isReconnecting = false;
return Future.error(ConnectError('could not reconnect ICE'));
throw ConnectError('could not reconnect ICE');
}
Future<void> _configurePeerConnections() async {
@@ -204,18 +205,18 @@ class RTCEngine with SignalClientDelegate {
subscriber = PCTransport(subPC);
pubPC.onIceCandidate = (RTCIceCandidate candidate) {
client.sendIceCandidate(candidate, SignalTarget.PUBLISHER);
client.sendIceCandidate(candidate, lk_rtc.SignalTarget.PUBLISHER);
};
subPC.onIceCandidate = (RTCIceCandidate candidate) {
client.sendIceCandidate(candidate, SignalTarget.SUBSCRIBER);
client.sendIceCandidate(candidate, lk_rtc.SignalTarget.SUBSCRIBER);
};
pubPC.onRenegotiationNeeded = () {
pubPC.onRenegotiationNeeded = () async {
if (pubPC.iceConnectionState == null ||
pubPC.iceConnectionState == RTCIceConnectionState.RTCIceConnectionStateNew) {
return;
}
negotiate();
await negotiate();
};
pubPC.onIceConnectionState = (RTCIceConnectionState state) {
@@ -272,27 +273,26 @@ class RTCEngine with SignalClientDelegate {
return;
}
final dp = DataPacket.fromBuffer(message.binary);
final dp = lk_models.DataPacket.fromBuffer(message.binary);
switch (dp.whichValue()) {
case DataPacket_Value.speaker:
onActiveSpeakerchangedCallback?.call(dp.speaker.speakers);
case lk_models.DataPacket_Value.speaker:
onActiveSpeakerUpdated?.call(dp.speaker.speakers);
break;
case DataPacket_Value.user:
onDataMessageCallback?.call(dp.user, dp.kind);
case lk_models.DataPacket_Value.user:
onDataMessage?.call(dp.user, dp.kind);
break;
default:
// do nothing
}
}
void _handleDisconnect(String reason) {
if (isClosed) {
return;
}
Future<void> _handleDisconnect(String reason) async {
if (isClosed) return;
logger.fine('disconnected $reason');
if (reconnectAttempts >= maxReconnectAttempts) {
logger.info('could not connect after $reconnectAttempts, giving up');
close();
await close();
onDisconnected?.call();
return;
}
@@ -310,7 +310,7 @@ class RTCEngine with SignalClientDelegate {
//------------------ SignalClient Delegate methods -------------------------//
@override
void onConnected(JoinResponse response) async {
Future<void> onConnected(lk_rtc.JoinResponse response) async {
// create peer connections
isClosed = false;
@@ -331,67 +331,72 @@ class RTCEngine with SignalClientDelegate {
await _configurePeerConnections();
negotiate();
await negotiate();
joinCompleter?.complete(Future.value(response));
joinCompleter = null;
}
@override
void onClose([String? reason]) {
_handleDisconnect('signal');
Future<void> onClose([String? reason]) async {
await _handleDisconnect('signal');
}
@override
void onOffer(RTCSessionDescription sd) async {
Future<void> onOffer(RTCSessionDescription sd) async {
final sub = subscriber;
if (sub == null) {
return;
}
if (sub == null) return;
await sub.setRemoteDescription(sd);
final answer = await sub.pc.createAnswer();
logger.fine('Created answer');
logger.finer('sdp: ${answer.sdp}');
await sub.pc.setLocalDescription(answer);
client.sendAnswer(answer);
}
@override
void onAnswer(RTCSessionDescription sd) {
if (publisher == null) {
return;
}
publisher?.setRemoteDescription(sd);
Future<void> onAnswer(RTCSessionDescription sd) async {
if (publisher == null) return;
logger.fine('Received answer');
logger.finer('sdp: ${sd.sdp}');
await publisher!.setRemoteDescription(sd);
}
@override
void onTrickle(RTCIceCandidate candidate, SignalTarget target) {
if (target == SignalTarget.SUBSCRIBER) {
subscriber?.addIceCandidate(candidate);
} else if (target == SignalTarget.PUBLISHER) {
publisher?.addIceCandidate(candidate);
Future<void> onTrickle(RTCIceCandidate candidate, lk_rtc.SignalTarget target) async {
if (target == lk_rtc.SignalTarget.SUBSCRIBER) {
await subscriber?.addIceCandidate(candidate);
} else if (target == lk_rtc.SignalTarget.PUBLISHER) {
await publisher?.addIceCandidate(candidate);
}
}
@override
void onParticipantUpdate(List<ParticipantInfo> updates) {
onParticipantUpdateCallback?.call(updates);
Future<void> onParticipantUpdate(List<lk_models.ParticipantInfo> updates) async {
onParticipantUpdated?.call(updates);
}
@override
void onLocalTrackPublished(TrackPublishedResponse response) {
Future<void> onLocalTrackPublished(lk_rtc.TrackPublishedResponse response) async {
final completer = pendingTrackResolvers.remove(response.cid);
completer?.complete(Future.value(response.track));
}
@override
void onActiveSpeakersChanged(List<SpeakerInfo> speakers) {
onActiveSpeakerchangedCallback?.call(speakers);
Future<void> onActiveSpeakersChanged(List<lk_models.SpeakerInfo> speakers) async {
onActiveSpeakerUpdated?.call(speakers);
}
@override
void onLeave(LeaveRequest req) {
close();
Future<void> onLeave(lk_rtc.LeaveRequest req) async {
await close();
onDisconnected?.call();
}
@override
Future<void> onMuteTrack(lk_rtc.MuteTrackRequest req) async {
onRemoteMute?.call(req.sid, req.muted);
}
}
+210 -153
View File
@@ -3,134 +3,191 @@ import 'dart:convert';
import 'dart:developer';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:http/http.dart' as http;
import 'package:livekit_client/src/ws/interface.dart';
import 'package:synchronized/synchronized.dart' as sync;
import 'errors.dart';
import 'logger.dart';
import 'options.dart';
import 'proto/livekit_models.pb.dart' as lk_models;
import 'proto/livekit_rtc.pb.dart' as lk_rtc;
import 'track/track.dart';
import 'version.dart';
import 'proto/livekit_models.pb.dart';
import 'proto/livekit_rtc.pb.dart';
import '_websocket_api.dart'
if (dart.library.io) '_websocket_io.dart'
if (dart.library.html) '_websocket_html.dart' as platform;
mixin SignalClientDelegate {
// initial connection established
void onConnected(JoinResponse response);
Future<void> onConnected(lk_rtc.JoinResponse response);
// websocket has closed
void onClose([String? reason]);
Future<void> onClose([String? reason]);
// when a server offer is received
void onOffer(RTCSessionDescription sd);
Future<void> onOffer(RTCSessionDescription sd);
// when an answer from server is received
void onAnswer(RTCSessionDescription sd);
Future<void> onAnswer(RTCSessionDescription sd);
// when server has a new ICE candidate
void onTrickle(RTCIceCandidate candidate, SignalTarget target);
Future<void> onTrickle(RTCIceCandidate candidate, lk_rtc.SignalTarget target);
// participant has changed
void onParticipantUpdate(List<ParticipantInfo> updates);
Future<void> onParticipantUpdate(List<lk_models.ParticipantInfo> updates);
// when a track has been added successfully
void onLocalTrackPublished(TrackPublishedResponse response);
Future<void> onLocalTrackPublished(lk_rtc.TrackPublishedResponse response);
// active speaker has changed
void onActiveSpeakersChanged(List<SpeakerInfo> speakers);
Future<void> onActiveSpeakersChanged(List<lk_models.SpeakerInfo> speakers);
// when server sends this client a leave message
void onLeave(LeaveRequest req);
Future<void> onLeave(lk_rtc.LeaveRequest req);
// explicit mute track
Future<void> onMuteTrack(lk_rtc.MuteTrackRequest req);
}
extension LKUriExt on Uri {
bool get isSecureScheme => ['https', 'wss'].contains(scheme);
}
class SignalClient {
SignalClientDelegate? delegate;
static const protocolVersion = 2;
final _lock = sync.Lock();
SignalClientDelegate? delegate;
bool _connected = false;
WebSocketChannel? _ws;
LKWebSocket? _ws;
SignalClient();
bool get connected => _connected;
Future<void> join(String url, String token, JoinOptions? options) async {
final rtcUrl = '$url/rtc';
var params = _joinParams(token);
if (options != null && options.autoSubscribe != null) {
params += '&auto_subscribe=${options.autoSubscribe! ? '1' : '0'}';
}
Uri _buildUri(
String uriOrString, {
required String token,
ConnectOptions? options,
bool reconnect = false,
bool validate = false,
bool forceSecure = false,
}) {
final Uri uri = Uri.parse(uriOrString);
final useSecure = uri.isSecureScheme || forceSecure;
final httpScheme = useSecure ? 'https' : 'http';
final wsScheme = useSecure ? 'wss' : 'ws';
return uri.replace(
scheme: validate ? httpScheme : wsScheme,
path: validate ? 'validate' : 'rtc',
queryParameters: <String, String>{
'access_token': token,
if (options != null) 'auto_subscribe': options.autoSubscribe ? '1' : '0',
if (reconnect) 'reconnect': '1',
'protocol': protocolVersion.toString(),
},
);
}
Future<void> join(
String uriString,
String token, {
ConnectOptions? options,
}) async {
// Create default options if null
options ??= const ConnectOptions();
final rtcUri = _buildUri(
uriString,
token: token,
options: options,
);
try {
final ws = await platform.connectToWebSocket(Uri.parse(rtcUrl + params));
ws.stream.listen(_handleMessage, onError: _handleError, onDone: _handleDone);
_ws = ws;
} catch (e) {
final completer = Completer<void>();
final validateUri = Uri.parse('http${rtcUrl.substring(2)}/validate$params');
http.get(validateUri).then((response) {
if (response.statusCode != 200) {
completer.completeError(ConnectError(response.body));
} else {
completer.completeError(ConnectError());
}
}).catchError((dynamic e) {
completer.completeError(ConnectError());
});
_ws = await LKWebSocket.connect(
rtcUri,
LKWebSocketOptions(
onData: _onSocketData,
onDispose: _onSocketDone,
onError: _handleError,
),
);
} catch (socketError) {
// Re-build same uri for validate mode
final validateUri = _buildUri(
uriString,
token: token,
options: options,
validate: true,
forceSecure: rtcUri.isSecureScheme,
);
return completer.future;
// Attempt Validation
try {
final validateResponse = await http.get(validateUri);
if (validateResponse.statusCode != 200) throw ConnectError(validateResponse.body);
throw ConnectError();
} catch (error) {
// Pass it up if it's already a `ConnectError`
if (error is ConnectError) rethrow;
// HTTP doesn't work either
throw ConnectError();
}
}
}
Future<void> reconnect(String url, String token) async {
Future<void> reconnect(
String uriString,
String token,
) async {
_connected = false;
_ws?.sink.close();
_ws?.dispose();
_ws = null;
url += '/rtc';
var params = _joinParams(token);
params += '&reconnect=1';
final uri = Uri.parse(url + params);
final rtcUri = _buildUri(
uriString,
token: token,
reconnect: true,
);
_ws = await LKWebSocket.connect(
rtcUri,
LKWebSocketOptions(
onData: _onSocketData,
onDispose: _onSocketDone,
onError: _handleError,
),
);
final ws = await platform.connectToWebSocket(uri);
_ws = ws;
_connected = true;
}
void close() {
_connected = false;
_ws?.sink.close();
_ws?.dispose();
}
void sendOffer(RTCSessionDescription offer) {
_sendRequest(SignalRequest(
offer: fromRTCSessionDescription(offer),
));
}
void sendOffer(RTCSessionDescription offer) => _sendRequest(lk_rtc.SignalRequest(
offer: fromRTCSessionDescription(offer),
));
void sendAnswer(RTCSessionDescription answer) {
_sendRequest(SignalRequest(
answer: fromRTCSessionDescription(answer),
));
}
void sendAnswer(RTCSessionDescription answer) => _sendRequest(lk_rtc.SignalRequest(
answer: fromRTCSessionDescription(answer),
));
void sendIceCandidate(RTCIceCandidate candidate, SignalTarget target) {
_sendRequest(SignalRequest(
trickle: TrickleRequest(
candidateInit: fromRTCIceCandidate(candidate),
target: target,
)));
}
void sendIceCandidate(RTCIceCandidate candidate, lk_rtc.SignalTarget target) => _sendRequest(
lk_rtc.SignalRequest(
trickle: lk_rtc.TrickleRequest(
candidateInit: fromRTCIceCandidate(candidate),
target: target,
),
),
);
void sendMuteTrack(String trackSid, bool muted) {
_sendRequest(SignalRequest(
mute: MuteTrackRequest(
sid: trackSid,
muted: muted,
),
));
}
void sendMuteTrack(String trackSid, bool muted) => _sendRequest(lk_rtc.SignalRequest(
mute: lk_rtc.MuteTrackRequest(
sid: trackSid,
muted: muted,
),
));
void sendAddTrack(
{required String cid,
required String name,
required TrackType type,
TrackDimension? dimension}) {
final req = AddTrackRequest(
void sendAddTrack({
required String cid,
required String name,
required lk_models.TrackType type,
TrackDimension? dimension,
}) {
final req = lk_rtc.AddTrackRequest(
cid: cid,
name: name,
type: type,
@@ -139,109 +196,109 @@ class SignalClient {
req.width = dimension.width;
req.height = dimension.height;
}
_sendRequest(SignalRequest(
_sendRequest(lk_rtc.SignalRequest(
addTrack: req,
));
}
void sendUpdateTrackSettings(UpdateTrackSettings settings) {
_sendRequest(SignalRequest(
trackSetting: settings,
));
}
void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) =>
_sendRequest(lk_rtc.SignalRequest(
trackSetting: settings,
));
void sendUpdateSubscription(UpdateSubscription subscription) {
_sendRequest(SignalRequest(
subscription: subscription,
));
}
void sendUpdateSubscription(lk_rtc.UpdateSubscription subscription) =>
_sendRequest(lk_rtc.SignalRequest(
subscription: subscription,
));
void sendSetSimulcastLayers(String trackSid, List<VideoQuality> layers) {
_sendRequest(SignalRequest(
simulcast: SetSimulcastLayers(
trackSid: trackSid,
layers: layers,
)));
}
void sendSetSimulcastLayers(String trackSid, List<lk_rtc.VideoQuality> layers) =>
_sendRequest(lk_rtc.SignalRequest(
simulcast: lk_rtc.SetSimulcastLayers(
trackSid: trackSid,
layers: layers,
),
));
void sendLeave() {
_sendRequest(SignalRequest(
leave: LeaveRequest(),
));
}
void sendLeave() => _sendRequest(lk_rtc.SignalRequest(
leave: lk_rtc.LeaveRequest(),
));
void _sendRequest(SignalRequest req) {
void _sendRequest(lk_rtc.SignalRequest req) {
if (_ws == null) {
log('could not send message, not connected');
return;
}
final buf = req.writeToBuffer();
_ws?.sink.add(buf);
_ws?.send(buf);
}
void _handleMessage(dynamic message) {
if (message is! List<int>) {
return;
}
final msg = SignalResponse.fromBuffer(message);
switch (msg.whichMessage()) {
case SignalResponse_Message.join:
if (!_connected) {
_connected = true;
delegate?.onConnected(msg.join);
}
break;
case SignalResponse_Message.answer:
delegate?.onAnswer(toRTCSessionDescription(msg.answer));
break;
case SignalResponse_Message.offer:
delegate?.onOffer(toRTCSessionDescription(msg.offer));
break;
case SignalResponse_Message.trickle:
delegate?.onTrickle(toRTCIceCandidate(msg.trickle.candidateInit), msg.trickle.target);
break;
case SignalResponse_Message.update:
delegate?.onParticipantUpdate(msg.update.participants);
break;
case SignalResponse_Message.trackPublished:
delegate?.onLocalTrackPublished(msg.trackPublished);
break;
case SignalResponse_Message.speaker:
delegate?.onActiveSpeakersChanged(msg.speaker.speakers);
break;
case SignalResponse_Message.leave:
delegate?.onLeave(msg.leave);
break;
default:
log('unsupported message: ' + json.encode(msg));
}
Future<void> _onSocketData(dynamic message) async {
if (message is! List<int>) return;
final msg = lk_rtc.SignalResponse.fromBuffer(message);
// Ensure previous delegate method's future is completed
// before calling another method
await _lock.synchronized(() async {
//
switch (msg.whichMessage()) {
case lk_rtc.SignalResponse_Message.join:
if (!_connected) {
_connected = true;
await delegate?.onConnected(msg.join);
}
break;
case lk_rtc.SignalResponse_Message.answer:
await delegate?.onAnswer(toRTCSessionDescription(msg.answer));
break;
case lk_rtc.SignalResponse_Message.offer:
await delegate?.onOffer(toRTCSessionDescription(msg.offer));
break;
case lk_rtc.SignalResponse_Message.trickle:
await delegate?.onTrickle(
toRTCIceCandidate(msg.trickle.candidateInit),
msg.trickle.target,
);
break;
case lk_rtc.SignalResponse_Message.update:
await delegate?.onParticipantUpdate(msg.update.participants);
break;
case lk_rtc.SignalResponse_Message.trackPublished:
await delegate?.onLocalTrackPublished(msg.trackPublished);
break;
case lk_rtc.SignalResponse_Message.speaker:
await delegate?.onActiveSpeakersChanged(msg.speaker.speakers);
break;
case lk_rtc.SignalResponse_Message.leave:
await delegate?.onLeave(msg.leave);
break;
case lk_rtc.SignalResponse_Message.mute:
await delegate?.onMuteTrack(msg.mute);
break;
default:
log('unsupported message: ' + json.encode(msg));
}
});
}
void _handleError(Object error) {
void _handleError(dynamic error) {
logger.warning('received websocket error $error');
}
void _handleDone() {
if (!_connected) {
return;
}
void _onSocketDone() {
if (!_connected) return;
_ws = null;
_connected = false;
delegate?.onClose();
}
}
String _joinParams(String token) {
return '?access_token=$token&protocol=$protocolVersion';
}
RTCSessionDescription toRTCSessionDescription(SessionDescription sd) {
RTCSessionDescription toRTCSessionDescription(lk_rtc.SessionDescription sd) {
return RTCSessionDescription(sd.sdp, sd.type);
}
SessionDescription fromRTCSessionDescription(RTCSessionDescription rsd) {
return SessionDescription(type: rsd.type, sdp: rsd.sdp);
lk_rtc.SessionDescription fromRTCSessionDescription(RTCSessionDescription rsd) {
return lk_rtc.SessionDescription(type: rsd.type, sdp: rsd.sdp);
}
RTCIceCandidate toRTCIceCandidate(String candidateInit) {
+7 -7
View File
@@ -1,29 +1,29 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../proto/livekit_models.pbenum.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '_audio_api.dart' if (dart.library.html) '_audio_html.dart' as audio;
import 'local_audio_track.dart';
import 'track.dart';
import '_audio_api.dart' if (dart.library.html) '_audio_html.dart' as audio;
class AudioTrack extends Track {
MediaStream? mediaStream;
AudioTrack(String name, MediaStreamTrack track, this.mediaStream)
: super(TrackType.AUDIO, name, track);
: super(lk_models.TrackType.AUDIO, name, track);
/// Start playing audio track. On web platform, create an audio element and
/// start playback
void start() {
if (this is! LocalAudioTrack) {
audio.startAudio(getCid(), mediaTrack);
audio.startAudio(getCid(), mediaStreamTrack);
}
}
@override
void stop() {
mediaStream?.dispose();
Future<void> stop() async {
await mediaStream?.dispose();
mediaStream = null;
audio.stopAudio(getCid());
super.stop();
await super.stop();
}
}
+16 -16
View File
@@ -1,28 +1,28 @@
import 'dart:async';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/src/track/audio_track.dart';
import '../errors.dart';
import 'audio_track.dart';
import 'options.dart';
class LocalAudioTrack extends AudioTrack {
LocalAudioTrack(String name, MediaStreamTrack track, MediaStream stream)
: super(name, track, stream);
LocalAudioTrack(
String name,
MediaStreamTrack track,
MediaStream stream,
) : super(name, track, stream);
/// Creates a new audio track from the default audio input device.
static Future<LocalAudioTrack> createTrack([LocalAudioTrackOptions? options]) async {
try {
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'audio': true,
'video': false,
});
static Future<LocalAudioTrack> create([LocalAudioTrackOptions? options]) async {
// try {
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'audio': true,
'video': false,
});
if (stream.getAudioTracks().isEmpty) {
return Future.error(TrackCreateError());
}
if (stream.getAudioTracks().isEmpty) throw TrackCreateError();
return LocalAudioTrack('', stream.getAudioTracks().first, stream);
} catch (e) {
return Future.error(e);
}
return LocalAudioTrack('', stream.getAudioTracks().first, stream);
}
}
+12 -6
View File
@@ -1,23 +1,29 @@
import 'package:livekit_client/src/logger.dart';
import '../participant/local_participant.dart';
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import 'track.dart';
import 'track_publication.dart';
class LocalTrackPublication extends TrackPublication {
final LocalParticipant _participant;
LocalTrackPublication(TrackInfo info, Track track, this._participant) : super.fromInfo(info) {
LocalTrackPublication(
lk_models.TrackInfo info,
Track track,
this._participant,
) : super.fromInfo(info) {
this.track = track;
}
/// Mute or unmute the current track. When muted, track will stop sending data
@override
set muted(bool val) {
if (val == muted) {
return;
}
if (val == muted) return;
logger.finer('setMute: ${val}');
super.muted = val;
track?.mediaTrack.enabled = !val;
track?.mediaStreamTrack.enabled = !val;
_participant.engine.client.sendMuteTrack(sid, val);
if (val) {
+89 -42
View File
@@ -1,66 +1,113 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../errors.dart';
import '../logger.dart';
import 'options.dart';
import 'track.dart';
import 'video_track.dart';
/// A video track from the local device. Use static methods in this class to create
/// video tracks.
class LocalVideoTrack extends VideoTrack {
//
// Options used for this track
//
LocalVideoTrackOptions currentOptions;
//
// Private constructor
//
LocalVideoTrack._(
String name,
MediaStreamTrack mediaTrack,
MediaStream stream,
this.currentOptions,
) : super(name, mediaTrack, stream);
RTCRtpSender? get sender => transceiver?.sender;
LocalVideoTrack(String name, MediaStreamTrack mediaTrack, MediaStream stream)
: super(name, mediaTrack, stream);
/// Creates a LocalVideoTrack from camera input.
static Future<LocalVideoTrack> createCameraTrack([LocalVideoTrackOptions? options]) async {
options ??= LocalVideoTrackOptions(params: VideoPresets.qhd);
try {
final stream = await _createCameraStream(options);
return LocalVideoTrack('camera', stream.getVideoTracks().first, stream);
} catch (e) {
return Future.error(e);
}
}
/// Restarts the track with new options. This is useful when switching between
/// front and back cameras.
Future<void> restartTrack([LocalVideoTrackOptions? options]) async {
if (sender == null) {
return Future.error(TrackCreateError('could not restart track'));
Future<void> restartTrack([
LocalVideoTrackOptions? options,
]) async {
if (sender == null) throw TrackCreateError('could not restart track');
if (options != null && currentOptions.runtimeType != options.runtimeType) {
throw Exception('options must be a ${currentOptions.runtimeType}');
}
options ??= LocalVideoTrackOptions(params: VideoPresets.qhd);
currentOptions = options ?? currentOptions;
try {
final stream = await _createCameraStream(options);
final track = stream.getVideoTracks().first;
mediaStream = stream;
await mediaTrack.stop();
mediaTrack = track;
await sender?.replaceTrack(track);
} catch (e) {
return Future.error(e);
}
final stream = await _createStream(currentOptions);
final track = stream.getVideoTracks().first;
setMediaStream(stream);
await mediaStreamTrack.stop();
mediaStreamTrack = track;
await sender?.replaceTrack(track);
}
static Future<MediaStream> _createCameraStream(LocalVideoTrackOptions? options) async {
options ??= LocalVideoTrackOptions(params: VideoPresets.qhd);
/// Creates a LocalVideoTrack from camera input.
static Future<LocalVideoTrack> createCameraTrack([
CameraTrackOptions? options,
]) async {
options ??= const CameraTrackOptions();
final stream = await _createStream(options);
return LocalVideoTrack._(
Track.cameraName,
stream.getVideoTracks().first,
stream,
options,
);
}
try {
final stream = await navigator.mediaDevices.getUserMedia(<String, dynamic>{
'audio': false,
'video': options.mediaConstraints,
});
static Future<LocalVideoTrack> createScreenTrack([
ScreenTrackOptions? options,
]) async {
options ??= const ScreenTrackOptions();
final stream = await _createStream(options);
return LocalVideoTrack._(
Track.screenShareName,
stream.getVideoTracks().first,
stream,
options,
);
}
if (stream.getVideoTracks().isEmpty) {
return Future.error(TrackCreateError());
}
static Future<MediaStream> _createStream(
LocalVideoTrackOptions options,
) async {
final constraints = <String, dynamic>{
'audio': false,
'video': options.toMediaConstraintsMap(),
};
return stream;
} catch (e) {
return Future.error(e);
final MediaStream stream;
if (options is ScreenTrackOptions) {
stream = await navigator.mediaDevices.getDisplayMedia(constraints);
} else {
// options is CameraVideoTrackOptions
stream = await navigator.mediaDevices.getUserMedia(constraints);
}
if (stream.getVideoTracks().isEmpty) throw TrackCreateError();
return stream;
}
}
//
// Convenience extensions
//
extension LKLocalVideoTrackExt on LocalVideoTrack {
// Calls restartTrack under the hood
Future<void> setCameraPosition(CameraPosition position) async {
final options = currentOptions;
if (options is! CameraTrackOptions) {
logger.warning('Not a camera track');
return;
}
await restartTrack(
options.copyWith(cameraPosition: position),
);
}
}
+223 -55
View File
@@ -1,26 +1,8 @@
/// Options when creating a LocalVideoTrack.
class LocalVideoTrackOptions {
CameraPosition position = CameraPosition.front;
VideoParameter params;
import 'package:flutter_webrtc/flutter_webrtc.dart';
LocalVideoTrackOptions({
VideoParameter? params,
CameraPosition? position,
}) : params = VideoPresets.qhd {
if (params != null) {
this.params = params;
}
if (position != null) {
this.position = position;
}
}
Map<String, dynamic> get mediaConstraints {
return <String, dynamic>{
'mandatory': params.mediaConstraints,
'facingMode': position == CameraPosition.front ? 'user' : 'environment',
};
}
enum LocalVideoTrackType {
camera,
display,
}
enum CameraPosition {
@@ -28,43 +10,229 @@ enum CameraPosition {
back,
}
class VideoParameter {
int width;
int height;
int fps;
int? bitrate;
VideoParameter(
this.width,
this.height,
this.fps, {
this.bitrate,
});
Map<String, dynamic> get mediaConstraints {
return <String, dynamic>{
'minWidth': width,
'minHeight': height,
'minFrameRate': fps,
};
}
extension LKCameraPositionExt on CameraPosition {
CameraPosition swap() => {
CameraPosition.front: CameraPosition.back,
CameraPosition.back: CameraPosition.front,
}[this]!;
}
class VideoPresets {
static final qvga = VideoParameter(320, 180, 15);
static final vga = VideoParameter(640, 360, 30);
static final qhd = VideoParameter(960, 540, 30);
static final hd = VideoParameter(1280, 720, 30);
static final fhd = VideoParameter(1920, 1080, 30);
class CameraTrackOptions extends LocalVideoTrackOptions {
final CameraPosition cameraPosition;
static final List<VideoParameter> all = [
qvga,
vga,
qhd,
hd,
fhd,
const CameraTrackOptions({
this.cameraPosition = CameraPosition.front,
VideoParameters params = VideoParameters.presetQHD169,
}) : super(params: params);
@override
Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{
...super.toMediaConstraintsMap(),
'facingMode': cameraPosition == CameraPosition.front ? 'user' : 'environment',
};
// Returns new options with updated properties
CameraTrackOptions copyWith({
VideoParameters? params,
CameraPosition? cameraPosition,
}) =>
CameraTrackOptions(
params: params ?? this.params,
cameraPosition: cameraPosition ?? this.cameraPosition,
);
}
class ScreenTrackOptions extends LocalVideoTrackOptions {
const ScreenTrackOptions();
}
/// Options when creating a LocalVideoTrack.
abstract class LocalVideoTrackOptions {
// final LocalVideoTrackType type;
final VideoParameters params;
const LocalVideoTrackOptions({
this.params = VideoParameters.presetQHD169,
});
Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{
'mandatory': params.toMediaConstraintsMap(),
};
}
class VideoEncoding {
final int maxFramerate;
final int? maxBitrate;
const VideoEncoding({
required this.maxFramerate,
this.maxBitrate,
});
@override
String toString() => '${runtimeType}(maxFramerate: ${maxFramerate}, maxBitrate: ${maxBitrate})';
}
extension VideoEncodingExt on VideoEncoding {
RTCRtpEncoding toRTCRtpEncoding({
String? rid,
double? scaleResolutionDownBy = 1.0,
int? numTemporalLayers,
}) =>
RTCRtpEncoding(
rid: rid,
scaleResolutionDownBy: scaleResolutionDownBy,
maxFramerate: maxFramerate,
maxBitrate: maxBitrate,
numTemporalLayers: numTemporalLayers,
);
}
class VideoParameters {
final String description;
final int width;
final int height;
final VideoEncoding encoding;
const VideoParameters({
required this.description,
required this.width,
required this.height,
required this.encoding,
});
//
// TODO: Make sure the resolutions are correct
//
static const presetQVGA169 = VideoParameters(
description: 'QVGA(320x180) 16:9',
width: 320,
height: 180,
encoding: VideoEncoding(
maxBitrate: 125000,
maxFramerate: 15,
),
);
static const presetVGA169 = VideoParameters(
description: 'VGA(640x360) 16:9',
width: 640,
height: 360,
encoding: VideoEncoding(
maxBitrate: 400000,
maxFramerate: 30,
),
);
static const presetQHD169 = VideoParameters(
description: 'QHD(960x540) 16:9',
width: 960,
height: 540,
encoding: VideoEncoding(
maxBitrate: 800000,
maxFramerate: 30,
),
);
static const presetHD169 = VideoParameters(
description: 'HD(1280x720) 16:9',
width: 1280,
height: 720,
encoding: VideoEncoding(
maxBitrate: 2500000,
maxFramerate: 30,
),
);
static const presetFHD169 = VideoParameters(
description: 'FHD(1920x1080) 16:9',
width: 1920,
height: 1080,
encoding: VideoEncoding(
maxBitrate: 4000000,
maxFramerate: 30,
),
);
static const presetQVGA43 = VideoParameters(
description: 'QVGA(240x180) 4:3',
width: 240,
height: 180,
encoding: VideoEncoding(
maxBitrate: 100000,
maxFramerate: 15,
),
);
static const presetVGA43 = VideoParameters(
description: 'VGA(480x360) 4:3',
width: 480,
height: 360,
encoding: VideoEncoding(
maxBitrate: 320000,
maxFramerate: 30,
),
);
static const presetQHD43 = VideoParameters(
description: 'QHD(720x540) 4:3',
width: 720,
height: 540,
encoding: VideoEncoding(
maxBitrate: 640000,
maxFramerate: 30,
),
);
static const presetHD43 = VideoParameters(
description: 'HD(960x720) 4:3',
width: 960,
height: 720,
encoding: VideoEncoding(
maxBitrate: 2000000,
maxFramerate: 30,
),
);
static const presetFHD43 = VideoParameters(
description: 'FHD(1440x1080) 4:3',
width: 1440,
height: 1080,
encoding: VideoEncoding(
maxBitrate: 3200000,
maxFramerate: 30,
),
);
static final List<VideoParameters> presets169 = [
presetQVGA169,
presetVGA169,
presetQHD169,
presetHD169,
presetFHD169,
];
static final List<VideoParameters> presets43 = [
presetQVGA43,
presetVGA43,
presetQHD43,
presetHD43,
presetFHD43,
];
//
// TODO: Return constraints that will work for all platforms (Web & Mobile)
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
//
Map<String, dynamic> toMediaConstraintsMap() => <String, dynamic>{
'maxWidth': width,
'maxHeight': height,
'maxFrameRate': encoding.maxFramerate,
};
}
/// Options when creating an LocalAudioTrack. Placeholder for now.
class LocalAudioTrackOptions {}
class LocalAudioTrackOptions {
const LocalAudioTrackOptions();
}
+14 -9
View File
@@ -1,6 +1,6 @@
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_rtc.pbserver.dart';
import '../participant/remote_participant.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../proto/livekit_rtc.pb.dart' as lk_rtc;
import 'track.dart';
import 'track_publication.dart';
@@ -10,10 +10,11 @@ class RemoteTrackPublication extends TrackPublication {
final RemoteParticipant _participant;
bool _unsubscribed = false;
bool _disabled = false;
VideoQuality _videoQuality = VideoQuality.HIGH;
lk_rtc.VideoQuality _videoQuality = lk_rtc.VideoQuality.HIGH;
VideoQuality get videoQuality => _videoQuality;
set videoQuality(VideoQuality val) {
lk_rtc.VideoQuality get videoQuality => _videoQuality;
set videoQuality(lk_rtc.VideoQuality val) {
if (val == _videoQuality) return;
_videoQuality = val;
_sendUpdateTrackSettings();
@@ -56,21 +57,25 @@ class RemoteTrackPublication extends TrackPublication {
_participant.roomDelegate?.onTrackUnmuted(_participant, this);
}
if (subscribed) {
track?.mediaTrack.enabled = !val;
track?.mediaStreamTrack.enabled = !val;
}
_participant.muteChanged();
}
RemoteTrackPublication(TrackInfo info, this._participant, [Track? track]) : super.fromInfo(info) {
RemoteTrackPublication(
lk_models.TrackInfo info,
this._participant, [
Track? track,
]) : super.fromInfo(info) {
this.track = track;
}
void _sendUpdateTrackSettings() {
final settings = UpdateTrackSettings(
final settings = lk_rtc.UpdateTrackSettings(
trackSids: [sid],
disabled: _disabled,
);
if (kind == TrackType.VIDEO) {
if (kind == lk_models.TrackType.VIDEO) {
settings.quality = _videoQuality;
}
_participant.client.sendUpdateTrackSettings(settings);
+11 -10
View File
@@ -1,7 +1,7 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:uuid/uuid.dart';
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
class TrackDimension {
int width;
@@ -12,24 +12,25 @@ class TrackDimension {
/// Wrapper around a MediaStreamTrack with additional metadata.
class Track {
static const cameraName = 'camera';
static const screenShareName = 'screen';
String name;
TrackType kind;
MediaStreamTrack mediaTrack;
lk_models.TrackType kind;
MediaStreamTrack mediaStreamTrack;
String? sid;
RTCRtpTransceiver? transceiver;
String? _cid;
Track(this.kind, this.name, this.mediaTrack);
Track(this.kind, this.name, this.mediaStreamTrack);
bool get muted => mediaTrack.muted == null ? false : mediaTrack.muted!;
bool get muted => mediaStreamTrack.muted == null ? false : mediaStreamTrack.muted!;
RTCRtpMediaType get mediaType {
switch (kind) {
case TrackType.AUDIO:
case lk_models.TrackType.AUDIO:
return RTCRtpMediaType.RTCRtpMediaTypeAudio;
case TrackType.VIDEO:
case lk_models.TrackType.VIDEO:
return RTCRtpMediaType.RTCRtpMediaTypeVideo;
// this should never happen
default:
@@ -38,7 +39,7 @@ class Track {
}
String getCid() {
var cid = _cid ?? mediaTrack.id;
var cid = _cid ?? mediaStreamTrack.id;
if (cid == null) {
const uuid = Uuid();
@@ -48,7 +49,7 @@ class Track {
return cid;
}
void stop() {
mediaTrack.stop();
Future<void> stop() async {
await mediaStreamTrack.stop();
}
}
+6 -6
View File
@@ -1,4 +1,4 @@
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import 'track.dart';
/// Represents a track that's published to the server. This class contains
@@ -7,14 +7,14 @@ class TrackPublication {
Track? track;
String name;
String sid;
TrackType kind;
lk_models.TrackType kind;
bool muted = false;
bool simulcasted = false;
TrackDimension? dimension;
bool get subscribed => track != null;
TrackPublication.fromInfo(TrackInfo info)
TrackPublication.fromInfo(lk_models.TrackInfo info)
: sid = info.sid,
name = info.name,
kind = info.type {
@@ -22,12 +22,12 @@ class TrackPublication {
}
/// True when the track is published with name [Track.screenShareName].
bool get isScreenShare => kind == TrackType.VIDEO && name == Track.screenShareName;
bool get isScreenShare => kind == lk_models.TrackType.VIDEO && name == Track.screenShareName;
void updateFromInfo(TrackInfo info) {
void updateFromInfo(lk_models.TrackInfo info) {
muted = info.muted;
simulcasted = info.simulcast;
if (info.type == TrackType.VIDEO) {
if (info.type == lk_models.TrackType.VIDEO) {
dimension = TrackDimension(info.width, info.height);
}
}
+18 -10
View File
@@ -1,29 +1,37 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../proto/livekit_models.pb.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import 'track.dart';
/// A video track will notify when its mediaTrack has changed.
class VideoTrack extends Track with ChangeNotifier {
MediaStream? _mediaStream;
MediaStream _mediaStream;
VideoTrack(String name, MediaStreamTrack mediaTrack, this._mediaStream)
: super(TrackType.VIDEO, name, mediaTrack);
VideoTrack(
String name,
MediaStreamTrack mediaTrack,
this._mediaStream,
) : super(
lk_models.TrackType.VIDEO,
name,
mediaTrack,
);
MediaStream? get mediaStream => _mediaStream;
MediaStream get mediaStream => _mediaStream;
/// internal use
/// {@nodoc}
set mediaStream(MediaStream? stream) {
void setMediaStream(MediaStream stream) {
_mediaStream = stream;
notifyListeners();
}
@override
stop() {
super.stop();
_mediaStream?.dispose();
_mediaStream = null;
Future<void> stop() async {
await super.stop();
await _mediaStream.dispose();
// _mediaStream = null;
}
}
+41 -9
View File
@@ -1,36 +1,68 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'logger.dart';
/// a wrapper around PeerConnection
class PCTransport {
RTCPeerConnection pc;
List<RTCIceCandidate> pendingCandidates = [];
final RTCPeerConnection pc;
final List<RTCIceCandidate> _pendingCandidates = [];
bool restartingIce = false;
PCTransport(this.pc);
Future<void> dispose() async {
// Ensure callbacks won't fire any more
pc.onRenegotiationNeeded = null;
pc.onIceCandidate = null;
pc.onIceConnectionState = null;
pc.onTrack = null;
List<RTCRtpSender> senders = [];
try {
senders = await pc.getSenders();
} catch (_) {}
for (final e in senders) {
try {
await pc.removeTrack(e);
} catch (_) {}
}
await pc.close();
await pc.dispose();
}
Future<void> setRemoteDescription(RTCSessionDescription sd) async {
await pc.setRemoteDescription(sd);
await Future.forEach<RTCIceCandidate>(pendingCandidates, (candidate) async {
await Future.forEach<RTCIceCandidate>(_pendingCandidates, (candidate) async {
await pc.addCandidate(candidate);
});
pendingCandidates.clear();
_pendingCandidates.clear();
restartingIce = false;
}
Future<void> addIceCandidate(RTCIceCandidate candidate) async {
final desc = await getRemoteDescription();
if (desc != null && !restartingIce) {
return pc.addCandidate(candidate);
await pc.addCandidate(candidate);
return;
}
pendingCandidates.add(candidate);
_pendingCandidates.add(candidate);
}
Future<RTCSessionDescription?> getRemoteDescription() async {
if (pc.iceConnectionState == null) {
return null;
// Checking agains null doesn't work as intended
// if (pc.iceConnectionState == null) return null;
try {
final result = await pc.getRemoteDescription();
logger.fine('pc.getRemoteDescription $result');
return result;
} catch (_) {
logger.warning('pc.getRemoteDescription did throw: $_');
}
return pc.getRemoteDescription();
}
}
+87
View File
@@ -0,0 +1,87 @@
//
//
//
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'options.dart';
import 'track/options.dart';
class Utils {
static List<VideoParameters> _presetsForResolution(
int width,
int height,
) {
final double aspect = width / height;
if ((aspect - 16.0 / 9.0).abs() < (aspect - 4.0 / 3.0).abs()) return VideoParameters.presets169;
return VideoParameters.presets43;
}
static VideoParameters _findPresetForResolution(
int width,
int height, {
required List<VideoParameters> presets,
}) {
assert(presets.isNotEmpty, 'presets should not be empty');
VideoParameters result = presets.first;
for (final preset in presets) {
if (width >= preset.width && height >= preset.height) result = preset;
}
return result;
}
static List<RTCRtpEncoding>? computeVideoEncodings({
int? width,
int? height,
TrackPublishOptions? options,
}) {
options ??= const TrackPublishOptions();
VideoEncoding? videoEncoding = options.videoEncoding;
if ((videoEncoding == null && !options.simulcast) || width == null || height == null) {
// don't set encoding when we are not simulcasting and user isn't restricting
// encoding parameters
return null;
}
final presets = _presetsForResolution(width, height);
if (videoEncoding == null) {
// find the right encoding based on width/height
final preset = _findPresetForResolution(width, height, presets: presets);
// print('Using preset: ${preset.id}');
videoEncoding = preset.encoding;
// log.debug('using video encoding', videoEncoding);
}
// Not simulcast
if (!options.simulcast) return [videoEncoding.toRTCRtpEncoding()];
// Compute for simulcast
final midPreset = presets[1];
final lowPreset = presets[0];
return [
videoEncoding.toRTCRtpEncoding(
rid: 'f',
),
// if resolution is high enough, we would send both h and q res..
// otherwise only send h
if (height * 0.7 >= midPreset.height) ...[
midPreset.encoding.toRTCRtpEncoding(
rid: 'h',
scaleResolutionDownBy: height / midPreset.height,
),
lowPreset.encoding.toRTCRtpEncoding(
rid: 'q',
scaleResolutionDownBy: height / lowPreset.height,
),
] else
lowPreset.encoding.toRTCRtpEncoding(
rid: 'h',
scaleResolutionDownBy: height / lowPreset.height,
),
];
}
}
-2
View File
@@ -1,2 +0,0 @@
const version = '0.4.0';
const protocolVersion = 2;
+9 -6
View File
@@ -1,22 +1,24 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../track/video_track.dart';
import '../track/local_video_track.dart';
import '../track/video_track.dart';
/// Widget that renders a [VideoTrack].
class VideoTrackRenderer extends StatefulWidget {
final VideoTrack track;
final RTCVideoRenderer renderer;
final RTCVideoViewObjectFit fit;
VideoTrackRenderer(this.track)
: renderer = RTCVideoRenderer(),
VideoTrackRenderer(
this.track, {
this.fit = RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
}) : renderer = RTCVideoRenderer(),
super(key: ValueKey(track.sid));
@override
State<StatefulWidget> createState() {
return _VideoTrackRendererState();
}
State<StatefulWidget> createState() => _VideoTrackRendererState();
}
class _VideoTrackRendererState extends State<VideoTrackRenderer> {
@@ -63,6 +65,7 @@ class _VideoTrackRendererState extends State<VideoTrackRenderer> {
_renderer,
mirror: isLocal,
filterQuality: FilterQuality.medium,
objectFit: widget.fit,
);
}
}
+41
View File
@@ -0,0 +1,41 @@
import 'platform/io.dart' if (dart.library.html) 'platform/web.dart';
class LKWebSocketError implements Exception {
final int code;
const LKWebSocketError._(this.code);
static LKWebSocketError unknown() => const LKWebSocketError._(0);
static LKWebSocketError connect() => const LKWebSocketError._(1);
@override
String toString() => {
LKWebSocketError.unknown(): 'Unknown error',
LKWebSocketError.connect(): 'Failed to connect',
}[this]!;
}
typedef LKWebSocketOnData = Function(dynamic data);
typedef LKWebSocketOnError = Function(dynamic error);
typedef LKWebSocketOnDispose = Function();
class LKWebSocketOptions {
final LKWebSocketOnData? onData;
final LKWebSocketOnError? onError;
final LKWebSocketOnDispose? onDispose;
const LKWebSocketOptions({
this.onData,
this.onError,
this.onDispose,
});
}
abstract class LKWebSocket {
void send(List<int> data);
void dispose();
static Future<LKWebSocket> connect(
Uri uri, [
LKWebSocketOptions? options,
]) =>
lkWebSocketConnect(uri, options);
}
+53
View File
@@ -0,0 +1,53 @@
import 'dart:async';
import 'dart:io' as io;
import 'package:livekit_client/src/logger.dart';
import '../interface.dart';
Future<LKWebSocketIO> lkWebSocketConnect(
Uri uri, [
LKWebSocketOptions? options,
]) =>
LKWebSocketIO.connect(uri, options);
class LKWebSocketIO implements LKWebSocket {
final io.WebSocket _ws;
final LKWebSocketOptions? options;
late final StreamSubscription _subscription;
LKWebSocketIO._(
this._ws, [
this.options,
]) {
_subscription = _ws.listen(
(dynamic data) => options?.onData?.call(data),
onDone: () => dispose(),
);
}
@override
void dispose() {
options?.onDispose?.call();
_subscription.cancel();
_ws.close();
}
@override
void send(List<int> data) => _ws.add(data);
static Future<LKWebSocketIO> connect(
Uri uri, [
LKWebSocketOptions? options,
]) async {
logger.fine('LKWebSocketIO connect (uri: ${uri.toString()})');
try {
final ws = await io.WebSocket.connect(uri.toString());
logger.fine('LKWebSocketIO connected');
return LKWebSocketIO._(ws, options);
} catch (_) {
logger.severe('LKWebSocketIO error ${_}');
throw LKWebSocketError.connect();
}
}
}
+54
View File
@@ -0,0 +1,54 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'dart:typed_data';
import '../interface.dart';
Future<LKWebSocketWeb> lkWebSocketConnect(
Uri uri, [
LKWebSocketOptions? options,
]) =>
LKWebSocketWeb.connect(uri, options);
class LKWebSocketWeb implements LKWebSocket {
final html.WebSocket _ws;
final LKWebSocketOptions? options;
late final StreamSubscription _messageSubscription;
late final StreamSubscription _closeSubscription;
LKWebSocketWeb._(
this._ws, [
this.options,
]) {
_ws.binaryType = 'arraybuffer';
_messageSubscription = _ws.onMessage.listen((_) {
dynamic _data = _.data is ByteBuffer ? _.data.asUint8List() : _.data;
options?.onData?.call(_data);
});
_closeSubscription = _ws.onClose.listen((_) => dispose());
}
@override
void send(List<int> data) => _ws.send(data);
@override
void dispose() {
options?.onDispose?.call();
_messageSubscription.cancel();
_closeSubscription.cancel();
_ws.close();
}
static Future<LKWebSocketWeb> connect(
Uri uri, [
LKWebSocketOptions? options,
]) async {
final completer = Completer<LKWebSocketWeb>();
final ws = html.WebSocket(uri.toString());
ws.onOpen.listen((_) => completer.complete(LKWebSocketWeb._(ws, options)));
ws.onError.listen((_) => completer.completeError(LKWebSocketError.connect()));
return completer.future;
}
}