Update call sounds.

pull/1/head
Matthew Chen 7 years ago
parent a44a117612
commit a0f4723fa2

@ -10,9 +10,9 @@
#import "ConversationViewItem.h" #import "ConversationViewItem.h"
#import "DateUtil.h" #import "DateUtil.h"
#import "DebugUIPage.h" #import "DebugUIPage.h"
#import "DebugUITableViewController.h"
#import "FingerprintViewController.h" #import "FingerprintViewController.h"
#import "HomeViewController.h" #import "HomeViewController.h"
#import "DebugUITableViewController.h"
#import "MediaDetailViewController.h" #import "MediaDetailViewController.h"
#import "NSString+OWS.h" #import "NSString+OWS.h"
#import "NotificationsManager.h" #import "NotificationsManager.h"
@ -51,6 +51,7 @@
#import <SignalMessaging/OWSLogger.h> #import <SignalMessaging/OWSLogger.h>
#import <SignalMessaging/OWSPreferences.h> #import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h> #import <SignalMessaging/OWSProfileManager.h>
#import <SignalMessaging/OWSSounds.h>
#import <SignalMessaging/Release.h> #import <SignalMessaging/Release.h>
#import <SignalMessaging/ThreadUtil.h> #import <SignalMessaging/ThreadUtil.h>
#import <SignalMessaging/UIColor+OWS.h> #import <SignalMessaging/UIColor+OWS.h>

@ -37,7 +37,7 @@ struct AudioSource: Hashable {
let localizedName = isBuiltInEarPiece ? UIDevice.current.localizedModel : portDescription.portName let localizedName = isBuiltInEarPiece ? UIDevice.current.localizedModel : portDescription.portName
self.init(localizedName: localizedName, self.init(localizedName: localizedName,
image:#imageLiteral(resourceName: "button_phone_white"), // TODO image: #imageLiteral(resourceName: "button_phone_white"), // TODO
isBuiltInSpeaker: false, isBuiltInSpeaker: false,
isBuiltInEarPiece: isBuiltInEarPiece, isBuiltInEarPiece: isBuiltInEarPiece,
portDescription: portDescription) portDescription: portDescription)
@ -102,42 +102,6 @@ protocol CallAudioServiceDelegate: class {
} }
} }
class Sound: NSObject {
static let incomingRing = Sound(filePath: "r", fileExtension: "caf", loop: true)
static let outgoingRing = Sound(filePath: "outring", fileExtension: "mp3", loop: true)
static let dialing = Sound(filePath: "sonarping", fileExtension: "mp3", loop: true)
static let busy = Sound(filePath: "busy", fileExtension: "mp3", loop: false)
static let failure = Sound(filePath: "failure", fileExtension: "mp3", loop: false)
let filePath: String
let fileExtension: String
let url: URL
let loop: Bool
init(filePath: String, fileExtension: String, loop: Bool) {
self.filePath = filePath
self.fileExtension = fileExtension
self.url = Bundle.main.url(forResource: self.filePath, withExtension: self.fileExtension)!
self.loop = loop
}
lazy var player: AVAudioPlayer? = {
let newPlayer: AVAudioPlayer?
do {
try newPlayer = AVAudioPlayer(contentsOf: self.url, fileTypeHint: nil)
if self.loop {
newPlayer?.numberOfLoops = -1
}
} catch {
owsFail("\(self.logTag) failed to build audio player with error: \(error)")
newPlayer = nil
}
return newPlayer
}()
}
// MARK: Vibration config // MARK: Vibration config
private let vibrateRepeatDuration = 1.6 private let vibrateRepeatDuration = 1.6
@ -175,7 +139,7 @@ protocol CallAudioServiceDelegate: class {
internal func stateDidChange(call: SignalCall, state: CallState) { internal func stateDidChange(call: SignalCall, state: CallState) {
AssertIsOnMainThread() AssertIsOnMainThread()
self.handleState(call:call) self.handleState(call: call)
} }
internal func muteDidChange(call: SignalCall, isMuted: Bool) { internal func muteDidChange(call: SignalCall, isMuted: Bool) {
@ -340,7 +304,7 @@ protocol CallAudioServiceDelegate: class {
// HACK: Without this async, dialing sound only plays once. I don't really understand why. Does the audioSession // HACK: Without this async, dialing sound only plays once. I don't really understand why. Does the audioSession
// need some time to settle? Is somethign else interrupting our session? // need some time to settle? Is somethign else interrupting our session?
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2) {
self.play(sound: Sound.dialing) self.play(sound: OWSSound.callConnecting)
} }
} }
@ -353,7 +317,7 @@ protocol CallAudioServiceDelegate: class {
Logger.debug("\(self.logTag) \(#function)") Logger.debug("\(self.logTag) \(#function)")
AssertIsOnMainThread() AssertIsOnMainThread()
self.play(sound: Sound.outgoingRing) self.play(sound: OWSSound.callOutboundRinging)
} }
private func handleLocalRinging(call: SignalCall) { private func handleLocalRinging(call: SignalCall) {
@ -372,7 +336,7 @@ protocol CallAudioServiceDelegate: class {
Logger.debug("\(self.logTag) \(#function)") Logger.debug("\(self.logTag) \(#function)")
AssertIsOnMainThread() AssertIsOnMainThread()
play(sound: Sound.failure) play(sound: OWSSound.callFailure)
handleCallEnded(call: call) handleCallEnded(call: call)
} }
@ -389,14 +353,14 @@ protocol CallAudioServiceDelegate: class {
vibrate() vibrate()
handleCallEnded(call:call) handleCallEnded(call: call)
} }
private func handleBusy(call: SignalCall) { private func handleBusy(call: SignalCall) {
Logger.debug("\(self.logTag) \(#function)") Logger.debug("\(self.logTag) \(#function)")
AssertIsOnMainThread() AssertIsOnMainThread()
play(sound: Sound.busy) play(sound: OWSSound.callBusy)
// Let the busy sound play for 4 seconds. The full file is longer than necessary // Let the busy sound play for 4 seconds. The full file is longer than necessary
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4.0) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4.0) {
@ -422,12 +386,12 @@ protocol CallAudioServiceDelegate: class {
stopAnyRingingVibration() stopAnyRingingVibration()
} }
private func play(sound: Sound) { private func play(sound: OWSSound) {
guard let newPlayer = sound.player else { guard let newPlayer = OWSSounds.audioPlayer(for: sound) else {
owsFail("\(self.logTag) unable to build player") owsFail("\(self.logTag) unable to build player for sound: \(OWSSounds.displayName(for: sound))")
return return
} }
Logger.info("\(self.logTag) playing sound: \(sound.filePath)") Logger.info("\(self.logTag) playing sound: \(OWSSounds.displayName(for: sound))")
// It's important to stop the current player **before** starting the new player. In the case that // It's important to stop the current player **before** starting the new player. In the case that
// we're playing the same sound, since the player is memoized on the sound instance, we'd otherwise // we're playing the same sound, since the player is memoized on the sound instance, we'd otherwise
@ -449,7 +413,8 @@ protocol CallAudioServiceDelegate: class {
self?.ringVibration() self?.ringVibration()
} }
vibrateTimer?.fire() vibrateTimer?.fire()
play(sound: Sound.incomingRing) let sound = OWSSounds.ringtoneSound(for: call.thread)
play(sound: sound)
} }
private func stopAnyRingingVibration() { private func stopAnyRingingVibration() {

@ -113,7 +113,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
if Environment.current().preferences.isCallKitPrivacyEnabled() { if Environment.current().preferences.isCallKitPrivacyEnabled() {
let callKitId = CallKitCallManager.kAnonymousCallHandlePrefix + call.localId.uuidString let callKitId = CallKitCallManager.kAnonymousCallHandlePrefix + call.localId.uuidString
update.remoteHandle = CXHandle(type: .generic, value: callKitId) update.remoteHandle = CXHandle(type: .generic, value: callKitId)
TSStorageManager.shared().setPhoneNumber(call.remotePhoneNumber, forCallKitId:callKitId) TSStorageManager.shared().setPhoneNumber(call.remotePhoneNumber, forCallKitId: callKitId)
update.localizedCallerName = NSLocalizedString("CALLKIT_ANONYMOUS_CONTACT_NAME", comment: "The generic name used for calls if CallKit privacy is enabled") update.localizedCallerName = NSLocalizedString("CALLKIT_ANONYMOUS_CONTACT_NAME", comment: "The generic name used for calls if CallKit privacy is enabled")
} else { } else {
update.localizedCallerName = self.contactsManager.stringForConversationTitle(withPhoneIdentifier: call.remotePhoneNumber) update.localizedCallerName = self.contactsManager.stringForConversationTitle(withPhoneIdentifier: call.remotePhoneNumber)
@ -124,6 +124,12 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
disableUnsupportedFeatures(callUpdate: update) disableUnsupportedFeatures(callUpdate: update)
// Update the provider configuration to reflect the caller's ringtone.
let sound = OWSSounds.ringtoneSound(for: call.thread)
let providerConfiguration = provider.configuration
providerConfiguration.ringtoneSound = OWSSounds.filename(for: sound)
provider.configuration = providerConfiguration
// Report the incoming call to the system // Report the incoming call to the system
provider.reportNewIncomingCall(with: call.localId, update: update) { error in provider.reportNewIncomingCall(with: call.localId, update: update) { error in
/* /*

@ -60,7 +60,8 @@
UILocalNotification *notification = [UILocalNotification new]; UILocalNotification *notification = [UILocalNotification new];
notification.category = PushManagerCategoriesIncomingCall; notification.category = PushManagerCategoriesIncomingCall;
// Rather than using notification sounds, we control the ringtone and repeat vibrations with the CallAudioManager. // Rather than using notification sounds, we control the ringtone and repeat vibrations with the CallAudioManager.
notification.soundName = @"r.caf"; OWSSound sound = [OWSSounds ringtoneSoundForThread:call.thread];
notification.soundName = [OWSSounds filenameForSound:sound];
NSString *localCallId = call.localId.UUIDString; NSString *localCallId = call.localId.UUIDString;
notification.userInfo = @{ PushManagerUserInfoKeysLocalCallId : localCallId }; notification.userInfo = @{ PushManagerUserInfoKeysLocalCallId : localCallId };

@ -45,10 +45,17 @@ typedef NS_ENUM(NSUInteger, OWSSound) {
OWSSound_Twinkle, OWSSound_Twinkle,
OWSSound_Uplift, OWSSound_Uplift,
OWSSound_Waves, OWSSound_Waves,
// Calls
OWSSound_CallConnecting,
OWSSound_CallOutboundRinging,
OWSSound_CallBusy,
OWSSound_CallFailure,
}; };
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class AVAudioPlayer;
@class TSThread; @class TSThread;
@interface OWSSounds : NSObject @interface OWSSounds : NSObject
@ -71,6 +78,20 @@ NS_ASSUME_NONNULL_BEGIN
+ (OWSSound)notificationSoundForThread:(TSThread *)thread; + (OWSSound)notificationSoundForThread:(TSThread *)thread;
+ (void)setNotificationSound:(OWSSound)sound forThread:(TSThread *)thread; + (void)setNotificationSound:(OWSSound)sound forThread:(TSThread *)thread;
#pragma mark - Ringtones
+ (NSArray<NSNumber *> *)allRingtoneSounds;
+ (OWSSound)globalRingtoneSound;
+ (void)setGlobalRingtoneSound:(OWSSound)sound;
+ (OWSSound)ringtoneSoundForThread:(TSThread *)thread;
+ (void)setRingtoneSound:(OWSSound)sound forThread:(TSThread *)thread;
#pragma mark - Calls
+ (nullable AVAudioPlayer *)audioPlayerForSound:(OWSSound)sound;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -3,6 +3,7 @@
// //
#import "OWSSounds.h" #import "OWSSounds.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioServices.h> #import <AudioToolbox/AudioServices.h>
#import <SignalServiceKit/TSStorageManager.h> #import <SignalServiceKit/TSStorageManager.h>
#import <SignalServiceKit/TSThread.h> #import <SignalServiceKit/TSThread.h>
@ -11,6 +12,9 @@
NSString *const kOWSSoundsStorageNotificationCollection = @"kOWSSoundsStorageNotificationCollection"; NSString *const kOWSSoundsStorageNotificationCollection = @"kOWSSoundsStorageNotificationCollection";
NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlobalNotificationKey"; NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlobalNotificationKey";
NSString *const kOWSSoundsStorageRingtoneCollection = @"kOWSSoundsStorageRingtoneCollection";
NSString *const kOWSSoundsStorageGlobalRingtoneKey = @"kOWSSoundsStorageGlobalRingtoneKey";
@interface OWSSounds () @interface OWSSounds ()
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@ -200,6 +204,16 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
return @"Uplift"; return @"Uplift";
case OWSSound_Waves: case OWSSound_Waves:
return @"Waves"; return @"Waves";
// Calls
case OWSSound_CallConnecting:
return @"Call Connecting";
case OWSSound_CallOutboundRinging:
return @"Call Outboung Ringing";
case OWSSound_CallBusy:
return @"Call Busy";
case OWSSound_CallFailure:
return @"Call Failure";
} }
} }
@ -291,6 +305,16 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
return @"Uplift.m4r"; return @"Uplift.m4r";
case OWSSound_Waves: case OWSSound_Waves:
return @"Waves.m4r"; return @"Waves.m4r";
// Calls
case OWSSound_CallConnecting:
return @"sonarping.mp3";
case OWSSound_CallOutboundRinging:
return @"outring.mp3";
case OWSSound_CallBusy:
return @"busy.mp3";
case OWSSound_CallFailure:
return @"failure.mp3";
} }
} }
@ -374,4 +398,66 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob
inCollection:kOWSSoundsStorageNotificationCollection]; inCollection:kOWSSoundsStorageNotificationCollection];
} }
#pragma mark - Ringtones
+ (OWSSound)defaultRingtoneSound
{
return OWSSound_Opening;
}
+ (OWSSound)globalRingtoneSound
{
OWSSounds *instance = OWSSounds.sharedManager;
NSNumber *_Nullable value = [instance.dbConnection objectForKey:kOWSSoundsStorageGlobalRingtoneKey
inCollection:kOWSSoundsStorageRingtoneCollection];
// Default to the global default.
return (value ? (OWSSound)value.intValue : [self defaultRingtoneSound]);
}
+ (void)setGlobalRingtoneSound:(OWSSound)sound
{
OWSSounds *instance = OWSSounds.sharedManager;
[instance.dbConnection setObject:@(sound)
forKey:kOWSSoundsStorageGlobalRingtoneKey
inCollection:kOWSSoundsStorageRingtoneCollection];
}
+ (OWSSound)ringtoneSoundForThread:(TSThread *)thread
{
OWSSounds *instance = OWSSounds.sharedManager;
NSNumber *_Nullable value =
[instance.dbConnection objectForKey:thread.uniqueId inCollection:kOWSSoundsStorageRingtoneCollection];
// Default to the "global" ringtone sound, which in turn will default to the global default.
return (value ? (OWSSound)value.intValue : [self globalRingtoneSound]);
}
+ (void)setRingtoneSound:(OWSSound)sound forThread:(TSThread *)thread
{
OWSSounds *instance = OWSSounds.sharedManager;
[instance.dbConnection setObject:@(sound) forKey:thread.uniqueId inCollection:kOWSSoundsStorageRingtoneCollection];
}
#pragma mark - Calls
+ (BOOL)shouldAudioPlayerLoopForSound:(OWSSound)sound
{
return (sound == OWSSound_CallConnecting || sound == OWSSound_CallOutboundRinging ||
[self.allRingtoneSounds containsObject:@(sound)]);
}
+ (nullable AVAudioPlayer *)audioPlayerForSound:(OWSSound)sound
{
NSURL *soundURL = [OWSSounds soundURLForSound:sound];
NSError *error;
AVAudioPlayer *_Nullable player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
if (error || !player) {
OWSFail(@"%@ audioPlayerForSound failed with error: %@.", self.logTag, error);
return nil;
}
if ([self shouldAudioPlayerLoopForSound:sound]) {
player.numberOfLoops = -1;
}
return player;
}
@end @end

Loading…
Cancel
Save