diff --git a/Sharpcaster.Test/LoggingTester.cs b/Sharpcaster.Test/LoggingTester.cs index 350b336..255f1d5 100644 --- a/Sharpcaster.Test/LoggingTester.cs +++ b/Sharpcaster.Test/LoggingTester.cs @@ -21,7 +21,7 @@ public void TestLogging() { List logLines = []; _ = TestHelper.GetClientWithTestOutput(output, assertableLog: logLines); - Assert.Equal("[LAUNCH_ERROR,RECEIVER_STATUS,QUEUE_CHANGE,QUEUE_ITEM_IDS,QUEUE_ITEMS,DEVICE_UPDATED,MULTIZONE_STATUS,INVALID_REQUEST,LOAD_CANCELLED,LOAD_FAILED,MEDIA_STATUS,PING,CLOSE]", logLines[0]); + Assert.Equal("[LAUNCH_ERROR,RECEIVER_STATUS,QUEUE_CHANGE,QUEUE_ITEM_IDS,QUEUE_ITEMS,DEVICE_UPDATED,MULTIZONE_STATUS,ERROR,INVALID_REQUEST,LOAD_CANCELLED,LOAD_FAILED,MEDIA_STATUS,PING,CLOSE]", logLines[0]); } [Theory] diff --git a/Sharpcaster.Test/MediaChannelTester.cs b/Sharpcaster.Test/MediaChannelTester.cs index 0dd72be..cd3b2cf 100644 --- a/Sharpcaster.Test/MediaChannelTester.cs +++ b/Sharpcaster.Test/MediaChannelTester.cs @@ -1,4 +1,5 @@ using Sharpcaster.Interfaces; +using Sharpcaster.Messages.Media; using Sharpcaster.Models; using Sharpcaster.Models.Media; using Sharpcaster.Models.Queue; @@ -14,18 +15,21 @@ namespace Sharpcaster.Test { [Collection("SingleCollection")] - public class MediaChannelTester : IClassFixture { + public class MediaChannelTester : IClassFixture + { private ITestOutputHelper output; - public MediaChannelTester(ITestOutputHelper outputHelper, ChromecastDevicesFixture fixture) { + public MediaChannelTester(ITestOutputHelper outputHelper, ChromecastDevicesFixture fixture) + { output = outputHelper; output.WriteLine("Fixture has found " + ChromecastDevicesFixture.Receivers?.Count + " receivers with " + fixture.GetSearchesCnt() + " searche(s)."); } - [Theory(Skip ="Skipped for autotesting because manual intervention on device is needed for this test!")] + [Theory(Skip = "Skipped for autotesting because manual intervention on device is needed for this test!")] [MemberData(nameof(ChromecastReceiversFilter.GetJblSpeaker), MemberType = typeof(ChromecastReceiversFilter))] - public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receiver) { + public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receiver) + { // To get this test Passing, you have to manually operate the used Chromecast device! // I use it with a JBL speaker device. This device has 5 buttons. (ON/OFF, Vol-, Vol+, Play/Pause, (and WLAN-Connect)) @@ -40,28 +44,38 @@ public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receive // var TestHelper = new TestHelper(); ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); - if (receiver.Model == "JBL Playlist") { + if (receiver.Model == "JBL Playlist") + { - var media = new Media { + var media = new Media + { ContentUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4" }; AutoResetEvent _disconnectReceived = new AutoResetEvent(false); IMediaChannel mediaChannel = client.MediaChannel; - mediaChannel.StatusChanged += (object sender, EventArgs e) => { - try { + mediaChannel.StatusChanged += (object sender, EventArgs e) => + { + try + { MediaStatus status = mediaChannel.MediaStatus; output.WriteLine(status?.PlayerState.ToString()); - } catch (Exception) { + } + catch (Exception) + { } }; - client.Disconnected += (object sender, EventArgs e) => { - try { + client.Disconnected += (object sender, EventArgs e) => + { + try + { _disconnectReceived.Set(); output.WriteLine("Disconnect received."); - } catch (Exception) { + } + catch (Exception) + { } }; @@ -74,16 +88,19 @@ public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receive client = await TestHelper.CreateConnectAndLoadAppClient(output); status = await client.MediaChannel.LoadAsync(media); Assert.Equal(PlayerStateType.Playing, status.PlayerState); - } else { + } + else + { Assert.Fail("This test only runs with a 'JBL Playlist' device and also needs manual operations!"); } await client.DisconnectAsync(); } - + [Theory] [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] - public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver receiver) { + public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver receiver) + { var TestHelper = new TestHelper(); ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); @@ -96,16 +113,21 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re var mediaStatusChanged = 0; //We are setting up an event to listen to status change. Because we don't know when the audio has started to play - mediaChannel.StatusChanged += async (object sender, EventArgs e) => { - try { + mediaChannel.StatusChanged += async (object sender, EventArgs e) => + { + try + { mediaStatusChanged += 1; MediaStatus status = mediaChannel.MediaStatus; int currentItemId = status?.CurrentItemId ?? -1; - - if (currentItemId != -1 && status.PlayerState == PlayerStateType.Playing) { - if (status?.Items?.ToList()?.Where(i => i.ItemId == currentItemId).FirstOrDefault()?.Media?.ContentUrl?.Equals(MyCd[0].Media.ContentUrl) ?? false) { - if (testSequenceCount == 0) { + if (currentItemId != -1 && status.PlayerState == PlayerStateType.Playing) + { + + if (status?.Items?.ToList()?.Where(i => i.ItemId == currentItemId).FirstOrDefault()?.Media?.ContentUrl?.Equals(MyCd[0].Media.ContentUrl) ?? false) + { + if (testSequenceCount == 0) + { testSequenceCount++; output.WriteLine("First Test Track started playin. listen for some seconds...."); await Task.Delay(6000); @@ -113,7 +135,9 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re status = await mediaChannel.QueueNextAsync(); // Asserts // ... - } else { + } + else + { testSequenceCount++; output.WriteLine("First Test Track started for the 2nd time. Stop and end the test"); await Task.Delay(1000); @@ -122,16 +146,20 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re _autoResetEvent.Set(); } - } else if (status?.Items?.ToList()?.Where(i => i.ItemId == currentItemId).FirstOrDefault()?.Media?.ContentUrl?.Equals(MyCd[1].Media.ContentUrl) ?? false) { + } + else if (status?.Items?.ToList()?.Where(i => i.ItemId == currentItemId).FirstOrDefault()?.Media?.ContentUrl?.Equals(MyCd[1].Media.ContentUrl) ?? false) + { output.WriteLine("2nd Test Track started playin. listen for some seconds...."); testSequenceCount++; await Task.Delay(6000); output.WriteLine("Lets goto back to first one"); status = await mediaChannel.QueuePrevAsync(); - } + } } - } catch (Exception ex) { + } + catch (Exception ex) + { output?.WriteLine(ex.ToString()); Assert.Fail(ex.ToString()); } @@ -154,10 +182,11 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re [Theory] //[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] // This sometimes give a INVALID_MEDIA_SESSION_ID on my Chromecast Audio .... [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] - public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) { + public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) + { var TestHelper = new TestHelper(); ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); - + QueueItem[] MyCd = TestHelper.CreateTestCd(); MediaStatus status = await client.MediaChannel.QueueLoadAsync(MyCd); @@ -172,8 +201,9 @@ public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) Assert.Equal(4, ids.Length); - foreach (int id in ids) { - QueueItem[] items = await client.MediaChannel.QueueGetItemsAsync(new int[] {id}); + foreach (int id in ids) + { + QueueItem[] items = await client.MediaChannel.QueueGetItemsAsync(new int[] { id }); Assert.Single(items); } @@ -186,7 +216,8 @@ public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) [Theory] [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] - public async Task TestLoadingMediaQueue(ChromecastReceiver receiver) { + public async Task TestLoadingMediaQueue(ChromecastReceiver receiver) + { var TestHelper = new TestHelper(); ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); @@ -264,7 +295,8 @@ public async Task TestLoadingAndPausingMedia(ChromecastReceiver receiver) //runSequence += "."; if (client.MediaChannel.Status.FirstOrDefault()?.PlayerState == PlayerStateType.Playing) { - if (firstPlay) { + if (firstPlay) + { firstPlay = false; runSequence += "p"; mediaStatus = await client.MediaChannel.PauseAsync(); @@ -272,7 +304,7 @@ public async Task TestLoadingAndPausingMedia(ChromecastReceiver receiver) runSequence += "P"; _autoResetEvent.Set(); } - } + } }; runSequence += "1"; @@ -306,17 +338,22 @@ public async Task TestLoadingAndStoppingMedia(ChromecastReceiver receiver) //We are setting up an event to listen to status change. Because we don't know when the video has started to play client.MediaChannel.StatusChanged += async (object sender, EventArgs e) => { - try { - if (client.MediaChannel.MediaStatus?.PlayerState == PlayerStateType.Playing) { - if (firstPlay) { + try + { + if (client.MediaChannel.MediaStatus?.PlayerState == PlayerStateType.Playing) + { + if (firstPlay) + { firstPlay = false; await Task.Delay(2000); // Listen for some time mediaStatus = await client.MediaChannel.StopAsync(); _autoResetEvent.Set(); } } - } catch (Exception ex) { - output.WriteLine("Exception in Event Handler: " + ex.ToString()); + } + catch (Exception ex) + { + output.WriteLine("Exception in Event Handler: " + ex.ToString()); } }; @@ -347,11 +384,12 @@ public async Task TestFailingLoadMedia(ChromecastReceiver receiver) try { mediaStatus = await client.MediaChannel.LoadAsync(media); - } catch (Exception ex) + } + catch (Exception ex) { loadFailedException = ex; } - + Assert.Equal("Load failed", loadFailedException?.Message); } @@ -470,6 +508,32 @@ public async Task TestRepeatingAllAndShuffleQueueMedia(ChromecastReceiver receiv Assert.Equal(RepeatModeType.ALL_AND_SHUFFLE, test.RepeatMode); } - } + [Theory] + [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + public async Task TestFailingQueue(ChromecastReceiver receiver) + { + + var TestHelper = new TestHelper(); + ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); + bool errorHappened = false; + + QueueItem[] MyCd = TestHelper.CreateFailingQueu(); + + client.MediaChannel.ErrorHappened += (object sender, ErrorMessage e) => + { + output.WriteLine("Error happened: " + e.DetailedErrorCode); + errorHappened = true; + }; + try + { + var result = await client.MediaChannel.QueueLoadAsync(MyCd); + } catch (Exception ex) + { + output.WriteLine("Exception happened: " + ex.Message); + } + await Task.Delay(200); + Assert.True(errorHappened); + } + } } diff --git a/Sharpcaster.Test/helper/TestHelper.cs b/Sharpcaster.Test/helper/TestHelper.cs index 155e4aa..652fd19 100644 --- a/Sharpcaster.Test/helper/TestHelper.cs +++ b/Sharpcaster.Test/helper/TestHelper.cs @@ -325,5 +325,30 @@ public QueueItem[] CreateTestCd() return MyCd; } + public QueueItem[] CreateFailingQueu() + { + QueueItem[] queueItems = + [ + + new QueueItem() { + Media = new Media { + ContentUrl = "https://audionautix.com/Music/AwayInAManger.mp3" + } + }, + new QueueItem() { + Media = new Media { + ContentUrl = "https://audionautix.com/Music/CarolOfTheBells.mp3" + } + }, + new QueueItem() { + Media = new Media { + ContentUrl = "https://audionautix.com/Music/JoyToTheWorld.mp3" + } + } + + ]; + return queueItems; + } + } } diff --git a/Sharpcaster/Channels/MediaChannel.cs b/Sharpcaster/Channels/MediaChannel.cs index 7daa8fc..3d295f6 100644 --- a/Sharpcaster/Channels/MediaChannel.cs +++ b/Sharpcaster/Channels/MediaChannel.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Sharpcaster.Interfaces; +using Sharpcaster.Messages; using Sharpcaster.Messages.Media; using Sharpcaster.Messages.Queue; using Sharpcaster.Models.ChromecastStatus; @@ -17,6 +18,10 @@ namespace Sharpcaster.Channels /// public class MediaChannel : StatusChannel>, IMediaChannel { + /// + /// Raised when error is received + /// + public event EventHandler ErrorHappened; public MediaStatus MediaStatus { get => Status.FirstOrDefault(); } /// /// Initializes a new instance of MediaChannel class @@ -68,6 +73,17 @@ public async Task LoadAsync(Media media, bool autoPlay = true) return await SendAsync(new LoadMessage() { SessionId = status.Application.SessionId, Media = media, AutoPlay = autoPlay }, status.Application); } + public override Task OnMessageReceivedAsync(IMessage message) + { + switch (message) + { + case ErrorMessage errorMessage: + ErrorHappened?.Invoke(this, errorMessage); + throw new Exception("Errored: " + errorMessage.DetailedErrorCode); + } + return base.OnMessageReceivedAsync(message); + } + /// /// Plays the media /// @@ -109,7 +125,18 @@ public async Task SeekAsync(double seconds) public async Task QueueLoadAsync(QueueItem[] items, long? currentTime = null, RepeatModeType repeatMode = RepeatModeType.OFF, long? startIndex = null) { var chromecastStatus = Client.GetChromecastStatus(); - return (await SendAsync(new QueueLoadMessage() { SessionId = chromecastStatus.Application.SessionId, Items = items, CurrentTime = currentTime, RepeatMode = repeatMode, StartIndex = startIndex }, chromecastStatus.Application.TransportId)).Status?.FirstOrDefault(); + var response = await SendAsync(new QueueLoadMessage() { SessionId = chromecastStatus.Application.SessionId, Items = items, CurrentTime = currentTime, RepeatMode = repeatMode, StartIndex = startIndex }, chromecastStatus.Application.TransportId); + + switch (response) + { + case MediaStatusMessage mediaStatusMessage: + return mediaStatusMessage.Status?.FirstOrDefault(); + case LoadFailedMessage _: + throw new Exception("Load failed"); + case LoadCancelledMessage _: + throw new Exception("Load cancelled"); + } + throw new Exception("Unknown response"); } public async Task QueueNextAsync() diff --git a/Sharpcaster/Interfaces/IMediaChannel.cs b/Sharpcaster/Interfaces/IMediaChannel.cs index b89dcf0..6fc42d0 100644 --- a/Sharpcaster/Interfaces/IMediaChannel.cs +++ b/Sharpcaster/Interfaces/IMediaChannel.cs @@ -1,5 +1,7 @@ -using Sharpcaster.Models.Media; +using Sharpcaster.Messages.Media; +using Sharpcaster.Models.Media; using Sharpcaster.Models.Queue; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -8,6 +10,7 @@ namespace Sharpcaster.Interfaces { /// Interface for the media channel /// public interface IMediaChannel : IStatusChannel>, IChromecastChannel { + event EventHandler ErrorHappened; /// /// Loads a media ///