Skip to content

v2.4.0

Choose a tag to compare

@releasecreator releasecreator released this 22 Apr 23:43
· 1 commit to main since this release
f79d005

New in 2.4.0

Fixes and improvements

  • Fixed a rare crash that occurs due to inconsistent state in the core SDK.
  • iOS: Fixed a deadlock that occurs when the MCSubscriber or MCPublisher are dealloc'ed on the millicast callback queue.

Deprecations

  • Deprecated the MCAudioConfiguration.pan property which was introduced in 2.3.0.

Features

  • iOS: Introduce an API to allow managing AVAudioPlayerNode used for audio playback in the SDK. The API looks like:
    let yourDelegate = AudioPlayerNodeDelegate()
    let audioConfiguration = MCAudioConfiguration()
    audioConfiguration.audioPlayerNodeDelegate = yourDelegate
    MCSDKConfiguration.configure(audioConfiguration)

The above global method MCSDKConfiguration.configure(...) should be called on app startup, before any Millicast objects are created. It can only be called once during application lifetime. Further calls
to this API will be ignored.

Here, the delegate refers to the user's implementation of the MCAudioPlayerNodeDelegate protocol:

class AudioPlayerNodeDelegate: NSObject, MCAudioPlayerNodeDelegate {

    func audioPlayerNodeDidBecomeAvailable(_ node: AVAudioPlayerNode, audioFormat format: AVAudioFormat, controller: any MCAudioPlayerNodePlaybackController) {
        // The user is expected to store the above arguments. 
        // The user also can only use the controller to start/stop playback on
        // the player node and not use the node directly for that purpose.
    }

    func audioPlayerNodeDidStartPlaying(_ node: AVAudioPlayerNode, error: Error?) {
        // This callback is invoked when the `controller.requestPlay` is called.
    }

    func audioPlayerNodeDidStopPlaying(_ node: AVAudioPlayerNode, error: Error?) {
        // This callback is invoked when the `controller.requestStop` is called.
    }

    func audioPlayerNodeDidBecomeUnAvailable(_ node: AVAudioPlayerNode) {
        // The user must disconnect the node from the engine synchronously here.
    }
}

While the API gives the users the freedom to control the SDK playback, such as performing channel mapping for example, it is expected that users setup their own AVAudioEngine and make sure the nodes are connected properly before attempting to request playback from the controller. A simple example looks like:

    private var audioEngine: AVAudioEngine
    private var playerNode: AVAudioPlayerNode?
    private var format: AVAudioFormat?
    private var controller: MCAudioPlayerNodePlaybackController?

    func audioPlayerNodeDidBecomeAvailable(_ node: AVAudioPlayerNode, audioFormat format: AVAudioFormat, controller: any MCAudioPlayerNodePlaybackController) {
        self.playerNode = node
        self.format = format
        self.controller = controller
        
        
        let outputNode = self.audioEngine.outputNode
        let outputFormat = outputNode.outputFormat(forBus: 0)

        
        self.audioEngine.attach(node)
        self.audioEngine.connect(node,
                                 to: self.audioEngine.mainMixerNode,
                                 format: format)
        self.audioEngine.connect(self.audioEngine.mainMixerNode,
                                 to: self.audioEngine.outputNode,
                                 format: outputFormat)
        

        self.audioEngine.prepare()
        
        
        do {
            try self.audioEngine.start()
            controller.requestPlay()
        } catch {
            os_log(.error, "Failed to start audio engine %@", error.localizedDescription)
        }
    }