Skip to content

Commit 80ec248

Browse files
committed
[ios] add ringtone/ringback/busytone supports
1 parent a67a54d commit 80ec248

File tree

3 files changed

+273
-20
lines changed

3 files changed

+273
-20
lines changed

index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ var InCallManager = {
55
start: function(setup) {
66
let auto = (setup.auto === false) ? false : true;
77
let media = (setup.media === 'video') ? 'video' : 'audio';
8-
let ringback = (!!setup.ringback) ? (setup.ringback === 'bundle') ? 'bundle' : 'sys' : '';
8+
let ringback = (!!setup.ringback) ? (typeof setup.ringback === 'string') ? setup.ringback : "" : "";
99
_InCallManager.start(media, auto, ringback);
1010
},
1111
stop: function(setup) {
12-
let busytone = (setup.busytone === true) ? true : false;
12+
let busytone = (!!setup.busytone) ? (typeof setup.busytone === 'string') ? setup.busytone : "" : "";
1313
_InCallManager.stop(busytone);
1414
},
1515
turnScreenOff: function() {
@@ -34,9 +34,9 @@ var InCallManager = {
3434
enable = (enable === true) ? true : false;
3535
_InCallManager.setMicrophoneMute(enable);
3636
},
37-
startRingtone: function(type) {
38-
type = (type === 'bundle') ? 'bundle' : 'sys';
39-
_InCallManager.startRingtone(type);
37+
startRingtone: function(ringtone) {
38+
ringtone = (typeof ringtone === 'string') ? ringtone : "_DEFAULT_";
39+
_InCallManager.startRingtone(ringtone);
4040
},
4141
stopRingtone: function() {
4242
_InCallManager.stopRingtone();

ios/RNInCallManager/RNInCallManager.swift

Lines changed: 263 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@ import NotificationCenter
1111
import 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+

ios/RNInCallManager/RNInCallManagerBridge.m

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99

1010
@interface RCT_EXTERN_REMAP_MODULE(InCallManager, RNInCallManager, NSObject)
1111

12-
RCT_EXTERN_METHOD(start:(NSString *)mediaType auto:(BOOL)auto)
13-
RCT_EXTERN_METHOD(stop)
12+
RCT_EXTERN_METHOD(start:(NSString *)mediaType auto:(BOOL)auto ringbackUriType:(NSString *)ringbackUriType)
13+
RCT_EXTERN_METHOD(stop:(NSString *)busytone)
1414
RCT_EXTERN_METHOD(turnScreenOn)
1515
RCT_EXTERN_METHOD(turnScreenOff)
1616
RCT_EXTERN_METHOD(setKeepScreenOn:(BOOL)enable)
1717
RCT_EXTERN_METHOD(setSpeakerphoneOn:(BOOL)enable)
1818
RCT_EXTERN_METHOD(setForceSpeakerphoneOn:(BOOL)enable)
1919
RCT_EXTERN_METHOD(setMicrophoneMute:(BOOL)enable)
20+
RCT_EXTERN_METHOD(stopRingback)
21+
RCT_EXTERN_METHOD(startRingtone:(NSString *)ringtoneUriType)
22+
RCT_EXTERN_METHOD(stopRingtone)
2023

2124
@end

0 commit comments

Comments
 (0)