Player component for iOS - bugs

When I asked whether the AVKit Foundation was suitable for seamless looping, I received the following answer from ChatGPT:

The AVKit framework is primarily intended for video playback and management. For seamless looping of audio files, the AVFoundation framework is more suitable. Specifically, the AVAudioPlayer or AVPlayer object from AVFoundation can be used for audio playback.

Here is a simple example using AVAudioPlayer in Swift to seamlessly loop an audio file:

import AVFoundation
class AudioLooper: NSObject, AVAudioPlayerDelegate {
    var audioPlayer: AVAudioPlayer?

    override init() {
        super.init()

        guard let audioFileURL = Bundle.main.url(forResource: "YourAudioFile", withExtension: "caf") else {
            print("Audio file not found.")
            return
        }

        do {
            audioPlayer = try AVAudioPlayer(contentsOf: audioFileURL)
            audioPlayer?.delegate = self
            audioPlayer?.numberOfLoops = -1 // -1 means infinite looping
            audioPlayer?.prepareToPlay()
        } catch {
            print("Error initializing AVAudioPlayer: \(error.localizedDescription)")
        }
    }

    func play() {
        audioPlayer?.play()
    }

    func stop() {
        audioPlayer?.stop()
    }

    // AVAudioPlayerDelegate method for seamless looping
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        if flag {
            player.play()
        }
    }
}

// Using the class
let audioLooper = AudioLooper()
audioLooper.play()

Replace "YourAudioFile" with the name of your audio file and adjust the file extension. This implementation uses AVAudioPlayer and sets numberOfLoops to -1 to enable infinite looping. The delegate is used to implement the audioPlayerDidFinishPlaying method, which triggers replay when playback finishes.

Keep in mind that this is a simple example and may not be suitable for all use cases. Depending on your application's requirements, you might also consider using AVPlayer and AVPlayerLooper for more advanced features.


And this is pretty similar to how I did it with Xcode & Swift.

Strangely, the AVFoundation framework is imported/used for the VideoPlayer component:

Why not for the Player component?
@ewpatton

Here is a suggestion on how to play a sound also in the background (idle mode) using the AVFoundation framework:

import AVFoundation

class AudioPlayer {
    var player: AVAudioPlayer?

    init() {
        // Configure the audio session for background playback
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch {
            print("Error configuring audio session: \(error.localizedDescription)")
        }
    }

    func playAudio() {
        guard let audioFileURL = Bundle.main.url(forResource: "your_audio_file", withExtension: "mp3") else {
            print("Audio file not found.")
            return
        }

        do {
            // Initialize AVAudioPlayer
            player = try AVAudioPlayer(contentsOf: audioFileURL)
            player?.numberOfLoops = -1 // -1 means infinite looping
            player?.prepareToPlay()
            player?.play()
        } catch {
            print("Error initializing AVAudioPlayer: \(error.localizedDescription)")
        }
    }
}

// Example usage
let audioPlayer = AudioPlayer()
audioPlayer.playAudio()

Do you see an opportunity to consider this in the new year? Of course, the Player component should also be able to play in the background (as is the case with Android). In addition, the problem with seamless looping could also be (easily) solved with the AVFoundation framework.

It would be nice to hear from you about this before Christmas. :pray:

1 Like

We may end up needing different logic in the companion versus not, but we will see. In particular, I would be concerned that Apple might decide that background audio support is not necessary in the companion and reject an update that includes that. If that ends up being the case, we will have to wire things up in some way that compiled apps can take the background audio capability.

1 Like

Of course it doesn't matter how it's arranged with Companion. In any case it should work with the compiled app. And where is the problem in using the AVFoundation framework also for the Player component (and not just for the VideoPlayer component)?

Nothing precludes us from switching other than the time to update and test everything. The two components were implemented by two different individuals at different times and so different frameworks ended up being used.

1 Like

Ok, I'll do the testing and I'm pretty thorough at it. :smiley:

Btw, the VideoPlayer also has some issues.
I tried to play a loopable sound (caf/IM4A format, duration 11 sec) with the VideoPlayer:

grafik

.GetDuration and .SeekTo_ms are in sec (not millis).

And unfortunately there is no loop setting (unlike the Player), because since the VideoPlayer uses AVFoundation, looping would most likely work with the VideoPlayer component.

However, as expected, the VideoPlayer does not play in the background (which usually makes sense, as long as you don't use it as an audio player).

Thanks for the report. I've fixed the issue of seconds vs milliseconds in this PR:

1 Like

The Player related issues will be fixed as part of PR 3078:

We are waiting for TestFlight review of 2.64.6, which will have both sets of changes. I will post a formal announcement once that's available.

1 Like

Thank you VERY MUCH! :+1:

1 Like

This is most likely my best Christmas present (regardless of whether I even get another one :wink:).
Merry Christmas!

1 Like

I just tested it with a perfectly looped sound. It works perfectly, even in the background. Tested with Companion 2.64.6 and the compiled app (IPA).
Thank you so much!

Another important note:
Seamless looping now also seems to work for compressed audio formats (accepted by Apple / iOS such as M4A, AAC, MP3, ...).

I have no idea since which iOS version this has been possible, as I have always had to use the IM4A (caf) format in Xcode/Swift. This is of course a big advantage because it makes app packages significantly smaller.

If I download a sound like this, I get this path with which the sound can be played without any problems. This path doesn't change with Companion when I repeat this process. However, with the compiled app (IPA) that doesn't seem to be the case. At least not when I reinstall the IPA (ad hoc).

How can I find out if the file already exists or gets a new path when the app is reinstalled. Unfortunately, the File component (File.Exists) doesn't work with iOS (as many other methods also don't work)?

/private/var/mobile/Containers/Data/Application/77E6D664-136A-4501-BDE4-97596C422A1E/tmp/Musik-1.m4a
(The path is almost the same with the IPA.)


The file/storage system is even more mysterious on iOS than Android. :wink: :upside_down_face:

I usually package all the required (audio/media) files into the assets (i.e. the app package), but in this case I want to avoid it, otherwise I would have to re-sign the app (aia limit: 30 MB) via "codesign" / Xcode. So some files have to be downloaded when you first start the app.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

Since you store the pathname in TinyDB, it seems like you would just need to check if that key is present or not to determine whether the download succeeded on future runs. We will work on making the File component maximally compatible with the Android version, but it seems like this problem can be solved without adding an implementation of File.Exists on the critical path.

The path stored in TinyDB is present, but this path no longer seems to work with the IPA after a reinstallation (ad hoc). I conclude that this file (i.e. the downloaded m4a file) no longer exists. The download process would have to be repeated in this case. And to be able to check this, I only see 2 options:

  1. There is a way to check this via the File component (File.Exists) or
  2. If the sound is not playing (if Player.IsNotPlaying, download...).

Interesting. Naturally, I expect that a reinstallation would result in a different UUID for the sandbox, so the stored file name in TinyDB wouldn't be valid. But TinyDB itself is a file stored in the sandbox so in theory a reinstallation should result in it being cleared (which would trigger a re-download). I will review the File component and see what resources we have to bring it more current with the Android functionality.