From 48bb06c9cdeac5f6698b6322683af38653458626 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 21:49:56 +0000
Subject: [PATCH 1/6] Initial plan
From 69c9a692157c92520ef45279b266e49d42ba319f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 21:54:38 +0000
Subject: [PATCH 2/6] Always seek to live stream when resuming radio playback
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Services/RadioPlayerService.cs | 85 +++++++++++++++++++++++++----
1 file changed, 73 insertions(+), 12 deletions(-)
diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs
index ca8ab21..a1bb9c0 100644
--- a/Trdo/Services/RadioPlayerService.cs
+++ b/Trdo/Services/RadioPlayerService.cs
@@ -170,8 +170,21 @@ private RadioPlayerService()
Debug.WriteLine("[RadioPlayerService] External state change detected (likely hardware button)");
if (currentState == MediaPlaybackState.Playing)
{
- _watchdog.NotifyUserIntentionToPlay();
- Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play (hardware button)");
+ // For live radio streams, when resuming via hardware buttons we need to
+ // refresh the stream to get the live position instead of resuming from
+ // the buffered position. Queue a refresh on the UI thread.
+ Debug.WriteLine("[RadioPlayerService] External play detected - will refresh stream for live playback");
+ TryEnqueueOnUi(() =>
+ {
+ try
+ {
+ RefreshStreamForLivePlayback();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[RadioPlayerService] Failed to refresh stream: {ex.Message}");
+ }
+ });
}
else if (currentState == MediaPlaybackState.Paused)
{
@@ -328,19 +341,21 @@ public void Play()
try
{
- // Ensure we have a fresh media source
- if (_player.Source == null)
+ // Always create a fresh media source to ensure we're streaming live content.
+ // For live radio streams, resuming from a paused position causes issues
+ // if there's been a long gap, so we always seek to the live stream.
+ if (_player.Source is MediaSource oldMedia)
{
- Debug.WriteLine("[RadioPlayerService] Player.Source is null, creating new MediaSource");
- Uri uri = new(_streamUrl);
- _player.Source = MediaSource.CreateFromUri(uri);
- Debug.WriteLine($"[RadioPlayerService] Created new MediaSource from URL: {_streamUrl}");
- }
- else
- {
- Debug.WriteLine($"[RadioPlayerService] Player.Source exists, current state: {(_player.Source as MediaSource)?.State}");
+ Debug.WriteLine("[RadioPlayerService] Disposing old MediaSource to ensure live stream");
+ oldMedia.Reset();
+ oldMedia.Dispose();
}
+ Debug.WriteLine("[RadioPlayerService] Creating fresh MediaSource for live stream");
+ Uri uri = new(_streamUrl);
+ _player.Source = MediaSource.CreateFromUri(uri);
+ Debug.WriteLine($"[RadioPlayerService] Created new MediaSource from URL: {_streamUrl}");
+
Debug.WriteLine("[RadioPlayerService] Calling _player.Play()...");
_isInternalStateChange = true;
_player.Play();
@@ -382,6 +397,52 @@ public void Play()
Debug.WriteLine($"=== Play END ===");
}
+ ///
+ /// Refreshes the stream to ensure live playback.
+ /// Used when external controls (like hardware buttons) resume playback,
+ /// to ensure we're always streaming live content instead of resuming from a buffered position.
+ ///
+ private void RefreshStreamForLivePlayback()
+ {
+ Debug.WriteLine("=== RefreshStreamForLivePlayback START ===");
+
+ if (string.IsNullOrWhiteSpace(_streamUrl))
+ {
+ Debug.WriteLine("[RadioPlayerService] No stream URL set, skipping refresh");
+ Debug.WriteLine("=== RefreshStreamForLivePlayback END ===");
+ return;
+ }
+
+ try
+ {
+ // Create a fresh media source to get the live stream position
+ if (_player.Source is MediaSource oldMedia)
+ {
+ Debug.WriteLine("[RadioPlayerService] Disposing old MediaSource for live stream refresh");
+ oldMedia.Reset();
+ oldMedia.Dispose();
+ }
+
+ Debug.WriteLine("[RadioPlayerService] Creating fresh MediaSource for live stream");
+ Uri uri = new(_streamUrl);
+ _isInternalStateChange = true;
+ _player.Source = MediaSource.CreateFromUri(uri);
+ _player.Play();
+ _isInternalStateChange = false;
+ Debug.WriteLine($"[RadioPlayerService] Stream refreshed for live playback: {_streamUrl}");
+
+ _watchdog.NotifyUserIntentionToPlay();
+ Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
+ }
+ catch (Exception ex)
+ {
+ _isInternalStateChange = false;
+ Debug.WriteLine($"[RadioPlayerService] EXCEPTION in RefreshStreamForLivePlayback: {ex.Message}");
+ }
+
+ Debug.WriteLine("=== RefreshStreamForLivePlayback END ===");
+ }
+
///
/// Stop playback and clean up resources
///
From 9a4acde0927f97034b36c879f38a567b606dbbc0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 21:56:01 +0000
Subject: [PATCH 3/6] Refactor: Eliminate code duplication in
RefreshStreamForLivePlayback
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Services/RadioPlayerService.cs | 23 ++++-------------------
1 file changed, 4 insertions(+), 19 deletions(-)
diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs
index a1bb9c0..703f87b 100644
--- a/Trdo/Services/RadioPlayerService.cs
+++ b/Trdo/Services/RadioPlayerService.cs
@@ -413,30 +413,15 @@ private void RefreshStreamForLivePlayback()
return;
}
+ // Play() already creates a fresh MediaSource to ensure live streaming,
+ // so we just delegate to it to avoid code duplication.
try
{
- // Create a fresh media source to get the live stream position
- if (_player.Source is MediaSource oldMedia)
- {
- Debug.WriteLine("[RadioPlayerService] Disposing old MediaSource for live stream refresh");
- oldMedia.Reset();
- oldMedia.Dispose();
- }
-
- Debug.WriteLine("[RadioPlayerService] Creating fresh MediaSource for live stream");
- Uri uri = new(_streamUrl);
- _isInternalStateChange = true;
- _player.Source = MediaSource.CreateFromUri(uri);
- _player.Play();
- _isInternalStateChange = false;
- Debug.WriteLine($"[RadioPlayerService] Stream refreshed for live playback: {_streamUrl}");
-
- _watchdog.NotifyUserIntentionToPlay();
- Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
+ Play();
+ Debug.WriteLine("[RadioPlayerService] Stream refreshed for live playback via Play()");
}
catch (Exception ex)
{
- _isInternalStateChange = false;
Debug.WriteLine($"[RadioPlayerService] EXCEPTION in RefreshStreamForLivePlayback: {ex.Message}");
}
From e6cbcd69ab811823dd5118fad9311ce1906fd6a8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 21:59:22 +0000
Subject: [PATCH 4/6] Improve exception handling documentation in
RefreshStreamForLivePlayback
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Services/RadioPlayerService.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs
index 703f87b..4d7bf98 100644
--- a/Trdo/Services/RadioPlayerService.cs
+++ b/Trdo/Services/RadioPlayerService.cs
@@ -415,6 +415,9 @@ private void RefreshStreamForLivePlayback()
// Play() already creates a fresh MediaSource to ensure live streaming,
// so we just delegate to it to avoid code duplication.
+ // Exceptions are caught here because this method is called from event handlers
+ // where unhandled exceptions could crash the application. The watchdog service
+ // will attempt recovery if the stream fails to start.
try
{
Play();
@@ -423,6 +426,7 @@ private void RefreshStreamForLivePlayback()
catch (Exception ex)
{
Debug.WriteLine($"[RadioPlayerService] EXCEPTION in RefreshStreamForLivePlayback: {ex.Message}");
+ Debug.WriteLine($"[RadioPlayerService] The watchdog service will attempt recovery if enabled");
}
Debug.WriteLine("=== RefreshStreamForLivePlayback END ===");
From c274943fa9a7149fd9f04267defb7156b9a72720 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 25 Nov 2025 05:02:53 +0000
Subject: [PATCH 5/6] Use SeekToLive instead of disposing MediaSource on resume
Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com>
---
Trdo/Services/RadioPlayerService.cs | 100 +++++++++++++++++-----------
1 file changed, 61 insertions(+), 39 deletions(-)
diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs
index 4d7bf98..7fe0ad6 100644
--- a/Trdo/Services/RadioPlayerService.cs
+++ b/Trdo/Services/RadioPlayerService.cs
@@ -171,18 +171,17 @@ private RadioPlayerService()
if (currentState == MediaPlaybackState.Playing)
{
// For live radio streams, when resuming via hardware buttons we need to
- // refresh the stream to get the live position instead of resuming from
- // the buffered position. Queue a refresh on the UI thread.
- Debug.WriteLine("[RadioPlayerService] External play detected - will refresh stream for live playback");
+ // seek to the live position instead of resuming from the buffered position.
+ Debug.WriteLine("[RadioPlayerService] External play detected - will seek to live position");
TryEnqueueOnUi(() =>
{
try
{
- RefreshStreamForLivePlayback();
+ SeekToLiveOnResume();
}
catch (Exception ex)
{
- Debug.WriteLine($"[RadioPlayerService] Failed to refresh stream: {ex.Message}");
+ Debug.WriteLine($"[RadioPlayerService] Failed to seek to live: {ex.Message}");
}
});
}
@@ -341,20 +340,18 @@ public void Play()
try
{
- // Always create a fresh media source to ensure we're streaming live content.
- // For live radio streams, resuming from a paused position causes issues
- // if there's been a long gap, so we always seek to the live stream.
- if (_player.Source is MediaSource oldMedia)
+ // Ensure we have a media source
+ if (_player.Source == null)
{
- Debug.WriteLine("[RadioPlayerService] Disposing old MediaSource to ensure live stream");
- oldMedia.Reset();
- oldMedia.Dispose();
+ Debug.WriteLine("[RadioPlayerService] Player.Source is null, creating new MediaSource");
+ Uri uri = new(_streamUrl);
+ _player.Source = MediaSource.CreateFromUri(uri);
+ Debug.WriteLine($"[RadioPlayerService] Created new MediaSource from URL: {_streamUrl}");
+ }
+ else
+ {
+ Debug.WriteLine($"[RadioPlayerService] Player.Source exists, current state: {(_player.Source as MediaSource)?.State}");
}
-
- Debug.WriteLine("[RadioPlayerService] Creating fresh MediaSource for live stream");
- Uri uri = new(_streamUrl);
- _player.Source = MediaSource.CreateFromUri(uri);
- Debug.WriteLine($"[RadioPlayerService] Created new MediaSource from URL: {_streamUrl}");
Debug.WriteLine("[RadioPlayerService] Calling _player.Play()...");
_isInternalStateChange = true;
@@ -362,6 +359,9 @@ public void Play()
_isInternalStateChange = false;
Debug.WriteLine("[RadioPlayerService] _player.Play() called successfully");
+ // For live streams, seek to the live position to avoid playing from buffered content
+ SeekToLive();
+
_watchdog.NotifyUserIntentionToPlay();
Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
}
@@ -383,6 +383,8 @@ public void Play()
_isInternalStateChange = false;
Debug.WriteLine("[RadioPlayerService] _player.Play() called successfully (retry)");
+ SeekToLive();
+
_watchdog.NotifyUserIntentionToPlay();
Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
}
@@ -398,38 +400,58 @@ public void Play()
}
///
- /// Refreshes the stream to ensure live playback.
+ /// Seeks the player to the live position.
+ /// For live streams, this moves the playback position to the end of the seekable range,
+ /// ensuring we're streaming live content rather than buffered content from an earlier time.
+ ///
+ private void SeekToLive()
+ {
+ try
+ {
+ MediaPlaybackSession session = _player.PlaybackSession;
+
+ // Check if there's a seekable range (for live streams, this represents the buffer)
+ if (session.IsSeekable && session.NaturalDuration.TotalSeconds > 0)
+ {
+ // Seek to the end of the buffer (live edge)
+ TimeSpan livePosition = session.NaturalDuration;
+ Debug.WriteLine($"[RadioPlayerService] Seeking to live position: {livePosition}");
+ session.Position = livePosition;
+ }
+ else
+ {
+ Debug.WriteLine("[RadioPlayerService] Stream is not seekable or has no duration, skipping seek to live");
+ }
+ }
+ catch (Exception ex)
+ {
+ // Seeking to live is a best-effort operation - don't fail playback if it doesn't work
+ Debug.WriteLine($"[RadioPlayerService] Failed to seek to live: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Seeks the player to the live position when resuming from external controls.
/// Used when external controls (like hardware buttons) resume playback,
- /// to ensure we're always streaming live content instead of resuming from a buffered position.
+ /// to ensure we're streaming live content instead of resuming from a buffered position.
///
- private void RefreshStreamForLivePlayback()
+ private void SeekToLiveOnResume()
{
- Debug.WriteLine("=== RefreshStreamForLivePlayback START ===");
+ Debug.WriteLine("=== SeekToLiveOnResume START ===");
if (string.IsNullOrWhiteSpace(_streamUrl))
{
- Debug.WriteLine("[RadioPlayerService] No stream URL set, skipping refresh");
- Debug.WriteLine("=== RefreshStreamForLivePlayback END ===");
+ Debug.WriteLine("[RadioPlayerService] No stream URL set, skipping seek to live");
+ Debug.WriteLine("=== SeekToLiveOnResume END ===");
return;
}
- // Play() already creates a fresh MediaSource to ensure live streaming,
- // so we just delegate to it to avoid code duplication.
- // Exceptions are caught here because this method is called from event handlers
- // where unhandled exceptions could crash the application. The watchdog service
- // will attempt recovery if the stream fails to start.
- try
- {
- Play();
- Debug.WriteLine("[RadioPlayerService] Stream refreshed for live playback via Play()");
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"[RadioPlayerService] EXCEPTION in RefreshStreamForLivePlayback: {ex.Message}");
- Debug.WriteLine($"[RadioPlayerService] The watchdog service will attempt recovery if enabled");
- }
+ // Just seek to the live position - no need to recreate the MediaSource
+ SeekToLive();
+ _watchdog.NotifyUserIntentionToPlay();
+ Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
- Debug.WriteLine("=== RefreshStreamForLivePlayback END ===");
+ Debug.WriteLine("=== SeekToLiveOnResume END ===");
}
///
From d2ce2b50ad285eb132e3e702958783bd17e15979 Mon Sep 17 00:00:00 2001
From: Joe Finney
Date: Tue, 25 Nov 2025 15:29:52 -0600
Subject: [PATCH 6/6] Fix the SeekToLive position setting logic getting called,
but it still doesn't work
---
Trdo/Services/RadioPlayerService.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs
index 7fe0ad6..b582a48 100644
--- a/Trdo/Services/RadioPlayerService.cs
+++ b/Trdo/Services/RadioPlayerService.cs
@@ -411,7 +411,7 @@ private void SeekToLive()
MediaPlaybackSession session = _player.PlaybackSession;
// Check if there's a seekable range (for live streams, this represents the buffer)
- if (session.IsSeekable && session.NaturalDuration.TotalSeconds > 0)
+ if (session.CanSeek)
{
// Seek to the end of the buffer (live edge)
TimeSpan livePosition = session.NaturalDuration;