Skip to content

Instantly share code, notes, and snippets.

View jubishop's full-sized avatar
🎼
Making a podcast app.

Justin Bishop jubishop

🎼
Making a podcast app.
View GitHub Profile

⏺ Based on my analysis, none of the changes since commit 1183550f4fbc would have caused this issue. Here's why:

Changes to playback code:

  1. All the PlayManager/PodAVPlayer changes are our debugging additions - These are purely additive (logging, error handling, isCurrentItem check). They don't
    modify any core playback behavior.
  2. The only pre-debugging change was d08b406f which makes pause() async to save position immediately - this doesn't affect playback mechanics.

Other changes:

  • HTMLText parsing improvements
  • Chapter navigation (new feature)
---
Analysis Summary
Based on the logs and code review, I found a reproducible edge case related to the AirPods sleep detection feature.
What Happened (Timeline)
┌─────────────────────┬─────────────────────
## MCP Usage
- If discussing Swift, SwiftUI, and iOS: Consult the apple-docs and apple-deep-docs mcps for up to date information.
## Repo Guardrails
- Never create commits or push unless the humans explicitly ask.
- Assume the working tree may hold user edits; respect them and avoid resets or reverts.
- Stay sandbox-friendly: ask for elevated access only when instructions require files outside the workspace.
## Build & Test
- Do not run builds or tests unless the request is explicit.

• - Event 70a4b966 happened on October 18, 2025 at 15:23:19 UTC on one iPhone18,1 running iOS 26.0.1. The crash is an EXC_BREAKPOINT raised inside CF_IS_OBJC on an AVFoundation worker thread while handling an AVTimebaseObserver callback, and it fired immediately after the system posted mediaServicesWereResetNotification (app was still in foreground).

  • During a media-services reset our playback stack runs handleMediaServicesReset() to tear down and rebuild audio state (PodHaven/Play/PlayManager.swift:470). That calls clearOnDeck() → PodAVPlayer.clear() which re-adds the periodic time observer after relaunching the episode (PodHaven/Play/Utility/ PodAVPlayer.swift:226).
  • In that observer we pass DispatchQueue.global(qos: .utility) to AVPlayer.addPeriodicTimeObserver. Apple explicitly documents that supplying a concurrent queue “isn’t supported and results in undefined behavior”; you must provide a serial queue or nil. citeturn6apple-docs-mcp__get_apple_doc_content0 Combined with
@jubishop
jubishop / llm.md
Created October 14, 2025 21:46
what a world.md

• Here’s the key sequence that can leave podcastList stale if we gate executeTrendingSectionFetch on trendingSection == currentTrendingSection:

  1. User taps a new chip. selectTrendingSection(:) updates currentTrendingSection, clears the list, triggers observeCurrentDisplay(), and kicks off performTrendingSectionFetch(: ).
  2. Before the fetch finishes, the user either jumps to a different trending chip or moves into search. At that instant the list already holds the “new” (possibly empty) dataset for what’s on screen.
  3. The original fetch eventually completes for the old section. If executeTrendingSectionFetch returns false (because the section no longer matches currentTrendingSection), the outer task skips observeCurrentDisplay(), so there’s no new syncPodcastListEntries() call.
  4. When the user returns to the chip whose data just landed, podcastList is still showing whatever collection was left from step 2—because we never updated it after the fetch completed—and it stays that
@jubishop
jubishop / gist:9b001eba30525084f1a35d3e7fbb3c8e
Created September 5, 2025 22:00
Staged changes in PodHaven FakeFileManager
diff --git a/PodHavenTests/Fakes/FakeFileManager.swift b/PodHavenTests/Fakes/FakeFileManager.swift
index 5d001e2..96972bb 100644
--- a/PodHavenTests/Fakes/FakeFileManager.swift
+++ b/PodHavenTests/Fakes/FakeFileManager.swift
@@ -3,115 +3,61 @@
import Foundation
@testable import PodHaven
final class FakeFileManager: FileManageable, @unchecked Sendable {
@jubishop
jubishop / staged.diff
Created September 5, 2025 21:59
Staged changes in PodHaven FakeFileManager
diff --git a/PodHavenTests/Fakes/FakeFileManager.swift b/PodHavenTests/Fakes/FakeFileManager.swift
index 5d001e2..96972bb 100644
--- a/PodHavenTests/Fakes/FakeFileManager.swift
+++ b/PodHavenTests/Fakes/FakeFileManager.swift
@@ -3,115 +3,61 @@
import Foundation
@testable import PodHaven
final class FakeFileManager: FileManageable, @unchecked Sendable {

For long-running episode downloads, switch CacheManager to schedule URLSession background downloads instead of relying on beginBackgroundTask. A background session lets the system continue transfers while your app is suspended or terminated, and relaunches the app to deliver completion events.

High-level approach

  • Use URLSessionConfiguration.background(withIdentifier:) with a stable identifier.
  • Create a URLSession with a URLSessionDownloadDelegate to receive progress and completion.
  • Start downloads with downloadTask(with:) and return immediately; don’t await the data.
  • In the delegate:
    • didWriteData: report progress
    • didFinishDownloadingTo: move the temp file to your cache directory, update the DB, and notify state
  • didCompleteWithError: handle failures/resume data
@jubishop
jubishop / String.swift
Created August 18, 2025 05:05
String hash
extension String {
// MARK: - Hashing
func hash(to length: Int = 8) -> String {
guard length > 0 else { return "" }
let data = self.data(using: .utf8)!
let hash = SHA256.hash(data: data)
let hashData = Data(hash)
private var duration: CMTime? {
guard let timeComponents = rssEpisode.iTunes.duration?.split(separator: ":").reversed(),
timeComponents.count <= 3
else { return CMTime.zero }
var seconds = 0
for (position, value) in timeComponents.enumerated() {
guard let value = Int(value) else { return CMTime.zero }
var multiplier = 1
for _ in 0..<position { multiplier *= 60 }