@@ -11,10 +11,21 @@ import NotificationCenter
1111import AVFoundation
1212
1313@objc ( RNInCallManager)
14- class RNInCallManager : NSObject {
14+ class RNInCallManager : NSObject , AVAudioPlayerDelegate {
1515 var bridge : RCTBridge ! // this is synthesized
1616 var currentDevice : UIDevice !
1717 var audioSession : AVAudioSession !
18+ var mRingtone : AVAudioPlayer !
19+ var mRingback : AVAudioPlayer !
20+ var mBusytone : AVAudioPlayer !
21+
22+ var defaultRingtoneUri : NSURL !
23+ var defaultRingbackUri : NSURL !
24+ var defaultBusytoneUri : NSURL !
25+ var bundleRingtoneUri : NSURL !
26+ var bundleRingbackUri : NSURL !
27+ var bundleBusytoneUri : NSURL !
28+
1829 var isProximitySupported : Bool = false
1930 var isProximityRegistered : Bool = false
2031 var proximityIsNear : Bool = false
@@ -37,10 +48,10 @@ class RNInCallManager: NSObject {
3748 }
3849
3950 deinit {
40- self . stop ( )
51+ self . stop ( " " )
4152 }
4253
43- @objc func start( media: String , auto: Bool ) -> Void {
54+ @objc func start( media: String , auto: Bool , ringbackUriType : String ) -> Void {
4455 guard !self . audioSessionInitialized else { return }
4556
4657 // --- audo is always true on ios
@@ -49,27 +60,42 @@ class RNInCallManager: NSObject {
4960 } else {
5061 self . defaultAudioMode = AVAudioSessionModeVoiceChat
5162 }
52- print ( " start InCallManager " )
63+ print ( " start() InCallManager " )
5364 self . storeOriginalAudioSetup ( )
65+ _ = try ? self . audioSession. setActive ( false )
5466 //self.audioSession.setCategory(defaultAudioCategory, options: [.DefaultToSpeaker, .AllowBluetooth])
5567 _ = try ? self . audioSession. setCategory ( self . defaultAudioCategory)
5668 _ = try ? self . audioSession. setMode ( self . defaultAudioMode)
69+ _ = try ? self . audioSession. setActive ( true )
70+ if !( ringbackUriType ?? " " ) . isEmpty {
71+ self . startRingback ( ringbackUriType)
72+ }
73+
5774 if media == " audio " {
5875 self . startProximitySensor ( )
5976 }
6077 self . setKeepScreenOn ( true )
6178 self . audioSessionInitialized = true
6279 }
6380
64- @objc func stop( ) -> Void {
81+ @objc func stop( busytoneUriType : String ) -> Void {
6582 guard self . audioSessionInitialized else { return }
6683
67- print ( " stop InCallManager " )
68- self . restoreOriginalAudioSetup ( )
69- self . stopProximitySensor ( )
70- self . setKeepScreenOn ( false )
71- NSNotificationCenter . defaultCenter ( ) . removeObserver ( self )
72- self . audioSessionInitialized = false
84+ self . stopRingback ( )
85+ if !( busytoneUriType ?? " " ) . isEmpty && self . startBusytone ( busytoneUriType) {
86+ // play busytone first, and call this func again when finish
87+ print ( " play busytone before stop InCallManager " )
88+ return
89+ } else {
90+ print ( " stop() InCallManager " )
91+ self . restoreOriginalAudioSetup ( )
92+ self . stopBusytone ( )
93+ self . stopProximitySensor ( )
94+ _ = try ? self . audioSession. setActive ( false , withOptions: . NotifyOthersOnDeactivation)
95+ self . setKeepScreenOn ( false )
96+ NSNotificationCenter . defaultCenter ( ) . removeObserver ( self )
97+ self . audioSessionInitialized = false
98+ }
7399 }
74100
75101 @objc func turnScreenOn( ) -> Void {
@@ -93,8 +119,8 @@ class RNInCallManager: NSObject {
93119 }
94120
95121 @objc func setForceSpeakerphoneOn( enable: Bool ) -> Void {
96- self . forceSpeakerOn = enable;
97- print ( " setForceSpeakerphoneOn( \( enable) ) " ) ;
122+ self . forceSpeakerOn = enable
123+ print ( " setForceSpeakerphoneOn( \( enable) ) " )
98124 if self . forceSpeakerOn {
99125 _ = try ? self . audioSession. overrideOutputAudioPort ( AVAudioSessionPortOverride . Speaker)
100126 } else {
@@ -154,4 +180,228 @@ class RNInCallManager: NSObject {
154180 func stopObserve( observer: AnyObject , name: String ? , object: AnyObject ? ) {
155181 NSNotificationCenter . defaultCenter ( ) . removeObserver ( observer, name: name, object: object)
156182 }
183+
184+ func getRingbackUri( _type: String ) -> NSURL ? {
185+ let fileBundle : String = " incallmanager_ringback "
186+ let fileBundleExt : String = " mp3 "
187+ let fileSysWithExt : String = " vc~ringing.caf "
188+ let fileSysPath : String = " /System/Library/Audio/UISounds "
189+ let type = ( _type == " " || _type == " _DEFAULT_ " ? fileSysWithExt : _type)
190+ return self . getAudioUri ( type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, & self . bundleRingbackUri, & self . defaultRingbackUri)
191+ }
192+
193+ func startRingback( ringbackUriType: String ) -> Void {
194+ // you may rejected by apple when publish app if you use system sound instead of bundled sound.
195+ print ( " startRingback() " )
196+ do {
197+ if self . mRingback != nil {
198+ if self . mRingback. playing {
199+ return
200+ } else {
201+ self . stopRingback ( )
202+ }
203+ }
204+ let ringbackUri : NSURL ? = getRingbackUri ( ringbackUriType)
205+ if ringbackUri == nil {
206+ print ( " no available ringback " )
207+ return
208+ }
209+ //self.storeOriginalAudioSetup()
210+ self . mRingback = try AVAudioPlayer ( contentsOfURL: ringbackUri!)
211+ self . mRingback. delegate = self
212+ self . mRingback. numberOfLoops = - 1 // you need to stop it explicitly
213+ self . mRingback. prepareToPlay ( )
214+ //self.audioSession.setCategory(defaultAudioCategory, options: [.DefaultToSpeaker, .AllowBluetooth])
215+
216+ _ = try ? self . audioSession. setActive ( false )
217+ _ = try ? self . audioSession. setCategory ( self . defaultAudioCategory)
218+ _ = try ? self . audioSession. setMode ( self . defaultAudioMode)
219+ _ = try ? self . audioSession. setActive ( true )
220+ self . mRingback. play ( )
221+ } catch {
222+ print ( " startRingtone() failed " )
223+ }
224+ }
225+
226+ @objc func stopRingback( ) -> Void {
227+ if self . mRingback != nil {
228+ print ( " stopRingback() " )
229+ self . mRingback. stop ( )
230+ self . mRingback = nil
231+ //self.restoreOriginalAudioSetup()
232+ //_ = try? self.audioSession.setActive(false, withOptions: .NotifyOthersOnDeactivation)
233+ }
234+ }
235+
236+ func getBusytoneUri( _type: String ) -> NSURL ? {
237+ let fileBundle : String = " incallmanager_busytone "
238+ let fileBundleExt : String = " mp3 "
239+ let fileSysWithExt : String = " ct-busy.caf " //ct-congestion.caf
240+ let fileSysPath : String = " /System/Library/Audio/UISounds "
241+ let type = ( _type == " " || _type == " _DEFAULT_ " ? fileSysWithExt : _type)
242+ return self . getAudioUri ( type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, & self . bundleBusytoneUri, & self . defaultBusytoneUri)
243+ }
244+
245+ func startBusytone( busytoneUriType: String ) -> Bool {
246+ // you may rejected by apple when publish app if you use system sound instead of bundled sound.
247+ print ( " startBusytone() " )
248+ do {
249+ if self . mBusytone != nil {
250+ if self . mBusytone. playing {
251+ return false
252+ } else {
253+ self . stopBusytone ( )
254+ }
255+ }
256+ let busytoneUri : NSURL ? = getBusytoneUri ( busytoneUriType)
257+ if busytoneUri == nil {
258+ print ( " no available busytone " )
259+ return false
260+ }
261+ //self.storeOriginalAudioSetup()
262+ self . mBusytone = try AVAudioPlayer ( contentsOfURL: busytoneUri!)
263+ self . mBusytone. delegate = self
264+ self . mBusytone. numberOfLoops = 0 // it's part of start(), will stop at stop()
265+ self . mBusytone. prepareToPlay ( )
266+ //self.audioSession.setCategory(defaultAudioCategory, options: [.DefaultToSpeaker, .AllowBluetooth])
267+
268+ _ = try ? self . audioSession. setActive ( false )
269+ _ = try ? self . audioSession. setCategory ( self . defaultAudioCategory)
270+ _ = try ? self . audioSession. setMode ( self . defaultAudioMode)
271+ _ = try ? self . audioSession. setActive ( true )
272+ self . mBusytone. play ( )
273+ } catch {
274+ print ( " startRingtone() failed " )
275+ return false
276+ }
277+ return true
278+ }
279+
280+ func stopBusytone( ) -> Void {
281+ if self . mBusytone != nil {
282+ print ( " stopBusytone() " )
283+ self . mBusytone. stop ( )
284+ self . mBusytone = nil
285+ //self.restoreOriginalAudioSetup()
286+ //_ = try? self.audioSession.setActive(false, withOptions: .NotifyOthersOnDeactivation)
287+ }
288+ }
289+
290+ func getRingtoneUri( _type: String ) -> NSURL ? {
291+ let fileBundle : String = " incallmanager_ringtone "
292+ let fileBundleExt : String = " mp3 "
293+ let fileSysWithExt : String = " Opening.m4r " //Marimba.m4r
294+ let fileSysPath : String = " /Library/Ringtones "
295+ let type = ( _type == " " || _type == " _DEFAULT_ " ? fileSysWithExt : _type)
296+ return self . getAudioUri ( type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, & self . bundleRingtoneUri, & self . defaultRingtoneUri)
297+ }
298+
299+ @objc func startRingtone( ringtoneUriType: String ) -> Void {
300+ // you may rejected by apple when publish app if you use system sound instead of bundled sound.
301+ print ( " startRingtone() " ) ;
302+ do {
303+ if self . mRingtone != nil {
304+ if self . mRingtone. playing {
305+ return
306+ } else {
307+ self . stopRingtone ( )
308+ }
309+ }
310+ let ringtoneUri : NSURL ? = getRingtoneUri ( ringtoneUriType)
311+ if ringtoneUri == nil {
312+ print ( " no available ringtone " )
313+ return
314+ }
315+ self . storeOriginalAudioSetup ( )
316+ self . mRingtone = try AVAudioPlayer ( contentsOfURL: ringtoneUri!)
317+ self . mRingtone. delegate = self
318+ self . mRingtone. numberOfLoops = - 1 // you need to stop it explicitly
319+ self . mRingtone. prepareToPlay ( )
320+ //self.audioSession.setCategory(defaultAudioCategory, options: [.DefaultToSpeaker, .AllowBluetooth])
321+
322+ _ = try ? self . audioSession. setActive ( false )
323+ _ = try ? self . audioSession. setCategory ( AVAudioSessionCategorySoloAmbient)
324+ _ = try ? self . audioSession. setMode ( AVAudioSessionModeDefault)
325+ _ = try ? self . audioSession. setActive ( true )
326+ self . mRingtone. play ( )
327+ } catch {
328+ print ( " startRingtone() failed " )
329+ }
330+ }
331+
332+ @objc func stopRingtone( ) -> Void {
333+ if self . mRingtone != nil {
334+ print ( " stopRingtone() " )
335+ self . mRingtone. stop ( )
336+ self . mRingtone = nil
337+ self . restoreOriginalAudioSetup ( )
338+ _ = try ? self . audioSession. setActive ( false , withOptions: . NotifyOthersOnDeactivation)
339+ }
340+ }
341+
342+ func getAudioUri( _type: String , _ fileBundle: String , _ fileBundleExt: String , _ fileSysWithExt: String , _ fileSysPath: String , inout _ uriBundle: NSURL ! , inout _ uriDefault: NSURL ! ) -> NSURL ? {
343+ var type = _type
344+ if type == " _BUNDLE_ " {
345+ if uriBundle == nil {
346+ uriBundle = NSBundle . mainBundle ( ) . URLForResource ( fileBundle, withExtension: fileBundleExt)
347+ if uriBundle == nil {
348+ print ( " getAudioUri() \( fileBundle) . \( fileBundleExt) not found in bundle. " )
349+ type = fileSysWithExt
350+ } else {
351+ return uriBundle
352+ }
353+ } else {
354+ return uriBundle
355+ }
356+ }
357+
358+ if uriDefault == nil {
359+ let target : String = " \( fileSysPath) / \( type) "
360+ uriDefault = self . getSysFileUri ( target)
361+ if uriDefault == nil {
362+ return nil
363+ }
364+ }
365+ return uriDefault;
366+ }
367+
368+ func getSysFileUri( target: String ) -> NSURL ? {
369+ let fileManager : NSFileManager = NSFileManager ( )
370+ let url : NSURL = NSURL ( fileURLWithPath: target, isDirectory: false )
371+ var isTargetDirectory : ObjCBool = ObjCBool ( false )
372+ if fileManager. fileExistsAtPath ( url. path!, isDirectory: & isTargetDirectory) {
373+ if !isTargetDirectory {
374+ //print("\(url.URLByDeletingPathExtension?.lastPathComponent)")
375+ return url
376+ }
377+ }
378+ return nil
379+ }
380+
381+ func audioPlayerDidFinishPlaying( player: AVAudioPlayer ! , successfully flag: Bool ) {
382+ // --- this only called when all loop played. it means, an infinite (numberOfLoops = -1) loop will never into here.
383+ //if player.url!.isFileReferenceURL() {
384+ let filename = player. url? . URLByDeletingPathExtension? . lastPathComponent
385+ if filename == self . bundleBusytoneUri? . URLByDeletingPathExtension? . lastPathComponent
386+ || filename == self . defaultBusytoneUri? . URLByDeletingPathExtension? . lastPathComponent {
387+ self . stopBusytone ( )
388+ self . stop ( " " )
389+ }
390+ print ( " finished playing: \( filename) " )
391+ }
392+
393+ func audioPlayerDecodeErrorDidOccur( player: AVAudioPlayer ! , error: NSError ! ) {
394+ print ( " \( error. localizedDescription) " )
395+ }
396+
397+ // --- Deprecated in iOS 8.0.
398+ func audioPlayerBeginInterruption( player: AVAudioPlayer ! ) {
399+ }
400+
401+ // --- Deprecated in iOS 8.0.
402+ func audioPlayerEndInterruption( player: AVAudioPlayer ! ) {
403+ }
404+
157405}
406+
407+
0 commit comments