Skip to content
Draft
78 changes: 75 additions & 3 deletions Trdo/Services/RadioPlayerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
}
}

private RadioPlayerService()

Check warning on line 138 in Trdo/Services/RadioPlayerService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_uiQueue' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
{
Debug.WriteLine("=== RadioPlayerService Constructor START ===");

Expand Down Expand Up @@ -170,13 +170,25 @@
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
// 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
{
SeekToLiveOnResume();
}
catch (Exception ex)
{
Debug.WriteLine($"[RadioPlayerService] Failed to seek to live: {ex.Message}");
}
});
}
else if (currentState == MediaPlaybackState.Paused)
{
// Only notify pause intent if explicitly paused (not buffering, opening, or other states)
_watchdog.NotifyUserIntentionToPause();

Check warning on line 191 in Trdo/Services/RadioPlayerService.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to pause (hardware button)");
}
// For other states (Buffering, Opening, None), don't change watchdog intent
Expand Down Expand Up @@ -328,7 +340,7 @@

try
{
// Ensure we have a fresh media source
// Ensure we have a media source
if (_player.Source == null)
{
Debug.WriteLine("[RadioPlayerService] Player.Source is null, creating new MediaSource");
Expand All @@ -347,6 +359,9 @@
_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");
}
Expand All @@ -368,6 +383,8 @@
_isInternalStateChange = false;
Debug.WriteLine("[RadioPlayerService] _player.Play() called successfully (retry)");

SeekToLive();

_watchdog.NotifyUserIntentionToPlay();
Debug.WriteLine("[RadioPlayerService] Notified watchdog of user intention to play");
}
Expand All @@ -382,6 +399,61 @@
Debug.WriteLine($"=== Play END ===");
}

/// <summary>
/// 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.
/// </summary>
private void SeekToLive()
{
try
{
MediaPlaybackSession session = _player.PlaybackSession;

// Check if there's a seekable range (for live streams, this represents the buffer)
if (session.CanSeek)
{
// 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}");
}
}

/// <summary>
/// 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 streaming live content instead of resuming from a buffered position.
/// </summary>
private void SeekToLiveOnResume()
{
Debug.WriteLine("=== SeekToLiveOnResume START ===");

if (string.IsNullOrWhiteSpace(_streamUrl))
{
Debug.WriteLine("[RadioPlayerService] No stream URL set, skipping seek to live");
Debug.WriteLine("=== SeekToLiveOnResume END ===");
return;
}

// 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("=== SeekToLiveOnResume END ===");
}

/// <summary>
/// Stop playback and clean up resources
/// </summary>
Expand Down
Loading