Dynacast (#62)

* signal

* engine

* dynacast option

* implement

* update for non simulcast

* use low when rid is null

* firefox comment
This commit is contained in:
Hiroshi Horie
2022-01-03 09:02:06 +07:00
committed by GitHub
parent 44a42eafea
commit ea5c324328
8 changed files with 100 additions and 6 deletions
+6 -6
View File
@@ -56,7 +56,7 @@ packages:
name: dart_webrtc name: dart_webrtc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.2" version: "1.0.3"
eva_icons_flutter: eva_icons_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -134,14 +134,14 @@ packages:
name: flutter_webrtc name: flutter_webrtc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.0" version: "0.8.1"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_fonts name: google_fonts
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
http: http:
dependency: transitive dependency: transitive
description: description:
@@ -239,7 +239,7 @@ packages:
name: path_provider_android name: path_provider_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.9" version: "2.0.11"
path_provider_ios: path_provider_ios:
dependency: transitive dependency: transitive
description: description:
@@ -316,7 +316,7 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.1" version: "6.0.2"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -461,7 +461,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "2.3.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
+2
View File
@@ -600,6 +600,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
..on<SignalConnectionQualityUpdateEvent>((event) => events.emit(event)) ..on<SignalConnectionQualityUpdateEvent>((event) => events.emit(event))
// relay // relay
..on<SignalStreamStateUpdatedEvent>((event) => events.emit(event)) ..on<SignalStreamStateUpdatedEvent>((event) => events.emit(event))
// relay to Room
..on<SignalSubscribedQualityUpdatedEvent>((event) => events.emit(event))
..on<SignalLeaveEvent>((event) async { ..on<SignalLeaveEvent>((event) async {
if (connectionState == ConnectionState.reconnecting) { if (connectionState == ConnectionState.reconnecting) {
logger.warning('Received leave signal while engine is reconnecting.'); logger.warning('Received leave signal while engine is reconnecting.');
+19
View File
@@ -56,6 +56,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
/// sid of the room /// sid of the room
String? sid; String? sid;
/// Server version
String? get serverVersion => _serverVersion;
String? _serverVersion;
List<Participant> _activeSpeakers = []; List<Participant> _activeSpeakers = [];
/// a list of participants that are actively speaking, including local participant. /// a list of participants that are actively speaking, including local participant.
@@ -109,6 +113,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
sid = joinResponse.room.sid; sid = joinResponse.room.sid;
name = joinResponse.room.name; name = joinResponse.room.name;
_serverVersion = joinResponse.serverVersion;
logger.fine( logger.fine(
'Connected to LiveKit server, version: ${joinResponse.serverVersion}'); 'Connected to LiveKit server, version: ${joinResponse.serverVersion}');
@@ -168,6 +173,20 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
await publication?.unmute(); await publication?.unmute();
} }
}) })
..on<SignalSubscribedQualityUpdatedEvent>((event) {
// Signal for Dynacast
final options = roomOptions ?? const RoomOptions();
// Dynacast is off or is unsupported
if (!options.dynacast || _serverVersion == '0.15.1') return;
// Find the publication
final publication = localParticipant?.trackPublications[event.trackSid];
if (publication == null) {
logger.warning(
'Received subscribed quality update for unknown track (${event.trackSid})');
return;
}
publication.updatePublishingLayers(event.updates);
})
..on<EngineTrackAddedEvent>((event) async { ..on<EngineTrackAddedEvent>((event) async {
logger.fine('EngineTrackAddedEvent trackSid:${event.track.id}'); logger.fine('EngineTrackAddedEvent trackSid:${event.track.id}');
+6
View File
@@ -180,6 +180,12 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
updates: msg.streamStateUpdate.streamStates, updates: msg.streamStateUpdate.streamStates,
)); ));
break; break;
case lk_rtc.SignalResponse_Message.subscribedQualityUpdate:
events.emit(SignalSubscribedQualityUpdatedEvent(
trackSid: msg.subscribedQualityUpdate.trackSid,
updates: msg.subscribedQualityUpdate.subscribedQualities,
));
break;
default: default:
logger.warning('skipping unsupported signal message'); logger.warning('skipping unsupported signal message');
} }
+8
View File
@@ -132,3 +132,11 @@ extension PBStreamStateExt on lk_rtc.StreamState {
}[this] ?? }[this] ??
StreamState.paused; StreamState.paused;
} }
extension VideoQualityExt on lk_models.VideoQuality {
String toRid() => {
lk_models.VideoQuality.HIGH: 'f',
lk_models.VideoQuality.MEDIUM: 'h',
lk_models.VideoQuality.LOW: 'q',
}[this]!;
}
+11
View File
@@ -196,6 +196,17 @@ class SignalStreamStateUpdatedEvent
}); });
} }
@internal
class SignalSubscribedQualityUpdatedEvent
with SignalEvent, EngineEvent, InternalEvent {
final String trackSid;
final List<lk_rtc.SubscribedQuality> updates;
const SignalSubscribedQualityUpdatedEvent({
required this.trackSid,
required this.updates,
});
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Engine events // Engine events
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
+6
View File
@@ -52,6 +52,11 @@ class RoomOptions {
/// Defaults to false. /// Defaults to false.
final bool adaptiveStream; final bool adaptiveStream;
/// enable Dynacast, off by default. With Dynacast dynamically pauses
/// video layers that are not being consumed by any subscribers, significantly
/// reducing publishing CPU and bandwidth usage.
final bool dynacast;
/// Set this to false in case you would like to stop the track yourself. /// Set this to false in case you would like to stop the track yourself.
/// If you set this to false, make sure you call [Track.stop]. /// If you set this to false, make sure you call [Track.stop].
/// Defaults to true. /// Defaults to true.
@@ -64,6 +69,7 @@ class RoomOptions {
this.defaultVideoPublishOptions = const VideoPublishOptions(), this.defaultVideoPublishOptions = const VideoPublishOptions(),
this.defaultAudioPublishOptions = const AudioPublishOptions(), this.defaultAudioPublishOptions = const AudioPublishOptions(),
this.adaptiveStream = false, this.adaptiveStream = false,
this.dynacast = false,
this.stopLocalTrackOnUnpublish = true, this.stopLocalTrackOnUnpublish = true,
}); });
} }
+42
View File
@@ -1,5 +1,11 @@
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import '../extensions.dart';
import '../logger.dart';
import '../participant/local.dart'; import '../participant/local.dart';
import '../proto/livekit_models.pb.dart' as lk_models; import '../proto/livekit_models.pb.dart' as lk_models;
import '../proto/livekit_rtc.pb.dart' as lk_rtc;
import '../track/local/local.dart'; import '../track/local/local.dart';
import 'track_publication.dart'; import 'track_publication.dart';
@@ -27,4 +33,40 @@ class LocalTrackPublication<T extends LocalTrack> extends TrackPublication<T> {
/// Unmute the track associated with this publication /// Unmute the track associated with this publication
Future<void> unmute() async => await track?.unmute(); Future<void> unmute() async => await track?.unmute();
@internal
void updatePublishingLayers(List<lk_rtc.SubscribedQuality> layers) async {
//
final params = track?.sender?.parameters;
if (params == null) return;
final encodings = params.encodings;
if (encodings == null) return;
bool didChange = false;
for (final encoding in encodings) {
final layer = layers.firstWhereOrNull((e) =>
// If there is exact match, use it
(e.quality.toRid() == encoding.rid) ||
// Use low layer if rid is null (not simulcast)
(encoding.rid == null && e.quality == lk_models.VideoQuality.LOW));
if (layer != null && encoding.active != layer.enabled) {
encoding.active = layer.enabled;
logger.fine('Setting layer ${layer.quality} to ${layer.enabled}');
// FireFox does not support setting encoding.active to false, so we
// have a workaround of lowering its bitrate and resolution to the min.
// TODO: Workaround for firefox
didChange = true;
}
}
if (didChange) {
params.encodings = encodings;
final result = await track?.sender?.setParameters(params);
if (result == false) {
logger.warning('Failed to update sender parameters');
}
}
}
} }