From 7d448e79deff8ceb7e35007fc6502a462e64ad4e Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Fri, 10 Apr 2026 17:05:12 -0700 Subject: [PATCH] bug fixes --- Shared/Audio/AudioPlayer.swift | 2 +- iOS/Views/NowPlaying/NowPlayingView.swift | 43 +++++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Shared/Audio/AudioPlayer.swift b/Shared/Audio/AudioPlayer.swift index fa9a5b7..f52c48d 100644 --- a/Shared/Audio/AudioPlayer.swift +++ b/Shared/Audio/AudioPlayer.swift @@ -1173,7 +1173,7 @@ class AudioPlayer: NSObject, ObservableObject { isUsingOfflineVis = false offlineVisProgress = 0 offlineVisFPS = VisualizerSettings.shared.effectiveFPS - let points = VisualizerSettings.shared.numberOfPoints + let points = VisualizerSettings.shared.nowPlaying.numberOfPoints let cutoff = VisualizerSettings.shared.frequencyCutoff Task { diff --git a/iOS/Views/NowPlaying/NowPlayingView.swift b/iOS/Views/NowPlaying/NowPlayingView.swift index 03518d3..97d64c6 100644 --- a/iOS/Views/NowPlaying/NowPlayingView.swift +++ b/iOS/Views/NowPlaying/NowPlayingView.swift @@ -1610,12 +1610,32 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate { // MARK: - MTAudioProcessingTap private func installTap(on playerItem: AVPlayerItem) -> Bool { - guard let audioTrack = playerItem.asset.tracks(withMediaType: .audio).first else { + // loadTracks is async (tracks(withMediaType:) deprecated iOS 16) + // We load synchronously from the asset's already-loaded track list if available, + // falling back to a blocking load. Since this is called off the main thread in + // a Task, a brief synchronous wait is acceptable. + let asset = playerItem.asset + var audioTrack: AVAssetTrack? + + // Fast path: tracks already in memory (common for items that are playing) + let loadedTracks = asset.tracks(withMediaType: .audio) + if let first = loadedTracks.first { + audioTrack = first + } else { + // Async load — bridge with a semaphore so we stay synchronous from the caller's view + let sema = DispatchSemaphore(value: 0) + Task { + audioTrack = try? await asset.loadTracks(withMediaType: .audio).first + sema.signal() + } + sema.wait() + } + + guard let audioTrack else { DebugLogger.shared.log("Shazam: no audio track on playerItem", category: "Audio") return false } - // Store self as opaque pointer in tap storage var callbacks = MTAudioProcessingTapCallbacks( version: kMTAudioProcessingTapCallbacksVersion_0, clientInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), @@ -1624,15 +1644,15 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate { }, finalize: nil, prepare: { tap, maxFrames, processingFormat in - // Capture the source format so we can build the converter on first buffer let storage = Unmanaged .fromOpaque(MTAudioProcessingTapGetStorage(tap)) .takeUnretainedValue() let format = AVAudioFormat(streamDescription: processingFormat) storage.analysisQueue.async { storage.sourceFormat = format - if let src = format, let dst = storage.targetFormat as AVAudioFormat? { - storage.converter = try? AVAudioConverter(from: src, to: dst) + // AVAudioConverter.init does not throw — no try? needed + if let src = format { + storage.converter = AVAudioConverter(from: src, to: storage.targetFormat) } } }, @@ -1640,19 +1660,22 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate { process: shazamTapProcess ) - var newTap: Unmanaged? + var rawTap: Unmanaged? let status = MTAudioProcessingTapCreate( kCFAllocatorDefault, &callbacks, - kMTAudioProcessingTapCreationFlag_PostEffects, &newTap + kMTAudioProcessingTapCreationFlag_PostEffects, &rawTap ) - guard status == noErr, let newTap else { + guard status == noErr, let rawTap else { DebugLogger.shared.log("Shazam: MTAudioProcessingTapCreate failed \(status)", category: "Audio") return false } - tap = newTap + + // takeRetainedValue transfers ownership (ARC takes over) — assign to our ivar + let tapValue = rawTap.takeRetainedValue() + tap = Unmanaged.passRetained(tapValue) // re-wrap for cleanup later let inputParams = AVMutableAudioMixInputParameters(track: audioTrack) - inputParams.audioTapProcessor = newTap.takeRetainedValue() + inputParams.audioTapProcessor = tapValue // passes the already-retained value let mix = AVMutableAudioMix() mix.inputParameters = [inputParams]