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:
@@ -1,5 +0,0 @@
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
Future<WebSocketChannel> connectToWebSocket(Uri uri) {
|
||||
throw UnsupportedError('no implementations found');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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})';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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=');
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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=');
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
const version = '0.4.0';
|
||||
const protocolVersion = 2;
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user