diff --git a/dogfooding/ios/Flutter/AppFrameworkInfo.plist b/dogfooding/ios/Flutter/AppFrameworkInfo.plist
index 9625e105d..7c5696400 100644
--- a/dogfooding/ios/Flutter/AppFrameworkInfo.plist
+++ b/dogfooding/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 11.0
+ 12.0
diff --git a/dogfooding/ios/Podfile b/dogfooding/ios/Podfile
index 082893759..2d385faf6 100644
--- a/dogfooding/ios/Podfile
+++ b/dogfooding/ios/Podfile
@@ -43,7 +43,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
-
+
# fix xcode 15 DT_TOOLCHAIN_DIR - remove after fix oficially - https://github.com/CocoaPods/CocoaPods/issues/12065
installer.aggregate_targets.each do |target|
target.xcconfigs.each do |variant, xcconfig|
diff --git a/dogfooding/lib/screens/call_stats_screen.dart b/dogfooding/lib/screens/call_stats_screen.dart
index b0e095703..08d900ab3 100644
--- a/dogfooding/lib/screens/call_stats_screen.dart
+++ b/dogfooding/lib/screens/call_stats_screen.dart
@@ -28,134 +28,180 @@ class CallStatsScreen extends StatelessWidget {
final subscriberBitrate = state?.subscriberStats?.bitrateKbps;
final publisherBitrate = state?.publisherStats?.bitrateKbps;
- return Scaffold(
- appBar: AppBar(
- title: Text(
- 'Stats',
- style: textTheme.title3.apply(color: Colors.white),
- ),
- centerTitle: true,
- backgroundColor: Theme.of(context).scaffoldBackgroundColor,
- actions: [
- IconButton(
- icon: const Icon(
- Icons.close,
- color: Colors.white,
- ),
- onPressed: () {
- Navigator.of(context).pop();
- },
+ return SafeArea(
+ top: false,
+ child: Scaffold(
+ appBar: AppBar(
+ title: Text(
+ 'Stats',
+ style: textTheme.title3.apply(color: Colors.white),
),
- ],
- ),
- body: SingleChildScrollView(
- child: Column(
- children: [
- ListTile(
- leading: StreamUserAvatar(user: currentUser!),
- title: const Text(
- 'Call ID',
- style: TextStyle(color: Colors.white),
- ),
- subtitle: Text(
- call.callCid.value,
- style: const TextStyle(color: Colors.white),
+ centerTitle: true,
+ backgroundColor: Theme.of(context).scaffoldBackgroundColor,
+ actions: [
+ IconButton(
+ icon: const Icon(
+ Icons.close,
+ color: Colors.white,
),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
),
- if (snapshot.hasData) ...[
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Row(
+ ],
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ children: [
+ ListTile(
+ leading: StreamUserAvatar(user: currentUser!),
+ title: const Text(
+ 'Call ID',
+ style: TextStyle(color: Colors.white),
+ ),
+ subtitle: Text(
+ call.callCid.value,
+ style: const TextStyle(color: Colors.white),
+ ),
+ ),
+ if (snapshot.hasData) ...[
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ children: [
+ const Icon(Icons.network_check,
+ color: Colors.white),
+ const SizedBox(width: 8),
+ Text(
+ 'Call latency',
+ style:
+ textTheme.title3.apply(color: Colors.white),
+ ),
+ ],
+ ),
+ ),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ child: Text(
+ 'Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.',
+ style: TextStyle(color: Colors.white),
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ SizedBox(
+ height: 200,
+ child: StatsLatencyChart(
+ latencyHistory: state!.latencyHistory,
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ children: [
+ const Icon(Icons.bar_chart, color: Colors.white),
+ const SizedBox(width: 8),
+ Text(
+ 'Call performance',
+ style:
+ textTheme.title3.apply(color: Colors.white),
+ ),
+ ],
+ ),
+ ),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ child: Text(
+ 'Review the key data points below to assess call performance.',
+ style: TextStyle(color: Colors.white),
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ Row(
children: [
- const Icon(Icons.network_check, color: Colors.white),
- const SizedBox(width: 8),
- Text(
- 'Call latency',
- style: textTheme.title3.apply(color: Colors.white),
+ Expanded(
+ child: LatencyOrJitterItem(
+ title: 'Latency',
+ value: state.publisherStats?.latency ?? 0,
+ ),
),
],
),
- ),
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- child: Text(
- 'Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.',
- style: TextStyle(color: Colors.white),
+ Row(
+ children: [
+ Expanded(
+ child: LatencyOrJitterItem(
+ title: 'Receive jitter',
+ value: state.subscriberStats?.jitterInMs,
+ ),
+ ),
+ Expanded(
+ child: LatencyOrJitterItem(
+ title: 'Publish jitter',
+ value: state.publisherStats?.jitterInMs,
+ ),
+ ),
+ ],
),
- ),
- const SizedBox(
- height: 16,
- ),
- SizedBox(
- height: 200,
- child: StatsLatencyChart(
- latencyHistory: state!.latencyHistory,
+ Row(
+ children: [
+ Expanded(
+ child: StatsItem(
+ title: 'Publish bitrate',
+ value: publisherBitrate == null
+ ? '--'
+ : '${state.publisherStats?.bitrateKbps} Kbps',
+ ),
+ ),
+ Expanded(
+ child: StatsItem(
+ title: 'Receive bitrate',
+ value: subscriberBitrate == null
+ ? '--'
+ : '${state.subscriberStats?.bitrateKbps} Kbps',
+ ),
+ ),
+ ],
),
- ),
- const SizedBox(
- height: 16,
- ),
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: Row(
+ Row(
children: [
- const Icon(Icons.bar_chart, color: Colors.white),
- const SizedBox(width: 8),
- Text(
- 'Call performance',
- style: textTheme.title3.apply(color: Colors.white),
+ Expanded(
+ child: StatsItem(
+ title: 'Publish resolution',
+ value:
+ "${state.publisherStats?.resolution} | ${state.publisherStats?.videoCodec?.join('+')}",
+ ),
+ ),
+ Expanded(
+ child: StatsItem(
+ title: 'Reveive resolution',
+ value:
+ "${state.subscriberStats?.resolution} | ${state.subscriberStats?.videoCodec?.join('+')}",
+ ),
),
],
),
- ),
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- child: Text(
- 'Review the key data points below to assess call performance.',
- style: TextStyle(color: Colors.white),
+ StatsItem(
+ title: 'Region',
+ value: state.localStats?.sfu,
),
- ),
- const SizedBox(
- height: 16,
- ),
- LatencyOrJitterItem(
- title: 'Latency',
- value: state.publisherStats?.latency ?? 0,
- ),
- LatencyOrJitterItem(
- title: 'Receive jitter',
- value: state.subscriberStats?.jitterInMs,
- ),
- LatencyOrJitterItem(
- title: 'Publish jitter',
- value: state.publisherStats?.jitterInMs,
- ),
- StatsItem(
- title: 'Region',
- value: state.localStats?.sfu,
- ),
- StatsItem(
- title: 'SDK Version',
- value: state.localStats?.sdkVersion,
- ),
- StatsItem(
- title: 'WebRTC Version',
- value: state.localStats?.webRtcVersion,
- ),
- StatsItem(
- title: 'Publish bitrate',
- value: publisherBitrate == null
- ? '--'
- : '${state.publisherStats?.bitrateKbps} Kbps',
- ),
- StatsItem(
- title: 'Receive bitrate',
- value: subscriberBitrate == null
- ? '--'
- : '${state.subscriberStats?.bitrateKbps} Kbps',
- ),
- ]
- ],
+ StatsItem(
+ title: 'SDK Version',
+ value: state.localStats?.sdkVersion,
+ ),
+ StatsItem(
+ title: 'WebRTC Version',
+ value: state.localStats?.webRtcVersion,
+ ),
+ ]
+ ],
+ ),
),
),
);
@@ -205,7 +251,7 @@ class StatsItem extends StatelessWidget {
final theme = StreamVideoTheme.of(context);
return Container(
- margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: AppColorPalette.buttonSecondary,
@@ -232,7 +278,6 @@ class StatsItem extends StatelessWidget {
),
),
if (trailing != null) ...[
- const Spacer(),
trailing!,
],
],
diff --git a/dogfooding/lib/screens/home_screen.dart b/dogfooding/lib/screens/home_screen.dart
index 42261d382..5da0e8475 100644
--- a/dogfooding/lib/screens/home_screen.dart
+++ b/dogfooding/lib/screens/home_screen.dart
@@ -67,7 +67,17 @@ class _HomeScreenState extends State {
if (callId.isEmpty) callId = generateAlphanumericString(12);
unawaited(showLoadingIndicator(context));
- _call = _streamVideo.makeCall(callType: kCallType, id: callId);
+ _call = _streamVideo.makeCall(
+ callType: kCallType,
+ id: callId,
+ // Uncomment to force a specific codec when publishing video track
+ // preferences: DefaultCallPreferences(
+ // clientPublishOptions: ClientPublishOptions(
+ // preferredCodec: PreferredCodec.av1,
+ // fmtpLine: 'level-idx=5;profile=0;tier=0',
+ // ),
+ // ),
+ );
bool isRinging = memberIds.isNotEmpty;
@@ -283,6 +293,8 @@ class _JoinForm extends StatelessWidget {
child: TextField(
controller: callIdController,
style: const TextStyle(color: Colors.white),
+ autocorrect: false,
+ enableSuggestions: false,
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
@@ -296,6 +308,8 @@ class _JoinForm extends StatelessWidget {
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
isDense: true,
+ hintStyle:
+ const TextStyle(color: AppColorPalette.secondaryText),
hintText: 'Enter call id',
// suffix button to generate a random call id
suffixIcon: IconButton(
diff --git a/dogfooding/pubspec.yaml b/dogfooding/pubspec.yaml
index ee19441b9..2985b5b5b 100644
--- a/dogfooding/pubspec.yaml
+++ b/dogfooding/pubspec.yaml
@@ -39,7 +39,7 @@ dependencies:
stream_video_screen_sharing: ^0.6.1
dependency_overrides:
- archive: ^3.6.1
+ wakelock_plus: ^1.2.9
stream_video:
path: ../packages/stream_video
stream_video_flutter:
diff --git a/packages/stream_video/lib/open_api/video/coordinator/model/transcription_settings_response.dart b/packages/stream_video/lib/open_api/video/coordinator/model/transcription_settings_response.dart
index 246845e81..fb11af64b 100644
--- a/packages/stream_video/lib/open_api/video/coordinator/model/transcription_settings_response.dart
+++ b/packages/stream_video/lib/open_api/video/coordinator/model/transcription_settings_response.dart
@@ -200,5 +200,3 @@ class TranscriptionSettingsResponseModeEnumTypeTransformer {
/// Singleton [TranscriptionSettingsResponseModeEnumTypeTransformer] instance.
static TranscriptionSettingsResponseModeEnumTypeTransformer? _instance;
}
-
-
diff --git a/packages/stream_video/lib/open_api/video/coordinator/model/user_response.dart b/packages/stream_video/lib/open_api/video/coordinator/model/user_response.dart
index b463e1e24..936d2a0cd 100644
--- a/packages/stream_video/lib/open_api/video/coordinator/model/user_response.dart
+++ b/packages/stream_video/lib/open_api/video/coordinator/model/user_response.dart
@@ -40,7 +40,8 @@ class UserResponse {
DateTime createdAt;
/// Custom data for this object
- Map custom;
+ // MANUAL_EDIT: allow null values
+ Map custom;
/// Date of deactivation
///
@@ -224,7 +225,8 @@ class UserResponse {
? (json[r'blocked_user_ids'] as Iterable).cast().toList(growable: false)
: const [],
createdAt: mapDateTime(json, r'created_at', r'')!,
- custom: mapCastOfType(json, r'custom')!,
+ // MANUAL_EDIT: allow null values
+ custom: mapCastOfType(json, r'custom')!,
deactivatedAt: mapDateTime(json, r'deactivated_at', r''),
deletedAt: mapDateTime(json, r'deleted_at', r''),
id: mapValueOfType(json, r'id')!,
diff --git a/packages/stream_video/lib/protobuf/video/sfu/event/events.pb.dart b/packages/stream_video/lib/protobuf/video/sfu/event/events.pb.dart
index a38f79a33..33415cd9c 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/event/events.pb.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/event/events.pb.dart
@@ -39,6 +39,7 @@ enum SfuEvent_EventPayload {
callEnded,
participantUpdated,
participantMigrationComplete,
+ changePublishOptions,
notSet
}
@@ -66,6 +67,7 @@ class SfuEvent extends $pb.GeneratedMessage {
CallEnded? callEnded,
ParticipantUpdated? participantUpdated,
ParticipantMigrationComplete? participantMigrationComplete,
+ ChangePublishOptions? changePublishOptions,
}) {
final $result = create();
if (subscriberOffer != null) {
@@ -131,6 +133,9 @@ class SfuEvent extends $pb.GeneratedMessage {
if (participantMigrationComplete != null) {
$result.participantMigrationComplete = participantMigrationComplete;
}
+ if (changePublishOptions != null) {
+ $result.changePublishOptions = changePublishOptions;
+ }
return $result;
}
SfuEvent._() : super();
@@ -159,10 +164,11 @@ class SfuEvent extends $pb.GeneratedMessage {
23 : SfuEvent_EventPayload.callEnded,
24 : SfuEvent_EventPayload.participantUpdated,
25 : SfuEvent_EventPayload.participantMigrationComplete,
+ 27 : SfuEvent_EventPayload.changePublishOptions,
0 : SfuEvent_EventPayload.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SfuEvent', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.event'), createEmptyInstance: create)
- ..oo(0, [1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25])
+ ..oo(0, [1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27])
..aOM(1, _omitFieldNames ? '' : 'subscriberOffer', subBuilder: SubscriberOffer.create)
..aOM(2, _omitFieldNames ? '' : 'publisherAnswer', subBuilder: PublisherAnswer.create)
..aOM(3, _omitFieldNames ? '' : 'connectionQualityChanged', subBuilder: ConnectionQualityChanged.create)
@@ -184,6 +190,7 @@ class SfuEvent extends $pb.GeneratedMessage {
..aOM(23, _omitFieldNames ? '' : 'callEnded', subBuilder: CallEnded.create)
..aOM(24, _omitFieldNames ? '' : 'participantUpdated', subBuilder: ParticipantUpdated.create)
..aOM(25, _omitFieldNames ? '' : 'participantMigrationComplete', subBuilder: ParticipantMigrationComplete.create)
+ ..aOM(27, _omitFieldNames ? '' : 'changePublishOptions', subBuilder: ChangePublishOptions.create)
..hasRequiredFields = false
;
@@ -484,6 +491,108 @@ class SfuEvent extends $pb.GeneratedMessage {
void clearParticipantMigrationComplete() => clearField(25);
@$pb.TagNumber(25)
ParticipantMigrationComplete ensureParticipantMigrationComplete() => $_ensure(20);
+
+ /// ChangePublishOptions is sent to signal the change in publish options such as a new codec or simulcast layers
+ @$pb.TagNumber(27)
+ ChangePublishOptions get changePublishOptions => $_getN(21);
+ @$pb.TagNumber(27)
+ set changePublishOptions(ChangePublishOptions v) { setField(27, v); }
+ @$pb.TagNumber(27)
+ $core.bool hasChangePublishOptions() => $_has(21);
+ @$pb.TagNumber(27)
+ void clearChangePublishOptions() => clearField(27);
+ @$pb.TagNumber(27)
+ ChangePublishOptions ensureChangePublishOptions() => $_ensure(21);
+}
+
+class ChangePublishOptions extends $pb.GeneratedMessage {
+ factory ChangePublishOptions({
+ $core.Iterable<$0.PublishOption>? publishOptions,
+ $core.String? reason,
+ }) {
+ final $result = create();
+ if (publishOptions != null) {
+ $result.publishOptions.addAll(publishOptions);
+ }
+ if (reason != null) {
+ $result.reason = reason;
+ }
+ return $result;
+ }
+ ChangePublishOptions._() : super();
+ factory ChangePublishOptions.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory ChangePublishOptions.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChangePublishOptions', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.event'), createEmptyInstance: create)
+ ..pc<$0.PublishOption>(1, _omitFieldNames ? '' : 'publishOptions', $pb.PbFieldType.PM, subBuilder: $0.PublishOption.create)
+ ..aOS(2, _omitFieldNames ? '' : 'reason')
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ChangePublishOptions clone() => ChangePublishOptions()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ChangePublishOptions copyWith(void Function(ChangePublishOptions) updates) => super.copyWith((message) => updates(message as ChangePublishOptions)) as ChangePublishOptions;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ChangePublishOptions create() => ChangePublishOptions._();
+ ChangePublishOptions createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static ChangePublishOptions getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static ChangePublishOptions? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.List<$0.PublishOption> get publishOptions => $_getList(0);
+
+ @$pb.TagNumber(2)
+ $core.String get reason => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set reason($core.String v) { $_setString(1, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasReason() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearReason() => clearField(2);
+}
+
+class ChangePublishOptionsComplete extends $pb.GeneratedMessage {
+ factory ChangePublishOptionsComplete() => create();
+ ChangePublishOptionsComplete._() : super();
+ factory ChangePublishOptionsComplete.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory ChangePublishOptionsComplete.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChangePublishOptionsComplete', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.event'), createEmptyInstance: create)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ChangePublishOptionsComplete clone() => ChangePublishOptionsComplete()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ChangePublishOptionsComplete copyWith(void Function(ChangePublishOptionsComplete) updates) => super.copyWith((message) => updates(message as ChangePublishOptionsComplete)) as ChangePublishOptionsComplete;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ChangePublishOptionsComplete create() => ChangePublishOptionsComplete._();
+ ChangePublishOptionsComplete createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static ChangePublishOptionsComplete getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static ChangePublishOptionsComplete? _defaultInstance;
}
class ParticipantMigrationComplete extends $pb.GeneratedMessage {
@@ -1218,6 +1327,9 @@ class JoinRequest extends $pb.GeneratedMessage {
@$core.Deprecated('This field is deprecated.')
$core.bool? fastReconnect,
ReconnectDetails? reconnectDetails,
+ $core.String? publisherSdp,
+ $core.Iterable<$0.PublishOption>? preferredPublishOptions,
+ $core.Iterable<$0.SubscribeOption>? preferredSubscribeOptions,
}) {
final $result = create();
if (token != null) {
@@ -1243,6 +1355,15 @@ class JoinRequest extends $pb.GeneratedMessage {
if (reconnectDetails != null) {
$result.reconnectDetails = reconnectDetails;
}
+ if (publisherSdp != null) {
+ $result.publisherSdp = publisherSdp;
+ }
+ if (preferredPublishOptions != null) {
+ $result.preferredPublishOptions.addAll(preferredPublishOptions);
+ }
+ if (preferredSubscribeOptions != null) {
+ $result.preferredSubscribeOptions.addAll(preferredSubscribeOptions);
+ }
return $result;
}
JoinRequest._() : super();
@@ -1257,6 +1378,9 @@ class JoinRequest extends $pb.GeneratedMessage {
..aOM(5, _omitFieldNames ? '' : 'migration', subBuilder: Migration.create)
..aOB(6, _omitFieldNames ? '' : 'fastReconnect')
..aOM(7, _omitFieldNames ? '' : 'reconnectDetails', subBuilder: ReconnectDetails.create)
+ ..aOS(8, _omitFieldNames ? '' : 'publisherSdp')
+ ..pc<$0.PublishOption>(9, _omitFieldNames ? '' : 'preferredPublishOptions', $pb.PbFieldType.PM, subBuilder: $0.PublishOption.create)
+ ..pc<$0.SubscribeOption>(10, _omitFieldNames ? '' : 'preferredSubscribeOptions', $pb.PbFieldType.PM, subBuilder: $0.SubscribeOption.create)
..hasRequiredFields = false
;
@@ -1369,6 +1493,21 @@ class JoinRequest extends $pb.GeneratedMessage {
void clearReconnectDetails() => clearField(7);
@$pb.TagNumber(7)
ReconnectDetails ensureReconnectDetails() => $_ensure(6);
+
+ @$pb.TagNumber(8)
+ $core.String get publisherSdp => $_getSZ(7);
+ @$pb.TagNumber(8)
+ set publisherSdp($core.String v) { $_setString(7, v); }
+ @$pb.TagNumber(8)
+ $core.bool hasPublisherSdp() => $_has(7);
+ @$pb.TagNumber(8)
+ void clearPublisherSdp() => clearField(8);
+
+ @$pb.TagNumber(9)
+ $core.List<$0.PublishOption> get preferredPublishOptions => $_getList(8);
+
+ @$pb.TagNumber(10)
+ $core.List<$0.SubscribeOption> get preferredSubscribeOptions => $_getList(9);
}
class ReconnectDetails extends $pb.GeneratedMessage {
@@ -1551,6 +1690,7 @@ class JoinResponse extends $pb.GeneratedMessage {
$0.CallState? callState,
$core.bool? reconnected,
$core.int? fastReconnectDeadlineSeconds,
+ $core.Iterable<$0.PublishOption>? publishOptions,
}) {
final $result = create();
if (callState != null) {
@@ -1562,6 +1702,9 @@ class JoinResponse extends $pb.GeneratedMessage {
if (fastReconnectDeadlineSeconds != null) {
$result.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
}
+ if (publishOptions != null) {
+ $result.publishOptions.addAll(publishOptions);
+ }
return $result;
}
JoinResponse._() : super();
@@ -1572,6 +1715,7 @@ class JoinResponse extends $pb.GeneratedMessage {
..aOM<$0.CallState>(1, _omitFieldNames ? '' : 'callState', subBuilder: $0.CallState.create)
..aOB(2, _omitFieldNames ? '' : 'reconnected')
..a<$core.int>(3, _omitFieldNames ? '' : 'fastReconnectDeadlineSeconds', $pb.PbFieldType.O3)
+ ..pc<$0.PublishOption>(4, _omitFieldNames ? '' : 'publishOptions', $pb.PbFieldType.PM, subBuilder: $0.PublishOption.create)
..hasRequiredFields = false
;
@@ -1624,6 +1768,9 @@ class JoinResponse extends $pb.GeneratedMessage {
$core.bool hasFastReconnectDeadlineSeconds() => $_has(2);
@$pb.TagNumber(3)
void clearFastReconnectDeadlineSeconds() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.List<$0.PublishOption> get publishOptions => $_getList(3);
}
/// ParticipantJoined is fired when a user joins a call
@@ -2272,11 +2419,19 @@ class AudioLevelChanged extends $pb.GeneratedMessage {
class AudioSender extends $pb.GeneratedMessage {
factory AudioSender({
$0.Codec? codec,
+ $0.TrackType? trackType,
+ $core.int? publishOptionId,
}) {
final $result = create();
if (codec != null) {
$result.codec = codec;
}
+ if (trackType != null) {
+ $result.trackType = trackType;
+ }
+ if (publishOptionId != null) {
+ $result.publishOptionId = publishOptionId;
+ }
return $result;
}
AudioSender._() : super();
@@ -2285,6 +2440,8 @@ class AudioSender extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AudioSender', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.event'), createEmptyInstance: create)
..aOM<$0.Codec>(2, _omitFieldNames ? '' : 'codec', subBuilder: $0.Codec.create)
+ ..e<$0.TrackType>(3, _omitFieldNames ? '' : 'trackType', $pb.PbFieldType.OE, defaultOrMaker: $0.TrackType.TRACK_TYPE_UNSPECIFIED, valueOf: $0.TrackType.valueOf, enumValues: $0.TrackType.values)
+ ..a<$core.int>(4, _omitFieldNames ? '' : 'publishOptionId', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@@ -2319,6 +2476,24 @@ class AudioSender extends $pb.GeneratedMessage {
void clearCodec() => clearField(2);
@$pb.TagNumber(2)
$0.Codec ensureCodec() => $_ensure(0);
+
+ @$pb.TagNumber(3)
+ $0.TrackType get trackType => $_getN(1);
+ @$pb.TagNumber(3)
+ set trackType($0.TrackType v) { setField(3, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasTrackType() => $_has(1);
+ @$pb.TagNumber(3)
+ void clearTrackType() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.int get publishOptionId => $_getIZ(2);
+ @$pb.TagNumber(4)
+ set publishOptionId($core.int v) { $_setSignedInt32(2, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasPublishOptionId() => $_has(2);
+ @$pb.TagNumber(4)
+ void clearPublishOptionId() => clearField(4);
}
/// VideoLayerSetting is used to specify various parameters of a particular encoding in simulcast.
@@ -2464,6 +2639,8 @@ class VideoSender extends $pb.GeneratedMessage {
factory VideoSender({
$0.Codec? codec,
$core.Iterable? layers,
+ $0.TrackType? trackType,
+ $core.int? publishOptionId,
}) {
final $result = create();
if (codec != null) {
@@ -2472,6 +2649,12 @@ class VideoSender extends $pb.GeneratedMessage {
if (layers != null) {
$result.layers.addAll(layers);
}
+ if (trackType != null) {
+ $result.trackType = trackType;
+ }
+ if (publishOptionId != null) {
+ $result.publishOptionId = publishOptionId;
+ }
return $result;
}
VideoSender._() : super();
@@ -2481,6 +2664,8 @@ class VideoSender extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'VideoSender', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.event'), createEmptyInstance: create)
..aOM<$0.Codec>(2, _omitFieldNames ? '' : 'codec', subBuilder: $0.Codec.create)
..pc(3, _omitFieldNames ? '' : 'layers', $pb.PbFieldType.PM, subBuilder: VideoLayerSetting.create)
+ ..e<$0.TrackType>(4, _omitFieldNames ? '' : 'trackType', $pb.PbFieldType.OE, defaultOrMaker: $0.TrackType.TRACK_TYPE_UNSPECIFIED, valueOf: $0.TrackType.valueOf, enumValues: $0.TrackType.values)
+ ..a<$core.int>(5, _omitFieldNames ? '' : 'publishOptionId', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@@ -2518,6 +2703,24 @@ class VideoSender extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.List get layers => $_getList(1);
+
+ @$pb.TagNumber(4)
+ $0.TrackType get trackType => $_getN(2);
+ @$pb.TagNumber(4)
+ set trackType($0.TrackType v) { setField(4, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasTrackType() => $_has(2);
+ @$pb.TagNumber(4)
+ void clearTrackType() => clearField(4);
+
+ @$pb.TagNumber(5)
+ $core.int get publishOptionId => $_getIZ(3);
+ @$pb.TagNumber(5)
+ set publishOptionId($core.int v) { $_setSignedInt32(3, v); }
+ @$pb.TagNumber(5)
+ $core.bool hasPublishOptionId() => $_has(3);
+ @$pb.TagNumber(5)
+ void clearPublishOptionId() => clearField(5);
}
/// sent to users when they need to change the quality of their video
diff --git a/packages/stream_video/lib/protobuf/video/sfu/event/events.pbjson.dart b/packages/stream_video/lib/protobuf/video/sfu/event/events.pbjson.dart
index 68a15436b..fc9a91371 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/event/events.pbjson.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/event/events.pbjson.dart
@@ -38,6 +38,7 @@ const SfuEvent$json = {
{'1': 'call_ended', '3': 23, '4': 1, '5': 11, '6': '.stream.video.sfu.event.CallEnded', '9': 0, '10': 'callEnded'},
{'1': 'participant_updated', '3': 24, '4': 1, '5': 11, '6': '.stream.video.sfu.event.ParticipantUpdated', '9': 0, '10': 'participantUpdated'},
{'1': 'participant_migration_complete', '3': 25, '4': 1, '5': 11, '6': '.stream.video.sfu.event.ParticipantMigrationComplete', '9': 0, '10': 'participantMigrationComplete'},
+ {'1': 'change_publish_options', '3': 27, '4': 1, '5': 11, '6': '.stream.video.sfu.event.ChangePublishOptions', '9': 0, '10': 'changePublishOptions'},
],
'8': [
{'1': 'event_payload'},
@@ -79,7 +80,33 @@ final $typed_data.Uint8List sfuEventDescriptor = $convert.base64Decode(
'LmV2ZW50LlBhcnRpY2lwYW50VXBkYXRlZEgAUhJwYXJ0aWNpcGFudFVwZGF0ZWQSfAoecGFydG'
'ljaXBhbnRfbWlncmF0aW9uX2NvbXBsZXRlGBkgASgLMjQuc3RyZWFtLnZpZGVvLnNmdS5ldmVu'
'dC5QYXJ0aWNpcGFudE1pZ3JhdGlvbkNvbXBsZXRlSABSHHBhcnRpY2lwYW50TWlncmF0aW9uQ2'
- '9tcGxldGVCDwoNZXZlbnRfcGF5bG9hZA==');
+ '9tcGxldGUSZAoWY2hhbmdlX3B1Ymxpc2hfb3B0aW9ucxgbIAEoCzIsLnN0cmVhbS52aWRlby5z'
+ 'ZnUuZXZlbnQuQ2hhbmdlUHVibGlzaE9wdGlvbnNIAFIUY2hhbmdlUHVibGlzaE9wdGlvbnNCDw'
+ 'oNZXZlbnRfcGF5bG9hZA==');
+
+@$core.Deprecated('Use changePublishOptionsDescriptor instead')
+const ChangePublishOptions$json = {
+ '1': 'ChangePublishOptions',
+ '2': [
+ {'1': 'publish_options', '3': 1, '4': 3, '5': 11, '6': '.stream.video.sfu.models.PublishOption', '10': 'publishOptions'},
+ {'1': 'reason', '3': 2, '4': 1, '5': 9, '10': 'reason'},
+ ],
+};
+
+/// Descriptor for `ChangePublishOptions`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List changePublishOptionsDescriptor = $convert.base64Decode(
+ 'ChRDaGFuZ2VQdWJsaXNoT3B0aW9ucxJPCg9wdWJsaXNoX29wdGlvbnMYASADKAsyJi5zdHJlYW'
+ '0udmlkZW8uc2Z1Lm1vZGVscy5QdWJsaXNoT3B0aW9uUg5wdWJsaXNoT3B0aW9ucxIWCgZyZWFz'
+ 'b24YAiABKAlSBnJlYXNvbg==');
+
+@$core.Deprecated('Use changePublishOptionsCompleteDescriptor instead')
+const ChangePublishOptionsComplete$json = {
+ '1': 'ChangePublishOptionsComplete',
+};
+
+/// Descriptor for `ChangePublishOptionsComplete`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List changePublishOptionsCompleteDescriptor = $convert.base64Decode(
+ 'ChxDaGFuZ2VQdWJsaXNoT3B0aW9uc0NvbXBsZXRl');
@$core.Deprecated('Use participantMigrationCompleteDescriptor instead')
const ParticipantMigrationComplete$json = {
@@ -249,6 +276,7 @@ const JoinRequest$json = {
{'1': 'token', '3': 1, '4': 1, '5': 9, '10': 'token'},
{'1': 'session_id', '3': 2, '4': 1, '5': 9, '10': 'sessionId'},
{'1': 'subscriber_sdp', '3': 3, '4': 1, '5': 9, '10': 'subscriberSdp'},
+ {'1': 'publisher_sdp', '3': 8, '4': 1, '5': 9, '10': 'publisherSdp'},
{'1': 'client_details', '3': 4, '4': 1, '5': 11, '6': '.stream.video.sfu.models.ClientDetails', '10': 'clientDetails'},
{
'1': 'migration',
@@ -268,18 +296,25 @@ const JoinRequest$json = {
'10': 'fastReconnect',
},
{'1': 'reconnect_details', '3': 7, '4': 1, '5': 11, '6': '.stream.video.sfu.event.ReconnectDetails', '10': 'reconnectDetails'},
+ {'1': 'preferred_publish_options', '3': 9, '4': 3, '5': 11, '6': '.stream.video.sfu.models.PublishOption', '10': 'preferredPublishOptions'},
+ {'1': 'preferred_subscribe_options', '3': 10, '4': 3, '5': 11, '6': '.stream.video.sfu.models.SubscribeOption', '10': 'preferredSubscribeOptions'},
],
};
/// Descriptor for `JoinRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List joinRequestDescriptor = $convert.base64Decode(
'CgtKb2luUmVxdWVzdBIUCgV0b2tlbhgBIAEoCVIFdG9rZW4SHQoKc2Vzc2lvbl9pZBgCIAEoCV'
- 'IJc2Vzc2lvbklkEiUKDnN1YnNjcmliZXJfc2RwGAMgASgJUg1zdWJzY3JpYmVyU2RwEk0KDmNs'
- 'aWVudF9kZXRhaWxzGAQgASgLMiYuc3RyZWFtLnZpZGVvLnNmdS5tb2RlbHMuQ2xpZW50RGV0YW'
- 'lsc1INY2xpZW50RGV0YWlscxJDCgltaWdyYXRpb24YBSABKAsyIS5zdHJlYW0udmlkZW8uc2Z1'
- 'LmV2ZW50Lk1pZ3JhdGlvbkICGAFSCW1pZ3JhdGlvbhIpCg5mYXN0X3JlY29ubmVjdBgGIAEoCE'
- 'ICGAFSDWZhc3RSZWNvbm5lY3QSVQoRcmVjb25uZWN0X2RldGFpbHMYByABKAsyKC5zdHJlYW0u'
- 'dmlkZW8uc2Z1LmV2ZW50LlJlY29ubmVjdERldGFpbHNSEHJlY29ubmVjdERldGFpbHM=');
+ 'IJc2Vzc2lvbklkEiUKDnN1YnNjcmliZXJfc2RwGAMgASgJUg1zdWJzY3JpYmVyU2RwEiMKDXB1'
+ 'Ymxpc2hlcl9zZHAYCCABKAlSDHB1Ymxpc2hlclNkcBJNCg5jbGllbnRfZGV0YWlscxgEIAEoCz'
+ 'ImLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLkNsaWVudERldGFpbHNSDWNsaWVudERldGFpbHMS'
+ 'QwoJbWlncmF0aW9uGAUgASgLMiEuc3RyZWFtLnZpZGVvLnNmdS5ldmVudC5NaWdyYXRpb25CAh'
+ 'gBUgltaWdyYXRpb24SKQoOZmFzdF9yZWNvbm5lY3QYBiABKAhCAhgBUg1mYXN0UmVjb25uZWN0'
+ 'ElUKEXJlY29ubmVjdF9kZXRhaWxzGAcgASgLMiguc3RyZWFtLnZpZGVvLnNmdS5ldmVudC5SZW'
+ 'Nvbm5lY3REZXRhaWxzUhByZWNvbm5lY3REZXRhaWxzEmIKGXByZWZlcnJlZF9wdWJsaXNoX29w'
+ 'dGlvbnMYCSADKAsyJi5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5QdWJsaXNoT3B0aW9uUhdwcm'
+ 'VmZXJyZWRQdWJsaXNoT3B0aW9ucxJoChtwcmVmZXJyZWRfc3Vic2NyaWJlX29wdGlvbnMYCiAD'
+ 'KAsyKC5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5TdWJzY3JpYmVPcHRpb25SGXByZWZlcnJlZF'
+ 'N1YnNjcmliZU9wdGlvbnM=');
@$core.Deprecated('Use reconnectDetailsDescriptor instead')
const ReconnectDetails$json = {
@@ -329,6 +364,7 @@ const JoinResponse$json = {
{'1': 'call_state', '3': 1, '4': 1, '5': 11, '6': '.stream.video.sfu.models.CallState', '10': 'callState'},
{'1': 'reconnected', '3': 2, '4': 1, '5': 8, '10': 'reconnected'},
{'1': 'fast_reconnect_deadline_seconds', '3': 3, '4': 1, '5': 5, '10': 'fastReconnectDeadlineSeconds'},
+ {'1': 'publish_options', '3': 4, '4': 3, '5': 11, '6': '.stream.video.sfu.models.PublishOption', '10': 'publishOptions'},
],
};
@@ -337,7 +373,8 @@ final $typed_data.Uint8List joinResponseDescriptor = $convert.base64Decode(
'CgxKb2luUmVzcG9uc2USQQoKY2FsbF9zdGF0ZRgBIAEoCzIiLnN0cmVhbS52aWRlby5zZnUubW'
'9kZWxzLkNhbGxTdGF0ZVIJY2FsbFN0YXRlEiAKC3JlY29ubmVjdGVkGAIgASgIUgtyZWNvbm5l'
'Y3RlZBJFCh9mYXN0X3JlY29ubmVjdF9kZWFkbGluZV9zZWNvbmRzGAMgASgFUhxmYXN0UmVjb2'
- '5uZWN0RGVhZGxpbmVTZWNvbmRz');
+ '5uZWN0RGVhZGxpbmVTZWNvbmRzEk8KD3B1Ymxpc2hfb3B0aW9ucxgEIAMoCzImLnN0cmVhbS52'
+ 'aWRlby5zZnUubW9kZWxzLlB1Ymxpc2hPcHRpb25SDnB1Ymxpc2hPcHRpb25z');
@$core.Deprecated('Use participantJoinedDescriptor instead')
const ParticipantJoined$json = {
@@ -490,13 +527,17 @@ const AudioSender$json = {
'1': 'AudioSender',
'2': [
{'1': 'codec', '3': 2, '4': 1, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'codec'},
+ {'1': 'track_type', '3': 3, '4': 1, '5': 14, '6': '.stream.video.sfu.models.TrackType', '10': 'trackType'},
+ {'1': 'publish_option_id', '3': 4, '4': 1, '5': 5, '10': 'publishOptionId'},
],
};
/// Descriptor for `AudioSender`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List audioSenderDescriptor = $convert.base64Decode(
'CgtBdWRpb1NlbmRlchI0CgVjb2RlYxgCIAEoCzIeLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLk'
- 'NvZGVjUgVjb2RlYw==');
+ 'NvZGVjUgVjb2RlYxJBCgp0cmFja190eXBlGAMgASgOMiIuc3RyZWFtLnZpZGVvLnNmdS5tb2Rl'
+ 'bHMuVHJhY2tUeXBlUgl0cmFja1R5cGUSKgoRcHVibGlzaF9vcHRpb25faWQYBCABKAVSD3B1Ym'
+ 'xpc2hPcHRpb25JZA==');
@$core.Deprecated('Use videoLayerSettingDescriptor instead')
const VideoLayerSetting$json = {
@@ -527,6 +568,8 @@ const VideoSender$json = {
'2': [
{'1': 'codec', '3': 2, '4': 1, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'codec'},
{'1': 'layers', '3': 3, '4': 3, '5': 11, '6': '.stream.video.sfu.event.VideoLayerSetting', '10': 'layers'},
+ {'1': 'track_type', '3': 4, '4': 1, '5': 14, '6': '.stream.video.sfu.models.TrackType', '10': 'trackType'},
+ {'1': 'publish_option_id', '3': 5, '4': 1, '5': 5, '10': 'publishOptionId'},
],
};
@@ -534,7 +577,9 @@ const VideoSender$json = {
final $typed_data.Uint8List videoSenderDescriptor = $convert.base64Decode(
'CgtWaWRlb1NlbmRlchI0CgVjb2RlYxgCIAEoCzIeLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLk'
'NvZGVjUgVjb2RlYxJBCgZsYXllcnMYAyADKAsyKS5zdHJlYW0udmlkZW8uc2Z1LmV2ZW50LlZp'
- 'ZGVvTGF5ZXJTZXR0aW5nUgZsYXllcnM=');
+ 'ZGVvTGF5ZXJTZXR0aW5nUgZsYXllcnMSQQoKdHJhY2tfdHlwZRgEIAEoDjIiLnN0cmVhbS52aW'
+ 'Rlby5zZnUubW9kZWxzLlRyYWNrVHlwZVIJdHJhY2tUeXBlEioKEXB1Ymxpc2hfb3B0aW9uX2lk'
+ 'GAUgASgFUg9wdWJsaXNoT3B0aW9uSWQ=');
@$core.Deprecated('Use changePublishQualityDescriptor instead')
const ChangePublishQuality$json = {
diff --git a/packages/stream_video/lib/protobuf/video/sfu/models/models.pb.dart b/packages/stream_video/lib/protobuf/video/sfu/models/models.pb.dart
index 5a5a42589..b27dbd3d5 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/models/models.pb.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/models/models.pb.dart
@@ -697,22 +697,275 @@ class VideoLayer extends $pb.GeneratedMessage {
void clearQuality() => clearField(6);
}
+/// SubscribeOption represents the configuration options for subscribing to a track.
+class SubscribeOption extends $pb.GeneratedMessage {
+ factory SubscribeOption({
+ TrackType? trackType,
+ $core.Iterable? codecs,
+ }) {
+ final $result = create();
+ if (trackType != null) {
+ $result.trackType = trackType;
+ }
+ if (codecs != null) {
+ $result.codecs.addAll(codecs);
+ }
+ return $result;
+ }
+ SubscribeOption._() : super();
+ factory SubscribeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory SubscribeOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SubscribeOption', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.models'), createEmptyInstance: create)
+ ..e(1, _omitFieldNames ? '' : 'trackType', $pb.PbFieldType.OE, defaultOrMaker: TrackType.TRACK_TYPE_UNSPECIFIED, valueOf: TrackType.valueOf, enumValues: TrackType.values)
+ ..pc(2, _omitFieldNames ? '' : 'codecs', $pb.PbFieldType.PM, subBuilder: Codec.create)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ SubscribeOption clone() => SubscribeOption()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ SubscribeOption copyWith(void Function(SubscribeOption) updates) => super.copyWith((message) => updates(message as SubscribeOption)) as SubscribeOption;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static SubscribeOption create() => SubscribeOption._();
+ SubscribeOption createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static SubscribeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static SubscribeOption? _defaultInstance;
+
+ /// The type of the track being subscribed (e.g., video, screenshare).
+ @$pb.TagNumber(1)
+ TrackType get trackType => $_getN(0);
+ @$pb.TagNumber(1)
+ set trackType(TrackType v) { setField(1, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasTrackType() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearTrackType() => clearField(1);
+
+ /// The codecs supported by the subscriber for decoding tracks.
+ @$pb.TagNumber(2)
+ $core.List get codecs => $_getList(1);
+}
+
+/// PublishOption represents the configuration options for publishing a track.
+class PublishOption extends $pb.GeneratedMessage {
+ factory PublishOption({
+ TrackType? trackType,
+ Codec? codec,
+ $core.int? bitrate,
+ $core.int? fps,
+ $core.int? maxSpatialLayers,
+ $core.int? maxTemporalLayers,
+ VideoDimension? videoDimension,
+ $core.int? id,
+ }) {
+ final $result = create();
+ if (trackType != null) {
+ $result.trackType = trackType;
+ }
+ if (codec != null) {
+ $result.codec = codec;
+ }
+ if (bitrate != null) {
+ $result.bitrate = bitrate;
+ }
+ if (fps != null) {
+ $result.fps = fps;
+ }
+ if (maxSpatialLayers != null) {
+ $result.maxSpatialLayers = maxSpatialLayers;
+ }
+ if (maxTemporalLayers != null) {
+ $result.maxTemporalLayers = maxTemporalLayers;
+ }
+ if (videoDimension != null) {
+ $result.videoDimension = videoDimension;
+ }
+ if (id != null) {
+ $result.id = id;
+ }
+ return $result;
+ }
+ PublishOption._() : super();
+ factory PublishOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory PublishOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PublishOption', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.models'), createEmptyInstance: create)
+ ..e(1, _omitFieldNames ? '' : 'trackType', $pb.PbFieldType.OE, defaultOrMaker: TrackType.TRACK_TYPE_UNSPECIFIED, valueOf: TrackType.valueOf, enumValues: TrackType.values)
+ ..aOM(2, _omitFieldNames ? '' : 'codec', subBuilder: Codec.create)
+ ..a<$core.int>(3, _omitFieldNames ? '' : 'bitrate', $pb.PbFieldType.O3)
+ ..a<$core.int>(4, _omitFieldNames ? '' : 'fps', $pb.PbFieldType.O3)
+ ..a<$core.int>(5, _omitFieldNames ? '' : 'maxSpatialLayers', $pb.PbFieldType.O3)
+ ..a<$core.int>(6, _omitFieldNames ? '' : 'maxTemporalLayers', $pb.PbFieldType.O3)
+ ..aOM(7, _omitFieldNames ? '' : 'videoDimension', subBuilder: VideoDimension.create)
+ ..a<$core.int>(8, _omitFieldNames ? '' : 'id', $pb.PbFieldType.O3)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ PublishOption clone() => PublishOption()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ PublishOption copyWith(void Function(PublishOption) updates) => super.copyWith((message) => updates(message as PublishOption)) as PublishOption;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static PublishOption create() => PublishOption._();
+ PublishOption createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static PublishOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static PublishOption? _defaultInstance;
+
+ /// The type of the track being published (e.g., video, screenshare).
+ @$pb.TagNumber(1)
+ TrackType get trackType => $_getN(0);
+ @$pb.TagNumber(1)
+ set trackType(TrackType v) { setField(1, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasTrackType() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearTrackType() => clearField(1);
+
+ /// The codec to be used for encoding the track (e.g., VP8, VP9, H264).
+ @$pb.TagNumber(2)
+ Codec get codec => $_getN(1);
+ @$pb.TagNumber(2)
+ set codec(Codec v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasCodec() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearCodec() => clearField(2);
+ @$pb.TagNumber(2)
+ Codec ensureCodec() => $_ensure(1);
+
+ /// The target bitrate for the published track, in bits per second.
+ @$pb.TagNumber(3)
+ $core.int get bitrate => $_getIZ(2);
+ @$pb.TagNumber(3)
+ set bitrate($core.int v) { $_setSignedInt32(2, v); }
+ @$pb.TagNumber(3)
+ $core.bool hasBitrate() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearBitrate() => clearField(3);
+
+ /// The target frames per second (FPS) for video encoding.
+ @$pb.TagNumber(4)
+ $core.int get fps => $_getIZ(3);
+ @$pb.TagNumber(4)
+ set fps($core.int v) { $_setSignedInt32(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasFps() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearFps() => clearField(4);
+
+ /// The maximum number of spatial layers to send.
+ /// - For SVC (e.g., VP9), spatial layers downscale by a factor of 2:
+ /// - 1 layer: full resolution
+ /// - 2 layers: full resolution + half resolution
+ /// - 3 layers: full resolution + half resolution + quarter resolution
+ /// - For non-SVC codecs (e.g., VP8/H264), this determines the number of
+ /// encoded resolutions (e.g., quarter, half, full) sent for simulcast.
+ @$pb.TagNumber(5)
+ $core.int get maxSpatialLayers => $_getIZ(4);
+ @$pb.TagNumber(5)
+ set maxSpatialLayers($core.int v) { $_setSignedInt32(4, v); }
+ @$pb.TagNumber(5)
+ $core.bool hasMaxSpatialLayers() => $_has(4);
+ @$pb.TagNumber(5)
+ void clearMaxSpatialLayers() => clearField(5);
+
+ /// The maximum number of temporal layers for scalable video coding (SVC).
+ /// Temporal layers allow varying frame rates for different bandwidths.
+ @$pb.TagNumber(6)
+ $core.int get maxTemporalLayers => $_getIZ(5);
+ @$pb.TagNumber(6)
+ set maxTemporalLayers($core.int v) { $_setSignedInt32(5, v); }
+ @$pb.TagNumber(6)
+ $core.bool hasMaxTemporalLayers() => $_has(5);
+ @$pb.TagNumber(6)
+ void clearMaxTemporalLayers() => clearField(6);
+
+ /// The dimensions of the video (e.g., width and height in pixels).
+ /// Spatial layers are based on this base resolution. For example, if the base
+ /// resolution is 1280x720:
+ /// - Full resolution (1 layer) = 1280x720
+ /// - Half resolution (2 layers) = 640x360
+ /// - Quarter resolution (3 layers) = 320x180
+ @$pb.TagNumber(7)
+ VideoDimension get videoDimension => $_getN(6);
+ @$pb.TagNumber(7)
+ set videoDimension(VideoDimension v) { setField(7, v); }
+ @$pb.TagNumber(7)
+ $core.bool hasVideoDimension() => $_has(6);
+ @$pb.TagNumber(7)
+ void clearVideoDimension() => clearField(7);
+ @$pb.TagNumber(7)
+ VideoDimension ensureVideoDimension() => $_ensure(6);
+
+ /// The unique identifier for the publish request.
+ /// - This `id` is assigned exclusively by the SFU. Any `id` set by the client
+ /// in the `PublishOption` will be ignored and overwritten by the SFU.
+ /// - The primary purpose of this `id` is to uniquely identify each publish
+ /// request, even in scenarios where multiple publish requests for the same
+ /// `track_type` and `codec` are active simultaneously.
+ /// For example:
+ /// - A user may publish two tracks of the same type (e.g., video) and codec
+ /// (e.g., VP9) concurrently.
+ /// - This uniqueness ensures that individual requests can be managed
+ /// independently. For instance, an `id` is critical when stopping a specific
+ /// publish request without affecting others.
+ @$pb.TagNumber(8)
+ $core.int get id => $_getIZ(7);
+ @$pb.TagNumber(8)
+ set id($core.int v) { $_setSignedInt32(7, v); }
+ @$pb.TagNumber(8)
+ $core.bool hasId() => $_has(7);
+ @$pb.TagNumber(8)
+ void clearId() => clearField(8);
+}
+
class Codec extends $pb.GeneratedMessage {
factory Codec({
- $core.String? mimeType,
- $core.String? scalabilityMode,
+ $core.String? name,
$core.String? fmtp,
+ $core.int? clockRate,
+ $core.String? encodingParameters,
+ $core.int? payloadType,
}) {
final $result = create();
- if (mimeType != null) {
- $result.mimeType = mimeType;
- }
- if (scalabilityMode != null) {
- $result.scalabilityMode = scalabilityMode;
+ if (name != null) {
+ $result.name = name;
}
if (fmtp != null) {
$result.fmtp = fmtp;
}
+ if (clockRate != null) {
+ $result.clockRate = clockRate;
+ }
+ if (encodingParameters != null) {
+ $result.encodingParameters = encodingParameters;
+ }
+ if (payloadType != null) {
+ $result.payloadType = payloadType;
+ }
return $result;
}
Codec._() : super();
@@ -720,9 +973,11 @@ class Codec extends $pb.GeneratedMessage {
factory Codec.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Codec', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.models'), createEmptyInstance: create)
- ..aOS(1, _omitFieldNames ? '' : 'mimeType')
- ..aOS(2, _omitFieldNames ? '' : 'scalabilityMode')
- ..aOS(3, _omitFieldNames ? '' : 'fmtp')
+ ..aOS(10, _omitFieldNames ? '' : 'name')
+ ..aOS(12, _omitFieldNames ? '' : 'fmtp')
+ ..a<$core.int>(14, _omitFieldNames ? '' : 'clockRate', $pb.PbFieldType.OU3)
+ ..aOS(15, _omitFieldNames ? '' : 'encodingParameters')
+ ..a<$core.int>(16, _omitFieldNames ? '' : 'payloadType', $pb.PbFieldType.OU3)
..hasRequiredFields = false
;
@@ -747,32 +1002,50 @@ class Codec extends $pb.GeneratedMessage {
static Codec getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
static Codec? _defaultInstance;
- @$pb.TagNumber(1)
- $core.String get mimeType => $_getSZ(0);
- @$pb.TagNumber(1)
- set mimeType($core.String v) { $_setString(0, v); }
- @$pb.TagNumber(1)
- $core.bool hasMimeType() => $_has(0);
- @$pb.TagNumber(1)
- void clearMimeType() => clearField(1);
-
- @$pb.TagNumber(2)
- $core.String get scalabilityMode => $_getSZ(1);
- @$pb.TagNumber(2)
- set scalabilityMode($core.String v) { $_setString(1, v); }
- @$pb.TagNumber(2)
- $core.bool hasScalabilityMode() => $_has(1);
- @$pb.TagNumber(2)
- void clearScalabilityMode() => clearField(2);
+ @$pb.TagNumber(10)
+ $core.String get name => $_getSZ(0);
+ @$pb.TagNumber(10)
+ set name($core.String v) { $_setString(0, v); }
+ @$pb.TagNumber(10)
+ $core.bool hasName() => $_has(0);
+ @$pb.TagNumber(10)
+ void clearName() => clearField(10);
- @$pb.TagNumber(3)
- $core.String get fmtp => $_getSZ(2);
- @$pb.TagNumber(3)
- set fmtp($core.String v) { $_setString(2, v); }
- @$pb.TagNumber(3)
- $core.bool hasFmtp() => $_has(2);
- @$pb.TagNumber(3)
- void clearFmtp() => clearField(3);
+ @$pb.TagNumber(12)
+ $core.String get fmtp => $_getSZ(1);
+ @$pb.TagNumber(12)
+ set fmtp($core.String v) { $_setString(1, v); }
+ @$pb.TagNumber(12)
+ $core.bool hasFmtp() => $_has(1);
+ @$pb.TagNumber(12)
+ void clearFmtp() => clearField(12);
+
+ @$pb.TagNumber(14)
+ $core.int get clockRate => $_getIZ(2);
+ @$pb.TagNumber(14)
+ set clockRate($core.int v) { $_setUnsignedInt32(2, v); }
+ @$pb.TagNumber(14)
+ $core.bool hasClockRate() => $_has(2);
+ @$pb.TagNumber(14)
+ void clearClockRate() => clearField(14);
+
+ @$pb.TagNumber(15)
+ $core.String get encodingParameters => $_getSZ(3);
+ @$pb.TagNumber(15)
+ set encodingParameters($core.String v) { $_setString(3, v); }
+ @$pb.TagNumber(15)
+ $core.bool hasEncodingParameters() => $_has(3);
+ @$pb.TagNumber(15)
+ void clearEncodingParameters() => clearField(15);
+
+ @$pb.TagNumber(16)
+ $core.int get payloadType => $_getIZ(4);
+ @$pb.TagNumber(16)
+ set payloadType($core.int v) { $_setUnsignedInt32(4, v); }
+ @$pb.TagNumber(16)
+ $core.bool hasPayloadType() => $_has(4);
+ @$pb.TagNumber(16)
+ void clearPayloadType() => clearField(16);
}
class ICETrickle extends $pb.GeneratedMessage {
@@ -863,7 +1136,8 @@ class TrackInfo extends $pb.GeneratedMessage {
$core.bool? stereo,
$core.bool? red,
$core.bool? muted,
- $core.Iterable? preferredCodecs,
+ Codec? codec,
+ $core.int? publishOptionId,
}) {
final $result = create();
if (trackId != null) {
@@ -890,8 +1164,11 @@ class TrackInfo extends $pb.GeneratedMessage {
if (muted != null) {
$result.muted = muted;
}
- if (preferredCodecs != null) {
- $result.preferredCodecs.addAll(preferredCodecs);
+ if (codec != null) {
+ $result.codec = codec;
+ }
+ if (publishOptionId != null) {
+ $result.publishOptionId = publishOptionId;
}
return $result;
}
@@ -908,7 +1185,8 @@ class TrackInfo extends $pb.GeneratedMessage {
..aOB(8, _omitFieldNames ? '' : 'stereo')
..aOB(9, _omitFieldNames ? '' : 'red')
..aOB(10, _omitFieldNames ? '' : 'muted')
- ..pc(11, _omitFieldNames ? '' : 'preferredCodecs', $pb.PbFieldType.PM, subBuilder: Codec.create)
+ ..aOM(11, _omitFieldNames ? '' : 'codec', subBuilder: Codec.create)
+ ..a<$core.int>(12, _omitFieldNames ? '' : 'publishOptionId', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@@ -1001,7 +1279,24 @@ class TrackInfo extends $pb.GeneratedMessage {
void clearMuted() => clearField(10);
@$pb.TagNumber(11)
- $core.List get preferredCodecs => $_getList(8);
+ Codec get codec => $_getN(8);
+ @$pb.TagNumber(11)
+ set codec(Codec v) { setField(11, v); }
+ @$pb.TagNumber(11)
+ $core.bool hasCodec() => $_has(8);
+ @$pb.TagNumber(11)
+ void clearCodec() => clearField(11);
+ @$pb.TagNumber(11)
+ Codec ensureCodec() => $_ensure(8);
+
+ @$pb.TagNumber(12)
+ $core.int get publishOptionId => $_getIZ(9);
+ @$pb.TagNumber(12)
+ set publishOptionId($core.int v) { $_setSignedInt32(9, v); }
+ @$pb.TagNumber(12)
+ $core.bool hasPublishOptionId() => $_has(9);
+ @$pb.TagNumber(12)
+ void clearPublishOptionId() => clearField(12);
}
class Error extends $pb.GeneratedMessage {
diff --git a/packages/stream_video/lib/protobuf/video/sfu/models/models.pbjson.dart b/packages/stream_video/lib/protobuf/video/sfu/models/models.pbjson.dart
index 1ff5d3d93..815d412d3 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/models/models.pbjson.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/models/models.pbjson.dart
@@ -394,20 +394,63 @@ final $typed_data.Uint8List videoLayerDescriptor = $convert.base64Decode(
'bhIYCgdiaXRyYXRlGAQgASgNUgdiaXRyYXRlEhAKA2ZwcxgFIAEoDVIDZnBzEj8KB3F1YWxpdH'
'kYBiABKA4yJS5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5WaWRlb1F1YWxpdHlSB3F1YWxpdHk=');
+@$core.Deprecated('Use subscribeOptionDescriptor instead')
+const SubscribeOption$json = {
+ '1': 'SubscribeOption',
+ '2': [
+ {'1': 'track_type', '3': 1, '4': 1, '5': 14, '6': '.stream.video.sfu.models.TrackType', '10': 'trackType'},
+ {'1': 'codecs', '3': 2, '4': 3, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'codecs'},
+ ],
+};
+
+/// Descriptor for `SubscribeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List subscribeOptionDescriptor = $convert.base64Decode(
+ 'Cg9TdWJzY3JpYmVPcHRpb24SQQoKdHJhY2tfdHlwZRgBIAEoDjIiLnN0cmVhbS52aWRlby5zZn'
+ 'UubW9kZWxzLlRyYWNrVHlwZVIJdHJhY2tUeXBlEjYKBmNvZGVjcxgCIAMoCzIeLnN0cmVhbS52'
+ 'aWRlby5zZnUubW9kZWxzLkNvZGVjUgZjb2RlY3M=');
+
+@$core.Deprecated('Use publishOptionDescriptor instead')
+const PublishOption$json = {
+ '1': 'PublishOption',
+ '2': [
+ {'1': 'track_type', '3': 1, '4': 1, '5': 14, '6': '.stream.video.sfu.models.TrackType', '10': 'trackType'},
+ {'1': 'codec', '3': 2, '4': 1, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'codec'},
+ {'1': 'bitrate', '3': 3, '4': 1, '5': 5, '10': 'bitrate'},
+ {'1': 'fps', '3': 4, '4': 1, '5': 5, '10': 'fps'},
+ {'1': 'max_spatial_layers', '3': 5, '4': 1, '5': 5, '10': 'maxSpatialLayers'},
+ {'1': 'max_temporal_layers', '3': 6, '4': 1, '5': 5, '10': 'maxTemporalLayers'},
+ {'1': 'video_dimension', '3': 7, '4': 1, '5': 11, '6': '.stream.video.sfu.models.VideoDimension', '10': 'videoDimension'},
+ {'1': 'id', '3': 8, '4': 1, '5': 5, '10': 'id'},
+ ],
+};
+
+/// Descriptor for `PublishOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List publishOptionDescriptor = $convert.base64Decode(
+ 'Cg1QdWJsaXNoT3B0aW9uEkEKCnRyYWNrX3R5cGUYASABKA4yIi5zdHJlYW0udmlkZW8uc2Z1Lm'
+ '1vZGVscy5UcmFja1R5cGVSCXRyYWNrVHlwZRI0CgVjb2RlYxgCIAEoCzIeLnN0cmVhbS52aWRl'
+ 'by5zZnUubW9kZWxzLkNvZGVjUgVjb2RlYxIYCgdiaXRyYXRlGAMgASgFUgdiaXRyYXRlEhAKA2'
+ 'ZwcxgEIAEoBVIDZnBzEiwKEm1heF9zcGF0aWFsX2xheWVycxgFIAEoBVIQbWF4U3BhdGlhbExh'
+ 'eWVycxIuChNtYXhfdGVtcG9yYWxfbGF5ZXJzGAYgASgFUhFtYXhUZW1wb3JhbExheWVycxJQCg'
+ '92aWRlb19kaW1lbnNpb24YByABKAsyJy5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5WaWRlb0Rp'
+ 'bWVuc2lvblIOdmlkZW9EaW1lbnNpb24SDgoCaWQYCCABKAVSAmlk');
+
@$core.Deprecated('Use codecDescriptor instead')
const Codec$json = {
'1': 'Codec',
'2': [
- {'1': 'mime_type', '3': 1, '4': 1, '5': 9, '10': 'mimeType'},
- {'1': 'scalability_mode', '3': 2, '4': 1, '5': 9, '10': 'scalabilityMode'},
- {'1': 'fmtp', '3': 3, '4': 1, '5': 9, '10': 'fmtp'},
+ {'1': 'payload_type', '3': 16, '4': 1, '5': 13, '10': 'payloadType'},
+ {'1': 'name', '3': 10, '4': 1, '5': 9, '10': 'name'},
+ {'1': 'clock_rate', '3': 14, '4': 1, '5': 13, '10': 'clockRate'},
+ {'1': 'encoding_parameters', '3': 15, '4': 1, '5': 9, '10': 'encodingParameters'},
+ {'1': 'fmtp', '3': 12, '4': 1, '5': 9, '10': 'fmtp'},
],
};
/// Descriptor for `Codec`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List codecDescriptor = $convert.base64Decode(
- 'CgVDb2RlYxIbCgltaW1lX3R5cGUYASABKAlSCG1pbWVUeXBlEikKEHNjYWxhYmlsaXR5X21vZG'
- 'UYAiABKAlSD3NjYWxhYmlsaXR5TW9kZRISCgRmbXRwGAMgASgJUgRmbXRw');
+ 'CgVDb2RlYxIhCgxwYXlsb2FkX3R5cGUYECABKA1SC3BheWxvYWRUeXBlEhIKBG5hbWUYCiABKA'
+ 'lSBG5hbWUSHQoKY2xvY2tfcmF0ZRgOIAEoDVIJY2xvY2tSYXRlEi8KE2VuY29kaW5nX3BhcmFt'
+ 'ZXRlcnMYDyABKAlSEmVuY29kaW5nUGFyYW1ldGVycxISCgRmbXRwGAwgASgJUgRmbXRw');
@$core.Deprecated('Use iCETrickleDescriptor instead')
const ICETrickle$json = {
@@ -437,7 +480,8 @@ const TrackInfo$json = {
{'1': 'stereo', '3': 8, '4': 1, '5': 8, '10': 'stereo'},
{'1': 'red', '3': 9, '4': 1, '5': 8, '10': 'red'},
{'1': 'muted', '3': 10, '4': 1, '5': 8, '10': 'muted'},
- {'1': 'preferred_codecs', '3': 11, '4': 3, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'preferredCodecs'},
+ {'1': 'codec', '3': 11, '4': 1, '5': 11, '6': '.stream.video.sfu.models.Codec', '10': 'codec'},
+ {'1': 'publish_option_id', '3': 12, '4': 1, '5': 5, '10': 'publishOptionId'},
],
};
@@ -447,9 +491,9 @@ final $typed_data.Uint8List trackInfoDescriptor = $convert.base64Decode(
'EoDjIiLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLlRyYWNrVHlwZVIJdHJhY2tUeXBlEjsKBmxh'
'eWVycxgFIAMoCzIjLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLlZpZGVvTGF5ZXJSBmxheWVycx'
'IQCgNtaWQYBiABKAlSA21pZBIQCgNkdHgYByABKAhSA2R0eBIWCgZzdGVyZW8YCCABKAhSBnN0'
- 'ZXJlbxIQCgNyZWQYCSABKAhSA3JlZBIUCgVtdXRlZBgKIAEoCFIFbXV0ZWQSSQoQcHJlZmVycm'
- 'VkX2NvZGVjcxgLIAMoCzIeLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLkNvZGVjUg9wcmVmZXJy'
- 'ZWRDb2RlY3M=');
+ 'ZXJlbxIQCgNyZWQYCSABKAhSA3JlZBIUCgVtdXRlZBgKIAEoCFIFbXV0ZWQSNAoFY29kZWMYCy'
+ 'ABKAsyHi5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5Db2RlY1IFY29kZWMSKgoRcHVibGlzaF9v'
+ 'cHRpb25faWQYDCABKAVSD3B1Ymxpc2hPcHRpb25JZA==');
@$core.Deprecated('Use errorDescriptor instead')
const Error$json = {
diff --git a/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pb.dart b/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pb.dart
index e8ac65efc..097a8d015 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pb.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pb.dart
@@ -221,6 +221,151 @@ class StopNoiseCancellationResponse extends $pb.GeneratedMessage {
$0.Error ensureError() => $_ensure(0);
}
+class Reconnection extends $pb.GeneratedMessage {
+ factory Reconnection({
+ $core.double? timeSeconds,
+ $0.WebsocketReconnectStrategy? strategy,
+ }) {
+ final $result = create();
+ if (timeSeconds != null) {
+ $result.timeSeconds = timeSeconds;
+ }
+ if (strategy != null) {
+ $result.strategy = strategy;
+ }
+ return $result;
+ }
+ Reconnection._() : super();
+ factory Reconnection.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory Reconnection.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Reconnection', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.signal'), createEmptyInstance: create)
+ ..a<$core.double>(1, _omitFieldNames ? '' : 'timeSeconds', $pb.PbFieldType.OF)
+ ..e<$0.WebsocketReconnectStrategy>(2, _omitFieldNames ? '' : 'strategy', $pb.PbFieldType.OE, defaultOrMaker: $0.WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, valueOf: $0.WebsocketReconnectStrategy.valueOf, enumValues: $0.WebsocketReconnectStrategy.values)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ Reconnection clone() => Reconnection()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ Reconnection copyWith(void Function(Reconnection) updates) => super.copyWith((message) => updates(message as Reconnection)) as Reconnection;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static Reconnection create() => Reconnection._();
+ Reconnection createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static Reconnection getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static Reconnection? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.double get timeSeconds => $_getN(0);
+ @$pb.TagNumber(1)
+ set timeSeconds($core.double v) { $_setFloat(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasTimeSeconds() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearTimeSeconds() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $0.WebsocketReconnectStrategy get strategy => $_getN(1);
+ @$pb.TagNumber(2)
+ set strategy($0.WebsocketReconnectStrategy v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasStrategy() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearStrategy() => clearField(2);
+}
+
+enum Telemetry_Data {
+ connectionTimeSeconds,
+ reconnection,
+ notSet
+}
+
+class Telemetry extends $pb.GeneratedMessage {
+ factory Telemetry({
+ $core.double? connectionTimeSeconds,
+ Reconnection? reconnection,
+ }) {
+ final $result = create();
+ if (connectionTimeSeconds != null) {
+ $result.connectionTimeSeconds = connectionTimeSeconds;
+ }
+ if (reconnection != null) {
+ $result.reconnection = reconnection;
+ }
+ return $result;
+ }
+ Telemetry._() : super();
+ factory Telemetry.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+ factory Telemetry.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+ static const $core.Map<$core.int, Telemetry_Data> _Telemetry_DataByTag = {
+ 1 : Telemetry_Data.connectionTimeSeconds,
+ 2 : Telemetry_Data.reconnection,
+ 0 : Telemetry_Data.notSet
+ };
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Telemetry', package: const $pb.PackageName(_omitMessageNames ? '' : 'stream.video.sfu.signal'), createEmptyInstance: create)
+ ..oo(0, [1, 2])
+ ..a<$core.double>(1, _omitFieldNames ? '' : 'connectionTimeSeconds', $pb.PbFieldType.OF)
+ ..aOM(2, _omitFieldNames ? '' : 'reconnection', subBuilder: Reconnection.create)
+ ..hasRequiredFields = false
+ ;
+
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ Telemetry clone() => Telemetry()..mergeFromMessage(this);
+ @$core.Deprecated(
+ 'Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ Telemetry copyWith(void Function(Telemetry) updates) => super.copyWith((message) => updates(message as Telemetry)) as Telemetry;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static Telemetry create() => Telemetry._();
+ Telemetry createEmptyInstance() => create();
+ static $pb.PbList createRepeated() => $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static Telemetry getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create);
+ static Telemetry? _defaultInstance;
+
+ Telemetry_Data whichData() => _Telemetry_DataByTag[$_whichOneof(0)]!;
+ void clearData() => clearField($_whichOneof(0));
+
+ @$pb.TagNumber(1)
+ $core.double get connectionTimeSeconds => $_getN(0);
+ @$pb.TagNumber(1)
+ set connectionTimeSeconds($core.double v) { $_setFloat(0, v); }
+ @$pb.TagNumber(1)
+ $core.bool hasConnectionTimeSeconds() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearConnectionTimeSeconds() => clearField(1);
+
+ @$pb.TagNumber(2)
+ Reconnection get reconnection => $_getN(1);
+ @$pb.TagNumber(2)
+ set reconnection(Reconnection v) { setField(2, v); }
+ @$pb.TagNumber(2)
+ $core.bool hasReconnection() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearReconnection() => clearField(2);
+ @$pb.TagNumber(2)
+ Reconnection ensureReconnection() => $_ensure(1);
+}
+
enum SendStatsRequest_DeviceState {
android,
apple,
@@ -239,6 +384,7 @@ class SendStatsRequest extends $pb.GeneratedMessage {
$0.InputDevices? videoDevices,
$0.AndroidState? android,
$0.AppleState? apple,
+ Telemetry? telemetry,
}) {
final $result = create();
if (sessionId != null) {
@@ -271,6 +417,9 @@ class SendStatsRequest extends $pb.GeneratedMessage {
if (apple != null) {
$result.apple = apple;
}
+ if (telemetry != null) {
+ $result.telemetry = telemetry;
+ }
return $result;
}
SendStatsRequest._() : super();
@@ -294,6 +443,7 @@ class SendStatsRequest extends $pb.GeneratedMessage {
..aOM<$0.InputDevices>(8, _omitFieldNames ? '' : 'videoDevices', subBuilder: $0.InputDevices.create)
..aOM<$0.AndroidState>(9, _omitFieldNames ? '' : 'android', subBuilder: $0.AndroidState.create)
..aOM<$0.AppleState>(10, _omitFieldNames ? '' : 'apple', subBuilder: $0.AppleState.create)
+ ..aOM(11, _omitFieldNames ? '' : 'telemetry', subBuilder: Telemetry.create)
..hasRequiredFields = false
;
@@ -418,6 +568,17 @@ class SendStatsRequest extends $pb.GeneratedMessage {
void clearApple() => clearField(10);
@$pb.TagNumber(10)
$0.AppleState ensureApple() => $_ensure(9);
+
+ @$pb.TagNumber(11)
+ Telemetry get telemetry => $_getN(10);
+ @$pb.TagNumber(11)
+ set telemetry(Telemetry v) { setField(11, v); }
+ @$pb.TagNumber(11)
+ $core.bool hasTelemetry() => $_has(10);
+ @$pb.TagNumber(11)
+ void clearTelemetry() => clearField(11);
+ @$pb.TagNumber(11)
+ Telemetry ensureTelemetry() => $_ensure(10);
}
class SendStatsResponse extends $pb.GeneratedMessage {
diff --git a/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pbjson.dart b/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pbjson.dart
index 867c7f29b..7cf5b4ae0 100644
--- a/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pbjson.dart
+++ b/packages/stream_video/lib/protobuf/video/sfu/signal_rpc/signal.pbjson.dart
@@ -67,6 +67,39 @@ final $typed_data.Uint8List stopNoiseCancellationResponseDescriptor = $convert.b
'Ch1TdG9wTm9pc2VDYW5jZWxsYXRpb25SZXNwb25zZRI0CgVlcnJvchgBIAEoCzIeLnN0cmVhbS'
'52aWRlby5zZnUubW9kZWxzLkVycm9yUgVlcnJvcg==');
+@$core.Deprecated('Use reconnectionDescriptor instead')
+const Reconnection$json = {
+ '1': 'Reconnection',
+ '2': [
+ {'1': 'time_seconds', '3': 1, '4': 1, '5': 2, '10': 'timeSeconds'},
+ {'1': 'strategy', '3': 2, '4': 1, '5': 14, '6': '.stream.video.sfu.models.WebsocketReconnectStrategy', '10': 'strategy'},
+ ],
+};
+
+/// Descriptor for `Reconnection`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List reconnectionDescriptor = $convert.base64Decode(
+ 'CgxSZWNvbm5lY3Rpb24SIQoMdGltZV9zZWNvbmRzGAEgASgCUgt0aW1lU2Vjb25kcxJPCghzdH'
+ 'JhdGVneRgCIAEoDjIzLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLldlYnNvY2tldFJlY29ubmVj'
+ 'dFN0cmF0ZWd5UghzdHJhdGVneQ==');
+
+@$core.Deprecated('Use telemetryDescriptor instead')
+const Telemetry$json = {
+ '1': 'Telemetry',
+ '2': [
+ {'1': 'connection_time_seconds', '3': 1, '4': 1, '5': 2, '9': 0, '10': 'connectionTimeSeconds'},
+ {'1': 'reconnection', '3': 2, '4': 1, '5': 11, '6': '.stream.video.sfu.signal.Reconnection', '9': 0, '10': 'reconnection'},
+ ],
+ '8': [
+ {'1': 'data'},
+ ],
+};
+
+/// Descriptor for `Telemetry`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List telemetryDescriptor = $convert.base64Decode(
+ 'CglUZWxlbWV0cnkSOAoXY29ubmVjdGlvbl90aW1lX3NlY29uZHMYASABKAJIAFIVY29ubmVjdG'
+ 'lvblRpbWVTZWNvbmRzEksKDHJlY29ubmVjdGlvbhgCIAEoCzIlLnN0cmVhbS52aWRlby5zZnUu'
+ 'c2lnbmFsLlJlY29ubmVjdGlvbkgAUgxyZWNvbm5lY3Rpb25CBgoEZGF0YQ==');
+
@$core.Deprecated('Use sendStatsRequestDescriptor instead')
const SendStatsRequest$json = {
'1': 'SendStatsRequest',
@@ -81,6 +114,7 @@ const SendStatsRequest$json = {
{'1': 'video_devices', '3': 8, '4': 1, '5': 11, '6': '.stream.video.sfu.models.InputDevices', '10': 'videoDevices'},
{'1': 'android', '3': 9, '4': 1, '5': 11, '6': '.stream.video.sfu.models.AndroidState', '9': 0, '10': 'android'},
{'1': 'apple', '3': 10, '4': 1, '5': 11, '6': '.stream.video.sfu.models.AppleState', '9': 0, '10': 'apple'},
+ {'1': 'telemetry', '3': 11, '4': 1, '5': 11, '6': '.stream.video.sfu.signal.Telemetry', '10': 'telemetry'},
],
'8': [
{'1': 'device_state'},
@@ -98,7 +132,8 @@ final $typed_data.Uint8List sendStatsRequestDescriptor = $convert.base64Decode(
'aWRlby5zZnUubW9kZWxzLklucHV0RGV2aWNlc1IMdmlkZW9EZXZpY2VzEkEKB2FuZHJvaWQYCS'
'ABKAsyJS5zdHJlYW0udmlkZW8uc2Z1Lm1vZGVscy5BbmRyb2lkU3RhdGVIAFIHYW5kcm9pZBI7'
'CgVhcHBsZRgKIAEoCzIjLnN0cmVhbS52aWRlby5zZnUubW9kZWxzLkFwcGxlU3RhdGVIAFIFYX'
- 'BwbGVCDgoMZGV2aWNlX3N0YXRl');
+ 'BwbGUSQAoJdGVsZW1ldHJ5GAsgASgLMiIuc3RyZWFtLnZpZGVvLnNmdS5zaWduYWwuVGVsZW1l'
+ 'dHJ5Ugl0ZWxlbWV0cnlCDgoMZGV2aWNlX3N0YXRl');
@$core.Deprecated('Use sendStatsResponseDescriptor instead')
const SendStatsResponse$json = {
@@ -368,6 +403,8 @@ const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> SignalServ
'.stream.video.sfu.models.InputDevices': $0.InputDevices$json,
'.stream.video.sfu.models.AndroidState': $0.AndroidState$json,
'.stream.video.sfu.models.AppleState': $0.AppleState$json,
+ '.stream.video.sfu.signal.Telemetry': Telemetry$json,
+ '.stream.video.sfu.signal.Reconnection': Reconnection$json,
'.stream.video.sfu.signal.SendStatsResponse': SendStatsResponse$json,
'.stream.video.sfu.signal.StartNoiseCancellationRequest': StartNoiseCancellationRequest$json,
'.stream.video.sfu.signal.StartNoiseCancellationResponse': StartNoiseCancellationResponse$json,
diff --git a/packages/stream_video/lib/src/call/call.dart b/packages/stream_video/lib/src/call/call.dart
index 950ebab65..a98cdad7d 100644
--- a/packages/stream_video/lib/src/call/call.dart
+++ b/packages/stream_video/lib/src/call/call.dart
@@ -25,6 +25,7 @@ import '../utils/cancelables.dart';
import '../utils/extensions.dart';
import '../utils/future.dart';
import '../utils/standard.dart';
+import '../webrtc/model/stats/rtc_codec.dart';
import '../webrtc/model/stats/rtc_ice_candidate_pair.dart';
import '../webrtc/model/stats/rtc_inbound_rtp_video_stream.dart';
import '../webrtc/model/stats/rtc_outbound_rtp_video_stream.dart';
@@ -145,7 +146,8 @@ class Call {
}) {
final finalCallPreferences = preferences ?? DefaultCallPreferences();
final finalRetryPolicy = retryPolicy ?? const RetryPolicy();
- final finalSdpPolicy = sdpPolicy ?? const SdpPolicy();
+ final finalSdpPolicy =
+ sdpPolicy ?? const SdpPolicy(spdEditingEnabled: false);
final stateManager = _makeStateManager(
callCid,
@@ -183,7 +185,9 @@ class Call {
CallCredentials? credentials,
}) : _sessionFactory = CallSessionFactory(
callCid: stateManager.callState.callCid,
- sdpEditor: SdpEditorImpl(sdpPolicy),
+ sdpEditor: sdpPolicy.spdEditingEnabled
+ ? SdpEditorImpl(sdpPolicy)
+ : NoOpSdpEditor(),
),
_stateManager = stateManager,
_permissionsManager = permissionManager,
@@ -628,7 +632,7 @@ class Call {
final reconnectDetails =
_reconnectStrategy == SfuReconnectionStrategy.unspecified
? null
- : _previousSession?.getReconnectDetails(_reconnectStrategy);
+ : await _previousSession?.getReconnectDetails(_reconnectStrategy);
if (performingRejoin || performingMigration || !isWsHealthy) {
_logger.v(
@@ -650,6 +654,7 @@ class Call {
});
}
},
+ clientPublishOptions: _preferences.clientPublishOptions,
);
dynascaleManager.init(
@@ -720,9 +725,6 @@ class Call {
_stateManager.lifecycleCallConnected();
}
- _logger.v(() => '[join] apllying connect options');
- await _applyConnectOptions();
-
_logger.v(() => '[join] completed');
return const Result.success(none);
});
@@ -911,7 +913,13 @@ class Call {
localStats: localStats,
);
- final result = await session.start(reconnectDetails: reconnectDetails);
+ final result = await session.start(
+ reconnectDetails: reconnectDetails,
+ onRtcManagerCreatedCallback: (_) async {
+ _logger.v(() => '[startSession] applying connect options');
+ await _applyConnectOptions();
+ },
+ );
return result.fold(
success: (success) {
@@ -933,25 +941,12 @@ class Call {
state.value.subscriberStats ?? PeerConnectionStats.empty();
if (stats.peerType == StreamPeerType.publisher) {
- final mediaStatsF = stats.stats
- .whereType()
- .where((s) => s.rid == 'f')
- .map(MediaStatsInfo.fromRtcOutboundRtpVideoStream)
- .firstOrNull;
- final mediaStatsH = stats.stats
- .whereType()
- .where((s) => s.rid == 'h')
- .map(MediaStatsInfo.fromRtcOutboundRtpVideoStream)
- .firstOrNull;
- final mediaStatsQ = stats.stats
+ final allStats = stats.stats
.whereType()
- .where((s) => s.rid == 'q')
- .map(MediaStatsInfo.fromRtcOutboundRtpVideoStream)
- .firstOrNull;
+ .map(MediaStatsInfo.fromRtcOutboundRtpVideoStream);
- final allStats = [mediaStatsF, mediaStatsH, mediaStatsQ];
final mediaStats = allStats.firstWhereOrNull(
- (s) => s?.width != null && s?.height != null && s?.fps != null,
+ (s) => s.width != null && s.height != null && s.fps != null,
);
final jitterInMs = ((mediaStats?.jitter ?? 0) * 1000).toInt();
@@ -959,10 +954,36 @@ class Call {
? '${mediaStats.width} x ${mediaStats.height} @ ${mediaStats.fps}fps'
: null;
+ var activeOutbound = allStats.toList();
+
+ if (publisherStats.outboundMediaStats.isNotEmpty) {
+ activeOutbound = activeOutbound
+ .where(
+ (s) =>
+ publisherStats.outboundMediaStats.none((i) => s.id == i.id) ||
+ publisherStats.outboundMediaStats
+ .firstWhere((i) => i.id == s.id)
+ .bytesSent !=
+ s.bytesSent,
+ )
+ .toList();
+ }
+
+ final codec = stats.stats
+ .whereType()
+ .where((c) => c.mimeType?.startsWith('video') ?? false)
+ .where((c) => activeOutbound.any((s) => s.videoCodecId == c.id))
+ .map((c) => c.mimeType?.replaceFirst('video/', ''))
+ .where((c) => c != null)
+ .cast()
+ .toList();
+
publisherStats = publisherStats.copyWith(
resolution: resolution,
qualityDropReason: mediaStats?.qualityLimit,
jitterInMs: jitterInMs,
+ videoCodec: codec,
+ outboundMediaStats: allStats.toList(),
);
}
@@ -977,9 +998,17 @@ class Call {
? '${inboudRtpVideo.frameWidth} x ${inboudRtpVideo.frameHeight} @ ${inboudRtpVideo.framesPerSecond}fps'
: null;
+ final codecStats = stats.stats
+ .whereType()
+ .where((c) => c.mimeType?.startsWith('video') ?? false)
+ .firstOrNull;
+
+ final codec = codecStats?.mimeType?.replaceFirst('video/', '');
+
subscriberStats = subscriberStats.copyWith(
resolution: resolution,
jitterInMs: jitterInMs,
+ videoCodec: codec != null ? [codec] : [],
);
}
diff --git a/packages/stream_video/lib/src/call/session/call_session.dart b/packages/stream_video/lib/src/call/session/call_session.dart
index 92f1d6db3..9e29ba3e3 100644
--- a/packages/stream_video/lib/src/call/session/call_session.dart
+++ b/packages/stream_video/lib/src/call/session/call_session.dart
@@ -6,11 +6,13 @@ import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_webrtc_flutter/stream_webrtc_flutter.dart' as rtc;
+import 'package:synchronized/synchronized.dart';
import 'package:system_info2/system_info2.dart';
import 'package:thermal/thermal.dart';
import '../../../protobuf/video/sfu/event/events.pb.dart' as sfu_events;
import '../../../protobuf/video/sfu/models/models.pb.dart' as sfu_models;
+import '../../../protobuf/video/sfu/models/models.pbenum.dart';
import '../../../protobuf/video/sfu/signal_rpc/signal.pb.dart' as sfu;
import '../../../stream_video.dart';
import '../../../version.g.dart';
@@ -56,6 +58,7 @@ class CallSession extends Disposable {
required this.dynascaleManager,
required this.onPeerConnectionIssue,
required SdpEditor sdpEditor,
+ this.clientPublishOptions,
this.joinResponseTimeout = const Duration(seconds: 5),
}) : sfuClient = SfuClient(
baseUrl: config.sfuUrl,
@@ -108,9 +111,13 @@ class CallSession extends Disposable {
final SfuWebSocket sfuWS;
final RtcManagerFactory rtcManagerFactory;
final OnPeerConnectionIssue onPeerConnectionIssue;
+ final ClientPublishOptions? clientPublishOptions;
final Duration joinResponseTimeout;
+ final Lock _sfuEventsLock = Lock();
+ final Lock _negotiationLock = Lock();
+
RtcManager? rtcManager;
BehaviorSubject? _rtcManagerSubject;
StreamSubscription? _eventsSubscription;
@@ -163,7 +170,7 @@ class CallSession extends Disposable {
version: deviceInfo.systemVersion,
);
device = sfu_models.Device(
- name: deviceInfo.model,
+ name: deviceInfo.utsname.machine,
);
} else if (CurrentPlatform.isWeb) {
final browserInfo = await DeviceInfoPlugin().webBrowserInfo;
@@ -208,12 +215,13 @@ class CallSession extends Disposable {
}
}
- sfu_events.ReconnectDetails getReconnectDetails(
+ Future getReconnectDetails(
SfuReconnectionStrategy strategy, {
String? migratingFromSfuId,
int? reconnectAttempts,
- }) {
- final announcedTracks = rtcManager?.getPublisherTrackInfos().toDTO();
+ }) async {
+ final announcedTracks = await rtcManager?.getAnnouncedTracksForReconnect();
+
final subscribedTracks = dynascaleManager
.getTrackSubscriptions(ignoreOverride: true)
.values
@@ -222,7 +230,7 @@ class CallSession extends Disposable {
return sfu_events.ReconnectDetails(
strategy: strategy.toDto(),
- announcedTracks: announcedTracks,
+ announcedTracks: announcedTracks?.toDTO(),
subscriptions: subscribedTracks,
previousSessionId: sessionId,
fromSfuId: migratingFromSfuId ?? '',
@@ -230,9 +238,14 @@ class CallSession extends Disposable {
);
}
- Future>
- start({
+ Future<
+ Result<
+ ({
+ SfuCallState callState,
+ Duration fastReconnectDeadline,
+ })>> start({
sfu_events.ReconnectDetails? reconnectDetails,
+ FutureOr Function(RtcManager)? onRtcManagerCreatedCallback,
}) async {
try {
_logger.d(() => '[start] no args');
@@ -272,8 +285,28 @@ class CallSession extends Disposable {
_logger.v(() => '[start] sfu connected');
- final subscriberSdp = await RtcManager.getGenericSdp();
- _logger.v(() => '[start] subscriberSdp.len: ${subscriberSdp.length}');
+ final subscriberSdp =
+ await RtcManager.getGenericSdp(rtc.TransceiverDirection.RecvOnly);
+ final publisherSdp =
+ await RtcManager.getGenericSdp(rtc.TransceiverDirection.SendOnly);
+
+ _logger.v(
+ () => '[start] subscriberSdp.len: ${subscriberSdp.length}, '
+ 'publisherSdp.len: ${publisherSdp.length}',
+ );
+
+ final isReconnecting = reconnectDetails != null &&
+ reconnectDetails.strategy !=
+ WebsocketReconnectStrategy
+ .WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED;
+
+ final preferredPublishOptions = isReconnecting
+ ? rtcManager?.publishOptions.map((o) => o.toDTO())
+ : clientPublishOptions?.getPreferredPublishOptions();
+
+ final preferredSubscribeOptions = isReconnecting
+ ? null
+ : clientPublishOptions?.getPreferredSubscriberOptions();
sfuWS.send(
sfu_events.SfuRequest(
@@ -282,7 +315,10 @@ class CallSession extends Disposable {
token: config.sfuToken,
sessionId: sessionId,
subscriberSdp: subscriberSdp,
+ publisherSdp: publisherSdp,
reconnectDetails: reconnectDetails,
+ preferredPublishOptions: preferredPublishOptions,
+ preferredSubscribeOptions: preferredSubscribeOptions,
),
),
);
@@ -302,6 +338,7 @@ class CallSession extends Disposable {
_logger.v(() => '[start] localTrackId: $localTrackId');
rtcManager = await rtcManagerFactory.makeRtcManager(
publisherId: localTrackId,
+ publishOptions: event.publishOptions,
)
..onPublisherIceCandidate = _onLocalIceCandidate
..onSubscriberIceCandidate = _onLocalIceCandidate
@@ -313,6 +350,7 @@ class CallSession extends Disposable {
..onRemoteTrackReceived = _onRemoteTrackReceived
..onStatsReceived = _onStatsReceived;
+ await onRtcManagerCreatedCallback?.call(rtcManager!);
_rtcManagerSubject!.add(rtcManager!);
await observePeerConnectionStats();
@@ -351,8 +389,15 @@ class CallSession extends Disposable {
try {
_logger.d(() => '[fastReconnect] no args');
- final genericSdp = await RtcManager.getGenericSdp();
- _logger.v(() => '[fastReconnect] genericSdp.len: ${genericSdp.length}');
+ final subscriberSdp =
+ await RtcManager.getGenericSdp(rtc.TransceiverDirection.RecvOnly);
+ final publisherSdp =
+ await RtcManager.getGenericSdp(rtc.TransceiverDirection.SendOnly);
+
+ _logger.v(
+ () => '[fastReconnect] subscriberSdp.len: ${subscriberSdp.length}, '
+ 'publisherSdp.len: ${publisherSdp.length},',
+ );
await _ensureClientDetails();
@@ -366,8 +411,12 @@ class CallSession extends Disposable {
clientDetails: _clientDetails,
token: config.sfuToken,
sessionId: sessionId,
- subscriberSdp: genericSdp,
- reconnectDetails: getReconnectDetails(SfuReconnectionStrategy.fast),
+ subscriberSdp: subscriberSdp,
+ publisherSdp: publisherSdp,
+ reconnectDetails:
+ await getReconnectDetails(SfuReconnectionStrategy.fast),
+ preferredPublishOptions:
+ rtcManager?.publishOptions.map((o) => o.toDTO()),
),
),
);
@@ -385,10 +434,10 @@ class CallSession extends Disposable {
await rtcManager?.publisher.pc.restartIce();
- final announcedTracks =
+ final remoteTracks =
rtcManager!.tracks.values.whereType().toList();
- for (final track in announcedTracks) {
+ for (final track in remoteTracks) {
await _onRemoteTrackReceived(rtcManager!.publisher, track);
}
@@ -470,39 +519,43 @@ class CallSession extends Disposable {
Future _onSfuEvent(SfuEvent event) async {
_logger.log(event.logPriority, () => '[onSfuEvent] event: $event');
- if (event is SfuSubscriberOfferEvent) {
- await _onSubscriberOffer(event);
- } else if (event is SfuIceTrickleEvent) {
- await _onRemoteIceCandidate(event);
- } else if (event is SfuParticipantLeftEvent) {
- await _onParticipantLeft(event);
- } else if (event is SfuTrackPublishedEvent) {
- await _onTrackPublished(event);
- } else if (event is SfuTrackUnpublishedEvent) {
- await _onTrackUnpublished(event);
- } else if (event is SfuChangePublishQualityEvent) {
- await _onPublishQualityChanged(event);
- }
+ await _sfuEventsLock.synchronized(() async {
+ if (event is SfuSubscriberOfferEvent) {
+ await _onSubscriberOffer(event);
+ } else if (event is SfuIceTrickleEvent) {
+ await _onRemoteIceCandidate(event);
+ } else if (event is SfuParticipantLeftEvent) {
+ await _onParticipantLeft(event);
+ } else if (event is SfuTrackPublishedEvent) {
+ await _onTrackPublished(event);
+ } else if (event is SfuTrackUnpublishedEvent) {
+ await _onTrackUnpublished(event);
+ } else if (event is SfuChangePublishQualityEvent) {
+ await _onPublishQualityChanged(event);
+ } else if (event is SfuChangePublishOptionsEvent) {
+ await _onPublishOptionsChanged(event);
+ }
- if (event is SfuJoinResponseEvent) {
- stateManager.sfuJoinResponse(event);
- } else if (event is SfuParticipantJoinedEvent) {
- stateManager.sfuParticipantJoined(event);
- } else if (event is SfuParticipantUpdatedEvent) {
- stateManager.sfuParticipantUpdated(event);
- } else if (event is SfuParticipantLeftEvent) {
- stateManager.sfuParticipantLeft(event);
- } else if (event is SfuConnectionQualityChangedEvent) {
- stateManager.sfuConnectionQualityChanged(event);
- } else if (event is SfuAudioLevelChangedEvent) {
- stateManager.sfuUpdateAudioLevelChanged(event);
- } else if (event is SfuTrackPublishedEvent) {
- stateManager.sfuTrackPublished(event);
- } else if (event is SfuTrackUnpublishedEvent) {
- stateManager.sfuTrackUnpublished(event);
- } else if (event is SfuDominantSpeakerChangedEvent) {
- stateManager.sfuDominantSpeakerChanged(event);
- }
+ if (event is SfuJoinResponseEvent) {
+ stateManager.sfuJoinResponse(event);
+ } else if (event is SfuParticipantJoinedEvent) {
+ stateManager.sfuParticipantJoined(event);
+ } else if (event is SfuParticipantUpdatedEvent) {
+ stateManager.sfuParticipantUpdated(event);
+ } else if (event is SfuParticipantLeftEvent) {
+ stateManager.sfuParticipantLeft(event);
+ } else if (event is SfuConnectionQualityChangedEvent) {
+ stateManager.sfuConnectionQualityChanged(event);
+ } else if (event is SfuAudioLevelChangedEvent) {
+ stateManager.sfuUpdateAudioLevelChanged(event);
+ } else if (event is SfuTrackPublishedEvent) {
+ stateManager.sfuTrackPublished(event);
+ } else if (event is SfuTrackUnpublishedEvent) {
+ stateManager.sfuTrackUnpublished(event);
+ } else if (event is SfuDominantSpeakerChangedEvent) {
+ stateManager.sfuDominantSpeakerChanged(event);
+ }
+ });
}
Future _onParticipantLeft(SfuParticipantLeftEvent event) async {
@@ -585,15 +638,20 @@ class CallSession extends Disposable {
) async {
_logger.d(() => '[onPublishQualityChanged] event: $event');
- final enabledRids = event.videoSenders.firstOrNull?.layers
- .where((e) => e.active)
- .map((e) => e.name)
- .toSet() ??
- {};
+ final usedCodec =
+ stateManager.callState.publisherStats?.videoCodec?.firstOrNull;
- _logger.v(() => '[onPublishQualityChanged] Enabled RIDs: $enabledRids');
+ for (final videoSender in event.videoSenders) {
+ await rtcManager?.onPublishQualityChanged(videoSender, usedCodec);
+ }
+ }
+
+ Future _onPublishOptionsChanged(
+ SfuChangePublishOptionsEvent event,
+ ) async {
+ _logger.d(() => '[_onPublishOptionsChanged] event: $event');
- return await rtcManager?.onPublishQualityChanged(enabledRids);
+ return await rtcManager?.onPublishOptionsChanged(event.publishOptions);
}
Future _onSubscriberOffer(SfuSubscriberOfferEvent event) async {
@@ -693,44 +751,41 @@ class CallSession extends Disposable {
}
Future _onRenegotiationNeeded(StreamPeerConnection pc) async {
- _logger.d(() => '[negotiate] type: ${pc.type}');
+ await _negotiationLock.synchronized(() async {
+ _logger.d(() => '[negotiate] type: ${pc.type}');
- final offer = await pc.createOffer();
- if (offer is! Success) return;
+ final offer = await pc.createOffer();
+ if (offer is! Success) return;
- final tracksInfo = rtcManager!.getPublisherTrackInfos();
- if (tracksInfo.isEmpty) {
- _logger.w(() => '[negotiate] rejected(tracksInfo is empty): $tracksInfo');
- return;
- }
+ final sdp = offer.data.sdp;
+ final tracksInfo = await rtcManager!.getAnnouncedTracks(sdp: sdp);
- for (final track in tracksInfo) {
- _logger.v(
- () => '[negotiate] track.id: ${track.trackId}, '
- 'track.type: ${track.trackType}',
- );
- for (final layer in [...?track.layers]) {
- _logger.v(() => '[negotiate] layer: $layer');
+ if (tracksInfo.isEmpty) {
+ _logger
+ .w(() => '[negotiate] rejected(tracksInfo is empty): $tracksInfo');
+ return;
}
- }
- final pubResult = await sfuClient.setPublisher(
- sfu.SetPublisherRequest(
- sdp: offer.data.sdp,
- sessionId: sessionId,
- tracks: tracksInfo.toDTO(),
- ),
- );
+ _logger.v(() => '[negotiate] announcing tracks: $tracksInfo');
- if (pubResult is! Success) {
- _logger.w(() => '[negotiate] #setPublisher; failed: $pubResult');
- return;
- }
+ final pubResult = await sfuClient.setPublisher(
+ sfu.SetPublisherRequest(
+ sdp: sdp,
+ sessionId: sessionId,
+ tracks: tracksInfo.toDTO(),
+ ),
+ );
- final ansResult = await pc.setRemoteAnswer(pubResult.data.sdp);
- if (ansResult is! Success) {
- _logger.w(() => '[negotiate] #setRemoteAnswer; failed: $ansResult');
- }
+ if (pubResult is! Success) {
+ _logger.w(() => '[negotiate] #setPublisher; failed: $pubResult');
+ return;
+ }
+
+ final ansResult = await pc.setRemoteAnswer(pubResult.data.sdp);
+ if (ansResult is! Success) {
+ _logger.w(() => '[negotiate] #setRemoteAnswer; failed: $ansResult');
+ }
+ });
}
Future _onRemoteTrackReceived(
@@ -981,6 +1036,8 @@ extension RtcTracksInfoMapper on List {
trackId: info.trackId,
trackType: info.trackType?.toDTO(),
mid: info.mid,
+ publishOptionId: info.publishOptionId,
+ codec: info.codec?.toDTO(),
layers: info.layers?.map((layer) {
return sfu_models.VideoLayer(
rid: layer.rid,
@@ -1009,3 +1066,37 @@ extension SfuSubscriptionDetailsEx on List {
}).toList();
}
}
+
+extension on ClientPublishOptions {
+ List? getPreferredPublishOptions() {
+ if (preferredCodec == null) return null;
+
+ return [
+ sfu_models.PublishOption(
+ codec: sfu_models.Codec(
+ name: preferredCodec?.name,
+ fmtp: fmtpLine,
+ ),
+ bitrate: preferredBitrate,
+ maxSpatialLayers: maxSimulcastLayers,
+ trackType: sfu_models.TrackType.TRACK_TYPE_VIDEO,
+ ),
+ ];
+ }
+
+ List? getPreferredSubscriberOptions() {
+ if (subscriberCodec == null) return null;
+
+ return [
+ sfu_models.SubscribeOption(
+ codecs: [
+ sfu_models.Codec(
+ name: subscriberCodec?.name,
+ fmtp: subscriberFmtpLine,
+ ),
+ ],
+ trackType: sfu_models.TrackType.TRACK_TYPE_VIDEO,
+ ),
+ ];
+ }
+}
diff --git a/packages/stream_video/lib/src/call/session/call_session_factory.dart b/packages/stream_video/lib/src/call/session/call_session_factory.dart
index f7f0898a3..2439da835 100644
--- a/packages/stream_video/lib/src/call/session/call_session_factory.dart
+++ b/packages/stream_video/lib/src/call/session/call_session_factory.dart
@@ -3,6 +3,7 @@ import 'package:uuid/uuid.dart';
import '../../core/utils.dart';
import '../../logger/impl/tagged_logger.dart';
import '../../models/call_cid.dart';
+import '../../models/call_client_publish_options.dart';
import '../../models/call_credentials.dart';
import '../../types/other.dart';
import '../../webrtc/sdp/editor/sdp_editor.dart';
@@ -30,6 +31,7 @@ class CallSessionFactory {
required CallStateNotifier stateManager,
required DynascaleManager dynascaleManager,
required OnPeerConnectionIssue onPeerConnectionFailure,
+ ClientPublishOptions? clientPublishOptions,
}) async {
final finalSessionId = sessionId ?? const Uuid().v4();
_logger.d(() => '[makeCallSession] sessionId: $finalSessionId($sessionId)');
@@ -58,6 +60,7 @@ class CallSessionFactory {
dynascaleManager: dynascaleManager,
sdpEditor: sdpEditor,
onPeerConnectionIssue: onPeerConnectionFailure,
+ clientPublishOptions: clientPublishOptions,
);
}
diff --git a/packages/stream_video/lib/src/coordinator/open_api/open_api_extensions.dart b/packages/stream_video/lib/src/coordinator/open_api/open_api_extensions.dart
index e2eab3f2e..e59ed1ec1 100644
--- a/packages/stream_video/lib/src/coordinator/open_api/open_api_extensions.dart
+++ b/packages/stream_video/lib/src/coordinator/open_api/open_api_extensions.dart
@@ -1,5 +1,3 @@
-import 'package:collection/collection.dart';
-
import '../../../../open_api/video/coordinator/api.dart' as open;
import '../../../stream_video.dart';
import '../../errors/video_error.dart';
diff --git a/packages/stream_video/lib/src/models/call_client_publish_options.dart b/packages/stream_video/lib/src/models/call_client_publish_options.dart
new file mode 100644
index 000000000..a0c007000
--- /dev/null
+++ b/packages/stream_video/lib/src/models/call_client_publish_options.dart
@@ -0,0 +1,35 @@
+class ClientPublishOptions {
+ ClientPublishOptions({
+ this.preferredCodec,
+ this.fmtpLine,
+ this.preferredBitrate,
+ this.maxSimulcastLayers,
+ this.subscriberCodec,
+ this.subscriberFmtpLine,
+ });
+
+ /// The preferred codec to use when publishing the video stream.
+ final PreferredCodec? preferredCodec;
+
+ /// The fmtp line for the video codec.
+ final String? fmtpLine;
+
+ /// The preferred bitrate to use when publishing the video stream.
+ final int? preferredBitrate;
+
+ /// The maximum number of simulcast layers to use when publishing the video stream.
+ final int? maxSimulcastLayers;
+
+ /// The preferred subscription (incoming video stream) codec.
+ final PreferredCodec? subscriberCodec;
+
+ /// The fmtp line for the subscriber codec.
+ final String? subscriberFmtpLine;
+}
+
+enum PreferredCodec {
+ vp8,
+ h264,
+ vp9,
+ av1,
+}
diff --git a/packages/stream_video/lib/src/models/call_preferences.dart b/packages/stream_video/lib/src/models/call_preferences.dart
index 7b38a088e..8c986051a 100644
--- a/packages/stream_video/lib/src/models/call_preferences.dart
+++ b/packages/stream_video/lib/src/models/call_preferences.dart
@@ -1,8 +1,10 @@
+import 'call_client_publish_options.dart';
+
abstract class CallPreferences {
Duration get connectTimeout;
Duration get reactionAutoDismissTime;
-
bool get dropIfAloneInRingingFlow;
+ ClientPublishOptions? get clientPublishOptions;
}
class DefaultCallPreferences implements CallPreferences {
@@ -10,6 +12,7 @@ class DefaultCallPreferences implements CallPreferences {
this.connectTimeout = const Duration(seconds: 60),
this.reactionAutoDismissTime = const Duration(seconds: 5),
this.dropIfAloneInRingingFlow = true,
+ this.clientPublishOptions,
});
@override
@@ -20,4 +23,7 @@ class DefaultCallPreferences implements CallPreferences {
@override
final bool dropIfAloneInRingingFlow;
+
+ @override
+ final ClientPublishOptions? clientPublishOptions;
}
diff --git a/packages/stream_video/lib/src/models/call_settings.dart b/packages/stream_video/lib/src/models/call_settings.dart
index e46a873ef..fdb7ba944 100644
--- a/packages/stream_video/lib/src/models/call_settings.dart
+++ b/packages/stream_video/lib/src/models/call_settings.dart
@@ -395,6 +395,7 @@ class StreamTargetResolution extends AbstractSettings {
encoding: RtcVideoEncoding(
maxFramerate: 30,
maxBitrate: bitrate ?? defaultBitrate,
+ quality: RtcVideoQuality.high,
),
);
}
diff --git a/packages/stream_video/lib/src/models/call_stats.dart b/packages/stream_video/lib/src/models/call_stats.dart
index 2adeb9078..3ebbfa5da 100644
--- a/packages/stream_video/lib/src/models/call_stats.dart
+++ b/packages/stream_video/lib/src/models/call_stats.dart
@@ -47,6 +47,8 @@ class PeerConnectionStats {
required this.qualityDropReason,
required this.jitterInMs,
required this.bitrateKbps,
+ this.videoCodec = const [],
+ this.outboundMediaStats = const [],
});
factory PeerConnectionStats.empty() => const PeerConnectionStats(
@@ -55,6 +57,7 @@ class PeerConnectionStats {
qualityDropReason: null,
jitterInMs: null,
bitrateKbps: null,
+ videoCodec: null,
);
final int? latency;
@@ -62,10 +65,12 @@ class PeerConnectionStats {
final String? qualityDropReason;
final int? jitterInMs;
final double? bitrateKbps;
+ final List? videoCodec;
+ final List outboundMediaStats;
@override
String toString() {
- return 'PeerConnectionStats{latency: $latency, resolution: $resolution, qualityDropReason: $qualityDropReason, jitterInMs: $jitterInMs, bitrateKbps: $bitrateKbps}';
+ return 'PeerConnectionStats{latency: $latency, resolution: $resolution, qualityDropReason: $qualityDropReason, jitterInMs: $jitterInMs, bitrateKbps: $bitrateKbps, videoCodec: $videoCodec}';
}
PeerConnectionStats copyWith({
@@ -74,6 +79,8 @@ class PeerConnectionStats {
String? qualityDropReason,
int? jitterInMs,
double? bitrateKbps,
+ List? videoCodec,
+ List? outboundMediaStats,
}) {
return PeerConnectionStats(
latency: latency ?? this.latency,
@@ -81,6 +88,8 @@ class PeerConnectionStats {
qualityDropReason: qualityDropReason ?? this.qualityDropReason,
jitterInMs: jitterInMs ?? this.jitterInMs,
bitrateKbps: bitrateKbps ?? this.bitrateKbps,
+ videoCodec: videoCodec ?? this.videoCodec,
+ outboundMediaStats: outboundMediaStats ?? this.outboundMediaStats,
);
}
@@ -93,7 +102,9 @@ class PeerConnectionStats {
resolution == other.resolution &&
qualityDropReason == other.qualityDropReason &&
jitterInMs == other.jitterInMs &&
- bitrateKbps == other.bitrateKbps;
+ bitrateKbps == other.bitrateKbps &&
+ outboundMediaStats == other.outboundMediaStats &&
+ videoCodec == other.videoCodec;
@override
int get hashCode =>
@@ -101,12 +112,17 @@ class PeerConnectionStats {
resolution.hashCode ^
qualityDropReason.hashCode ^
jitterInMs.hashCode ^
- bitrateKbps.hashCode;
+ bitrateKbps.hashCode ^
+ outboundMediaStats.hashCode ^
+ videoCodec.hashCode;
}
@immutable
class MediaStatsInfo {
const MediaStatsInfo({
+ required this.id,
+ required this.bytesSent,
+ required this.videoCodecId,
required this.qualityLimit,
required this.jitter,
required this.width,
@@ -119,6 +135,9 @@ class MediaStatsInfo {
RtcOutboundRtpVideoStream stream,
) =>
MediaStatsInfo(
+ id: stream.id,
+ bytesSent: stream.bytesSent,
+ videoCodecId: stream.codecId,
qualityLimit: stream.qualityLimitationReason,
jitter: stream.jitter,
width: stream.frameWidth,
@@ -127,16 +146,19 @@ class MediaStatsInfo {
deviceLatency: stream.totalPacketSendDelay,
);
+ final String? id;
final String? qualityLimit;
final double? jitter;
final int? width;
final int? height;
final double? fps;
final double? deviceLatency;
+ final int? bytesSent;
+ final String? videoCodecId;
@override
String toString() {
- return 'MediaStatsInfo{qualityLimit: $qualityLimit, jitter: $jitter, width: $width, height: $height, fps: $fps, deviceLatency: $deviceLatency}';
+ return 'MediaStatsInfo{qualityLimit: $qualityLimit, jitter: $jitter, width: $width, height: $height, fps: $fps, deviceLatency: $deviceLatency, bytesSent: $bytesSent, videoCodec: $videoCodecId}';
}
@override
@@ -144,15 +166,21 @@ class MediaStatsInfo {
identical(this, other) ||
other is MediaStatsInfo &&
runtimeType == other.runtimeType &&
+ id == other.id &&
qualityLimit == other.qualityLimit &&
jitter == other.jitter &&
width == other.width &&
height == other.height &&
fps == other.fps &&
- deviceLatency == other.deviceLatency;
+ deviceLatency == other.deviceLatency &&
+ bytesSent == other.bytesSent &&
+ videoCodecId == other.videoCodecId;
@override
int get hashCode =>
+ id.hashCode ^
+ bytesSent.hashCode ^
+ videoCodecId.hashCode ^
qualityLimit.hashCode ^
jitter.hashCode ^
width.hashCode ^
diff --git a/packages/stream_video/lib/src/models/models.dart b/packages/stream_video/lib/src/models/models.dart
index 1bca502a7..a959847cf 100644
--- a/packages/stream_video/lib/src/models/models.dart
+++ b/packages/stream_video/lib/src/models/models.dart
@@ -1,4 +1,5 @@
export 'call_cid.dart';
+export 'call_client_publish_options.dart';
export 'call_created_data.dart';
export 'call_credentials.dart';
export 'call_egress.dart';
diff --git a/packages/stream_video/lib/src/sfu/data/events/sfu_event_mapper_extensions.dart b/packages/stream_video/lib/src/sfu/data/events/sfu_event_mapper_extensions.dart
index 61aa4f480..b6360da04 100644
--- a/packages/stream_video/lib/src/sfu/data/events/sfu_event_mapper_extensions.dart
+++ b/packages/stream_video/lib/src/sfu/data/events/sfu_event_mapper_extensions.dart
@@ -1,17 +1,16 @@
import '../../../../protobuf/video/sfu/event/events.pb.dart' as sfu_events;
import '../../../../protobuf/video/sfu/models/models.pb.dart' as sfu_models;
+import '../../../../stream_video.dart';
import '../models/sfu_audio_level.dart';
import '../models/sfu_audio_sender.dart';
import '../models/sfu_call_grants.dart';
import '../models/sfu_call_state.dart';
import '../models/sfu_codec.dart';
import '../models/sfu_connection_info.dart';
-import '../models/sfu_connection_quality.dart';
import '../models/sfu_error.dart';
-import '../models/sfu_goaway_reason.dart';
import '../models/sfu_model_mapper_extensions.dart';
import '../models/sfu_participant.dart';
-import '../models/sfu_track_type.dart';
+import '../models/sfu_publish_options.dart';
import '../models/sfu_video_layer_setting.dart';
import '../models/sfu_video_sender.dart';
import 'sfu_events.dart';
@@ -83,6 +82,17 @@ extension SfuEventMapper on sfu_events.SfuEvent {
.toList(),
);
+ case sfu_events.SfuEvent_EventPayload.changePublishOptions:
+ final payload = changePublishOptions;
+ return SfuChangePublishOptionsEvent(
+ publishOptions: payload.publishOptions
+ .map(
+ (it) => it.toDomain(),
+ )
+ .toList(),
+ reason: payload.reason,
+ );
+
case sfu_events.SfuEvent_EventPayload.joinResponse:
return SfuJoinResponseEvent(
callState: joinResponse.callState.toDomain(),
@@ -90,6 +100,11 @@ extension SfuEventMapper on sfu_events.SfuEvent {
fastReconnectDeadline: Duration(
seconds: joinResponse.fastReconnectDeadlineSeconds,
),
+ publishOptions: joinResponse.publishOptions
+ .map(
+ (it) => it.toDomain(),
+ )
+ .toList(),
);
case sfu_events.SfuEvent_EventPayload.participantJoined:
return SfuParticipantJoinedEvent(
@@ -320,32 +335,35 @@ extension SfuWebsocketReconnectStrategyExtension
}
}
-/// TODO
extension SfuAudioSenderExtension on sfu_events.AudioSender {
SfuAudioSender toDomain() {
return SfuAudioSender(
codec: codec.toDomain(),
+ trackType: trackType.toDomain(),
+ publishOptionId: publishOptionId,
);
}
}
-/// TODO
extension SfuVideoSenderExtension on sfu_events.VideoSender {
SfuVideoSender toDomain() {
return SfuVideoSender(
codec: codec.toDomain(),
layers: layers.map((it) => it.toDomain()).toList(),
+ trackType: trackType.toDomain(),
+ publishOptionId: publishOptionId,
);
}
}
-/// TODO
extension SfuCodecExtension on sfu_models.Codec {
SfuCodec toDomain() {
return SfuCodec(
- mimeType: mimeType,
- scalabilityMode: scalabilityMode,
- fmtp: fmtp,
+ payloadType: payloadType,
+ name: name,
+ fmtpLine: fmtp,
+ clockRate: clockRate,
+ encodingParameters: encodingParameters,
);
}
}
@@ -363,3 +381,21 @@ extension on sfu_events.VideoLayerSetting {
);
}
}
+
+extension on sfu_models.PublishOption {
+ SfuPublishOptions toDomain() {
+ return SfuPublishOptions(
+ id: id,
+ codec: codec.toDomain(),
+ videoDimension: RtcVideoDimension(
+ width: videoDimension.width,
+ height: videoDimension.height,
+ ),
+ trackType: trackType.toDomain(),
+ maxSpatialLayers: maxSpatialLayers,
+ maxTemporalLayers: maxTemporalLayers,
+ bitrate: bitrate,
+ fps: fps,
+ );
+ }
+}
diff --git a/packages/stream_video/lib/src/sfu/data/events/sfu_events.dart b/packages/stream_video/lib/src/sfu/data/events/sfu_events.dart
index 6144e0595..088930603 100644
--- a/packages/stream_video/lib/src/sfu/data/events/sfu_events.dart
+++ b/packages/stream_video/lib/src/sfu/data/events/sfu_events.dart
@@ -12,6 +12,7 @@ import '../models/sfu_connection_info.dart';
import '../models/sfu_error.dart';
import '../models/sfu_goaway_reason.dart';
import '../models/sfu_participant.dart';
+import '../models/sfu_publish_options.dart';
import '../models/sfu_track_type.dart';
import '../models/sfu_video_sender.dart';
@@ -36,11 +37,13 @@ class SfuJoinResponseEvent extends SfuEvent {
required this.callState,
this.isReconnected = false,
this.fastReconnectDeadline = Duration.zero,
+ this.publishOptions = const [],
});
final SfuCallState callState;
final bool isReconnected;
final Duration fastReconnectDeadline;
+ final List publishOptions;
@override
List