From f3de99a4bc80ee4d3956d89d76ed8343840641c6 Mon Sep 17 00:00:00 2001 From: Guillaume Faas <59444272+Tr00d@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:00:06 +0100 Subject: [PATCH] feat: update Broadcast and SIP (#233) * Add HlsStatus property on Broadcast * Add MaxBitRate property * Make Broadcast Bitrate an object with its own validation * Update converter for Bitrate * Add streams property to Dial feature * Add streams property to Dial feature --- .gitignore | 4 +- OpenTok/Broadcast.cs | 20 ++++++- OpenTok/BroadcastBitrate.cs | 53 +++++++++++++++++++ OpenTok/DialOptions.cs | 5 ++ OpenTok/OpenTok.cs | 6 ++- OpenTokTest/BroadcastBitrateTest.cs | 30 +++++++++++ OpenTokTest/BroadcastTests.cs | 6 +++ .../BroadcastTests/GetBroadcast-response.json | 3 +- .../GetBroadcastAsync-response.json | 3 +- .../StartBroadcast-response.json | 3 +- .../StartBroadcastAsync-response.json | 3 +- ...StartBroadcastWithRTMPandHLS-response.json | 1 + ...BroadcastWithRTMPandHLSAsync-response.json | 1 + OpenTokTest/DialTests.cs | 8 ++- 14 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 OpenTok/BroadcastBitrate.cs create mode 100644 OpenTokTest/BroadcastBitrateTest.cs diff --git a/.gitignore b/.gitignore index 7e6c5436..a9918195 100644 --- a/.gitignore +++ b/.gitignore @@ -220,5 +220,5 @@ pip-log.txt #Samples/HelloWorld/App.config #Samples/Archiving/App.config -## Jetbrains IDE -.idea/ +## Jetbrains Rider files +.idea/ \ No newline at end of file diff --git a/OpenTok/Broadcast.cs b/OpenTok/Broadcast.cs index 6637c0d0..6f80f71f 100644 --- a/OpenTok/Broadcast.cs +++ b/OpenTok/Broadcast.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -92,6 +91,7 @@ internal void CopyBroadcast(Broadcast broadcast) StreamMode = broadcast.StreamMode; Settings = broadcast.Settings; MultiBroadcastTag = broadcast.MultiBroadcastTag; + MaxBitRate = broadcast.MaxBitRate; if (BroadcastUrls == null) return; @@ -100,6 +100,11 @@ internal void CopyBroadcast(Broadcast broadcast) { Hls = BroadcastUrls["hls"].ToString(); } + + if (BroadcastUrls.ContainsKey("hlsStatus")) + { + HlsStatus = BroadcastUrls["hlsStatus"].ToString(); + } if (BroadcastUrls.ContainsKey("rtmp")) { @@ -201,6 +206,17 @@ internal void CopyBroadcast(Broadcast broadcast) [JsonProperty("multiBroadcastTag")] public string MultiBroadcastTag { get; set; } + /// + /// HLS status. Can be one of the following: 'connecting', 'ready', 'live', 'ended' or 'error'. + /// + public string HlsStatus { get; set; } + + /// + /// Maximum BitRate allowed for the broadcast composing. The default value is 2000000, which is also the maximum value. The minimum value is 400000. + /// + [JsonConverter(typeof(BroadcastBitrateConverter))] + public BroadcastBitrate MaxBitRate { get; set; } = new BroadcastBitrate(); + /// /// Stops the live broadcasting if it is started. /// diff --git a/OpenTok/BroadcastBitrate.cs b/OpenTok/BroadcastBitrate.cs new file mode 100644 index 00000000..37b32765 --- /dev/null +++ b/OpenTok/BroadcastBitrate.cs @@ -0,0 +1,53 @@ +using System; +using Newtonsoft.Json; +using OpenTokSDK.Exception; + +namespace OpenTokSDK +{ + /// + /// Represents a BitRate for broadcasts. + /// + public class BroadcastBitrate + { + private const int MaxBitrate = 2000000; + private const int MinBitrate = 400000; + + /// + /// The maximum BitRate allowed for the broadcast composing. + /// + public long Bitrate { get; } + + /// + /// Creates a BroadcastBitrate with the maximum BitRate. + /// + public BroadcastBitrate() => this.Bitrate = MaxBitrate; + + /// + /// Creates a BroadcastBitrate with a specific Bitrate. + /// + /// The Bitrate. + /// + /// When specified bitrate is lower than the minimum value, or higher than the + /// maximum value. + /// + public BroadcastBitrate(long bitrate) => + this.Bitrate = ValidateBitrate(bitrate) + ? bitrate + : throw new OpenTokArgumentException($"Bitrate value must be between {MinBitrate} and {MaxBitrate}."); + + private static bool ValidateBitrate(long bitrate) => bitrate <= MaxBitrate && bitrate >= MinBitrate; + } + + internal class BroadcastBitrateConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, BroadcastBitrate value, JsonSerializer serializer) => + writer.WriteValue(value?.Bitrate); + + public override BroadcastBitrate ReadJson(JsonReader reader, Type objectType, BroadcastBitrate existingValue, bool hasExistingValue, + JsonSerializer serializer) + { + var value = (long?)reader.Value; + return value.HasValue ? new BroadcastBitrate(value.Value) : null; + } + } +} \ No newline at end of file diff --git a/OpenTok/DialOptions.cs b/OpenTok/DialOptions.cs index 923e8e2f..24916cd8 100644 --- a/OpenTok/DialOptions.cs +++ b/OpenTok/DialOptions.cs @@ -50,5 +50,10 @@ public class DialOptions /// (true) or not (false, the default). /// public bool? ObserveForceMute { get; set; } + + /// + /// The stream IDs of the participants' which will be subscribed by the SIP participant. If not provided, all streams in session will be selected. + /// + public string[] Streams { get; set; } } } diff --git a/OpenTok/OpenTok.cs b/OpenTok/OpenTok.cs index 779cd209..edbd622e 100644 --- a/OpenTok/OpenTok.cs +++ b/OpenTok/OpenTok.cs @@ -1909,7 +1909,8 @@ public Sip Dial(string sessionId, string token, string sipUri, DialOptions optio auth = options?.Auth, secure = options?.Secure, video = options?.Video, - observeForceMute = options?.ObserveForceMute + observeForceMute = options?.ObserveForceMute, + streams = options?.Streams, } } }; @@ -1968,7 +1969,8 @@ public async Task DialAsync(string sessionId, string token, string sipUri, auth = options?.Auth, secure = options?.Secure, video = options?.Video, - observeForceMute = options?.ObserveForceMute + observeForceMute = options?.ObserveForceMute, + streams = options?.Streams, } } }; diff --git a/OpenTokTest/BroadcastBitrateTest.cs b/OpenTokTest/BroadcastBitrateTest.cs new file mode 100644 index 00000000..d0b1de90 --- /dev/null +++ b/OpenTokTest/BroadcastBitrateTest.cs @@ -0,0 +1,30 @@ +using OpenTokSDK; +using OpenTokSDK.Exception; +using Xunit; + +namespace OpenTokSDKTest +{ + public class BroadcastBitrateTest + { + [Fact] + public void Bitrate_ShouldHaveDefaultValue() => + Assert.Equal(2000000, new BroadcastBitrate().Bitrate); + + [Fact] + public void Bitrate_ShouldAllowMaximumValue()=> + Assert.Equal(2000000, new BroadcastBitrate(2000000).Bitrate); + + [Fact] + public void Bitrate_ShouldAllowMinimumValue()=> + Assert.Equal(400000, new BroadcastBitrate(400000).Bitrate); + + [Theory] + [InlineData(399999)] + [InlineData(2000001)] + public void Bitrate_ShouldThrowException_GivenValueIsOutsideRange(int invalidBitrate) + { + var exception = Assert.Throws(() => new BroadcastBitrate(invalidBitrate)); + Assert.Equal("Bitrate value must be between 400000 and 2000000.", exception.Message); + } + } +} \ No newline at end of file diff --git a/OpenTokTest/BroadcastTests.cs b/OpenTokTest/BroadcastTests.cs index 487472cc..fdefe11f 100644 --- a/OpenTokTest/BroadcastTests.cs +++ b/OpenTokTest/BroadcastTests.cs @@ -30,6 +30,7 @@ public void StartBroadcast() Assert.Equal(sessionId, broadcast.SessionId); Assert.NotNull(broadcast.Id); Assert.Equal(Broadcast.BroadcastStatus.STARTED, broadcast.Status); + Assert.Equal(2000000, broadcast.MaxBitRate.Bitrate); mockClient.Verify( httpClient => httpClient.Post(It.Is(url => url.Equals("v2/project/" + ApiKey + "/broadcast")), @@ -57,6 +58,7 @@ public async Task StartBroadcastAsync() Assert.Equal(sessionId, broadcast.SessionId); Assert.NotNull(broadcast.Id); Assert.Equal(Broadcast.BroadcastStatus.STARTED, broadcast.Status); + Assert.Equal(1000000, broadcast.MaxBitRate.Bitrate); mockClient.Verify( httpClient => httpClient.PostAsync(expectedUrl, It.IsAny>(), @@ -481,6 +483,7 @@ public void StartBroadcastWithRTMPandHLS() Assert.NotNull(broadcast.RtmpList); Assert.Equal(2, broadcast.RtmpList.Count); Assert.NotNull(broadcast.Hls); + Assert.Equal("ready", broadcast.HlsStatus); Assert.NotNull(broadcast.Id); Assert.Equal(Broadcast.BroadcastStatus.STARTED, broadcast.Status); @@ -530,6 +533,7 @@ public async Task StartBroadcastWithRTMPandHLSAsync() Assert.NotNull(broadcast.RtmpList); Assert.Equal(2, broadcast.RtmpList.Count); Assert.NotNull(broadcast.Hls); + Assert.Equal("ready", broadcast.HlsStatus); Assert.NotNull(broadcast.Id); Assert.Equal(Broadcast.BroadcastStatus.STARTED, broadcast.Status); @@ -1405,6 +1409,7 @@ public void GetBroadcast() Assert.NotNull(broadcast); Assert.Equal(broadcastId, broadcast.Id); Assert.NotNull(broadcast.Id); + Assert.Equal("ready", broadcast.HlsStatus); var expectedUrl = $"v2/project/{ApiKey}/broadcast/{broadcastId}"; mockClient.Verify(httpClient => httpClient.Get(It.Is(url => url.Equals(expectedUrl))), @@ -1427,6 +1432,7 @@ public async Task GetBroadcastAsync() Assert.NotNull(broadcast); Assert.Equal(broadcastId, broadcast.Id); Assert.NotNull(broadcast.Id); + Assert.Equal("ready", broadcast.HlsStatus); var expectedUrl = $"v2/project/{ApiKey}/broadcast/{broadcastId}"; mockClient.Verify(httpClient => httpClient.GetAsync(It.Is(url => url.Equals(expectedUrl)), null), diff --git a/OpenTokTest/Data/BroadcastTests/GetBroadcast-response.json b/OpenTokTest/Data/BroadcastTests/GetBroadcast-response.json index 16f14cd1..b6861f1d 100644 --- a/OpenTokTest/Data/BroadcastTests/GetBroadcast-response.json +++ b/OpenTokTest/Data/BroadcastTests/GetBroadcast-response.json @@ -7,6 +7,7 @@ "resolution": "640x480", "status": "started", "broadcastUrls": { - "hls": "http://server/fakepath/playlist.m3u8" + "hls": "http://server/fakepath/playlist.m3u8", + "hlsStatus" : "ready" } } \ No newline at end of file diff --git a/OpenTokTest/Data/BroadcastTests/GetBroadcastAsync-response.json b/OpenTokTest/Data/BroadcastTests/GetBroadcastAsync-response.json index 16f14cd1..b6861f1d 100644 --- a/OpenTokTest/Data/BroadcastTests/GetBroadcastAsync-response.json +++ b/OpenTokTest/Data/BroadcastTests/GetBroadcastAsync-response.json @@ -7,6 +7,7 @@ "resolution": "640x480", "status": "started", "broadcastUrls": { - "hls": "http://server/fakepath/playlist.m3u8" + "hls": "http://server/fakepath/playlist.m3u8", + "hlsStatus" : "ready" } } \ No newline at end of file diff --git a/OpenTokTest/Data/BroadcastTests/StartBroadcast-response.json b/OpenTokTest/Data/BroadcastTests/StartBroadcast-response.json index f7a997f2..faa46eca 100644 --- a/OpenTokTest/Data/BroadcastTests/StartBroadcast-response.json +++ b/OpenTokTest/Data/BroadcastTests/StartBroadcast-response.json @@ -9,5 +9,6 @@ "broadcastUrls": { "hls": "http://server/fakepath/playlist.m3u8" }, - "settings": { "hls": { "lowLatency": false } } + "settings": { "hls": { "lowLatency": false } }, + "maxBitRate": 2000000 } \ No newline at end of file diff --git a/OpenTokTest/Data/BroadcastTests/StartBroadcastAsync-response.json b/OpenTokTest/Data/BroadcastTests/StartBroadcastAsync-response.json index f7a997f2..20ba4eb0 100644 --- a/OpenTokTest/Data/BroadcastTests/StartBroadcastAsync-response.json +++ b/OpenTokTest/Data/BroadcastTests/StartBroadcastAsync-response.json @@ -9,5 +9,6 @@ "broadcastUrls": { "hls": "http://server/fakepath/playlist.m3u8" }, - "settings": { "hls": { "lowLatency": false } } + "settings": { "hls": { "lowLatency": false } }, + "maxBitRate": 1000000 } \ No newline at end of file diff --git a/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLS-response.json b/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLS-response.json index 9b827ce3..e1c4bfb7 100644 --- a/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLS-response.json +++ b/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLS-response.json @@ -8,6 +8,7 @@ "status": "started", "broadcastUrls": { "hls": "http://server/fakepath/playlist.m3u8", + "hlsStatus" : "ready", "rtmp": [ { "status": "connecting", diff --git a/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLSAsync-response.json b/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLSAsync-response.json index 9b827ce3..e1c4bfb7 100644 --- a/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLSAsync-response.json +++ b/OpenTokTest/Data/BroadcastTests/StartBroadcastWithRTMPandHLSAsync-response.json @@ -8,6 +8,7 @@ "status": "started", "broadcastUrls": { "hls": "http://server/fakepath/playlist.m3u8", + "hlsStatus" : "ready", "rtmp": [ { "status": "connecting", diff --git a/OpenTokTest/DialTests.cs b/OpenTokTest/DialTests.cs index 9b61f255..27753b03 100644 --- a/OpenTokTest/DialTests.cs +++ b/OpenTokTest/DialTests.cs @@ -164,7 +164,8 @@ public void DialCorrectData() Auth = new DialAuth { Username = "Tim", Password = "Bob" }, Secure = true, Video = true, - ObserveForceMute = true + ObserveForceMute = true, + Streams = new []{"stream1", "stream2"}, }; Dictionary headersSent = null; @@ -199,6 +200,7 @@ public void DialCorrectData() Assert.Equal(dialOptions.Secure, sip.secure); Assert.Equal(dialOptions.Video, sip.video); Assert.Equal(dialOptions.ObserveForceMute, sip.observeForceMute); + Assert.Equal(dialOptions.Streams, sip.streams); } [Fact] @@ -215,7 +217,8 @@ public async Task DialAsyncCorrectData() Auth = new DialAuth { Username = "Tim", Password = "Bob" }, Secure = true, Video = true, - ObserveForceMute = true + ObserveForceMute = true, + Streams = new []{"stream1", "stream2"}, }; Dictionary headersSent = null; @@ -250,6 +253,7 @@ public async Task DialAsyncCorrectData() Assert.Equal(dialOptions.Secure, sip.secure); Assert.Equal(dialOptions.Video, sip.video); Assert.Equal(dialOptions.ObserveForceMute, sip.observeForceMute); + Assert.Equal(dialOptions.Streams, sip.streams); } [Fact]