Improve playback error handling and retry logic#4053
Improve playback error handling and retry logic#4053SergioEstevao wants to merge 6 commits intotrunkfrom
Conversation
This is need to ensure that streaming playback can start again from zero.
Generated by 🚫 Danger |
There was a problem hiding this comment.
Pull request overview
This PR improves playback failure recovery and surfaces playback/download error details in the full-screen Now Playing UI to aid debugging.
Changes:
- Reset
haveCalledPlayerLoadbefore attempting a playback retry after a URL-load failure. - Subscribe the full-screen player to
playbackFailednotifications. - Add
updateError()to read and log playback/download error details from the current episode.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| podcasts/PlaybackManager.swift | Adjusts retry behavior after stream URL load failure by resetting internal “player loaded” state. |
| podcasts/NowPlayingPlayerItemViewController+Update.swift | Hooks full-screen Now Playing updates into playback failure events and logs stored error details. |
Comments suppressed due to low confidence (1)
podcasts/PlaybackManager.swift:2278
urlFailedToLoadruns inside an unstructuredTask, andServerPodcastManager.updatePodcastIfRequiredinvokes its completion onsubscribeQueue(background). This meanshaveCalledPlayerLoadandload(episode:autoPlay:...)are being touched off the main thread, which can race with playback state and UI-driven calls intoPlaybackManager. Consider hopping toMainActor(or a dedicated serial queue) before mutatingPlaybackManagerstate and before callingload.
haveCalledPlayerLoad = false
FileLog.shared.addMessage("PlaybackManager: URL failed to load, trying to update episode and playing again")
lastRetryEpisodeUuid = episodeUuid
ServerPodcastManager.shared.updatePodcastIfRequired(podcast: podcast) { [weak self] wasUpdated in
| updateChapterInfo() | ||
| updateChapterProgress() | ||
| updateColors() | ||
| updateError() |
There was a problem hiding this comment.
Constants.Notifications.playbackFailed is wired to update(), and update() now calls updateError(). This can cause the same error details to be logged repeatedly on unrelated update triggers (play/pause, chapter updates, foreground, etc.) and does a full UI refresh on playback-failed events. Consider observing playbackFailed with a dedicated handler that only invokes updateError() (and/or remove updateError() from the general update() path).
| updateError() |
| var errorMessage = "" | ||
| if let playbackError = playingEpisode.playbackErrorDetails { | ||
| errorMessage = playbackError | ||
| } | ||
| if !errorMessage.isEmpty { | ||
| print("Error: \(errorMessage)") | ||
| } | ||
|
|
||
| if let downloadError = playingEpisode.downloadErrorDetails { | ||
| print("Error: \(downloadError)") |
There was a problem hiding this comment.
updateError() uses print(...) for logging. In this codebase, playback-related logging appears to go through FileLog.shared.addMessage, and print can be noisy in production and hard to collect from real devices. Consider switching to the existing logging facility and include context (episode UUID/title, whether it’s playback vs download) to make the log actionable.
| var errorMessage = "" | |
| if let playbackError = playingEpisode.playbackErrorDetails { | |
| errorMessage = playbackError | |
| } | |
| if !errorMessage.isEmpty { | |
| print("Error: \(errorMessage)") | |
| } | |
| if let downloadError = playingEpisode.downloadErrorDetails { | |
| print("Error: \(downloadError)") | |
| let episodeTitle = playingEpisode.displayableTitle() | |
| if let playbackError = playingEpisode.playbackErrorDetails, !playbackError.isEmpty { | |
| FileLog.shared.addMessage("Playback error for episode '\(episodeTitle)': \(playbackError)") | |
| } | |
| if let downloadError = playingEpisode.downloadErrorDetails, !downloadError.isEmpty { | |
| FileLog.shared.addMessage("Download error for episode '\(episodeTitle)': \(downloadError)") |
| func updateError() { | ||
| guard let playingEpisode = PlaybackManager.shared.currentEpisode() else { return } | ||
| var errorMessage = "" | ||
| if let playbackError = playingEpisode.playbackErrorDetails { | ||
| errorMessage = playbackError | ||
| } | ||
| if !errorMessage.isEmpty { | ||
| print("Error: \(errorMessage)") | ||
| } | ||
|
|
||
| if let downloadError = playingEpisode.downloadErrorDetails { | ||
| print("Error: \(downloadError)") | ||
| } |
| <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Owh-jf-YTg" userLabel="Error View"> | ||
| <rect key="frame" x="0.0" y="796" width="414" height="100"/> | ||
| <subviews> | ||
| <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Error Message" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="96b-yr-mZE"> | ||
| <rect key="frame" x="8" y="8" width="398" height="16"/> | ||
| <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/> | ||
| <nil key="textColor"/> |
📘 Part of: # |
:---:
Fixes PCIOS-556
This PR improves error handling during playback in two ways:
haveCalledPlayerLoadtofalsebefore retrying playback when a URL fails to load, so the player correctly reloads instead of skipping the load step.playbackFailednotifications inNowPlayingPlayerItemViewControllerand add anupdateError()method that reads and logs playback/download error details from the current episode.To test
Checklist
CHANGELOG.mdif necessary.