Subscriber as primary (#8)

* Update protos

* Signal client

* Improve transport

* First implementation

* Fix publish

* Update `PCTransport` for debounce negotiation

* debounce func

* reconnect & events

* Update debounce func

* engine events

* make sure events don't emit after dispose

* prefix flutter_webrtc

* Cleaner `iceServers` update

* don't mutate user provided params

* fix tests

* event manager

* Fix: `Room.onDisconnected` gets fired multiple times

* data publish in example

* cleaner logic

* safer events

* un-prefix with LK

* organize imports

* Clean up

* remove `Tuple`

* `EventsEmitter` can now be directly listened to

`EventsEmitter` extends `EventsListenable`
This commit is contained in:
Hiroshi Horie
2021-09-18 00:10:16 +09:00
committed by GitHub
parent 50f56c5e1e
commit f4bd82a919
36 changed files with 1334 additions and 588 deletions
+23 -34
View File
@@ -1,7 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../errors.dart';
import '../extensions.dart';
import '../logger.dart';
import '../options.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
@@ -11,6 +12,7 @@ import '../track/local_track_publication.dart';
import '../track/local_video_track.dart';
import '../track/track.dart';
import '../track/track_publication.dart';
import '../types.dart';
import '../utils.dart';
import 'participant.dart';
@@ -35,7 +37,7 @@ class LocalParticipant extends Participant {
/// publish an audio track to the room
Future<TrackPublication> publishAudioTrack(LocalAudioTrack track) async {
if (audioTracks.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishError('track already exists');
throw TrackPublishException('track already exists');
}
// try {
@@ -45,14 +47,16 @@ class LocalParticipant extends Participant {
kind: track.kind,
);
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
final transceiverInit = rtc.RTCRtpTransceiverInit(
direction: rtc.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,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeAudio,
init: transceiverInit,
);
await _engine.negotiate();
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
@@ -67,7 +71,7 @@ class LocalParticipant extends Participant {
TrackPublishOptions? options,
}) async {
if (videoTracks.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishError('track already exists');
throw TrackPublishException('track already exists');
}
// Use default options from `ConnectOptions` if options is null
@@ -78,10 +82,9 @@ class LocalParticipant extends Participant {
name: track.name,
kind: track.kind,
);
logger.fine('publishVideoTrack addTrack response: ${trackInfo}');
//
// Video encodings and simulcasts
//
// use constraints passed to getUserMedia by default
int? width = track.currentOptions.params.width;
@@ -94,8 +97,6 @@ class LocalParticipant extends Participant {
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()`');
}
@@ -111,19 +112,20 @@ class LocalParticipant extends Participant {
logger.fine('Using encodings: ${encodings?.map((e) => e.toMap())}');
final transceiverInit = RTCRtpTransceiverInit(
direction: TransceiverDirection.SendOnly,
final transceiverInit = rtc.RTCRtpTransceiverInit(
direction: rtc.TransceiverDirection.SendOnly,
sendEncodings: encodings,
streams: [track.mediaStream],
);
//
// addTransceiver cannot pass in a kind parameter due to a bug in flutter-webrtc (web)
//
logger.fine('publishVideoTrack publisher: ${_engine.publisher}');
track.transceiver = await _engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo,
init: transceiverInit,
);
await _engine.negotiate();
final pub = LocalTrackPublication(trackInfo, track, this);
addTrackPublication(pub);
@@ -144,6 +146,7 @@ class LocalParticipant extends Participant {
final sender = track.transceiver?.sender;
if (sender != null) {
await engine.publisher?.pc.removeTrack(sender);
await engine.negotiate();
}
tracks.remove(pub.sid);
@@ -151,26 +154,13 @@ class LocalParticipant extends Participant {
/// 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,
lk_models.DataPacket_Kind reliability, {
Future<void> publishData(
List<int> data, {
Reliability reliability = Reliability.reliable,
List<String>? destinationSids,
}) {
RTCDataChannel? channel;
switch (reliability) {
case lk_models.DataPacket_Kind.RELIABLE:
channel = engine.reliableDC;
break;
case lk_models.DataPacket_Kind.LOSSY:
channel = engine.lossyDC;
break;
}
if (channel == null) {
return;
}
}) async {
final packet = lk_models.DataPacket(
kind: reliability,
kind: reliability.toPBType(),
user: lk_models.UserPacket(
payload: data,
participantSid: sid,
@@ -178,8 +168,7 @@ class LocalParticipant extends Participant {
),
);
final buffer = packet.writeToBuffer();
channel.send(RTCDataChannelMessage.fromBinary(buffer));
await engine.sendDataPacket(packet);
}
/// for internal use
+6 -1
View File
@@ -1,5 +1,7 @@
import 'package:flutter/foundation.dart';
import '../events.dart';
import '../managers/event.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
import '../track/remote_track_publication.dart';
import '../track/track.dart';
@@ -77,6 +79,9 @@ class Participant extends ChangeNotifier {
lk_models.ParticipantInfo? _participantInfo;
bool _isSpeaking = false;
// suppport for multiple event listeners
final events = EventsEmitter<ParticipantEvent>();
/// when the participant joined the room
DateTime get joinedAt {
final pi = _participantInfo;
@@ -159,7 +164,7 @@ class Participant extends ChangeNotifier {
}
// Convenience extension
extension LKParticipantExt on Participant {
extension ParticipantExt on Participant {
List<TrackPublication> get videoTracks =>
tracks.values.where((e) => e.kind == lk_models.TrackType.VIDEO).toList();
+6 -2
View File
@@ -1,4 +1,4 @@
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import '../logger.dart';
import '../proto/livekit_models.pb.dart' as lk_models;
@@ -35,7 +35,11 @@ class RemoteParticipant extends Participant {
/// for internal use
/// {@nodoc}
void addSubscribedMediaTrack(MediaStreamTrack mediaTrack, MediaStream stream, String? sid) async {
void addSubscribedMediaTrack(
rtc.MediaStreamTrack mediaTrack,
rtc.MediaStream stream,
String? sid,
) async {
if (sid == null) {
const msg = 'addSubscribedMediaTrack received null sid';
delegate?.onTrackSubscriptionFailed(this, '', msg);