Skip to content

Commit 6d9ffc9

Browse files
authored
Merge pull request #211 from GetStream/feature/uni-160-implement-handling-changepublishquality
Feature/uni 160 implement handling changepublishquality
2 parents 586266a + 097a1e7 commit 6d9ffc9

File tree

4 files changed

+229
-1
lines changed

4 files changed

+229
-1
lines changed

Packages/StreamVideo/Runtime/Core/LowLevelClient/PublisherPeerConnection.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using StreamVideo.Libs.Utils;
1717
using StreamVideo.v1.Sfu.Models;
1818
using StreamVideo.v1.Sfu.Signal;
19+
using SfuEvents = StreamVideo.v1.Sfu.Events;
1920
using Unity.WebRTC;
2021
using UnityEngine;
2122
using TrackType = StreamVideo.v1.Sfu.Models.TrackType;
@@ -171,6 +172,104 @@ public override Task RestartIce()
171172
return Negotiate(iceRestart: true);
172173
}
173174

175+
/// <summary>
176+
/// Apply quality changes requested by the SFU. Enables/disables simulcast layers and updates
177+
/// encoding parameters (bitrate, framerate, resolution scale) to match current subscriber demands.
178+
/// </summary>
179+
public void ChangePublishQuality(SfuEvents.VideoSender videoSenderSettings)
180+
{
181+
if (videoSenderSettings.TrackType != TrackType.Video)
182+
{
183+
Logs.WarningIfDebug(
184+
$"[{PeerType}] ChangePublishQuality: Ignoring track type: {videoSenderSettings.TrackType}");
185+
return;
186+
}
187+
188+
if (VideoSender == null)
189+
{
190+
Logs.WarningIfDebug($"[{PeerType}] ChangePublishQuality: VideoSender null, skip");
191+
return;
192+
}
193+
194+
var parameters = VideoSender.GetParameters();
195+
if (parameters.encodings == null || parameters.encodings.Length == 0)
196+
{
197+
Logs.WarningIfDebug($"[{PeerType}] ChangePublishQuality: No encodings on sender");
198+
return;
199+
}
200+
201+
PublishQualityDebugLogger.LogSfuRequest(Logs, PeerType.ToString(), videoSenderSettings);
202+
PublishQualityDebugLogger.LogStateBefore(Logs, PeerType.ToString(), parameters);
203+
204+
var changed = false;
205+
206+
foreach (var encoding in parameters.encodings)
207+
{
208+
var rid = string.IsNullOrEmpty(encoding.rid) ? "f" : encoding.rid;
209+
210+
SfuEvents.VideoLayerSetting matchingLayer = null;
211+
foreach (var layer in videoSenderSettings.Layers)
212+
{
213+
if (layer.Name == rid)
214+
{
215+
matchingLayer = layer;
216+
break;
217+
}
218+
}
219+
220+
var shouldBeActive = matchingLayer?.Active ?? false;
221+
if (encoding.active != shouldBeActive)
222+
{
223+
encoding.active = shouldBeActive;
224+
changed = true;
225+
}
226+
227+
if (matchingLayer == null || !matchingLayer.Active)
228+
{
229+
continue;
230+
}
231+
232+
if (matchingLayer.MaxBitrate > 0 && encoding.maxBitrate != (ulong)matchingLayer.MaxBitrate)
233+
{
234+
encoding.maxBitrate = (ulong)matchingLayer.MaxBitrate;
235+
changed = true;
236+
}
237+
238+
if (matchingLayer.ScaleResolutionDownBy >= 1f)
239+
{
240+
var targetScale = (double)matchingLayer.ScaleResolutionDownBy;
241+
if (encoding.scaleResolutionDownBy == null
242+
|| Math.Abs(encoding.scaleResolutionDownBy.Value - targetScale) > 0.001)
243+
{
244+
encoding.scaleResolutionDownBy = targetScale;
245+
changed = true;
246+
}
247+
}
248+
249+
if (matchingLayer.MaxFramerate > 0 && encoding.maxFramerate != matchingLayer.MaxFramerate)
250+
{
251+
encoding.maxFramerate = matchingLayer.MaxFramerate;
252+
changed = true;
253+
}
254+
}
255+
256+
if (!changed)
257+
{
258+
Logs.InfoIfDebug($"[{PeerType}] ChangePublishQuality: No changes needed");
259+
return;
260+
}
261+
262+
var error = VideoSender.SetParameters(parameters);
263+
if (error.errorType != RTCErrorType.None)
264+
{
265+
Logs.Error($"[{PeerType}] ChangePublishQuality: SetParameters FAILED: {error.errorType}");
266+
}
267+
else
268+
{
269+
PublishQualityDebugLogger.LogStateAfter(Logs, PeerType.ToString(), parameters);
270+
}
271+
}
272+
174273
private async Task Negotiate(bool iceRestart = false)
175274
{
176275
if (_isNegotiating)

Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1946,7 +1946,17 @@ private void OnSfuCallGrantsUpdated(CallGrantsUpdated callGrantsUpdated)
19461946
private void OnSfuChangePublishQuality(ChangePublishQuality changePublishQuality)
19471947
{
19481948
_sfuTracer?.Trace(PeerConnectionTraceKey.ChangePublishQuality, changePublishQuality);
1949-
// StreamTODO: Implement OnSfuChangePublishQuality
1949+
1950+
if (Publisher == null)
1951+
{
1952+
_logs.WarningIfDebug("Received ChangePublishQuality but Publisher is null");
1953+
return;
1954+
}
1955+
1956+
foreach (var videoSender in changePublishQuality.VideoSenders)
1957+
{
1958+
Publisher.ChangePublishQuality(videoSender);
1959+
}
19501960
}
19511961

19521962
private void OnSfuConnectionQualityChanged(ConnectionQualityChanged connectionQualityChanged)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.Text;
2+
using StreamVideo.Libs.Logs;
3+
using Unity.WebRTC;
4+
using SfuEvents = StreamVideo.v1.Sfu.Events;
5+
6+
namespace StreamVideo.Core.Utils
7+
{
8+
/// <summary>
9+
/// Compact debug logging for ChangePublishQuality events.
10+
/// Log format per layer: "rid=ON/OFF br=300k fps=15 sc=4"
11+
/// Abbreviations: br=bitrate, fps=framerate, sc=scaleResolutionDownBy
12+
/// </summary>
13+
internal static class PublishQualityDebugLogger
14+
{
15+
public static void LogSfuRequest(ILogs logs, string peerType,
16+
SfuEvents.VideoSender videoSenderSettings)
17+
{
18+
var sb = new StringBuilder();
19+
sb.Append($"[{peerType}] ChangePublishQuality SFU req:");
20+
21+
foreach (var layer in videoSenderSettings.Layers)
22+
{
23+
sb.Append($" {layer.Name}=");
24+
if (!layer.Active)
25+
{
26+
sb.Append("OFF |");
27+
continue;
28+
}
29+
30+
sb.Append("ON br=");
31+
sb.Append(FormatBitrate(layer.MaxBitrate));
32+
sb.Append(" fps=");
33+
sb.Append(layer.MaxFramerate);
34+
sb.Append(" sc=");
35+
sb.Append(layer.ScaleResolutionDownBy);
36+
sb.Append(" |");
37+
}
38+
39+
TrimTrailingSeparator(sb);
40+
logs.Warning(sb.ToString());
41+
}
42+
43+
public static void LogStateBefore(ILogs logs, string peerType,
44+
RTCRtpSendParameters parameters)
45+
{
46+
logs.Warning($"[{peerType}] ChangePublishQuality before: {FormatEncodings(parameters)}");
47+
}
48+
49+
public static void LogStateAfter(ILogs logs, string peerType,
50+
RTCRtpSendParameters parameters)
51+
{
52+
logs.Warning($"[{peerType}] ChangePublishQuality after: {FormatEncodings(parameters)}");
53+
}
54+
55+
private static string FormatEncodings(RTCRtpSendParameters parameters)
56+
{
57+
var sb = new StringBuilder();
58+
foreach (var encoding in parameters.encodings)
59+
{
60+
var rid = string.IsNullOrEmpty(encoding.rid) ? "f" : encoding.rid;
61+
sb.Append($"{rid}=");
62+
if (!encoding.active)
63+
{
64+
sb.Append("OFF | ");
65+
continue;
66+
}
67+
68+
sb.Append("ON br=");
69+
sb.Append(FormatBitrate(encoding.maxBitrate));
70+
sb.Append(" fps=");
71+
sb.Append(encoding.maxFramerate?.ToString() ?? "?");
72+
sb.Append(" sc=");
73+
sb.Append(encoding.scaleResolutionDownBy?.ToString("F0") ?? "?");
74+
sb.Append(" | ");
75+
}
76+
77+
if (sb.Length >= 3)
78+
{
79+
sb.Length -= 3;
80+
}
81+
82+
return sb.ToString();
83+
}
84+
85+
private static string FormatBitrate(ulong? bps)
86+
{
87+
if (!bps.HasValue) return "?";
88+
return bps.Value >= 1_000_000
89+
? $"{bps.Value / 1_000_000d:F1}m"
90+
: $"{bps.Value / 1_000d:F0}k";
91+
}
92+
93+
private static string FormatBitrate(int bps)
94+
{
95+
return bps >= 1_000_000
96+
? $"{bps / 1_000_000d:F1}m"
97+
: $"{bps / 1_000d:F0}k";
98+
}
99+
100+
private static void TrimTrailingSeparator(StringBuilder sb)
101+
{
102+
if (sb.Length >= 2 && sb[sb.Length - 1] == '|')
103+
{
104+
sb.Length -= 2;
105+
}
106+
}
107+
}
108+
}

Packages/StreamVideo/Runtime/Core/Utils/PublishQualityDebugLogger.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)