guru_sdk/guru_app/plugins/soundpool/ios/Classes/SwiftSoundpoolPlugin.swift

347 lines
14 KiB
Swift

import Flutter
import UIKit
import AVFoundation
public class SwiftSoundpoolPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "pl.ukaszapps/soundpool", binaryMessenger: registrar.messenger())
let instance = SwiftSoundpoolPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
private let counter = Atomic<Int>(0)
private lazy var wrappers = Dictionary<Int,SwiftSoundpoolPlugin.SoundpoolWrapper>()
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "initSoundpool":
// TODO create distinction between different types of audio playback
let attributes = call.arguments as! NSDictionary
initAudioSession(attributes)
let maxStreams = attributes["maxStreams"] as! Int
let enableRate = (attributes["ios_enableRate"] as? Bool) ?? true
let wrapper = SoundpoolWrapper(maxStreams, enableRate)
let index = counter.increment()
wrappers[index] = wrapper;
result(index)
case "dispose":
let attributes = call.arguments as! NSDictionary
let index = attributes["poolId"] as! Int
guard let wrapper = wrapperById(id: index) else {
print("Dispose attempt on not available pool (id: \(index)).")
result(FlutterError( code: "invalidArgs",
message: "Invalid poolId",
details: "Pool with id \(index) not found" ))
break
}
wrapper.stopAllStreams()
wrappers.removeValue(forKey: index)
result(nil)
default:
let attributes = call.arguments as! NSDictionary
let index = attributes["poolId"] as! Int
guard let wrapper = wrapperById(id: index) else {
print("Action '\(call.method)' attempt on not available pool (id: \(index)).")
result(FlutterError( code: "invalidArgs",
message: "Invalid poolId",
details: "Pool with id \(index) not found" ))
break
}
wrapper.handle(call, result: result)
}
}
private func initAudioSession(_ attributes: NSDictionary) {
if #available(iOS 10.0, *) {
guard let categoryAttr = attributes["ios_avSessionCategory"] as? String else {
return
}
let modeAttr = attributes["ios_avSessionMode"] as! String
let category: AVAudioSession.Category
switch categoryAttr {
case "ambient":
category = .ambient
case "playback":
category = .playback
case "playAndRecord":
category = .playAndRecord
case "multiRoute":
category = .multiRoute
default:
category = .soloAmbient
}
let mode: AVAudioSession.Mode
switch modeAttr {
case "moviePlayback":
mode = .moviePlayback
case "videoRecording":
mode = .videoRecording
case "voiceChat":
mode = .voiceChat
case "gameChat":
mode = .gameChat
case "videoChat":
mode = .videoChat
case "spokenAudio":
mode = .spokenAudio
case "measurement":
mode = .measurement
default:
mode = .default
}
do {
try AVAudioSession.sharedInstance().setCategory(category, mode: mode, options: AVAudioSession.CategoryOptions.mixWithOthers)
print("Audio session updated: category = '\(category)', mode = '\(mode)'.")
} catch (let e) {
//do nothing
print("Error while trying to set audio category: '\(e)'")
}
}
}
private func wrapperById(id: Int) -> SwiftSoundpoolPlugin.SoundpoolWrapper? {
if (id < 0){
return nil
}
let wrapper = wrappers[id]
return wrapper
}
class SoundpoolWrapper : NSObject {
private var maxStreams: Int
private var enableRate: Bool
private var streamIdProvider = Atomic<Int>(0)
private lazy var soundpool = [AVAudioPlayer]()
private lazy var streamsCount: Dictionary<Int, Int> = [Int: Int]()
private lazy var nowPlaying: Dictionary<Int, NowPlaying> = [Int: NowPlaying]()
init(_ maxStreams: Int, _ enableRate: Bool){
self.maxStreams = maxStreams
self.enableRate = enableRate
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let attributes = call.arguments as! NSDictionary
// print("\(call.method): \(attributes)")
switch call.method {
case "load":
let rawSound = attributes["rawSound"] as! FlutterStandardTypedData
do {
let audioPlayer = try AVAudioPlayer(data: rawSound.data)
if (enableRate){
audioPlayer.enableRate = true
}
audioPlayer.prepareToPlay()
let index = soundpool.count
soundpool.append(audioPlayer)
result(index)
} catch {
result(-1)
}
case "loadUri":
let soundUri = attributes["uri"] as! String
let url = URL(string: soundUri)
if (url != nil){
DispatchQueue.global(qos: .utility).async {
do {
let cachedSound = try Data(contentsOf: url!, options: NSData.ReadingOptions.mappedIfSafe)
DispatchQueue.main.async {
var value:Int = -1
do {
let audioPlayer = try AVAudioPlayer(data: cachedSound)
if (self.enableRate){
audioPlayer.enableRate = true
}
audioPlayer.prepareToPlay()
let index = self.self.soundpool.count
self.self.soundpool.append(audioPlayer)
value = index
} catch {
print("Unexpected error while preparing player: \(error).")
}
result(value)
}
} catch {
print("Unexpected error while downloading file: \(error).")
DispatchQueue.main.async {
result(-1)
}
}
}
} else {
result(-1)
}
case "play":
let soundId = attributes["soundId"] as! Int
let times = attributes["repeat"] as? Int
let rate = (attributes["rate"] as? Double) ?? 1.0
if (soundId < 0){
result(0)
break
}
guard var audioPlayer = playerBySoundId(soundId: soundId) else {
result(0)
break
}
do {
let currentCount = streamsCount[soundId] ?? 0
if (currentCount >= maxStreams){
result(0)
break
}
let nowPlayingData: NowPlaying
let streamId: Int = streamIdProvider.increment()
let delegate = SoundpoolDelegate(pool: self, soundId: soundId, streamId: streamId)
audioPlayer.delegate = delegate
nowPlayingData = NowPlaying(player: audioPlayer, delegate: delegate)
audioPlayer.numberOfLoops = times ?? 0
if (enableRate){
audioPlayer.enableRate = true
audioPlayer.rate = Float(rate)
}
if (audioPlayer.play()) {
streamsCount[soundId] = currentCount + 1
nowPlaying[streamId] = nowPlayingData
result(streamId)
} else {
result(0) // failed to play sound
}
// lets recreate the audioPlayer for next request - setting numberOfLoops has initially no effect
if let previousData = audioPlayer.data {
audioPlayer = try AVAudioPlayer(data: previousData)
} else if let previousUrl = audioPlayer.url {
audioPlayer = try AVAudioPlayer(contentsOf: previousUrl)
}
if (enableRate){
audioPlayer.enableRate = true
}
audioPlayer.prepareToPlay()
soundpool[soundId] = audioPlayer
} catch {
result(0)
}
case "pause":
let streamId = attributes["streamId"] as! Int
if let playingData = playerByStreamId(streamId: streamId) {
playingData.player.pause()
result(streamId)
} else {
result (-1)
}
case "resume":
let streamId = attributes["streamId"] as! Int
if let playingData = playerByStreamId(streamId: streamId) {
playingData.player.play()
result(streamId)
} else {
result (-1)
}
case "stop":
let streamId = attributes["streamId"] as! Int
if let nowPlaying = playerByStreamId(streamId: streamId) {
let audioPlayer = nowPlaying.player
audioPlayer.stop()
result(streamId)
// removing player
self.nowPlaying.removeValue(forKey: streamId)
nowPlaying.delegate.decreaseCounter()
audioPlayer.delegate = nil
} else {
result(-1)
}
case "setVolume":
let streamId = attributes["streamId"] as? Int
let soundId = attributes["soundId"] as? Int
let volume = attributes["volumeLeft"] as! Double
var audioPlayer: AVAudioPlayer? = nil;
if (streamId != nil){
audioPlayer = playerByStreamId(streamId: streamId!)?.player
} else if (soundId != nil){
audioPlayer = playerBySoundId(soundId: soundId!)
}
audioPlayer?.volume = Float(volume)
result(nil)
case "setRate":
if (enableRate){
let streamId = attributes["streamId"] as! Int
let rate = (attributes["rate"] as? Double) ?? 1.0
let audioPlayer: AVAudioPlayer? = playerByStreamId(streamId: streamId)?.player
audioPlayer?.rate = Float(rate)
}
result(nil)
case "release": // TODO this should distinguish between soundpools for different types of audio playbacks
stopAllStreams()
soundpool.removeAll()
result(nil)
default:
result("notImplemented")
}
}
func stopAllStreams() {
for audioPlayer in soundpool {
audioPlayer.stop()
}
}
private func playerByStreamId(streamId: Int) -> NowPlaying? {
let audioPlayer = nowPlaying[streamId]
return audioPlayer
}
private func playerBySoundId(soundId: Int) -> AVAudioPlayer? {
if (soundId >= soundpool.count || soundId < 0){
return nil
}
let audioPlayer = soundpool[soundId]
return audioPlayer
}
private class SoundpoolDelegate: NSObject, AVAudioPlayerDelegate {
private var soundId: Int
private var streamId: Int
private var pool: SoundpoolWrapper
init(pool: SoundpoolWrapper, soundId: Int, streamId: Int) {
self.soundId = soundId
self.pool = pool
self.streamId = streamId
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
decreaseCounter()
}
func decreaseCounter(){
pool.streamsCount[soundId] = (pool.streamsCount[soundId] ?? 1) - 1
pool.nowPlaying.removeValue(forKey: streamId)
}
}
private struct NowPlaying {
let player: AVAudioPlayer
let delegate: SoundpoolDelegate
}
}
}