bug fixes
This commit is contained in:
parent
ef6124e72e
commit
5b71feebfd
1 changed files with 30 additions and 47 deletions
|
|
@ -1566,15 +1566,15 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate {
|
|||
private var timeoutTask: Task<Void, Never>?
|
||||
private var hasDeliveredResult = false
|
||||
|
||||
// Tap state
|
||||
private var tap: Unmanaged<MTAudioProcessingTap>?
|
||||
private var converter: AVAudioConverter?
|
||||
private var sourceFormat: AVAudioFormat?
|
||||
private let targetFormat = AVAudioFormat(standardFormatWithSampleRate: 16_000, channels: 1)!
|
||||
private let analysisQueue = DispatchQueue(label: "com.navidrome.shazam", qos: .userInitiated)
|
||||
// Tap state — MTAudioProcessingTap is a CF type, ARC manages it directly
|
||||
private var tap: MTAudioProcessingTap?
|
||||
private var converter: AVAudioConverter?
|
||||
private var sourceFormat: AVAudioFormat?
|
||||
private let targetFormat = AVAudioFormat(standardFormatWithSampleRate: 16_000, channels: 1)!
|
||||
private let analysisQueue = DispatchQueue(label: "com.navidrome.shazam", qos: .userInitiated)
|
||||
|
||||
// Legacy mic fallback
|
||||
private var audioEngine: AVAudioEngine?
|
||||
private var audioEngine: AVAudioEngine?
|
||||
|
||||
private override init() { super.init() }
|
||||
|
||||
|
|
@ -1594,44 +1594,29 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate {
|
|||
|
||||
DebugLogger.shared.log("Shazam: starting recognition", category: "Audio")
|
||||
|
||||
// Prefer tap on AVPlayer (radio / stream / local AVPlayer path)
|
||||
// Prefer tap on AVPlayer — async track load, then install tap on main actor
|
||||
if let playerItem = AudioPlayer.shared.currentPlayerItem {
|
||||
if installTap(on: playerItem) {
|
||||
startTimeout(seconds: 15)
|
||||
DebugLogger.shared.log("Shazam: tap installed on AVPlayerItem", category: "Audio")
|
||||
return
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else { return }
|
||||
if await self.installTap(on: playerItem) {
|
||||
self.startTimeout(seconds: 15)
|
||||
DebugLogger.shared.log("Shazam: tap installed on AVPlayerItem", category: "Audio")
|
||||
} else {
|
||||
self.startMicFallback()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startMicFallback()
|
||||
}
|
||||
|
||||
// Fallback: microphone for local engine path
|
||||
startMicFallback()
|
||||
}
|
||||
|
||||
// MARK: - MTAudioProcessingTap
|
||||
|
||||
private func installTap(on playerItem: AVPlayerItem) -> Bool {
|
||||
// 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 {
|
||||
/// Async because `loadTracks(withMediaType:)` is the non-deprecated API (iOS 16+).
|
||||
private func installTap(on playerItem: AVPlayerItem) async -> Bool {
|
||||
// loadTracks is the non-deprecated replacement for tracks(withMediaType:)
|
||||
guard let audioTrack = try? await playerItem.asset
|
||||
.loadTracks(withMediaType: .audio).first else {
|
||||
DebugLogger.shared.log("Shazam: no audio track on playerItem", category: "Audio")
|
||||
return false
|
||||
}
|
||||
|
|
@ -1643,14 +1628,13 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate {
|
|||
tapStorageOut.pointee = clientInfo
|
||||
},
|
||||
finalize: nil,
|
||||
prepare: { tap, maxFrames, processingFormat in
|
||||
prepare: { tap, _, processingFormat in
|
||||
let storage = Unmanaged<ShazamRecognizer>
|
||||
.fromOpaque(MTAudioProcessingTapGetStorage(tap))
|
||||
.takeUnretainedValue()
|
||||
let format = AVAudioFormat(streamDescription: processingFormat)
|
||||
storage.analysisQueue.async {
|
||||
storage.sourceFormat = format
|
||||
// AVAudioConverter.init does not throw — no try? needed
|
||||
if let src = format {
|
||||
storage.converter = AVAudioConverter(from: src, to: storage.targetFormat)
|
||||
}
|
||||
|
|
@ -1660,22 +1644,21 @@ class ShazamRecognizer: NSObject, ObservableObject, SHSessionDelegate {
|
|||
process: shazamTapProcess
|
||||
)
|
||||
|
||||
var rawTap: Unmanaged<MTAudioProcessingTap>?
|
||||
// MTAudioProcessingTap is a CF type — Swift bridges it as MTAudioProcessingTap?
|
||||
var tapOut: MTAudioProcessingTap?
|
||||
let status = MTAudioProcessingTapCreate(
|
||||
kCFAllocatorDefault, &callbacks,
|
||||
kMTAudioProcessingTapCreationFlag_PostEffects, &rawTap
|
||||
kMTAudioProcessingTapCreationFlag_PostEffects, &tapOut
|
||||
)
|
||||
guard status == noErr, let rawTap else {
|
||||
guard status == noErr, let tapValue = tapOut else {
|
||||
DebugLogger.shared.log("Shazam: MTAudioProcessingTapCreate failed \(status)", category: "Audio")
|
||||
return false
|
||||
}
|
||||
|
||||
// takeRetainedValue transfers ownership (ARC takes over) — assign to our ivar
|
||||
let tapValue = rawTap.takeRetainedValue()
|
||||
tap = Unmanaged.passRetained(tapValue) // re-wrap for cleanup later
|
||||
tap = tapValue // ARC owns it
|
||||
|
||||
let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
|
||||
inputParams.audioTapProcessor = tapValue // passes the already-retained value
|
||||
inputParams.audioTapProcessor = tapValue
|
||||
|
||||
let mix = AVMutableAudioMix()
|
||||
mix.inputParameters = [inputParams]
|
||||
|
|
|
|||
Loading…
Reference in a new issue