Skip to content

Commit

Permalink
added errorhandling to mediachannel and wrote test case for it
Browse files Browse the repository at this point in the history
  • Loading branch information
Tapanila committed Aug 12, 2024
1 parent 01fff32 commit 64b0472
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Sharpcaster.Test/LoggingTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void TestLogging() {
List<string> 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]
Expand Down
144 changes: 104 additions & 40 deletions Sharpcaster.Test/MediaChannelTester.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sharpcaster.Interfaces;
using Sharpcaster.Messages.Media;
using Sharpcaster.Models;
using Sharpcaster.Models.Media;
using Sharpcaster.Models.Queue;
Expand All @@ -14,18 +15,21 @@ namespace Sharpcaster.Test
{

[Collection("SingleCollection")]
public class MediaChannelTester : IClassFixture<ChromecastDevicesFixture> {
public class MediaChannelTester : IClassFixture<ChromecastDevicesFixture>
{

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))
Expand All @@ -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)
{
}
};

Expand All @@ -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);
Expand All @@ -96,24 +113,31 @@ 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);
output.WriteLine("Lets goto next item");
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);
Expand All @@ -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());
}
Expand All @@ -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);
Expand All @@ -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);
}

Expand All @@ -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);

Expand Down Expand Up @@ -264,15 +295,16 @@ 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();
Assert.Equal(PlayerStateType.Paused, mediaStatus.PlayerState);
runSequence += "P";
_autoResetEvent.Set();
}
}
}
};

runSequence += "1";
Expand Down Expand Up @@ -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());
}
};

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
}
}
25 changes: 25 additions & 0 deletions Sharpcaster.Test/helper/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
}
29 changes: 28 additions & 1 deletion Sharpcaster/Channels/MediaChannel.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,6 +18,10 @@ namespace Sharpcaster.Channels
/// </summary>
public class MediaChannel : StatusChannel<MediaStatusMessage, IEnumerable<MediaStatus>>, IMediaChannel
{
/// <summary>
/// Raised when error is received
/// </summary>
public event EventHandler<ErrorMessage> ErrorHappened;
public MediaStatus MediaStatus { get => Status.FirstOrDefault(); }
/// <summary>
/// Initializes a new instance of MediaChannel class
Expand Down Expand Up @@ -68,6 +73,17 @@ public async Task<MediaStatus> 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);
}

/// <summary>
/// Plays the media
/// </summary>
Expand Down Expand Up @@ -109,7 +125,18 @@ public async Task<MediaStatus> SeekAsync(double seconds)
public async Task<MediaStatus> QueueLoadAsync(QueueItem[] items, long? currentTime = null, RepeatModeType repeatMode = RepeatModeType.OFF, long? startIndex = null)
{
var chromecastStatus = Client.GetChromecastStatus();
return (await SendAsync<MediaStatusMessage>(new QueueLoadMessage() { SessionId = chromecastStatus.Application.SessionId, Items = items, CurrentTime = currentTime, RepeatMode = repeatMode, StartIndex = startIndex }, chromecastStatus.Application.TransportId)).Status?.FirstOrDefault();
var response = await SendAsync<MessageWithId>(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<MediaStatus> QueueNextAsync()
Expand Down
Loading

0 comments on commit 64b0472

Please sign in to comment.