diff --git a/Trdo/Services/RadioPlayerService.cs b/Trdo/Services/RadioPlayerService.cs index ca8ab21..b582a48 100644 --- a/Trdo/Services/RadioPlayerService.cs +++ b/Trdo/Services/RadioPlayerService.cs @@ -170,8 +170,20 @@ 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 + // 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) { @@ -328,7 +340,7 @@ public void Play() 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"); @@ -347,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"); } @@ -368,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"); } @@ -382,6 +399,61 @@ public void Play() Debug.WriteLine($"=== Play END ==="); } + /// + /// 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.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}"); + } + } + + /// + /// 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. + /// + 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 ==="); + } + /// /// Stop playback and clean up resources ///