Dynacast (#62)
* signal * engine * dynacast option * implement * update for non simulcast * use low when rid is null * firefox comment
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
@@ -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}');
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]!;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user