The following code is an iPad App for iOS 16.6 with Xcode 15.0 on macOS Sonoma 14.1.1.
In the
CoreAudioPlayer.swift
file in the method
playAudioFile()
i try to put the play position of the file back to frame
0
. But that fails with the error:
Init CoreAudioPlayer with `kick`
2023-11-29 19:58:42.477729+0100 HexaDeca[3249:1400596] Metal API Validation Enabled
Current Frame before playing: 0
Playing Optional(file:///private/var/containers/Bundle/Application/19E65197-60D7-415A-91AD-E91F82FF5BCC/HexaDeca.app/kick.wav)
Current Frame before playing: 49863
2023-11-29 19:58:57.538153+0100 HexaDeca[3249:1400575] [default] ExtAudioFile.cpp:1116 about to throw -66568: seek to frame in audio file
Can't seek. Status: -66568
I tried various other frames to seek to but it seems the method ExtAudioFileSeek()
always fails. The currentFrame
delivers the correct frame. Before i play the file the first time it prints 0
on a second time it prints 49863
. Which shows that after playing the file the first time it's frame position is at the end of the file. I would like to set the position back to frame 0
in order to be able to play the file again. Also made sure the file is "seekable", a 24 bit stereo wav file. Also it is used only once and ExtAudioFileOpenURL()
should open it in read only mode.
// ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
CircleButton(sampleName: "kick")
extension Color {
// Define a Color initializer from a hex value
init(hex: Int, alpha: Double = 1.0) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xFF) / 255.0,
green: Double((hex >> 8) & 0xFF) / 255.0,
blue: Double((hex) & 0xFF) / 255.0,
opacity: alpha
#Preview {
ContentView()
// CircleButton.swift
import SwiftUI
struct CircleButton: View {
@State private var buttonSize: CGFloat = 56.0
@StateObject private var coreAudioPlayer: CoreAudioPlayer
init(sampleName: String) {
self.sampleName = sampleName
_coreAudioPlayer = StateObject(wrappedValue: CoreAudioPlayer(filePath: sampleName))
var body: some View {
Circle()
.fill(Color(hex: 0x111111))
.frame(width: buttonSize - 10, height: buttonSize - 10)
.opacity(0.8)
.onTapGesture {
coreAudioPlayer.playAudioFile()
// CoreAudioPlayer.swift
import AudioToolbox
import AVFoundation
// TODO: Fix File Resource Usage
// After certain amount of played samples the App stops playing sounds with the error
// 2023-11-29 00:54:11.224507+0100 HexaDeca[2671:1058009] [default]
// ExtAudioFile.cpp:210 about to throw -42: open audio file
// Error opening audio file: snare2 -42
class CoreAudioPlayer: NSObject, ObservableObject {
@Published var isPlaying = false
private var audioFile: ExtAudioFileRef?
private var audioGraph: AUGraph?
private var playerNode: AUNode = 0
private var audioUnit: AudioUnit?
private var fileURL: URL?
init(filePath: String) {
print("Init CoreAudioPlayer with `\(filePath)`")
super.init()
var status = noErr
guard let url = Bundle.main.url(forResource: filePath, withExtension: "wav") else {
print("File not found")
return
self.fileURL = url
// Safely unwrap fileURL before using it
guard let fileCFURL = self.fileURL else {
print("Invalid file URL")
return
status = ExtAudioFileOpenURL(fileCFURL as CFURL, &audioFile)
guard status == noErr else {
print("Error opening audio file: \(filePath) \(status)")
return
setupAudioGraph()
func setupAudioGraph() {
var status = noErr
status = NewAUGraph(&audioGraph)
guard status == noErr, let graph = audioGraph else {
print("Error creating AUGraph: \(status)")
return
var componentDescription = AudioComponentDescription(
componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_RemoteIO,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0,
componentFlagsMask: 0
status = AUGraphAddNode(graph, &componentDescription, &playerNode)
guard status == noErr else {
print("Error adding node to AUGraph: \(status)")
return
status = AUGraphOpen(graph)
guard status == noErr else {
print("Error opening AUGraph: \(status)")
return
status = AUGraphNodeInfo(graph, playerNode, nil, &audioUnit)
guard status == noErr else {
print("Error getting node info: \(status)")
return
status = AUGraphInitialize(graph)
guard status == noErr else {
print("Error initializing AUGraph: \(status)")
return
func playAudioFile() {
var status = noErr
// Ensure audioFile is not nil before using it
guard let audioFile = self.audioFile else {
print("Audio file is not initialized")
return
var audioFormat = AudioStreamBasicDescription()
var size = UInt32(MemoryLayout.size(ofValue: audioFormat))
status = ExtAudioFileGetProperty(audioFile, kExtAudioFileProperty_FileDataFormat, &size, &audioFormat)
guard status == noErr else {
print("Error getting file data format: \(status)")
return
// Check the current position of the audio file
var currentFrame: Int64 = 0
status = ExtAudioFileTell(audioFile, ¤tFrame)
guard status == noErr else {
print("Error getting current frame: \(status)")
return
print("Current Frame before playing: \(currentFrame)")
// Only seek if the current frame is not 0
if currentFrame != 0 {
// Reset the read position to the beginning of the file
status = ExtAudioFileSeek(audioFile, 0)
guard status == noErr else {
print("Can't seek. Status: \(status)")
return
guard let audioUnit = audioUnit else {
print("AudioUnit is not initialized 1")
return
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioFormat, size)
guard status == noErr else {
print("Error setting stream format: \(status)")
return
var renderCallback = AURenderCallbackStruct(inputProc: CoreAudioPlayer.renderingCallback, inputProcRefCon: Unmanaged.passUnretained(self).toOpaque())
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &renderCallback, UInt32(MemoryLayout<AURenderCallbackStruct>.size))
guard status == noErr else {
print("Error setting render callback: \(status)")
return
status = AUGraphStart(audioGraph!)
guard status == noErr else {
print("Error starting AUGraph: \(status)")
return
print("Playing \(String(describing: self.fileURL))")
private static let renderingCallback: AURenderCallback = { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
var total: Float = 0
// Retrieve your audio player instance
let audioPlayer = Unmanaged<CoreAudioPlayer>.fromOpaque(inRefCon).takeUnretainedValue()
guard let ioData = ioData else { return noErr }
// Clear the buffer before using it
for bufferIndex in 0..<Int(ioData.pointee.mNumberBuffers) {
let bufferPointer = UnsafeMutableAudioBufferListPointer(ioData)
if let data = bufferPointer[bufferIndex].mData {
memset(data, 0, Int(bufferPointer[bufferIndex].mDataByteSize))
guard let audioFile = audioPlayer.audioFile else {
return -1
var bufferList = AudioBufferList()
bufferList.mNumberBuffers = 1
bufferList.mBuffers.mNumberChannels = 2 // Assuming stereo audio
bufferList.mBuffers.mDataByteSize = inNumberFrames * 2 * UInt32(MemoryLayout<Float32>.size)
bufferList.mBuffers.mData = malloc(Int(bufferList.mBuffers.mDataByteSize))
var framesToRead = inNumberFrames
var status = ExtAudioFileRead(audioFile, &framesToRead, &bufferList)
guard status == noErr else {
print("Error reading from file: \(status)")
return status
if framesToRead > 0 {
if let sourceBuffer = bufferList.mBuffers.mData, let destinationBuffer = ioData.pointee.mBuffers.mData {
memcpy(destinationBuffer, sourceBuffer, Int(bufferList.mBuffers.mDataByteSize))
} else {
// Handle the error, perhaps by filling the destination buffer with silence
defer {
if let buffer = bufferList.mBuffers.mData {
free(buffer)
return noErr
deinit {
if let graph = audioGraph {
AUGraphStop(graph)
AUGraphUninitialize(graph)
AUGraphClose(graph)
if let file = audioFile {
ExtAudioFileDispose(file)