Repository navigation
v2.4.0
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
MCSubscriberorMCPublisherare dealloc'ed on the millicast callback queue.
Deprecations
- Deprecated the
MCAudioConfiguration.panproperty which was introduced in 2.3.0.
Features
- iOS: Introduce an API to allow managing
AVAudioPlayerNodeused 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)
}
}