Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions podcasts/NowPlayingPlayerItemViewController+Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension NowPlayingPlayerItemViewController {
addCustomObserver(Constants.Notifications.podcastChapterChanged, selector: #selector(updateChapterInfo))
addCustomObserver(Constants.Notifications.episodeDownloaded, selector: #selector(update))
addCustomObserver(UIApplication.willEnterForegroundNotification, selector: #selector(update))
addCustomObserver(Constants.Notifications.playbackFailed, selector: #selector(update))

addCustomObserver(Constants.Notifications.sleepTimerChanged, selector: #selector(sleepTimerUpdated))
addCustomObserver(Constants.Notifications.playerActionsUpdated, selector: #selector(reloadShelfActions))
Expand Down Expand Up @@ -63,6 +64,7 @@ extension NowPlayingPlayerItemViewController {
updateChapterInfo()
updateChapterProgress()
updateColors()
updateError()
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
updateError()

Copilot uses AI. Check for mistakes.

if !showingCustomImage {
ImageManager.sharedManager.loadImage(episode: playingEpisode, imageView: episodeImage, size: .page)
Expand Down Expand Up @@ -170,6 +172,21 @@ extension NowPlayingPlayerItemViewController {
timeSlider.indeterminant = PlaybackManager.shared.buffering() && PlaybackManager.shared.playing()
}

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)")
Comment on lines +177 to +186
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)")

Copilot uses AI. Check for mistakes.
}
Comment on lines +175 to +187
}

func updateProvisionalChapterInfoForTime(time: TimeInterval) {
guard let playingEpisode = PlaybackManager.shared.currentEpisode() else { return }

Expand Down
14 changes: 14 additions & 0 deletions podcasts/NowPlayingPlayerItemViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@ class NowPlayingPlayerItemViewController: PlayerItemViewController {

@IBOutlet weak var bottomControlsStackView: UIStackView!

@IBOutlet weak var errorContainer: ThemeableView! {
didSet {
errorContainer.style = .playerContrast06
}
}

@IBOutlet weak var errorLabel: ThemeableLabel! {
didSet {
errorLabel.font = .font(ofSize: 14, weight: .medium, scalingWith: .subheadline)
errorLabel.adjustsFontForContentSizeCategory = true
errorLabel.style = .playerContrast02
}
}

#if !APPCLIP
let chromecastBtn = PCAlwaysVisibleCastBtn()
#endif
Expand Down
35 changes: 30 additions & 5 deletions podcasts/NowPlayingPlayerItemViewController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<outlet property="episodeImage" destination="dXr-Zd-0wP" id="g0t-eE-u5G"/>
<outlet property="episodeInfoView" destination="MLe-7y-U6n" id="0K2-Ws-I3X"/>
<outlet property="episodeName" destination="ool-HF-az4" id="87D-Nv-oIM"/>
<outlet property="errorContainer" destination="Owh-jf-YTg" id="TJt-lH-W8r"/>
<outlet property="errorLabel" destination="96b-yr-mZE" id="PeX-yT-KUQ"/>
<outlet property="fillView" destination="sbN-fi-Mv7" id="AN8-6l-tnT"/>
<outlet property="floatingVideoView" destination="auM-3f-elA" id="OGa-U7-JAh"/>
<outlet property="playPauseBtn" destination="XXp-BW-AVr" id="Di2-7G-pn5"/>
Expand All @@ -41,7 +43,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="WxQ-7h-0WZ">
<rect key="frame" x="0.0" y="8" width="414" height="858"/>
<rect key="frame" x="0.0" y="8" width="414" height="816"/>
<subviews>
<imageView contentMode="scaleAspectFit" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="dXr-Zd-0wP">
<rect key="frame" x="30" y="0.0" width="354" height="354"/>
Expand Down Expand Up @@ -203,7 +205,7 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="30" translatesAutoresizingMaskIntoConstraints="NO" id="UmO-Rc-jIG" userLabel="Bottom Controls Stack View">
<rect key="frame" x="0.0" y="459" width="414" height="399"/>
<rect key="frame" x="0.0" y="459" width="414" height="357"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T2E-0H-hwM" userLabel="Time Slide">
<rect key="frame" x="0.0" y="0.0" width="414" height="30"/>
Expand Down Expand Up @@ -249,7 +251,7 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="250" distribution="fillProportionally" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="0FS-5h-EyM" userLabel="Play Skip Stack View">
<rect key="frame" x="10" y="146.5" width="394" height="80"/>
<rect key="frame" x="10" y="125.5" width="394" height="80"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="trailing" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ooq-fV-tVm" customClass="SkipButton" customModule="Pocket_Casts_App_Clip" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="137" height="80"/>
Expand Down Expand Up @@ -299,7 +301,7 @@
</constraints>
</stackView>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IM8-JJ-Wfy" userLabel="Action Buttons" customClass="ThemeableView" customModule="Pocket_Casts_App_Clip" customModuleProvider="target">
<rect key="frame" x="20" y="343" width="374" height="56"/>
<rect key="frame" x="20" y="301" width="374" height="56"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="ACg-CR-Hm8">
<rect key="frame" x="15" y="0.0" width="344" height="56"/>
Expand Down Expand Up @@ -377,6 +379,26 @@
<constraint firstAttribute="width" secondItem="auM-3f-elA" secondAttribute="height" multiplier="1:1" id="r9y-4s-XLM"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Owh-jf-YTg" userLabel="Error View" customClass="ThemeableView" customModule="Pocket_Casts_App_Clip" customModuleProvider="target">
<rect key="frame" x="0.0" y="840" width="414" height="56"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Error Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="96b-yr-mZE" customClass="ThemeableLabel" customModule="Pocket_Casts_App_Clip" customModuleProvider="target">
<rect key="frame" x="8" y="8" width="398" height="40"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.070000000000000007" colorSpace="custom" customColorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="96b-yr-mZE" firstAttribute="top" secondItem="Owh-jf-YTg" secondAttribute="top" constant="8" id="Tha-E8-VUo"/>
<constraint firstAttribute="trailingMargin" secondItem="96b-yr-mZE" secondAttribute="trailing" id="XpO-f7-0TS"/>
<constraint firstAttribute="bottom" secondItem="96b-yr-mZE" secondAttribute="bottom" constant="8" id="bf9-Jd-CIf"/>
<constraint firstAttribute="height" constant="56" id="eT2-Qf-DyX"/>
<constraint firstItem="96b-yr-mZE" firstAttribute="leading" secondItem="Owh-jf-YTg" secondAttribute="leadingMargin" id="klf-K7-FM8"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="Full Size Player">
Expand All @@ -388,11 +410,14 @@
<constraint firstItem="auM-3f-elA" firstAttribute="centerX" secondItem="dXr-Zd-0wP" secondAttribute="centerX" id="3QC-yl-rk2"/>
<constraint firstAttribute="trailing" secondItem="WxQ-7h-0WZ" secondAttribute="trailing" id="4fc-om-1OZ"/>
<constraint firstItem="auM-3f-elA" firstAttribute="centerY" secondItem="dXr-Zd-0wP" secondAttribute="centerY" id="P1f-xu-Ze5"/>
<constraint firstAttribute="bottom" secondItem="Owh-jf-YTg" secondAttribute="bottom" id="ckZ-tn-s0v"/>
<constraint firstItem="sam-0S-73x" firstAttribute="top" secondItem="dXr-Zd-0wP" secondAttribute="top" constant="10" id="fQK-oh-rpi"/>
<constraint firstItem="WxQ-7h-0WZ" firstAttribute="top" secondItem="Dsl-xJ-ho3" secondAttribute="top" constant="8" id="hS4-XE-bck"/>
<constraint firstAttribute="trailing" secondItem="Owh-jf-YTg" secondAttribute="trailing" id="jYr-cs-wkb"/>
<constraint firstItem="Owh-jf-YTg" firstAttribute="leading" secondItem="Dsl-xJ-ho3" secondAttribute="leading" id="ktT-yI-jhC"/>
<constraint firstItem="WxQ-7h-0WZ" firstAttribute="leading" secondItem="Dsl-xJ-ho3" secondAttribute="leading" id="wSN-3z-fOR"/>
<constraint firstItem="auM-3f-elA" firstAttribute="height" secondItem="dXr-Zd-0wP" secondAttribute="height" id="ycO-kv-OEO"/>
<constraint firstAttribute="bottom" secondItem="WxQ-7h-0WZ" secondAttribute="bottom" constant="30" id="zFk-ce-kom"/>
<constraint firstAttribute="bottom" secondItem="WxQ-7h-0WZ" secondAttribute="bottom" constant="72" id="zFk-ce-kom"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="-796" y="-267"/>
Expand Down
2 changes: 1 addition & 1 deletion podcasts/PlaybackManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2282,7 +2282,7 @@ class PlaybackManager: ServerPlaybackDelegate {
playbackDidFail(logMessage: "AVPlayerItemStatusFailed on currentItem", userMessage: nil)
return
}

haveCalledPlayerLoad = false
FileLog.shared.addMessage("PlaybackManager: URL failed to load, trying to update episode and playing again")
lastRetryEpisodeUuid = episodeUuid

Expand Down