├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── AudioDeviceExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── AudioDeviceExample.xcscheme ├── AudioDeviceExample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── AudioDevices │ ├── AudioDevices-Bridging-Header.h │ ├── ExampleAVAudioEngineDevice.h │ ├── ExampleAVAudioEngineDevice.m │ └── mixLoop.caf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── README.md └── ViewController.swift ├── Docs ├── access-tokens.md ├── call-from-history.md ├── managing-audio-interruptions.md ├── managing-push-credentials.md ├── migration-guide-2.x-3.x.md ├── migration-guide-3.x-4.x.md ├── migration-guide-5.x-6.x.md ├── new-features-3.0.md ├── new-features-4.0.md ├── push-credentials-via-conversations-api.md └── verified-caller-number.md ├── Images ├── audio-device-example-call-bob.png ├── audio-device-example-play.png ├── audio-engine-example.jpg ├── client-to-client.png ├── client-to-pstn.png ├── hang-up.png ├── incoming-call.png ├── keychain-api-key-secret.png ├── unverified-caller-number.png ├── update_push_credential.png └── verified-caller-number.png ├── LICENSE ├── ObjcVoiceQuickstart.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── ObjCVoiceQuickstart.xcscheme ├── ObjcVoiceQuickstart ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon.png │ │ └── Icon@2x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ObjCVoiceQuickstart.entitlements ├── TwilioLogo.png ├── ViewController.h ├── ViewController.m └── main.m ├── README.md ├── Server ├── .env.example ├── .gitignore ├── .nvmrc ├── LICENSE ├── assets │ ├── index.html │ └── style.css ├── functions │ ├── access-token.js │ ├── hello-world.js │ ├── incoming.js │ ├── make-call.js │ └── place-call.js ├── package-lock.json └── package.json ├── SwiftVoiceQuickstart.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── SwiftVoiceQuickstart.xcscheme ├── SwiftVoiceQuickstart ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon.png │ │ └── Icon@2x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SwiftVoiceQuickstart.entitlements ├── TwilioLogo.png └── ViewController.swift ├── VoiceQuickstart.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── WorkspaceSettings.xcsettings │ └── swiftpm │ └── Package.resolved └── ringtone └── ringtone.wav /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | > Before filing an issue please check that the issue is not already addressed by the following: 3 | > * [Voice SDK Guides](twilio.com/docs/api/voice-sdk) 4 | > * [GitHub Issues](https://github.com/twilio/voice-quickstart-swift/issues) 5 | > * [Changelog](https://www.twilio.com/docs/api/voice-sdk/ios/changelog) 6 | 7 | > Please ensure that you are not sharing any 8 | [Personally Identifiable Information(PII)](https://www.twilio.com/docs/glossary/what-is-personally-identifiable-information-pii) 9 | or sensitive account information (API keys, credentials, etc.) when reporting an issue. 10 | 11 | ### Description 12 | 13 | [Description of the issue] 14 | 15 | ### Steps to Reproduce 16 | 17 | 1. [Step one] 18 | 2. [Step two] 19 | 3. [Insert as many steps as needed] 20 | 21 | #### Code 22 | 23 | ```swift 24 | // Code that helps reproduce the issue 25 | ``` 26 | 27 | #### Expected Behavior 28 | 29 | [What you expect to happen] 30 | 31 | #### Actual Behavior 32 | 33 | [What actually happens] 34 | 35 | #### Reproduces How Often 36 | 37 | [What percentage of the time does it reproduce?] 38 | 39 | #### Twilio Call SID(s) 40 | 41 | You can find the Call SID in the SDK from TVOCall.sid or TVOCallInvite.callSid. The Call SID can also be found on the Twilio Calls Console: https://www.twilio.com/console/voice/calls/logs. 42 | 43 | #### Logs 44 | 45 | ``` 46 | // Log output when the issue occurs 47 | ``` 48 | 49 | ### Versions 50 | 51 | All relevant version information for the issue. 52 | 53 | #### Voice iOS SDK 54 | 55 | [e.g. 2.0.0-beta13 via CocoaPods] 56 | 57 | #### Xcode 58 | 59 | [e.g. 8.3.3] 60 | 61 | #### iOS Version 62 | 63 | [e.g. 10.3.3] 64 | 65 | #### iOS Device 66 | 67 | [e.g. iPhone 6s Plus] 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | Podfile.lock 3 | SwiftVoiceQuickstart.xcworkspace 4 | Carthage 5 | Cartfile.resolved 6 | 7 | xcuserdata/ 8 | *.xcuserstate 9 | -------------------------------------------------------------------------------- /AudioDeviceExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AudioDeviceExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AudioDeviceExample.xcodeproj/xcshareddata/xcschemes/AudioDeviceExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /AudioDeviceExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AudioDeviceExample 4 | // 5 | // Copyright © 2020 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | var window: UIWindow? 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /AudioDeviceExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /AudioDeviceExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AudioDeviceExample/AudioDevices/AudioDevices-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioDevices-Bridging-Header.h 3 | // AudioDeviceExample 4 | // 5 | // Copyright © 2018-2020 Twilio Inc. All rights reserved. 6 | // 7 | 8 | #import "ExampleAVAudioEngineDevice.h" 9 | -------------------------------------------------------------------------------- /AudioDeviceExample/AudioDevices/ExampleAVAudioEngineDevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleAVAudioEngineDevice.h 3 | // AudioDeviceExample 4 | // 5 | // Copyright © 2018-2020 Twilio Inc. All rights reserved. 6 | // 7 | 8 | @import TwilioVoice; 9 | 10 | NS_CLASS_AVAILABLE(NA, 11_0) 11 | @interface ExampleAVAudioEngineDevice : NSObject 12 | 13 | @property (nonatomic, assign, getter=isEnabled) BOOL enabled; 14 | 15 | - (void)playMusic; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AudioDeviceExample/AudioDevices/mixLoop.caf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/AudioDeviceExample/AudioDevices/mixLoop.caf -------------------------------------------------------------------------------- /AudioDeviceExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AudioDeviceExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /AudioDeviceExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | $(EXECUTABLE_NAME) would like to access your microphone 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIBackgroundModes 26 | 27 | audio 28 | voip 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /AudioDeviceExample/README.md: -------------------------------------------------------------------------------- 1 | # Twilio Voice TVOAudioDevice Example 2 | 3 | The project demonstrates how to use Twilio's Programmable Voice SDK with audio playback and recording functionality provided by a custom `TVOAudioDevice`. 4 | 5 | The example demonstrates the custom audio device **ExampleAVAudioEngineDevice**, which uses CoreAudio's VoiceProcessingIO audio unit to playback and record audio at up to 48KHz with built-in echo cancellation. The example uses two AVAudioEngine in manual rendering mode: 6 | 7 | 1. The upstream AVAudioEngine is used for mixing the Remote Participant's audio with audio from a file. The AudioUnit receives mixed audio samples from AVAudioEngine's output node. 8 | 2. The downstream AVAudioEngine is used for mixing the Local Participant's microphone audio with audio from a file. The Voice SDK receives the mixed audio samples from the AVAudioEngine's output node. 9 | 10 | This diagram describes how ExampleAVAudioEngineDevice uses TwilioVoice, AVAudioEngine, and CoreAudio - 11 | 12 | 13 | 14 | Please note, ExampleAVAudioEngineDevice requires iOS 11.0 or above. 15 | 16 | ### Setup 17 | 18 | See the master [README](https://github.com/twilio/voice-quickstart-ios/blob/master/README.md) for instructions on how to generate access tokens and make an outbound Call. 19 | 20 | This example requires Xcode 11.0 and the iOS 12.0 SDK, as well as a device running iOS 11.0 or above. 21 | 22 | ### Running 23 | 24 | Once you have configured your access token, build and run the example. Use the text field to make an outgoing call: 25 | 26 | 27 | 28 | Once the Call is connected, tap the "Play Music" button and let ExampleAVAudioEngineDevice mix some tunes into your track. 29 | 30 | 31 | 32 | ### Known Issues 33 | 34 | The AVAudioSession is configured and activated at device initialization time. Ideally, it would be better to activate the AVAudioSession only when audio playback or recording is needed. -------------------------------------------------------------------------------- /AudioDeviceExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AudioDeviceExample 4 | // 5 | // Copyright © 2020 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | import CallKit 9 | import UIKit 10 | 11 | import TwilioVoice 12 | 13 | let twimlParamTo = "to" 14 | 15 | class ViewController: UIViewController { 16 | 17 | var accessToken: String? = <#Replace with Access Token string#> 18 | var activeCall: Call? 19 | var audioDevice: ExampleAVAudioEngineDevice = ExampleAVAudioEngineDevice() 20 | 21 | var callKitProvider: CXProvider 22 | var callKitCallController: CXCallController 23 | var userInitiatedDisconnect: Bool = false 24 | var callKitCompletionCallback: ((Bool) -> Void)? = nil 25 | 26 | @IBOutlet weak var outgoingTextField: UITextField! 27 | @IBOutlet weak var callButton: UIButton! 28 | @IBOutlet weak var callControlView: UIView! 29 | @IBOutlet weak var muteSwitch: UISwitch! 30 | @IBOutlet weak var speakerSwitch: UISwitch! 31 | @IBOutlet weak var playMusicButton: UIButton! 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | // Do any additional setup after loading the view. 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | let configuration = CXProviderConfiguration(localizedName: "AudioDeviceExample") 40 | configuration.maximumCallGroups = 1 41 | configuration.maximumCallsPerCallGroup = 1 42 | 43 | callKitProvider = CXProvider(configuration: configuration) 44 | callKitCallController = CXCallController() 45 | 46 | super.init(coder: aDecoder) 47 | 48 | callKitProvider.setDelegate(self, queue: nil) 49 | 50 | TwilioVoiceSDK.audioDevice = audioDevice 51 | } 52 | 53 | deinit { 54 | // CallKit has an odd API contract where the developer must call invalidate or the CXProvider is leaked. 55 | callKitProvider.invalidate() 56 | } 57 | 58 | func toggleUIState(isEnabled: Bool, showCallControl: Bool) { 59 | callButton.isEnabled = isEnabled 60 | callControlView.isHidden = !showCallControl; 61 | muteSwitch.isOn = !showCallControl; 62 | speakerSwitch.isOn = showCallControl; 63 | } 64 | 65 | // MARK: AVAudioSession 66 | 67 | func toggleAudioRoute(toSpeaker: Bool) { 68 | // The mode set by the Voice SDK is "VoiceChat" so the default audio route is the built-in receiver. Use port override to switch the route. 69 | do { 70 | try AVAudioSession.sharedInstance().overrideOutputAudioPort(toSpeaker ? .speaker : .none) 71 | } catch { 72 | NSLog(error.localizedDescription) 73 | } 74 | } 75 | 76 | func showMicrophoneAccessRequest(_ uuid: UUID, _ handle: String) { 77 | let alertController = UIAlertController(title: "Voice Quick Start", 78 | message: "Microphone permission not granted", 79 | preferredStyle: .alert) 80 | 81 | let continueWithoutMic = UIAlertAction(title: "Continue without microphone", style: .default) { [weak self] _ in 82 | self?.performStartCallAction(uuid: uuid, handle: handle) 83 | } 84 | 85 | let goToSettings = UIAlertAction(title: "Settings", style: .default) { _ in 86 | UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, 87 | options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: false], 88 | completionHandler: nil) 89 | } 90 | 91 | let cancel = UIAlertAction(title: "Cancel", style: .cancel) { [weak self] _ in 92 | self?.toggleUIState(isEnabled: true, showCallControl: false) 93 | } 94 | 95 | [continueWithoutMic, goToSettings, cancel].forEach { alertController.addAction($0) } 96 | 97 | present(alertController, animated: true, completion: nil) 98 | } 99 | 100 | func checkRecordPermission(completion: @escaping (_ permissionGranted: Bool) -> Void) { 101 | let permissionStatus = AVAudioSession.sharedInstance().recordPermission 102 | 103 | switch permissionStatus { 104 | case .granted: 105 | // Record permission already granted. 106 | completion(true) 107 | case .denied: 108 | // Record permission denied. 109 | completion(false) 110 | case .undetermined: 111 | // Requesting record permission. 112 | // Optional: pop up app dialog to let the users know if they want to request. 113 | AVAudioSession.sharedInstance().requestRecordPermission { granted in completion(granted) } 114 | default: 115 | completion(false) 116 | } 117 | } 118 | 119 | @IBAction func callButtonTapped(_ sender: Any) { 120 | guard activeCall == nil else { 121 | userInitiatedDisconnect = true 122 | performEndCallAction(uuid: activeCall!.uuid!) 123 | toggleUIState(isEnabled: false, showCallControl: false) 124 | 125 | return 126 | } 127 | 128 | checkRecordPermission { [weak self] permissionGranted in 129 | let uuid = UUID() 130 | let handle = "Voice Bot" 131 | 132 | guard !permissionGranted else { 133 | self?.performStartCallAction(uuid: uuid, handle: handle) 134 | return 135 | } 136 | 137 | self?.showMicrophoneAccessRequest(uuid, handle) 138 | } 139 | } 140 | 141 | @IBAction func muteSwitchToggled(_ sender: UISwitch) { 142 | guard let activeCall = activeCall else { return } 143 | 144 | activeCall.isMuted = sender.isOn 145 | } 146 | 147 | @IBAction func speakerSwitchToggled(_ sender: UISwitch) { 148 | toggleAudioRoute(toSpeaker: sender.isOn) 149 | } 150 | 151 | @IBAction func playMusicButtonTapped(_ sender: UIButton) { 152 | audioDevice.playMusic() 153 | } 154 | } 155 | 156 | // MARK: - TVOCallDelegate 157 | 158 | extension ViewController: CallDelegate { 159 | func callDidStartRinging(call: Call) { 160 | NSLog("callDidStartRinging:") 161 | 162 | callButton.setTitle("Ringing", for: .normal) 163 | } 164 | 165 | func callDidConnect(call: Call) { 166 | NSLog("callDidConnect:") 167 | 168 | if let callKitCompletionCallback = callKitCompletionCallback { 169 | callKitCompletionCallback(true) 170 | } 171 | 172 | callButton.setTitle("Hang Up", for: .normal) 173 | toggleUIState(isEnabled: true, showCallControl: true) 174 | toggleAudioRoute(toSpeaker: true) 175 | } 176 | 177 | func call(call: Call, isReconnectingWithError error: Error) { 178 | NSLog("call:isReconnectingWithError:") 179 | 180 | callButton.setTitle("Reconnecting", for: .normal) 181 | toggleUIState(isEnabled: false, showCallControl: false) 182 | } 183 | 184 | func callDidReconnect(call: Call) { 185 | NSLog("callDidReconnect:") 186 | 187 | callButton.setTitle("Hang Up", for: .normal) 188 | toggleUIState(isEnabled: true, showCallControl: true) 189 | } 190 | 191 | func callDidFailToConnect(call: Call, error: Error) { 192 | NSLog("Call failed to connect: \(error.localizedDescription)") 193 | 194 | if let completion = callKitCompletionCallback { 195 | completion(false) 196 | } 197 | 198 | performEndCallAction(uuid: call.uuid!) 199 | callDisconnected(call) 200 | } 201 | 202 | func callDidDisconnect(call: Call, error: Error?) { 203 | if let error = error { 204 | NSLog("Call failed: \(error.localizedDescription)") 205 | } else { 206 | NSLog("Call disconnected") 207 | } 208 | 209 | if !userInitiatedDisconnect { 210 | var reason = CXCallEndedReason.remoteEnded 211 | 212 | if error != nil { 213 | reason = .failed 214 | } 215 | 216 | callKitProvider.reportCall(with: call.uuid!, endedAt: Date(), reason: reason) 217 | } 218 | 219 | callDisconnected(call) 220 | } 221 | 222 | func callDisconnected(_ call: Call) { 223 | if call == activeCall { 224 | activeCall = nil 225 | } 226 | 227 | userInitiatedDisconnect = false 228 | 229 | toggleUIState(isEnabled: true, showCallControl: false) 230 | callButton.setTitle("Call", for: .normal) 231 | } 232 | } 233 | 234 | // MARK: - CXProviderDelegate 235 | 236 | extension ViewController: CXProviderDelegate { 237 | func providerDidReset(_ provider: CXProvider) { 238 | NSLog("providerDidReset:") 239 | audioDevice.isEnabled = false 240 | } 241 | 242 | func providerDidBegin(_ provider: CXProvider) { 243 | NSLog("providerDidBegin") 244 | } 245 | 246 | func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { 247 | NSLog("provider:didActivateAudioSession:") 248 | audioDevice.isEnabled = true 249 | } 250 | 251 | func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { 252 | NSLog("provider:didDeactivateAudioSession:") 253 | audioDevice.isEnabled = false 254 | } 255 | 256 | func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { 257 | NSLog("provider:timedOutPerformingAction:") 258 | } 259 | 260 | func provider(_ provider: CXProvider, perform action: CXStartCallAction) { 261 | NSLog("provider:performStartCallAction:") 262 | 263 | provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date()) 264 | 265 | performVoiceCall(uuid: action.callUUID, client: "") { success in 266 | if success { 267 | provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date()) 268 | action.fulfill() 269 | } else { 270 | action.fail() 271 | } 272 | } 273 | } 274 | 275 | func provider(_ provider: CXProvider, perform action: CXEndCallAction) { 276 | NSLog("provider:performEndCallAction:") 277 | 278 | if let call = activeCall { 279 | call.disconnect() 280 | } 281 | 282 | action.fulfill() 283 | } 284 | 285 | func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { 286 | NSLog("provider:performSetHeldAction:") 287 | 288 | if let call = activeCall { 289 | call.isOnHold = action.isOnHold 290 | action.fulfill() 291 | } else { 292 | action.fail() 293 | } 294 | } 295 | 296 | func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { 297 | NSLog("provider:performSetMutedAction:") 298 | 299 | if let call = activeCall { 300 | call.isMuted = action.isMuted 301 | action.fulfill() 302 | } else { 303 | action.fail() 304 | } 305 | } 306 | 307 | 308 | // MARK: Call Kit Actions 309 | func performStartCallAction(uuid: UUID, handle: String) { 310 | let callHandle = CXHandle(type: .generic, value: handle) 311 | let startCallAction = CXStartCallAction(call: uuid, handle: callHandle) 312 | let transaction = CXTransaction(action: startCallAction) 313 | 314 | callKitCallController.request(transaction) { error in 315 | if let error = error { 316 | NSLog("StartCallAction transaction request failed: \(error.localizedDescription)") 317 | return 318 | } 319 | 320 | NSLog("StartCallAction transaction request successful") 321 | 322 | let callUpdate = CXCallUpdate() 323 | 324 | callUpdate.remoteHandle = callHandle 325 | callUpdate.supportsDTMF = true 326 | callUpdate.supportsHolding = true 327 | callUpdate.supportsGrouping = false 328 | callUpdate.supportsUngrouping = false 329 | callUpdate.hasVideo = false 330 | 331 | self.callKitProvider.reportCall(with: uuid, updated: callUpdate) 332 | } 333 | } 334 | 335 | func reportIncomingCall(from: String, uuid: UUID) { 336 | let callHandle = CXHandle(type: .generic, value: from) 337 | 338 | let callUpdate = CXCallUpdate() 339 | 340 | callUpdate.remoteHandle = callHandle 341 | callUpdate.supportsDTMF = true 342 | callUpdate.supportsHolding = true 343 | callUpdate.supportsGrouping = false 344 | callUpdate.supportsUngrouping = false 345 | callUpdate.hasVideo = false 346 | 347 | callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in 348 | if let error = error { 349 | NSLog("Failed to report incoming call successfully: \(error.localizedDescription).") 350 | } else { 351 | NSLog("Incoming call successfully reported.") 352 | } 353 | } 354 | } 355 | 356 | func performEndCallAction(uuid: UUID) { 357 | 358 | let endCallAction = CXEndCallAction(call: uuid) 359 | let transaction = CXTransaction(action: endCallAction) 360 | 361 | callKitCallController.request(transaction) { error in 362 | if let error = error { 363 | NSLog("EndCallAction transaction request failed: \(error.localizedDescription).") 364 | } else { 365 | NSLog("EndCallAction transaction request successful") 366 | } 367 | } 368 | } 369 | 370 | func performVoiceCall(uuid: UUID, client: String?, completionHandler: @escaping (Bool) -> Void) { 371 | guard let token = accessToken, token.count > 0 else { 372 | completionHandler(false) 373 | return 374 | } 375 | 376 | let connectOptions = ConnectOptions(accessToken: token) { builder in 377 | builder.params = [twimlParamTo: self.outgoingTextField.text ?? ""] 378 | builder.uuid = uuid 379 | } 380 | 381 | let call = TwilioVoiceSDK.connect(options: connectOptions, delegate: self) 382 | activeCall = call 383 | callKitCompletionCallback = completionHandler 384 | } 385 | } 386 | 387 | -------------------------------------------------------------------------------- /Docs/access-tokens.md: -------------------------------------------------------------------------------- 1 | ## Access Tokens 2 | 3 | The access token generated by your server component is a [jwt](https://jwt.io) that contains a `grant` for Programmable Voice, an `identity` that you specify, and a `time-to-live` that sets the lifetime of the generated access token. The default `time-to-live` is 1 hour and is configurable up to 24 hours using the Twilio helper libraries. 4 | 5 | ### Uses 6 | 7 | In the iOS SDK the access token is used for the following: 8 | 9 | 1. To make an outgoing call via `TwilioVoice.connect(...)` 10 | 2. To register or unregister for incoming notifications using VoIP Push Notifications via `TwilioVoice.registerWithAccessToken(...)` and `TwilioVoice.unregisterWithAccessToken(...)`. Once registered, incoming notifications are handled via a `TVOCallInvite` where you can choose to accept or reject the invite. When accepting the call an access token is not required. Internally the `TVOCallInvite` has its own access token that ensures it can connect to our infrastructure. 11 | 12 | ### Managing Expiry 13 | 14 | As mentioned above, an access token will eventually expire. If an access token has expired, our infrastructure will return error `TVOErrorAccessTokenExpired`/`20104` via `TVOCallDelegate` or a completion error when registering. 15 | 16 | There are number of techniques you can use to ensure that access token expiry is managed accordingly: 17 | 18 | - Always fetch a new access token from your access token server before making an outbound call. 19 | - Retain the access token until getting a `TVOErrorAccessTokenExpired`/`20104` error before fetching a new access token. 20 | - Retain the access token along with the timestamp of when it was requested so you can verify ahead of time whether the token has already expired based on the `time-to-live` being used by your server. 21 | - Prefetch the access token whenever the `UIApplication`, or `UIViewController` associated with an outgoing call is created. -------------------------------------------------------------------------------- /Docs/call-from-history.md: -------------------------------------------------------------------------------- 1 | ## Making Calls from Call History 2 | 3 | The document show a quick example of how to make outgoing calls from recent call history or from contacts. 4 | 5 | ### Steps 6 | 7 | #### 1. Specify supported handle types when initializing the `CXProvider` object. User activity callback will not be triggered if this is not set. 8 | 9 | ```.swift 10 | let configuration = CXProviderConfiguration(localizedName: "Voice Quickstart") 11 | configuration.maximumCallGroups = 1 12 | configuration.maximumCallsPerCallGroup = 1 13 | 14 | // Specify supported handle types so the app gets user activity callback when a call is made from call history 15 | configuration.supportedHandleTypes = [.generic, .phoneNumber] 16 | 17 | if let provider = CXProvider(configuration: configuration) { 18 | provider.setDelegate(self, queue: nil) 19 | } 20 | 21 | ``` 22 | 23 | ```.objc 24 | CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"Voice"]; 25 | configuration.maximumCallGroups = 1; 26 | configuration.maximumCallsPerCallGroup = 1; 27 | 28 | // Specify supported handle types so the app gets user activity callback when a call is made from call history 29 | configuration.supportedHandleTypes = [NSSet setWithArray:@[@(CXHandleTypeGeneric), @(CXHandleTypePhoneNumber)]]; 30 | 31 | self.callKitProvider = [[CXProvider alloc] initWithConfiguration:configuration]; 32 | [self.callKitProvider setDelegate:self queue:nil]; 33 | 34 | ``` 35 | 36 | #### 2. Implement the `[UIApplication continueUserActivity:restorationHandler:]` delegate method and use the `INStartAudioCallIntent` to start a call. 37 | 38 | ```.swift 39 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 40 | if let callIntent = userActivity.interaction?.intent as? INStartAudioCallIntent, 41 | let contact = callIntent.contacts?[0] { 42 | guard let handle = contact.personHandle?.value else { return false } 43 | // Start a new call with CallKit 44 | makeCall(handle) 45 | } 46 | 47 | return true 48 | } 49 | ``` 50 | 51 | ```.objc 52 | - (BOOL)application:(UIApplication *)application 53 | continueUserActivity:(NSUserActivity *)userActivity 54 | restorationHandler:(void(^)(NSArray> *restorableObjects))restorationHandler { 55 | 56 | INStartAudioCallIntent *callIntent = (INStartAudioCallIntent *)userActivity.interaction.intent; 57 | 58 | if (callIntent.contacts[0]) { 59 | NSString *handle = callIntent.contacts[0].personHandle.value; 60 | if ([handle length] > 0) { 61 | // Start a new call with CallKit 62 | [self makeCallWithHandle:handle]; 63 | } 64 | } 65 | 66 | return YES; 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /Docs/managing-audio-interruptions.md: -------------------------------------------------------------------------------- 1 | ## Managing Audio Interruptions 2 | 3 | Different versions of iOS deal with **AVAudioSession** interruptions sightly differently. This section documents how the Programmable Voice iOS SDK manages audio interruptions and resumes call audio after the interruption ends. There are currently some cases that the SDK cannot resume call audio automatically because iOS does not provide the necessary notifications to indicate that the interruption has ended. 4 | 5 | ### How Programmable Voice iOS SDK handles audio interruption 6 | - The `TVOCall` object registers itself as an observer of `AVAudioSessionInterruptionNotification` when it's created. 7 | - When the notification is fired and the interruption type is `AVAudioSessionInterruptionTypeBegan`, the `TVOCall` object automatically disables both the local and remote audio tracks. 8 | - When the SDK receives the notification with `AVAudioSessionInterruptionTypeEnded`, it re-enables the local and remote audio tracks and resumes the audio of active Calls. 9 | - We have noticed that on iOS 8 and 9, the interruption notification with `AVAudioSessionInterruptionTypeEnded` is not always fired therefore the SDK is not able to resume call audio automatically. This is a known issue and an alternative way is to use the `UIApplicationDidBecomeActiveNotification` and resume audio when the app is active again after the interruption. 10 | 11 | ### Notifications in different iOS versions 12 | Below is a table listing the system notifications received with different steps to trigger audio interruptions and resume during an active Voice SDK call. (Assume the app is in an active Voice SDK call) 13 | 14 | |Scenario|Notification of interruption-begins|Notification of interruption-ends|Call audio resumes?|Note| 15 | |---|---|---|---|---| 16 | |PSTN Interruption| 17 | |A.
PSTN interruption
Accept the PSTN incoming call
Remote party of PSTN call hangs up|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 18 | |B.
PSTN interruption
Accept the PSTN incoming call
Local party of PSTN call hangs up|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 19 | |C.
PSTN interruption
Reject PSTN|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 20 | |D.
PSTN interruption
Ignore PSTN|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 21 | |E.
PSTN interruption
Remote party of PSTN call hangs up before local party can answer|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 22 | |Other Types of Audio Interruption
(YouTube app as example)| 23 | |F.
Switch to YouTube app and play video
Stop the video
Switch back to Voice app|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:x: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:x: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|Interruption-ended notification is not fired on iOS 9.
Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11.
The `AVAudioSessionInterruptionOptionShouldResume` flag is `false`.| 24 | |G.
Switch to YouTube app and play video
Switch back to Voice app without stopping the video|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:x: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:x: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|Interruption-ended notification is not fired on iOS 9.
Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11.
The `AVAudioSessionInterruptionOptionShouldResume` flag is `false`.| 25 | |H.
Switch to YouTube app and play video
Double-press Home button and terminate YouTube app
Back to Voice app|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|:white_check_mark: iOS 9
:white_check_mark: iOS 10
:white_check_mark: iOS 11|Interruption-ended notification is not fired until the Voice app is back to the active state.
The `AVAudioSessionInterruptionOptionShouldResume` flag is `false`.| 26 | 27 | ### CallKit 28 | On iOS 10 and later, CallKit (if integrated) takes care of the interruption by providing a set of delegate methods so that the application can respond with proper audio device handling and state transitioning in order to ensure call audio works after the interruption has ended. 29 | 30 | #### Notifications & Callbacks during Interruption 31 | By enabling the `supportsHolding` flag of the `CXCallUpdate` object when reporting a call to the CallKit framework, you will see the **"Hold & Accept"** option when there is another PSTN or CallKit-enabled call. By pressing the **"Hold & Accept"** option, a series of things and callbacks will happen: 32 | 33 | 1. The `provider:performSetHeldCallAction:` delegate method is called with `CXSetHeldCallAction.isOnHold = YES`. Put the Voice call on-hold here and fulfill the action. 34 | 2. The `AVAudioSessionInterruptionNotification` notification is fired to indicate the AVAudioSession interruption has started. 35 | 3. CallKit will deactivate the AVAudioSession of your app and fire the `provider:didDeactivateAudioSession:` callback. 36 | 4. When the interrupting call ends, instead of getting the `AVAudioSessionInterruptionNotification` notification, the system will notify that you can resume the call audio that was put on-hold when the interruption began by calling the `provider:performSetHeldCallAction:` method again. **Note** that this callback is not fired if the interrupting call is disconnected by the remote party. 37 | 5. The AVAudioSession of the app will be activated again and you should re-enable the audio device of the SDK `TwilioVoice.audioDevice.enabled = YES` in the `provider:didActivateAudioSession:` method. 38 | 39 | |Scenario|Audio resumes after interrupion?|Note| 40 | |---|---|---| 41 | |A.
Hold & Accept
Hang up PSTN interruption on the local end|:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 42 | |B.
Hold & Accept
Remote party hangs up PSTN interruption|:x: iOS 10
:x: iOS 11|`provider:performSetHeldCallAction:` not called after the interruption ends.| 43 | |C.
Hold & Accept
Switch back to the Voice Call on system UI|:white_check_mark: iOS 10
:white_check_mark: iOS 11| | 44 | |D.
Reject|:white_check_mark: iOS 10
:white_check_mark: iOS 11|No actual audio interruption happened since the interrupting call is not answered| 45 | |E.
Ignore|:white_check_mark: iOS 10
:white_check_mark: iOS 11|No actual audio interruption happened since the interrupting call is not answered| 46 | 47 | In case 2, CallKit does not automatically resume call audio by calling `provider:performSetHeldCallAction:` method, but the system UI will show that the Voice call is still on-hold. You can resume the call using the **"Hold"** button, or use the `CXSetHeldCallAction` to lift the on-hold state programmatically. The app is also responsible of updating UI state to indicate the "hold" state of the call to avoid user confusion. 48 | 49 | ```.objc 50 | // Resume call audio programmatically after interruption 51 | CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:self.call.uuid onHold:holdSwitch.on]; 52 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:setHeldCallAction]; 53 | [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) { 54 | if (error) { 55 | NSLog(@"Failed to submit set-call-held transaction request"); 56 | } else { 57 | NSLog(@"Set-call-held transaction successfully done"); 58 | } 59 | }]; 60 | ``` -------------------------------------------------------------------------------- /Docs/managing-push-credentials.md: -------------------------------------------------------------------------------- 1 | ## Managing Push Credentials 2 | 3 | A push credential is a record for a push notification channel, for iOS this push credential is a push notification channel record for APNS VoIP. Push credentials are managed in the console under [Mobile Push Credentials](https://www.twilio.com/console/voice/sdks/credentials). 4 | 5 | Whenever a registration is performed via `TwilioVoice.registerWithAccessToken:deviceTokenData:completion` in the iOS SDK the `identity` and the `Push Credential SID` provided in the JWT based access token, along with the `device token` are used as a unique address to send APNS VoIP push notifications to this application instance whenever a call is made to reach that `identity`. Using `TwilioVoice.unregisterWithAccessToken:deviceTokenData:completion` removes the association for that `identity`. 6 | 7 | ### Updating a Push Credential 8 | 9 | If you need to change or update your credentials provided by Apple you can do so by selecting the Push Credential in the [console](https://www.twilio.com/console/voice/sdks/credentials) and adding your new `certificate` and `private key` in the text box provided on the Push Credential page shown below: 10 | 11 | 12 | 13 | ### Deleting a Push Credential 14 | 15 | We **do not recommend that you delete a Push Credential** unless the application that it was created for is no longer being used. 16 | 17 | If **your APNS VoIP certificate is expiring soon or has expired you should not delete your Push Credential**, instead you should update the Push Credential by following the `Updating a Push Credential` section. 18 | 19 | When a Push Credential is deleted **any associated registrations made with this Push Credential will be deleted**. Future attempts to reach an `identity` that was registered using the Push Credential SID of this deleted push credential **will fail**. 20 | 21 | If you are certain you want to delete a Push Credential you can click on `Delete this Credential` on the [console](https://www.twilio.com/console/voice/sdks/credentials) page of the selected Push Credential. 22 | 23 | Please ensure that after deleting the Push Credential you remove or replace the Push Credential SID when generating new access tokens. -------------------------------------------------------------------------------- /Docs/migration-guide-2.x-3.x.md: -------------------------------------------------------------------------------- 1 | ## 2.x to 3.x Migration Guide 2 | This section describes API or behavioral changes when upgrading from Voice iOS 2.x to Voice iOS 3.x. Each section provides code snippets to assist in transitioning to the new API. 3 | 4 | 1. [Making a Call](#making-a-call) 5 | 2. [TVOCallInvite Changes](#tvocallinvite-changes) 6 | 3. [Specifying a Media Region](#specifying-a-media-region) 7 | 4. [TVOConnectOptions & TVOAcceptOptions](#tvoconnectoptions-and-tvoacceptoptions) 8 | 5. [Media Establishment & Connectivity](#media-establishment-and-connectivity) 9 | 6. [CallKit](#callkit) 10 | 7. [Microphone Permission](#microphone-permission) 11 | 12 | #### Making a Call 13 | In Voice iOS 3.x, the API to make a call has changed from `[TwilioVoice call:params:delegate:]` to `[TwilioVoice connectWithAccessToken:delegate]` or `[TwilioVoice connectWithOptions:delegate:]`. 14 | 15 | ```.swift 16 | call = TwilioVoice.connect(with: connectOptions, delegate: self) 17 | ``` 18 | 19 | #### TVOCallInvite Changes 20 | In Voice iOS 3.x, the `notificationError:` delegate method is removed from the `TVONotificationDelegate` protocol and the `[TwilioVoice handleNotification:]` method no longer raises errors via this method if an invalid notification is provided, instead a `BOOL` value is returned when `[TwilioVoice handleNotification:]` is called. The returned value is `YES` when the provided data resulted in a `TVOCallInvite` or `TVOCancelledCallInvite` received in the `TVONotificationDelegate` methods. If `NO` is returned it means the data provided was not a Twilio Voice push notification. 21 | 22 | ```.swift 23 | func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) { 24 | if (!TwilioVoice.handleNotification(payload.dictionaryPayload, delegate: self)) { 25 | // The push notification was not a Twilio Voice push notification. 26 | } 27 | } 28 | 29 | // MARK: TVONotificationDelegate 30 | func callInviteReceived(_ callInvite: TVOCallInvite) { 31 | // Show notification to answer or reject call 32 | } 33 | 34 | func cancelledCallInviteReceived(_ cancelledCallInvite: TVOCancelledCallInvite) { 35 | // Hide notification 36 | } 37 | ``` 38 | 39 | The `TVOCallInvite` has an `accept()` and `reject()` method. `TVOCallInviteState` has been removed from the `TVOCallInvite` in favor of distinguishing between call invites and call invite cancellations with discrete stateless objects. While the `TVOCancelledCallInvite` simply provides the `to`, `from`, and `callSid` fields also available in the `TVOCallInvite`. The property `callSid` can be used to associate a `TVOCallInvite` with a `TVOCancelledCallInvite`. 40 | 41 | In Voice iOS 2.x passing a `cancel` notification into `[TwilioVoice handleNotification:delegate:]` would not raise a callback in the following two cases: 42 | - This callee accepted the call 43 | - This callee rejected the call 44 | 45 | However, in Voice iOS 3.x passing a `cancel` notification payload into `[TwilioVoice handleNotification:delegate:]` will always result in a callback. A callback is raised whenever a valid notification is provided to `[TwilioVoice handleNotification:delegate:]`. 46 | 47 | Note that Twilio will send a `cancel` notification to every registered device of the identity that accepts or rejects a call, even the device that accepted or rejected the call. 48 | 49 | #### Specifying a media region 50 | Previously, a media region could be specified via `[TwilioVoice setRegion:]`. Now this configuration can be provided as part of `TVOConnectOptions` or `TVOAcceptOptions` as shown below: 51 | 52 | ```.swift 53 | let connectOptions: TVOConnectOptions = TVOConnectOptions(accessToken: accessToken) { (builder) in 54 | builder.region = region 55 | } 56 | 57 | let acceptOptions: TVOAcceptOptions = TVOAcceptOptions(callInvite: callInvite!) { (builder) in 58 | builder.region = region 59 | } 60 | ``` 61 | 62 | #### TVOConnectOptions & TVOAcceptOptions 63 | To support configurability upon making or accepting a call, new classes have been added. Create a `TVOConnectOptions` object and make configurations via the `TVOConnectOptionsBuilder` in the `block`. Once `TVOConnectOptions` is created it can be provided when connecting a Call as shown below: 64 | 65 | ```.swift 66 | let options: TVOConnectOptions = TVOConnectOptions(accessToken: accessToken) { (builder) in 67 | builder.params = params 68 | } 69 | 70 | call = TwilioVoice.connect(with: options, delegate: self) 71 | ``` 72 | 73 | A `TVOCallInvite` can also be accepted using `TVOAcceptOptions` as shown below: 74 | 75 | ```.swift 76 | let options: TVOAcceptOptions = TVOAcceptOptions(callInvite: callInvite!) { (builder) in 77 | builder.uuid = callInvite.uuid 78 | } 79 | 80 | call = callInvite.accept(with: options, delegate: self) 81 | ``` 82 | 83 | #### Media Establishment & Connectivity 84 | The Voice iOS 3.x SDK uses WebRTC. The exchange of real-time media requires the use of Interactive Connectivity Establishment(ICE) to establish a media connection between the client and the media server. In some network environments where network access is restricted it may be necessary to provide ICE servers to establish a media connection. We reccomend using the [Network Traversal Service (NTS)](https://www.twilio.com/stun-turn) to obtain ICE servers. ICE servers can be provided when making or accepting a call by passing them into `TVOConnectOptions` or `TVOAcceptOptions` in the following way: 85 | 86 | ```.swift 87 | var iceServers: Array = Array() 88 | let iceServer1: TVOIceServer = TVOIceServer(urlString: "stun:global.stun.twilio.com:3478?transport=udp", 89 | username: "", 90 | password: "") 91 | iceServers.append(iceServer1) 92 | 93 | let iceServer2: TVOIceServer = TVOIceServer(urlString: "turn:global.turn.twilio.com:3478?transport=udp", 94 | username: "TURN_USERNAME", 95 | password: "TURN_PASSWORD") 96 | iceServers.append(iceServer2) 97 | 98 | let iceOptions: TVOIceOptions = TVOIceOptions { (builder) in 99 | builder.servers = iceServers 100 | } 101 | 102 | // Specify ICE options in the builder 103 | let connectOptions: TVOConnectOptions = TVOConnectOptions(accessToken: accessToken) { (builder) in 104 | builder.iceOptions = iceOptions 105 | } 106 | 107 | let acceptOptions: TVOAcceptOptions = TVOAcceptOptions(callInvite: callInvite!) { (builder) in 108 | builder.iceOptions = iceOptions 109 | } 110 | ``` 111 | 112 | #### CallKit 113 | The Voice iOS 3.x SDK deprecates the `CallKitIntegration` category from `TwilioVoice` in favor of a new property called `TVODefaultAudioDevice.enabled`. This property provides developers with a mechanism to enable or disable the activation of the audio device prior to connecting to a Call or to stop or start the audio device while you are already connected to a Call. A Call can now be connected without activating the audio device by setting `TVODefaultAudioDevice.enabled` to `NO` and can be enabled during the lifecycle of the Call by setting `TVODefaultAudioDevice.enabled` to `YES`. The default value is `YES`. This API change was made to ensure full compatibility with CallKit as well as supporting other use cases where developers may need to disable the audio device during a call. 114 | 115 | An example of managing the `TVODefaultAudioDevice` while connecting a CallKit Call: 116 | 117 | ```.swift 118 | var audioDevice: TVODefaultAudioDevice = TVODefaultAudioDevice() 119 | 120 | func provider(_ provider: CXProvider, perform action: CXStartCallAction) { 121 | audioDevice.isEnabled = false 122 | audioDevice.block(); 123 | 124 | self.performVoiceCall(uuid: action.callUUID, client: "") { (success) in 125 | if (success) { 126 | provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date()) 127 | action.fulfill() 128 | } else { 129 | action.fail() 130 | } 131 | } 132 | } 133 | 134 | func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { 135 | audioDevice.isEnabled = true 136 | } 137 | 138 | func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { 139 | audioDevice.isEnabled = false 140 | audioDevice.block(); 141 | 142 | self.performAnswerVoiceCall(uuid: action.callUUID) { (success) in 143 | if (success) { 144 | action.fulfill() 145 | } else { 146 | action.fail() 147 | } 148 | } 149 | 150 | action.fulfill() 151 | } 152 | 153 | func provider(_ provider: CXProvider, perform action: CXEndCallAction) { 154 | // Disconnect or reject the call 155 | 156 | audioDevice.isEnabled = true 157 | action.fulfill() 158 | } 159 | ``` 160 | 161 | See [CallKit Example](https://github.com/twilio/voice-quickstart-swift/blob/3.x/SwiftVoiceQuickstart/ViewController.swift) for the complete implementation. 162 | 163 | #### Microphone Permission 164 | Unlike Voice iOS 2.x SDKs where microphone permission is not optional in Voice 3.x SDKs, the call will connect even when the microphone permission is denied or disabled by the user, and the SDK will play the remote audio. To ensure the microphone permission is enabled prior to making or accepting a call you can add the following to request the permission beforehand: 165 | 166 | ```.swift 167 | func makeCall() { 168 | // User's microphone option 169 | let microphoneEnabled: Bool = true 170 | 171 | if (microphoneEnabled) { 172 | self.checkRecordPermission { (permissionGranted) in 173 | if (!permissionGranted) { 174 | // The user might want to revisit the Privacy settings. 175 | } else { 176 | // Permission granted. Continue to make call. 177 | } 178 | } 179 | } else { 180 | // Continue to make call without microphone. 181 | } 182 | } 183 | 184 | func checkRecordPermission(completion: @escaping (_ permissionGranted: Bool) -> Void) { 185 | let permissionStatus: AVAudioSessionRecordPermission = AVAudioSession.sharedInstance().recordPermission() 186 | 187 | switch permissionStatus { 188 | case AVAudioSessionRecordPermission.granted: 189 | // Record permission already granted. 190 | completion(true) 191 | break 192 | case AVAudioSessionRecordPermission.denied: 193 | // Record permission denied. 194 | completion(false) 195 | break 196 | case AVAudioSessionRecordPermission.undetermined: 197 | // Requesting record permission. 198 | // Optional: pop up app dialog to let the users know if they want to request. 199 | AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in 200 | completion(granted) 201 | }) 202 | break 203 | default: 204 | completion(false) 205 | break 206 | } 207 | } 208 | ``` 209 | 210 | -------------------------------------------------------------------------------- /Docs/migration-guide-3.x-4.x.md: -------------------------------------------------------------------------------- 1 | ## 3.x to 4.x Migration Guide 2 | 3 | Voice iOS SDK 4.0 introduced a new call state: `TVOCallStateReconnecting`. You will need to update any logic you have implemented that relies on the call state. The simplest approach is to treat a `TVOCallStateReconnecting` just like a `TVOCallStateConnected` and keep the current behavior. 4 | 5 | For more advanced behaviour, you can make use of TVOCallDelegate’s new protocol methods `call:isReconnectingWithError:` and `callDidReconnect:` to update the UI for example and indicate the ongoing disruption. 6 | 7 | For example: 8 | 9 | ``` 10 | func call(_ call: TVOCall, isReconnectingWithError error: Error) { 11 | NSLog(@"Call is reconnecting"); 12 | 13 | // Update UI 14 | // Check the error: It could be either 15 | // TVOErrorSignalingConnectionDisconnectedError (53001) or 16 | // TVOErrorMediaConnectionError (53405). 17 | } 18 | 19 | func callDidReconnect(_ call: TVOCall) { 20 | NSLog(@"Call reconnected"); 21 | 22 | // Update UI 23 | } 24 | ``` -------------------------------------------------------------------------------- /Docs/migration-guide-5.x-6.x.md: -------------------------------------------------------------------------------- 1 | ## 5.x to 6.x Migration Guide 2 | 3 | - The Voice SDK has been updated for better Swift interoperability. The `TVO` class prefix has been removed from all Twilio Voice types. 4 | 5 | ```swift 6 | let connectOptions = ConnectOptions(accessToken: accessToken) { builder in 7 | // build connect options using builder 8 | } 9 | let call = TwilioVoice.connect(options: connectOptions, delegate: self) 10 | ``` 11 | 12 | - This release has improved API for CallKit integration. In order to use CallKit with SDK, you must set `ConnectOptions.uuid` or `AcceptOptions.uuid` while making or answering a Call. When `ConnectOptions.uuid` or `AcceptOptions.uuid` is set, it is your responsibility to enable and disable the audio device. You should enable the audio device in `[CXProviderDelegate provider:didActivateAudioSession:]`, and disable the audio device in `[CXProviderDelegate provider:didDeactivateAudioSession:]`. 13 | 14 | Passing a uuid to make a Call with CallKit code snippets - 15 | 16 | ```swift 17 | let uuid = UUID() 18 | 19 | let connectOptions = ConnectOptions(accessToken: accessToken) { builder in 20 | builder.uuid = uuid 21 | } 22 | 23 | let call = TwilioVoice.connect(options: connectOptions, delegate: self) 24 | ``` 25 | 26 | 27 | Passing a uuid to answer an incoming Call with CallKit code snippets - 28 | 29 | ```swift 30 | let acceptOptions = AcceptOptions(callInvite: callInvite) { builder in 31 | builder.uuid = callInvite.uuid 32 | } 33 | 34 | let call = callInvite.accept(options: acceptOptions, delegate: self) 35 | ``` 36 | 37 | `ProviderDelegate` implementation code snippets - 38 | 39 | ```swift 40 | func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { 41 | audioDevice.isEnabled = true 42 | } 43 | 44 | func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { 45 | audioDevice.isEnabled = false 46 | } 47 | 48 | func providerDidReset(_ provider: CXProvider) { 49 | audioDevice.isEnabled = false 50 | } 51 | ``` 52 | 53 | Please note, if you are not using CallKit in your app, you must not set `ConnectOptions.uuid` or `AcceptOptions.uuid` while making or answering a call. The Voice SDK will enable the audio device for you when the `uuid` is `nil`. 54 | 55 | - The `[TwilioVoice registerWithAccessToken:deviceTokenData:completion:]` and the `[TwilioVoice unregisterWithAccessToken:deviceTokenData:completion:]` have been renamed to replace the `[TwilioVoice registerWithAccessToken:deviceToken:completion:]` and the `[TwilioVoice unregisterWithAccessToken:deviceToken:completion:]` methods and now take the `NSData` type device token as parameter. 56 | 57 | - The `uuid` property of `TVOCall` is now optional. 58 | 59 | - In this release, `[TVOCallDelegate callDidConnect:]` is raised when both the ICE connection state is connected and DTLS negotiation has completed. There is no change in behavior however the SDK can detect DTLS failures and raise `kTVOMediaDtlsTransportFailedErrorCode` if they occur. 60 | 61 | | Error Codes | ErrorCode | Error Message | 62 | | ------------| -----------| ------------- | 63 | | 53407 | TVOMediaDtlsTransportFailedErrorCode | Media connection failed due to DTLS handshake failure | -------------------------------------------------------------------------------- /Docs/new-features-3.0.md: -------------------------------------------------------------------------------- 1 | ## 3.0 New Features 2 | Voice iOS 3.0 has a number of new features listed below: 3 | 4 | 1. [WebRTC](#webrtc) 5 | 2. [Custom Parameters](#custom-parameters) 6 | 3. [Call Ringing APIs](#call-ringing-apis) 7 | 4. [Media Stats](#media-stats) 8 | 5. [Audio Device APIs](#audio-device-apis) 9 | * [Default Audio Device](#default-audio-device) 10 | * [Custom Audio Device](#custom-audio-device) 11 | 6. [Preferred Audio Codec](#preferred-audio-codec) 12 | 13 | #### WebRTC 14 | The SDK is built using Chromium WebRTC for iOS. This ensures that over time developers will get the best real-time media streaming capabilities available for iOS. Additionally, upgrades to new versions of Chromium WebRTC will happen without changing the public API whenever possible. 15 | 16 | #### Custom Parameters 17 | Custom Parameters is now supported in `TVOCallInvite`. `TVOCallInvite.customParamaters` returns a `NSDictionary` of custom parameters sent from the caller side to the callee. 18 | 19 | Pass custom parameters in TwiML: 20 | 21 | ```.xml 22 | 23 | 24 | 25 | 26 | bob 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | `callInvite.customParameters` returns a dictionary of key-value pairs passed in the TwiML: 35 | 36 | ```.objc 37 | { 38 | "caller_first_name" = "alice"; 39 | "caller_last_name" = "smith"; 40 | } 41 | ``` 42 | 43 | #### Call Ringing APIs 44 | Ringing is now provided as a call state. The delegate method `callDidStartRinging:` corresponding to this state transition is called once before the `callDidConnect:` method when the callee is being alerted of a Call. The behavior of this callback is determined by the `answerOnBridge` flag provided in the `Dial` verb of your TwiML application associated with this client. If the `answerOnBridge` flag is `false`, which is the default, the `callDidConnect:` callback will be called immediately after `callDidStartRinging:`. If the `answerOnBridge` flag is `true`, this will cause the `callDidConnect:` method being called only after the Call is answered. See [answerOnBridge](https://www.twilio.com/docs/voice/twiml/dial#answeronbridge) for more details on how to use it with the `Dial` TwiML verb. If the TwiML response contains a `Say` verb, then the `callDidConnect:` method will be called immediately after `callDidStartRinging:` is called, irrespective of the value of `answerOnBridge` being set to `true` or `false`. 45 | 46 | These changes are added as follows: 47 | 48 | ```.objc 49 | // TVOCall.h 50 | typedef NS_ENUM(NSUInteger, TVOCallState) { 51 | TVOCallStateConnecting = 0, ///< The Call is connecting. 52 | TVOCallStateRinging, ///< The Call is ringing. 53 | TVOCallStateConnected, ///< The Call is connected. 54 | TVOCallStateDisconnected ///< The Call is disconnected. 55 | }; 56 | 57 | // TVOCallDelegate.h 58 | @protocol TVOCallDelegate 59 | 60 | @optional 61 | - (void)callDidStartRinging:(nonnull TVOCall *)call; 62 | 63 | @end 64 | ``` 65 | 66 | #### Media Stats 67 | In Voice iOS 3.0 SDK you can now access media stats in a Call using the `[TVOCall getStatsWithBlock:]` method. 68 | 69 | ```.swift 70 | call.getStatsWith { (statsReports) in 71 | for report: TVOStatsReport in statsReports { 72 | let localAudioTracks: Array = report.localAudioTrackStats 73 | let localAudioTrackStats = localAudioTracks[0] 74 | let remoteAudioTracks: Array = report.remoteAudioTrackStats 75 | let remoteAudioTrackStats = remoteAudioTracks[0] 76 | 77 | print("Local Audio Track - audio level: \(localAudioTrackStats.audioLevel), packets sent: \(localAudioTrackStats.packetsSent)") 78 | print("Remote Audio Track - audio level: \(remoteAudioTrackStats.audioLevel), packets received: \(remoteAudioTrackStats.packetsReceived)") 79 | } 80 | } 81 | ``` 82 | 83 | ### Audio Device APIs 84 | 85 | #### Default Audio Device 86 | In Voice iOS 3.0 SDK, `TVODefaultAudioDevice` is used as the default device for rendering and capturing audio. 87 | An example of using `TVODefaultAudioDevice` to change the audio route from receiver to the speaker in a live call: 88 | 89 | ```.swift 90 | let audioDevice: TVODefaultAudioDevice = TwilioVoice.audioDevice as! TVODefaultAudioDevice 91 | audioDevice.block = { 92 | // We will execute `kDefaultAVAudioSessionConfigurationBlock` first. 93 | kDefaultAVAudioSessionConfigurationBlock() 94 | 95 | let session: AVAudioSession = AVAudioSession.sharedInstance() 96 | do { 97 | try session.overrideOutputAudioPort(.speaker) 98 | } catch _ { 99 | // Failed to change audio route from receiver to the speaker 100 | } 101 | } 102 | 103 | audioDevice.block(); 104 | ``` 105 | 106 | #### Custom Audio Device 107 | The `TVOAudioDevice` protocol gives you the ability to replace `TVODefaultAudioDevice`. By implementing the `TVOAudioDevice` protocol, you can write your own audio capturer to feed audio samples to the Voice SDK and an audio renderer to receive the remote audio samples. For example, you could integrate with `ReplayKit2` and capture application audio for broadcast or play music using `AVAssetReader`. 108 | 109 | Connecting to a Call using the `AVAudioSessionCategoryPlayback` category: 110 | 111 | ```.swift 112 | let audioDevice: TVOAudioDevice = TVODefaultAudioDevice { (builder) in 113 | // Execute the `kDefaultAVAudioSessionConfigurationBlock` first. 114 | kDefaultAVAudioSessionConfigurationBlock(); 115 | 116 | let session: AVAudioSession = AVAudioSession.sharedInstance() 117 | do { 118 | try session.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeVoiceChat, options: .allowBluetooth) 119 | } catch _ { 120 | // Failed to set AVAudioSession category 121 | } 122 | } 123 | 124 | TwilioVoice.audioDevice = audioDevice 125 | call = TwilioVoice.connect(with: connectOptions, delegate: self) 126 | ``` 127 | 128 | ### Preferred Audio Codec 129 | You can provide your preferred audio codecs in the `TVOConnectOptions` and the `TVOAcceptOptions`. Opus is the default codec used by the mobile infrastructure. To use PCMU as the negotiated audio codec instead of Opus you can add it as the first codec in the `preferredAudioCodecs` list. 130 | 131 | ```.swift 132 | let connectOptions: TVOConnectOptions = TVOConnectOptions(accessToken: accessToken) { (builder) in 133 | builder.preferredAudioCodecs = [ TVOPcmuCodec(), TVOOpusCodec() ] 134 | } 135 | 136 | call = TwilioVoice.connect(with: connectOptions, delegate: self) 137 | ``` 138 | 139 | -------------------------------------------------------------------------------- /Docs/new-features-4.0.md: -------------------------------------------------------------------------------- 1 | ## 4.0 New Features 2 | 3 | #### Reconnecting State and Callbacks 4 | 5 | `TVOCallStateReconnecting` is provided as a new Call state. A callback `call:isReconnectingWithError:` corresponding to this state transition is triggered when a network change is detected when Call is already in the `TVOCallStateConnected` state. If the Call is in `TVOCallStateConnecting`or in `TVOCallStateRinging` state when network change happened, the SDK will receive the `call:didFailToConnectWithError:` callback with the `TVOErrorConnectionError` error (31005). If a Call is reconnected after reconnection attempts, the application will receive the `callDidReconnect:` callback and the Call state transitions to `TVOCallStateConnected`. 6 | 7 | Updates: 8 | 9 | ``` 10 | typedef NS_ENUM(NSUInteger, TVOCallState) { 11 | TVOCallStateConnecting = 0, ///< The Call is connecting. 12 | TVOCallStateRinging, ///< The Call is ringing. 13 | TVOCallStateConnected, ///< The Call is connected. 14 | TVOCallStateReconnecting, ///< The Call is reconnecting. 15 | TVOCallStateDisconnected ///< The Call is disconnected. 16 | }; 17 | ``` 18 | 19 | ``` 20 | @protocol TVOCallDelegate 21 | 22 | @optional 23 | - (void)callDidStartRinging:(nonnull TVOCall *)call; 24 | - (void)call:(nonnull TVOCall *)call isReconnectingWithError:(nonnull NSError *)error; 25 | 26 | @end 27 | ``` -------------------------------------------------------------------------------- /Docs/push-credentials-via-conversations-api.md: -------------------------------------------------------------------------------- 1 | ## Create Push Credentials via the Conversations Credential Resource API 2 | 3 | Voice SDK users can manage their Push Credentials in the developer console (**Console > Account > Keys & Credentials > Credentials**). Currently the Push Credential management page only supports the default region (US1). To create or update Push Credentials for other regional (i.e. Australia) usage, developers can use the Conversations public API to manage their Push Credentials. Follow the instructions of the [Credential Resource API](https://www.twilio.com/docs/conversations/api/credential-resource) and replace the endpoint with the regional endpoint, for example https://conversations.dublin.ie1.twilio.com for the Ireland region. 4 | 5 | You will also need: 6 | - Apple VoIP Service certificate: follow the [instructions](https://github.com/twilio/voice-quickstart-ios#6-create-a-push-credential-with-your-voip-service-certificate) to get the certificate and key. 7 | - Twilio account credentials: find your API auth token for the specific region in the developer console. Go to **Console > Account > Keys & Credentials > API keys & tokens** and select the region in the dropdown menu. 8 | 9 | Example of creating an `IE1` Push Credential for APN: 10 | 11 | ``` 12 | curl -X POST https://conversations.dublin.ie1.twilio.com/v1/Credentials \ 13 | --data-urlencode "Type=apn" \ 14 | --data-urlencode "Certificate=$(cat $PATH_OF_CERT_PEM)" \ 15 | --data-urlencode "PrivateKey=$(cat $PATH_OF_KEY_PEM)" \ 16 | --data-urlencode "Sandbox=true" \ 17 | -u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN 18 | ``` 19 | 20 | To update a Push Credential (CR****) in `IE1`: 21 | 22 | ``` 23 | curl -X POST https://conversations.dublin.ie1.twilio.com/v1/Credentials/CR**** \ 24 | --data-urlencode "Type=apn" \ 25 | --data-urlencode "Certificate=$(cat $PATH_OF_CERT_PEM)" \ 26 | --data-urlencode "PrivateKey=$(cat $PATH_OF_KEY_PEM)" \ 27 | --data-urlencode "Sandbox=true" \ 28 | -u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN 29 | ``` 30 | 31 | # Note that currently the Conversations Credential Resource API is not available for the Australia region. Please reach out to the [Twilio Help Center](https://help.twilio.com/) if you need help managing your Push Credentials for the Australia region. 32 | -------------------------------------------------------------------------------- /Docs/verified-caller-number.md: -------------------------------------------------------------------------------- 1 | # Display verified caller number using TVOCallerInfo 2 | 3 | The [TVOCallerInfo](https://twilio.github.io/twilio-voice-ios/docs/latest/Classes/TVOCallerInfo.html) provides information about the caller. The `verified` property represents whether or not the caller's phone number has been verified by Twilio using **SHAKEN/STIR** validation. 4 | 5 | Use the information and display in the CallKit incoming call UI upon receiving the call invite 6 | 7 | ```.swift 8 | func callInviteReceived(_ callInvite: TVOCallInvite) { 9 | var callKitProviderName = "Voice Quickstart\n" 10 | let callerInfo: TVOCallerInfo = callInvite.callerInfo 11 | if let verified: NSNumber = callerInfo.verified { 12 | if verified.boolValue { 13 | callKitProviderName = "✅ Caller Verified\n" 14 | } 15 | } 16 | 17 | let configuration = CXProviderConfiguration(localizedName: callKitProviderName) 18 | configuration.maximumCallGroups = 1 19 | configuration.maximumCallsPerCallGroup = 1 20 | 21 | callKitProvider = CXProvider(configuration: configuration) 22 | callKitProvider.setDelegate(self, queue: nil) 23 | 24 | // Report to CallKit 25 | let callHandle = CXHandle(type: .generic, value: callInvite.from) 26 | let callUpdate = CXCallUpdate() 27 | callUpdate.remoteHandle = callHandle 28 | 29 | callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in 30 | if let error = error { 31 | NSLog("Failed to report incoming call successfully: \(error.localizedDescription).") 32 | } else { 33 | NSLog("Incoming call successfully reported.") 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | * Please note that the designated initializer `[CXProviderConfiguration initWithLocalizedName:]` has been deprecated on iOS 14. 40 | 41 | If the number of the caller is verified 42 | 43 | 44 | 45 | If the number is not validated at **A** level or there is no verification information 46 | 47 | 48 | -------------------------------------------------------------------------------- /Images/audio-device-example-call-bob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/audio-device-example-call-bob.png -------------------------------------------------------------------------------- /Images/audio-device-example-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/audio-device-example-play.png -------------------------------------------------------------------------------- /Images/audio-engine-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/audio-engine-example.jpg -------------------------------------------------------------------------------- /Images/client-to-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/client-to-client.png -------------------------------------------------------------------------------- /Images/client-to-pstn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/client-to-pstn.png -------------------------------------------------------------------------------- /Images/hang-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/hang-up.png -------------------------------------------------------------------------------- /Images/incoming-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/incoming-call.png -------------------------------------------------------------------------------- /Images/keychain-api-key-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/keychain-api-key-secret.png -------------------------------------------------------------------------------- /Images/unverified-caller-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/unverified-caller-number.png -------------------------------------------------------------------------------- /Images/update_push_credential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/update_push_credential.png -------------------------------------------------------------------------------- /Images/verified-caller-number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/Images/verified-caller-number.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2016-2017 Twilio, Inc. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ObjcVoiceQuickstart.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart.xcodeproj/xcshareddata/xcschemes/ObjCVoiceQuickstart.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Twilio Voice with Quickstart - Objective-C 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @import PushKit; 11 | 12 | @protocol PushKitEventDelegate 13 | 14 | - (void)credentialsUpdated:(PKPushCredentials *)credentials; 15 | - (void)credentialsInvalidated; 16 | - (void)incomingPushReceived:(PKPushPayload *)payload withCompletionHandler:(void (^)(void))completion; 17 | 18 | @end 19 | 20 | @interface AppDelegate : UIResponder 21 | 22 | @property (strong, nonatomic) UIWindow *window; 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Twilio Voice with Quickstart - Objective-C 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | #import "ViewController.h" 10 | 11 | @import TwilioVoice; 12 | 13 | @interface AppDelegate () 14 | 15 | @property (nonatomic, weak) id pushKitEventDelegate; 16 | @property (nonatomic, strong) PKPushRegistry *voipRegistry; 17 | 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 23 | NSLog(@"Twilio Voice Version: %@", [TwilioVoiceSDK sdkVersion]); 24 | 25 | ViewController* viewController = (ViewController*)self.window.rootViewController; 26 | self.pushKitEventDelegate = viewController; 27 | /* 28 | * Your app must initialize PKPushRegistry with PushKit push type VoIP at the launch time. As mentioned in the 29 | * [PushKit guidelines](https://developer.apple.com/documentation/pushkit/supporting_pushkit_notifications_in_your_app), 30 | * the system can't deliver push notifications to your app until you create a PKPushRegistry object for 31 | * VoIP push type and set the delegate. If your app delays the initialization of PKPushRegistry, your app may receive outdated 32 | * PushKit push notifications, and if your app decides not to report the received outdated push notifications to CallKit, iOS may 33 | * terminate your app. 34 | */ 35 | [self initializePushKit]; 36 | 37 | return YES; 38 | } 39 | 40 | - (void)initializePushKit { 41 | self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; 42 | self.voipRegistry.delegate = self; 43 | self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; 44 | } 45 | 46 | - (void)applicationWillResignActive:(UIApplication *)application { 47 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 48 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 49 | } 50 | 51 | - (void)applicationDidEnterBackground:(UIApplication *)application { 52 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 53 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 54 | } 55 | 56 | - (void)applicationWillEnterForeground:(UIApplication *)application { 57 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 58 | } 59 | 60 | - (void)applicationDidBecomeActive:(UIApplication *)application { 61 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 62 | } 63 | 64 | - (void)applicationWillTerminate:(UIApplication *)application { 65 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 66 | } 67 | 68 | #pragma mark - PKPushRegistryDelegate 69 | - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { 70 | NSLog(@"pushRegistry:didUpdatePushCredentials:forType:"); 71 | 72 | if ([type isEqualToString:PKPushTypeVoIP]) { 73 | if (self.pushKitEventDelegate && [self.pushKitEventDelegate respondsToSelector:@selector(credentialsUpdated:)]) { 74 | [self.pushKitEventDelegate credentialsUpdated:credentials]; 75 | } 76 | } 77 | } 78 | 79 | - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type { 80 | NSLog(@"pushRegistry:didInvalidatePushTokenForType:"); 81 | 82 | if ([type isEqualToString:PKPushTypeVoIP]) { 83 | if (self.pushKitEventDelegate && [self.pushKitEventDelegate respondsToSelector:@selector(credentialsInvalidated)]) { 84 | [self.pushKitEventDelegate credentialsInvalidated]; 85 | } 86 | } 87 | } 88 | 89 | - (void)pushRegistry:(PKPushRegistry *)registry 90 | didReceiveIncomingPushWithPayload:(PKPushPayload *)payload 91 | forType:(NSString *)type { 92 | NSLog(@"pushRegistry:didReceiveIncomingPushWithPayload:forType:"); 93 | 94 | if (self.pushKitEventDelegate && 95 | [self.pushKitEventDelegate respondsToSelector:@selector(incomingPushReceived:withCompletionHandler:)]) { 96 | [self.pushKitEventDelegate incomingPushReceived:payload withCompletionHandler:nil]; 97 | } 98 | } 99 | 100 | /** 101 | * This delegate method is available on iOS 11 and above. Call the completion handler once the 102 | * notification payload is passed to the `TwilioVoiceSDK.handleNotification()` method. 103 | */ 104 | - (void)pushRegistry:(PKPushRegistry *)registry 105 | didReceiveIncomingPushWithPayload:(PKPushPayload *)payload 106 | forType:(PKPushType)type 107 | withCompletionHandler:(void (^)(void))completion { 108 | NSLog(@"pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:"); 109 | 110 | if (self.pushKitEventDelegate && 111 | [self.pushKitEventDelegate respondsToSelector:@selector(incomingPushReceived:withCompletionHandler:)]) { 112 | [self.pushKitEventDelegate incomingPushReceived:payload withCompletionHandler:completion]; 113 | } 114 | 115 | if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion >= 13) { 116 | /* 117 | * The Voice SDK processes the call notification and returns the call invite synchronously. Report the incoming call to 118 | * CallKit and fulfill the completion before exiting this callback method. 119 | */ 120 | completion(); 121 | } 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@2x.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-Small@3x.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-40@3x.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "57x57", 45 | "idiom" : "iphone", 46 | "filename" : "Icon.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "57x57", 51 | "idiom" : "iphone", 52 | "filename" : "Icon@2x.png", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "60x60", 57 | "idiom" : "iphone", 58 | "filename" : "Icon-60@2x.png", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "60x60", 63 | "idiom" : "iphone", 64 | "filename" : "Icon-60@3x.png", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "20x20", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "20x20", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "size" : "29x29", 79 | "idiom" : "ipad", 80 | "filename" : "Icon-Small.png", 81 | "scale" : "1x" 82 | }, 83 | { 84 | "size" : "29x29", 85 | "idiom" : "ipad", 86 | "filename" : "Icon-Small@2x.png", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "size" : "40x40", 91 | "idiom" : "ipad", 92 | "filename" : "Icon-40.png", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "size" : "40x40", 97 | "idiom" : "ipad", 98 | "filename" : "Icon-40@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "size" : "50x50", 103 | "idiom" : "ipad", 104 | "filename" : "Icon-Small-50.png", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "size" : "50x50", 109 | "idiom" : "ipad", 110 | "filename" : "Icon-Small-50@2x.png", 111 | "scale" : "2x" 112 | }, 113 | { 114 | "size" : "72x72", 115 | "idiom" : "ipad", 116 | "filename" : "Icon-72.png", 117 | "scale" : "1x" 118 | }, 119 | { 120 | "size" : "72x72", 121 | "idiom" : "ipad", 122 | "filename" : "Icon-72@2x.png", 123 | "scale" : "2x" 124 | }, 125 | { 126 | "size" : "76x76", 127 | "idiom" : "ipad", 128 | "filename" : "Icon-76.png", 129 | "scale" : "1x" 130 | }, 131 | { 132 | "size" : "76x76", 133 | "idiom" : "ipad", 134 | "filename" : "Icon-76@2x.png", 135 | "scale" : "2x" 136 | }, 137 | { 138 | "size" : "83.5x83.5", 139 | "idiom" : "ipad", 140 | "filename" : "Icon-83.5@2x.png", 141 | "scale" : "2x" 142 | }, 143 | { 144 | "idiom" : "ios-marketing", 145 | "size" : "1024x1024", 146 | "scale" : "1x" 147 | } 148 | ], 149 | "info" : { 150 | "version" : 1, 151 | "author" : "xcode" 152 | } 153 | } -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSMicrophoneUsageDescription 24 | $(EXECUTABLE_NAME) would like to access your microphone 25 | UIBackgroundModes 26 | 27 | audio 28 | voip 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/ObjCVoiceQuickstart.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/TwilioLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ObjcVoiceQuickstart/TwilioLogo.png -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Twilio Voice with Quickstart - Objective-C 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /ObjcVoiceQuickstart/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Twilio Voice with Quickstart - Objective-C 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | @autoreleasepool { 13 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Twilio Voice Quickstart for iOS 2 | 3 | > Please see our [iOS 13 Migration Guide](https://github.com/twilio/twilio-voice-ios/blob/Releases/iOS-13-Migration-Guide.md) for the latest information on iOS 13. 4 | 5 | ## Get started with Voice on iOS 6 | * [Quickstart](#quickstart) - Run the swift quickstart app 7 | * [Examples](#examples) - Sample applications 8 | 9 | ## References 10 | * [Access Tokens](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/access-tokens.md) - Using access tokens 11 | * [Managing Audio Interruptions](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/managing-audio-interruptions.md) - Managing audio interruptions 12 | * [Managing Push Credentials](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/managing-push-credentials.md) - Managing push credentials 13 | * [Managing Regional Push Credentials using Conversations Credential Resource API](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/push-credentials-via-conversations-api.md) - Create or update push credentials for regional usage 14 | * [More Documentation](#more-documentation) - More documentation related to the Voice iOS SDK 15 | * [Issues and Support](#issues-and-support) - Filing issues and general support 16 | 17 | ## Voice iOS SDK Versions 18 | * [Migration Guide from 5.x to 6.x](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/migration-guide-5.x-6.x.md) - Migrating from 5.x to 6.x 19 | * [Migration Guide from 4.x to 5.x](https://github.com/twilio/twilio-voice-ios/blob/Releases/iOS-13-Migration-Guide.md) - Migrating from 4.x to 5.x 20 | * [4.0 New Features](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/new-features-4.0.md) - New features in 4.0 21 | * [Migration Guide from 3.x to 4.x](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/migration-guide-3.x-4.x.md) - Migrating from 3.x to 4.x 22 | * [3.0 New Features](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/new-features-3.0.md) - New features in 3.0 23 | * [Migration Guide from 2.x to 3.x](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/migration-guide-2.x-3.x.md) - Migrating from 2.x to 3.x 24 | 25 | ## Quickstart 26 | To get started with the quickstart application follow these steps. Steps 1-5 will enable the application to make a call. The remaining steps 6-9 will enable the application to receive incoming calls in the form of push notifications using Apple’s VoIP Service. 27 | 28 | 1. [Install the TwilioVoice framework](#bullet1) 29 | 2. [Use Twilio CLI to deploy access token and TwiML application to Twilio Serverless](#bullet2) 30 | 3. [Create a TwiML application for the access token](#bullet3) 31 | 4. [Generate an access token for the quickstart](#bullet4) 32 | 5. [Run the Swift Quickstart app](#bullet5) 33 | 6. [Create a Push Credential with your VoIP Service Certificate](#bullet6) 34 | 7. [Receive an incoming call](#bullet7) 35 | 8. [Make client to client call](#bullet8) 36 | 9. [Make client to PSTN call](#bullet9) 37 | 38 | ### 1. Install the TwilioVoice framework 39 | 40 | **Swift Package Manager** 41 | 42 | Twilio Voice is now distributed via Swift Package Manager. To consume Twilio Voice using Swift Package Manager, add the `https://github.com/twilio/twilio-voice-ios` repository as a `Swift Pacakge`. 43 | 44 | ### 2. Use Twilio CLI to deploy access token and TwiML application to Twilio Serverless 45 | 46 | You must have the following installed: 47 | 48 | * [Node.js v10+](https://nodejs.org/en/download/) 49 | * NPM v6+ (comes installed with newer Node versions) 50 | 51 | Run `npm install` to install all dependencies from NPM. 52 | 53 | Install [twilio-cli](https://www.twilio.com/docs/twilio-cli/quickstart) with: 54 | 55 | $ npm install -g twilio-cli 56 | 57 | Login to the Twilio CLI. You will be prompted for your Account SID and Auth Token, both of which you can find on the dashboard of your [Twilio console](https://twilio.com/console). 58 | 59 | $ twilio login 60 | 61 | Once successfully logged in, an API Key, a secret get created and stored in your keychain as the `twilio-cli` password in `SKxxxx|secret` format. Please make a note of these values to use them in the `Server/.env` file. 62 | 63 | 64 | 65 | This app requires the [Serverless plug-in](https://github.com/twilio-labs/plugin-serverless). Install the CLI plugin with: 66 | 67 | $ twilio plugins:install @twilio-labs/plugin-serverless 68 | 69 | Before deploying, create a `Server/.env` by copying from `Server/.env.example` 70 | 71 | $ cp Server/.env.example Server/.env 72 | 73 | Update `Server/.env` with your Account SID, auth token, API Key and secret 74 | 75 | ACCOUNT_SID=ACxxxx 76 | AUTH_TOKEN=xxxxxx 77 | API_KEY_SID=SKxxxx 78 | API_SECRET=xxxxxx 79 | APP_SID=APxxxx (available in step 3) 80 | PUSH_CREDENTIAL_SID=CRxxxx (available in step 6) 81 | 82 | The `Server` folder contains a basic server component which can be used to vend access tokens or generate TwiML response for making call to a number or another client. The app is deployed to Twilio Serverless with the `serverless` plug-in: 83 | 84 | $ cd Server 85 | $ twilio serverless:deploy 86 | 87 | The server component that's baked into this quickstart is in Node.js. If you’d like to roll your own or better understand the Twilio Voice server side implementations, please see the list of starter projects in the following supported languages below: 88 | 89 | * [voice-quickstart-server-java](https://github.com/twilio/voice-quickstart-server-java) 90 | * [voice-quickstart-server-node](https://github.com/twilio/voice-quickstart-server-node) 91 | * [voice-quickstart-server-php](https://github.com/twilio/voice-quickstart-server-php) 92 | * [voice-quickstart-server-python](https://github.com/twilio/voice-quickstart-server-python) 93 | 94 | Follow the instructions in the project's README to get the application server up and running locally and accessible via the public Internet. 95 | 96 | ### 3. Create a TwiML application for the Access Token 97 | 98 | Next, we need to create a TwiML application. A TwiML application identifies a public URL for retrieving [TwiML call control instructions](https://www.twilio.com/docs/voice/twiml). When your iOS app makes a call to the Twilio cloud, Twilio will make a webhook request to this URL, your application server will respond with generated TwiML, and Twilio will execute the instructions you’ve provided. 99 | 100 | Use Twilio CLI to create a TwiML app with the `make-call` endpoint you have just deployed (**Note: replace the value of `--voice-url` parameter with your `make-call` endpoint you just deployed to Twilio Serverless**) 101 | 102 | $ twilio api:core:applications:create \ 103 | --friendly-name=my-twiml-app \ 104 | --voice-method=POST \ 105 | --voice-url="https://my-quickstart-dev.twil.io/make-call" 106 | 107 | You should receive an Appliciation SID that looks like this 108 | 109 | APxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 110 | 111 | ### 4. Generate an access token for the quickstart 112 | 113 | Install the `token` plug-in 114 | 115 | $ twilio plugins:install @twilio-labs/plugin-token 116 | 117 | Use the TwiML App SID you just created to generate an access token 118 | 119 | $ twilio token:voice --identity=alice --voice-app-sid=APxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 120 | 121 | Copy the access token string. Your iOS app will use this token to connect to Twilio. 122 | 123 | ### 5. Run the Swift Quickstart app 124 | 125 | Now let’s go back to the `VoiceQuickstart.xcworkspace`. Update the placeholder of `accessToken` with access token string you just copied 126 | 127 | ```swift 128 | import UIKit 129 | import AVFoundation 130 | import PushKit 131 | import CallKit 132 | import TwilioVoice 133 | 134 | let accessToken = "PASTE_YOUR_ACCESS_TOKEN_HERE" 135 | let twimlParamTo = "to" 136 | 137 | let kCachedDeviceToken = "CachedDeviceToken" 138 | 139 | class ViewController: UIViewController { 140 | ... 141 | } 142 | ``` 143 | 144 | Build and run the app. Leave the text field empty and press the call button to start a call. You will hear the congratulatory message. Support for dialing another client or number is described in steps 8 and 9. Tap "Hang Up" to disconnect. 145 | 146 | 147 | 148 | ### 6. Create a Push Credential with your VoIP Service Certificate 149 | 150 | The Programmable Voice SDK uses Apple’s VoIP Services to let your application know when it is receiving an incoming call. If you want your users to receive incoming calls, you’ll need to enable VoIP Services in your application and generate a VoIP Services Certificate. 151 | 152 | Go to [Apple Developer portal](https://developer.apple.com/) and generate a VoIP Service Certificate. 153 | 154 | Once you have generated the VoIP Services Certificate, you will need to provide the certificate and key to Twilio so that Twilio can send push notifications to your app on your behalf. 155 | 156 | Export your VoIP Service Certificate as a `.p12` file from *Keychain Access* and extract the certificate and private key from the `.p12` file using the `openssl` command. 157 | 158 | $ openssl pkcs12 -in PATH_TO_YOUR_P12 -nokeys -out cert.pem -nodes 159 | $ openssl x509 -in cert.pem -out cert.pem 160 | $ openssl pkcs12 -in PATH_TO_YOUR_P12 -nocerts -out key.pem -nodes 161 | $ openssl rsa -in key.pem -out key.pem 162 | 163 | **Note: if you run into the unsupported encryption algorithm (RC2-40-CBC) issue like below, try adding `-legacy` to the `openssl pkcs12` command.** 164 | 165 | ``` 166 | Error outputting keys and certificates 167 | C0EF094DF87F0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties () 168 | ``` 169 | 170 | Use Twilio CLI to create a Push Credential using the cert and key. 171 | 172 | $ twilio api:chat:v2:credentials:create \ 173 | --type=apn \ 174 | --sandbox \ 175 | --friendly-name="voice-push-credential (sandbox)" \ 176 | --certificate="$(cat PATH_TO_CERT_PEM)" \ 177 | --private-key="$(cat PATH_TO_KEY_PEM)" 178 | 179 | This will return a Push Credential SID that looks like this 180 | 181 | CRxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 182 | 183 | The `--sandbox` option tells Twilio to send the notification requests to the sandbox endpoint of Apple's APNS service. Once the app is ready for distribution or store submission, create a separate Push Credential with a new VoIP Service certificate **without** the `--sandbox` option. 184 | 185 | **Note: we strongly recommend using different Twilio accounts (or subaccounts) to separate VoIP push notification requests for development and production apps.** 186 | 187 | Now let's generate another access token and add the Push Credential to the Voice Grant. 188 | 189 | $ twilio token:voice \ 190 | --identity=alice \ 191 | --voice-app-sid=APxxxx \ 192 | --push-credential-sid=CRxxxxs 193 | 194 | ### 7. Receive an incoming call 195 | 196 | You are now ready to receive incoming calls. Update your app with the access token generated from step 6 and rebuild your app. The `TwilioVoiceSDK.register()` method will register your mobile client with the PushKit device token as well as the access token. Once registered, hit your application server's **/place-call** endpoint: `https://my-quickstart-dev.twil.io/place-call?to=alice`. This will trigger a Twilio REST API request that will make an inbound call to the identity registered on your mobile app. Once your app accepts the call, you should hear a congratulatory message. 197 | 198 | Register your mobile client with the PushKit device token: 199 | 200 | ```.swift 201 | TwilioVoiceSDK.register(accessToken: accessToken, deviceToken: cachedDeviceToken) { error in 202 | if let error = error { 203 | NSLog("An error occurred while registering: \(error.localizedDescription)") 204 | } else { 205 | NSLog("Successfully registered for VoIP push notifications.") 206 | } 207 | } 208 | ``` 209 | 210 | Please note that your application must have `voip` enabled in the `UIBackgroundModes` of your app's plist in order to be able to receive push notifications. 211 | 212 | 213 | 214 | ### 8. Make client to client call 215 | 216 | To make client to client calls, you need the application running on two devices. To run the application on an additional device, make sure you use a different identity in your access token when registering the new device. 217 | 218 | Use the text field to specify the identity of the call receiver, then tap the "Call" button to make a call. The TwiML parameters used in `TwilioVoice.connect()` method should match the name used in the server. 219 | 220 | 221 | 222 | ### 9. Make client to PSTN call 223 | 224 | To make client to number calls, first get a verified Twilio number to your account via https://www.twilio.com/console/phone-numbers/verified. Update your server code and replace the `callerNumber` variable with the verified number. Restart the server so it uses the new value. 225 | 226 | 227 | 228 | ## Examples 229 | 230 | You will also find additional examples that provide more advanced use cases of the Voice SDK: 231 | 232 | - [AudioDevice](https://github.com/twilio/voice-quickstart-ios/tree/master/AudioDeviceExample) - Provide your own means to playback and record audio using a custom `TVOAudioDevice` and [CoreAudio](https://developer.apple.com/documentation/coreaudio). 233 | - [Making calls from history](https://github.com/twilio/voice-quickstart-ios/blob/master/Docs/call-from-history.md) - Use the `INStartAudioCallIntent` in the user activity delegate method to start a call from the history. 234 | 235 | ## More Documentation 236 | 237 | You can find the API documentation of the Voice SDK: 238 | 239 | * [TwilioVoice SDK API Doc](https://twilio.github.io/twilio-voice-ios/docs/latest/) 240 | 241 | ## Twilio Helper Libraries 242 | 243 | To learn more about how to use TwiML and the Programmable Voice Calls API, check out our TwiML quickstarts: 244 | 245 | * [TwiML Quickstart for Python](https://www.twilio.com/docs/voice/quickstart/python) 246 | * [TwiML Quickstart for Ruby](https://www.twilio.com/docs/voice/quickstart/ruby) 247 | * [TwiML Quickstart for PHP](https://www.twilio.com/docs/voice/quickstart/php) 248 | * [TwiML Quickstart for Java](https://www.twilio.com/docs/voice/quickstart/java) 249 | * [TwiML Quickstart for C#](https://www.twilio.com/docs/voice/quickstart/csharp) 250 | 251 | ## Issues and Support 252 | 253 | Please file any issues you find here on Github: [Voice Swift Quickstart](https://github.com/twilio/voice-quickstart-ios). 254 | Please ensure that you are not sharing any 255 | [Personally Identifiable Information(PII)](https://www.twilio.com/docs/glossary/what-is-personally-identifiable-information-pii) 256 | or sensitive account information (API keys, credentials, etc.) when reporting an issue. 257 | 258 | For general inquiries related to the Voice SDK you can [file a support ticket](https://support.twilio.com/hc/en-us/requests/new). 259 | 260 | ## License 261 | 262 | MIT 263 | -------------------------------------------------------------------------------- /Server/.env.example: -------------------------------------------------------------------------------- 1 | ACCOUNT_SID=ACxxxx 2 | AUTH_TOKEN=xxxxxx 3 | API_KEY_SID=SKxxxx 4 | API_SECRET=xxxxxx 5 | APP_SID=APxxxx 6 | PUSH_CREDENTIAL_SID=CRxxxx 7 | -------------------------------------------------------------------------------- /Server/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | npm-debug.log 4 | node_modules 5 | .twilio-functions 6 | -------------------------------------------------------------------------------- /Server/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/dubnium 2 | -------------------------------------------------------------------------------- /Server/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Twilio Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Server/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello Twilio Serverless! 9 | 10 | 11 |

Hello Twilio Serverless!

12 | 13 |
14 |

15 | Congratulations you just started a new Twilio 16 | Serverless project. 17 |

18 | 19 |

Assets

20 |

21 | Assets are static files, like HTML, CSS, JavaScript, images or audio 22 | files. 23 |

24 | 25 |

26 | This HTML page is an example of a public asset, you can 27 | access this by loading it in the browser. The HTML also refers to 28 | another public asset for CSS styles. 29 |

30 |

31 | You can also have private assets, there is an example 32 | private asset called message.private.js in the 33 | /assets directory. This file cannot be loaded in the 34 | browser, but you can load it as part of a function by finding its path 35 | using Runtime.getAssets() and then requiring the file. 36 | There is an example of this in 37 | /functions/private-message.js. 38 |

39 | 40 |

Functions

41 |

42 | Functions are JavaScript files that will respond to incoming HTTP 43 | requests. There are public and 44 | protected functions. 45 |

46 | 47 |

48 | Public functions respond to all HTTP requests. There is 49 | an example of a public function in 50 | /functions/hello-world.js. 51 |

52 | 53 |

54 | Protected functions will only respond to HTTP requests 55 | with a valid Twilio signature in the header. You can read more about 56 | validating requests from Twilio in the documentation. There is an example of a protected function in 59 | /functions/sms/reply.protected.js 60 |

61 | 62 |

twilio-run

63 | 64 |

65 | Functions and assets are served, deployed and debugged using 66 | twilio-run. You can serve the project locally with the command 69 | npm start which is really running 70 | twilio-run --env under the hood. If you want to see what 71 | else you can do with twilio-run enter 72 | npx twilio-run --help on the command line or check out 73 | the project documentation on GitHub. 76 |

77 |
78 | 79 |
80 |

81 | Made with 💖 by your friends at 82 | Twilio 83 |

84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /Server/assets/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | ::selection { 8 | background: #f22f46; 9 | color: white; 10 | } 11 | 12 | body { 13 | font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 14 | 'Helvetica Neue', Arial, sans-serif; 15 | color: #0d122b; 16 | border-top: 5px solid #f22f46; 17 | } 18 | 19 | header { 20 | padding: 2em; 21 | margin-bottom: 2em; 22 | max-width: 800px; 23 | margin: 0 auto; 24 | } 25 | 26 | header h1 { 27 | padding-bottom: 14px; 28 | border-bottom: 1px solid rgba(148, 151, 155, 0.2); 29 | } 30 | 31 | a { 32 | color: #008cff; 33 | } 34 | 35 | main { 36 | margin: 0 auto 6em; 37 | padding: 0 2em; 38 | max-width: 800px; 39 | } 40 | 41 | main p { 42 | margin-bottom: 2em; 43 | } 44 | 45 | main p code { 46 | font-size: 16px; 47 | font-family: 'Fira Mono', monospace; 48 | color: #f22f46; 49 | background-color: #f9f9f9; 50 | box-shadow: inset 0 0 0 1px #e8e8e8; 51 | font-size: inherit; 52 | line-height: 1.2; 53 | padding: 0.15em 0.4em; 54 | border-radius: 4px; 55 | display: inline-block; 56 | white-space: pre-wrap; 57 | } 58 | 59 | main h2 { 60 | margin-bottom: 1em; 61 | } 62 | 63 | footer { 64 | margin: 0 auto; 65 | max-width: 800px; 66 | text-align: center; 67 | } 68 | 69 | footer p { 70 | border-top: 1px solid rgba(148, 151, 155, 0.2); 71 | padding-top: 2em; 72 | margin: 0 2em; 73 | } 74 | -------------------------------------------------------------------------------- /Server/functions/access-token.js: -------------------------------------------------------------------------------- 1 | exports.handler = function(context, event, callback) { 2 | const AccessToken = require('twilio').jwt.AccessToken; 3 | const VoiceGrant = AccessToken.VoiceGrant; 4 | 5 | const twilioAccountSid = context.ACCOUNT_SID; 6 | const twilioApiKey = context.API_KEY_SID; 7 | const twilioApiSecret = context.API_SECRET; 8 | 9 | const outgoingApplicationSid = context.APP_SID; 10 | const pushCredentialSid = context.PUSH_CREDENTIAL_SID; 11 | const identity = 'user'; 12 | 13 | const voiceGrant = new VoiceGrant({ 14 | outgoingApplicationSid: outgoingApplicationSid, 15 | pushCredentialSid: pushCredentialSid 16 | }); 17 | 18 | const token = new AccessToken(twilioAccountSid, twilioApiKey, twilioApiSecret); 19 | token.addGrant(voiceGrant); 20 | token.identity = identity; 21 | 22 | callback(null, token.toJwt()); 23 | }; -------------------------------------------------------------------------------- /Server/functions/hello-world.js: -------------------------------------------------------------------------------- 1 | exports.handler = function(context, event, callback) { 2 | const twiml = new Twilio.twiml.VoiceResponse(); 3 | twiml.say('Hello World!'); 4 | callback(null, twiml); 5 | }; 6 | -------------------------------------------------------------------------------- /Server/functions/incoming.js: -------------------------------------------------------------------------------- 1 | exports.handler = function(context, event, callback) { 2 | const twiml = new Twilio.twiml.VoiceResponse(); 3 | twiml.say("Congratulations! You have received your first inbound call! Good bye."); 4 | 5 | callback(null, twiml.toString()); 6 | }; -------------------------------------------------------------------------------- /Server/functions/make-call.js: -------------------------------------------------------------------------------- 1 | const callerNumber = '1234567890'; 2 | const callerId = 'client:alice'; 3 | 4 | exports.handler = function(context, event, callback) { 5 | const twiml = new Twilio.twiml.VoiceResponse(); 6 | 7 | var to = event.to; 8 | if (!to) { 9 | twiml.say('Congratulations! You have made your first call! Good bye.'); 10 | } else if (isNumber(to)) { 11 | const dial = twiml.dial({callerId : callerNumber}); 12 | dial.number(to); 13 | } else { 14 | const dial = twiml.dial({callerId : callerId}); 15 | dial.client(to); 16 | } 17 | 18 | callback(null, twiml); 19 | }; 20 | 21 | function isNumber(to) { 22 | if(to.length == 1) { 23 | if(!isNaN(to)) { 24 | console.log("It is a 1 digit long number" + to); 25 | return true; 26 | } 27 | } else if(String(to).charAt(0) == '+') { 28 | number = to.substring(1); 29 | if(!isNaN(number)) { 30 | console.log("It is a number " + to); 31 | return true; 32 | }; 33 | } else { 34 | if(!isNaN(to)) { 35 | console.log("It is a number " + to); 36 | return true; 37 | } 38 | } 39 | console.log("not a number"); 40 | return false; 41 | } -------------------------------------------------------------------------------- /Server/functions/place-call.js: -------------------------------------------------------------------------------- 1 | const callerNumber = '1234567890'; 2 | const callerId = 'client:alice'; 3 | const defaultIdentity = 'alice'; 4 | 5 | exports.handler = function(context, event, callback) { 6 | var url = 'https://' + context.DOMAIN_NAME + '/incoming'; 7 | 8 | const client = context.getTwilioClient(); 9 | 10 | var to = event.to; 11 | if (!to) { 12 | client.calls.create({ 13 | url: url, 14 | to: 'client:' + defaultIdentity, 15 | from: callerId, 16 | }, function(err, result) { 17 | // End our function 18 | if (err) { 19 | callback(err, null); 20 | } else { 21 | callback(null, result); 22 | } 23 | }); 24 | } else if (isNumber(to)) { 25 | console.log("Calling number:" + to); 26 | client.calls.create({ 27 | url: url, 28 | to: to, 29 | from: callerNumber, 30 | }, function(err, result) { 31 | // End our function 32 | if (err) { 33 | callback(err, null); 34 | } else { 35 | callback(null, result); 36 | } 37 | }); 38 | } else { 39 | client.calls.create({ 40 | url: url, 41 | to: 'client:' + to, 42 | from: callerId, 43 | }, function(err, result) { 44 | // End our function 45 | if (err) { 46 | callback(err, null); 47 | } else { 48 | callback(null, result); 49 | } 50 | }); 51 | } 52 | }; 53 | 54 | function isNumber(to) { 55 | if(to.length == 1) { 56 | if(!isNaN(to)) { 57 | console.log("It is a 1 digit long number" + to); 58 | return true; 59 | } 60 | } else if(String(to).charAt(0) == '+') { 61 | number = to.substring(1); 62 | if(!isNaN(number)) { 63 | console.log("It is a number " + to); 64 | return true; 65 | }; 66 | } else { 67 | if(!isNaN(to)) { 68 | console.log("It is a number " + to); 69 | return true; 70 | } 71 | } 72 | console.log("not a number"); 73 | return false; 74 | } -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickstart", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "twilio-run", 9 | "deploy": "twilio-run deploy" 10 | }, 11 | "devDependencies": { 12 | "body-parser": "^1.18.2", 13 | "dotenv": "^4.0.0", 14 | "express": "^4.15.2", 15 | "twilio": "^3.57.0", 16 | "twilio-run": "^2.0.0" 17 | }, 18 | "engines": { 19 | "node": "10.17.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 24BA34371D874AC800B0B4F7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BA34361D874AC800B0B4F7 /* AppDelegate.swift */; }; 11 | 24BA34391D874AC800B0B4F7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BA34381D874AC800B0B4F7 /* ViewController.swift */; }; 12 | 24BA343C1D874AC800B0B4F7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 24BA343A1D874AC800B0B4F7 /* Main.storyboard */; }; 13 | 24BA343E1D874AC800B0B4F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 24BA343D1D874AC800B0B4F7 /* Assets.xcassets */; }; 14 | 24BA34411D874AC800B0B4F7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 24BA343F1D874AC800B0B4F7 /* LaunchScreen.storyboard */; }; 15 | 24BA34491D874BBD00B0B4F7 /* TwilioLogo.png in Resources */ = {isa = PBXBuildFile; fileRef = 24BA34481D874BBD00B0B4F7 /* TwilioLogo.png */; }; 16 | 8F56E8D625C8B25E00B5AB19 /* TwilioVoice in Frameworks */ = {isa = PBXBuildFile; productRef = 8F56E8D525C8B25E00B5AB19 /* TwilioVoice */; }; 17 | 8F6BABF92399B2F00030BE4F /* ringtone.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8F6BABF82399B2F00030BE4F /* ringtone.wav */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 24BA34331D874AC800B0B4F7 /* SwiftVoiceQuickstart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftVoiceQuickstart.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 24BA34361D874AC800B0B4F7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 24BA34381D874AC800B0B4F7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 24BA343B1D874AC800B0B4F7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 24BA343D1D874AC800B0B4F7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 24BA34401D874AC800B0B4F7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 24BA34421D874AC800B0B4F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 24BA34481D874BBD00B0B4F7 /* TwilioLogo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TwilioLogo.png; sourceTree = ""; }; 29 | 8F6BABF82399B2F00030BE4F /* ringtone.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ringtone.wav; sourceTree = ""; }; 30 | 8FAD20B72332DC3900E5B42F /* SwiftVoiceQuickstart.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftVoiceQuickstart.entitlements; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 24BA34301D874AC800B0B4F7 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | 8F56E8D625C8B25E00B5AB19 /* TwilioVoice in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 24BA342A1D874AC800B0B4F7 = { 46 | isa = PBXGroup; 47 | children = ( 48 | 24BA34351D874AC800B0B4F7 /* SwiftVoiceQuickstart */, 49 | 8F6BABF72399B2F00030BE4F /* ringtone */, 50 | 24BA34341D874AC800B0B4F7 /* Products */, 51 | 55E4DD2B129E858377D08E95 /* Pods */, 52 | 45B8A87752EE385BD0514D8C /* Frameworks */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 24BA34341D874AC800B0B4F7 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 24BA34331D874AC800B0B4F7 /* SwiftVoiceQuickstart.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 24BA34351D874AC800B0B4F7 /* SwiftVoiceQuickstart */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 8FAD20B72332DC3900E5B42F /* SwiftVoiceQuickstart.entitlements */, 68 | 24BA34361D874AC800B0B4F7 /* AppDelegate.swift */, 69 | 24BA34381D874AC800B0B4F7 /* ViewController.swift */, 70 | 24BA343A1D874AC800B0B4F7 /* Main.storyboard */, 71 | 24BA343D1D874AC800B0B4F7 /* Assets.xcassets */, 72 | 24BA343F1D874AC800B0B4F7 /* LaunchScreen.storyboard */, 73 | 24BA34421D874AC800B0B4F7 /* Info.plist */, 74 | 24BA34481D874BBD00B0B4F7 /* TwilioLogo.png */, 75 | ); 76 | path = SwiftVoiceQuickstart; 77 | sourceTree = ""; 78 | }; 79 | 45B8A87752EE385BD0514D8C /* Frameworks */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | ); 83 | name = Frameworks; 84 | sourceTree = ""; 85 | }; 86 | 55E4DD2B129E858377D08E95 /* Pods */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | ); 90 | name = Pods; 91 | sourceTree = ""; 92 | }; 93 | 8F6BABF72399B2F00030BE4F /* ringtone */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 8F6BABF82399B2F00030BE4F /* ringtone.wav */, 97 | ); 98 | path = ringtone; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 24BA34321D874AC800B0B4F7 /* SwiftVoiceQuickstart */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 24BA34451D874AC800B0B4F7 /* Build configuration list for PBXNativeTarget "SwiftVoiceQuickstart" */; 107 | buildPhases = ( 108 | 24BA342F1D874AC800B0B4F7 /* Sources */, 109 | 24BA34301D874AC800B0B4F7 /* Frameworks */, 110 | 24BA34311D874AC800B0B4F7 /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = SwiftVoiceQuickstart; 117 | packageProductDependencies = ( 118 | 8F56E8D525C8B25E00B5AB19 /* TwilioVoice */, 119 | ); 120 | productName = SwiftVoiceQuickstart; 121 | productReference = 24BA34331D874AC800B0B4F7 /* SwiftVoiceQuickstart.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 24BA342B1D874AC800B0B4F7 /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastSwiftUpdateCheck = 0800; 131 | LastUpgradeCheck = 0800; 132 | ORGANIZATIONNAME = "Twilio, Inc."; 133 | TargetAttributes = { 134 | 24BA34321D874AC800B0B4F7 = { 135 | CreatedOnToolsVersion = 8.0; 136 | ProvisioningStyle = Automatic; 137 | SystemCapabilities = { 138 | com.apple.BackgroundModes = { 139 | enabled = 1; 140 | }; 141 | com.apple.Push = { 142 | enabled = 1; 143 | }; 144 | }; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 24BA342E1D874AC800B0B4F7 /* Build configuration list for PBXProject "SwiftVoiceQuickstart" */; 149 | compatibilityVersion = "Xcode 3.2"; 150 | developmentRegion = English; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | English, 154 | en, 155 | Base, 156 | ); 157 | mainGroup = 24BA342A1D874AC800B0B4F7; 158 | packageReferences = ( 159 | 8F56E8D425C8B25E00B5AB19 /* XCRemoteSwiftPackageReference "twilio-voice-ios" */, 160 | ); 161 | productRefGroup = 24BA34341D874AC800B0B4F7 /* Products */; 162 | projectDirPath = ""; 163 | projectRoot = ""; 164 | targets = ( 165 | 24BA34321D874AC800B0B4F7 /* SwiftVoiceQuickstart */, 166 | ); 167 | }; 168 | /* End PBXProject section */ 169 | 170 | /* Begin PBXResourcesBuildPhase section */ 171 | 24BA34311D874AC800B0B4F7 /* Resources */ = { 172 | isa = PBXResourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 24BA34411D874AC800B0B4F7 /* LaunchScreen.storyboard in Resources */, 176 | 24BA343E1D874AC800B0B4F7 /* Assets.xcassets in Resources */, 177 | 24BA34491D874BBD00B0B4F7 /* TwilioLogo.png in Resources */, 178 | 8F6BABF92399B2F00030BE4F /* ringtone.wav in Resources */, 179 | 24BA343C1D874AC800B0B4F7 /* Main.storyboard in Resources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXResourcesBuildPhase section */ 184 | 185 | /* Begin PBXSourcesBuildPhase section */ 186 | 24BA342F1D874AC800B0B4F7 /* Sources */ = { 187 | isa = PBXSourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 24BA34391D874AC800B0B4F7 /* ViewController.swift in Sources */, 191 | 24BA34371D874AC800B0B4F7 /* AppDelegate.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin PBXVariantGroup section */ 198 | 24BA343A1D874AC800B0B4F7 /* Main.storyboard */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | 24BA343B1D874AC800B0B4F7 /* Base */, 202 | ); 203 | name = Main.storyboard; 204 | sourceTree = ""; 205 | }; 206 | 24BA343F1D874AC800B0B4F7 /* LaunchScreen.storyboard */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | 24BA34401D874AC800B0B4F7 /* Base */, 210 | ); 211 | name = LaunchScreen.storyboard; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXVariantGroup section */ 215 | 216 | /* Begin XCBuildConfiguration section */ 217 | 24BA34431D874AC800B0B4F7 /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | CLANG_ANALYZER_NONNULL = YES; 222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 223 | CLANG_CXX_LIBRARY = "libc++"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_WARN_BOOL_CONVERSION = YES; 227 | CLANG_WARN_CONSTANT_CONVERSION = YES; 228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INFINITE_RECURSION = YES; 233 | CLANG_WARN_INT_CONVERSION = YES; 234 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 235 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 236 | CLANG_WARN_UNREACHABLE_CODE = YES; 237 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 238 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 239 | COPY_PHASE_STRIP = NO; 240 | DEBUG_INFORMATION_FORMAT = dwarf; 241 | ENABLE_STRICT_OBJC_MSGSEND = YES; 242 | ENABLE_TESTABILITY = YES; 243 | GCC_C_LANGUAGE_STANDARD = gnu99; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PREPROCESSOR_DEFINITIONS = ( 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 255 | GCC_WARN_UNUSED_FUNCTION = YES; 256 | GCC_WARN_UNUSED_VARIABLE = YES; 257 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 258 | MTL_ENABLE_DEBUG_INFO = YES; 259 | ONLY_ACTIVE_ARCH = YES; 260 | SDKROOT = iphoneos; 261 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 262 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 263 | TARGETED_DEVICE_FAMILY = "1,2"; 264 | }; 265 | name = Debug; 266 | }; 267 | 24BA34441D874AC800B0B4F7 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_MODULES = YES; 275 | CLANG_ENABLE_OBJC_ARC = YES; 276 | CLANG_WARN_BOOL_CONVERSION = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 285 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu99; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 302 | MTL_ENABLE_DEBUG_INFO = NO; 303 | SDKROOT = iphoneos; 304 | SWIFT_COMPILATION_MODE = wholemodule; 305 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | VALIDATE_PRODUCT = YES; 308 | }; 309 | name = Release; 310 | }; 311 | 24BA34461D874AC800B0B4F7 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CODE_SIGN_ENTITLEMENTS = SwiftVoiceQuickstart/SwiftVoiceQuickstart.entitlements; 316 | DEVELOPMENT_TEAM = ""; 317 | INFOPLIST_FILE = SwiftVoiceQuickstart/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/Frameworks", 321 | ); 322 | PRODUCT_BUNDLE_IDENTIFIER = com.twilio.SwiftVoiceQuickstart; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 4.0; 325 | }; 326 | name = Debug; 327 | }; 328 | 24BA34471D874AC800B0B4F7 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 332 | CODE_SIGN_ENTITLEMENTS = SwiftVoiceQuickstart/SwiftVoiceQuickstart.entitlements; 333 | DEVELOPMENT_TEAM = ""; 334 | INFOPLIST_FILE = SwiftVoiceQuickstart/Info.plist; 335 | LD_RUNPATH_SEARCH_PATHS = ( 336 | "$(inherited)", 337 | "@executable_path/Frameworks", 338 | ); 339 | PRODUCT_BUNDLE_IDENTIFIER = com.twilio.SwiftVoiceQuickstart; 340 | PRODUCT_NAME = "$(TARGET_NAME)"; 341 | SWIFT_VERSION = 4.0; 342 | }; 343 | name = Release; 344 | }; 345 | /* End XCBuildConfiguration section */ 346 | 347 | /* Begin XCConfigurationList section */ 348 | 24BA342E1D874AC800B0B4F7 /* Build configuration list for PBXProject "SwiftVoiceQuickstart" */ = { 349 | isa = XCConfigurationList; 350 | buildConfigurations = ( 351 | 24BA34431D874AC800B0B4F7 /* Debug */, 352 | 24BA34441D874AC800B0B4F7 /* Release */, 353 | ); 354 | defaultConfigurationIsVisible = 0; 355 | defaultConfigurationName = Release; 356 | }; 357 | 24BA34451D874AC800B0B4F7 /* Build configuration list for PBXNativeTarget "SwiftVoiceQuickstart" */ = { 358 | isa = XCConfigurationList; 359 | buildConfigurations = ( 360 | 24BA34461D874AC800B0B4F7 /* Debug */, 361 | 24BA34471D874AC800B0B4F7 /* Release */, 362 | ); 363 | defaultConfigurationIsVisible = 0; 364 | defaultConfigurationName = Release; 365 | }; 366 | /* End XCConfigurationList section */ 367 | 368 | /* Begin XCRemoteSwiftPackageReference section */ 369 | 8F56E8D425C8B25E00B5AB19 /* XCRemoteSwiftPackageReference "twilio-voice-ios" */ = { 370 | isa = XCRemoteSwiftPackageReference; 371 | repositoryURL = "https://github.com/twilio/twilio-voice-ios"; 372 | requirement = { 373 | kind = upToNextMajorVersion; 374 | minimumVersion = 6.2.0; 375 | }; 376 | }; 377 | /* End XCRemoteSwiftPackageReference section */ 378 | 379 | /* Begin XCSwiftPackageProductDependency section */ 380 | 8F56E8D525C8B25E00B5AB19 /* TwilioVoice */ = { 381 | isa = XCSwiftPackageProductDependency; 382 | package = 8F56E8D425C8B25E00B5AB19 /* XCRemoteSwiftPackageReference "twilio-voice-ios" */; 383 | productName = TwilioVoice; 384 | }; 385 | /* End XCSwiftPackageProductDependency section */ 386 | }; 387 | rootObject = 24BA342B1D874AC800B0B4F7 /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart.xcodeproj/xcshareddata/xcschemes/SwiftVoiceQuickstart.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Twilio Voice Quickstart - Swift 4 | // 5 | // Copyright © 2016 Twilio, Inc. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import TwilioVoice 10 | import PushKit 11 | 12 | protocol PushKitEventDelegate: AnyObject { 13 | func credentialsUpdated(credentials: PKPushCredentials) -> Void 14 | func credentialsInvalidated() -> Void 15 | func incomingPushReceived(payload: PKPushPayload) -> Void 16 | func incomingPushReceived(payload: PKPushPayload, completion: @escaping () -> Void) -> Void 17 | } 18 | 19 | @UIApplicationMain 20 | class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate { 21 | 22 | var window: UIWindow? 23 | var pushKitEventDelegate: PushKitEventDelegate? 24 | var voipRegistry = PKPushRegistry.init(queue: DispatchQueue.main) 25 | 26 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 27 | NSLog("Twilio Voice Version: %@", TwilioVoiceSDK.sdkVersion()) 28 | 29 | let viewController = UIApplication.shared.windows.first?.rootViewController as? ViewController 30 | self.pushKitEventDelegate = viewController 31 | /* 32 | * Your app must initialize PKPushRegistry with PushKit push type VoIP at the launch time. As mentioned in the 33 | * [PushKit guidelines](https://developer.apple.com/documentation/pushkit/supporting_pushkit_notifications_in_your_app), 34 | * the system can't deliver push notifications to your app until you create a PKPushRegistry object for 35 | * VoIP push type and set the delegate. If your app delays the initialization of PKPushRegistry, your app may receive outdated 36 | * PushKit push notifications, and if your app decides not to report the received outdated push notifications to CallKit, iOS may 37 | * terminate your app. 38 | */ 39 | initializePushKit() 40 | 41 | return true 42 | } 43 | 44 | func initializePushKit() { 45 | voipRegistry.delegate = self 46 | voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) 47 | } 48 | 49 | func applicationWillResignActive(_ application: UIApplication) { 50 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 51 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 52 | } 53 | 54 | func applicationDidEnterBackground(_ application: UIApplication) { 55 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 56 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 57 | } 58 | 59 | func applicationWillEnterForeground(_ application: UIApplication) { 60 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 61 | } 62 | 63 | func applicationDidBecomeActive(_ application: UIApplication) { 64 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 65 | } 66 | 67 | func applicationWillTerminate(_ application: UIApplication) { 68 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 69 | } 70 | 71 | // MARK: PKPushRegistryDelegate 72 | func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { 73 | NSLog("pushRegistry:didUpdatePushCredentials:forType:") 74 | 75 | if let delegate = self.pushKitEventDelegate { 76 | delegate.credentialsUpdated(credentials: credentials) 77 | } 78 | } 79 | 80 | func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { 81 | NSLog("pushRegistry:didInvalidatePushTokenForType:") 82 | 83 | if let delegate = self.pushKitEventDelegate { 84 | delegate.credentialsInvalidated() 85 | } 86 | } 87 | 88 | /** 89 | * Try using the `pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:` method if 90 | * your application is targeting iOS 11. According to the docs, this delegate method is deprecated by Apple. 91 | */ 92 | func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { 93 | NSLog("pushRegistry:didReceiveIncomingPushWithPayload:forType:") 94 | 95 | if let delegate = self.pushKitEventDelegate { 96 | delegate.incomingPushReceived(payload: payload) 97 | } 98 | } 99 | 100 | /** 101 | * This delegate method is available on iOS 11 and above. Call the completion handler once the 102 | * notification payload is passed to the `TwilioVoiceSDK.handleNotification()` method. 103 | */ 104 | func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { 105 | NSLog("pushRegistry:didReceiveIncomingPushWithPayload:forType:completion:") 106 | 107 | if let delegate = self.pushKitEventDelegate { 108 | delegate.incomingPushReceived(payload: payload, completion: completion) 109 | } 110 | 111 | if let version = Float(UIDevice.current.systemVersion), version >= 13.0 { 112 | /** 113 | * The Voice SDK processes the call notification and returns the call invite synchronously. Report the incoming call to 114 | * CallKit and fulfill the completion before exiting this callback method. 115 | */ 116 | completion() 117 | } 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small.png", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@2x.png", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "size" : "29x29", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-Small@3x.png", 29 | "scale" : "3x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "size" : "40x40", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-40@3x.png", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "size" : "57x57", 45 | "idiom" : "iphone", 46 | "filename" : "Icon.png", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "size" : "57x57", 51 | "idiom" : "iphone", 52 | "filename" : "Icon@2x.png", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "size" : "60x60", 57 | "idiom" : "iphone", 58 | "filename" : "Icon-60@2x.png", 59 | "scale" : "2x" 60 | }, 61 | { 62 | "size" : "60x60", 63 | "idiom" : "iphone", 64 | "filename" : "Icon-60@3x.png", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "20x20", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "20x20", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "size" : "29x29", 79 | "idiom" : "ipad", 80 | "filename" : "Icon-Small.png", 81 | "scale" : "1x" 82 | }, 83 | { 84 | "size" : "29x29", 85 | "idiom" : "ipad", 86 | "filename" : "Icon-Small@2x.png", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "size" : "40x40", 91 | "idiom" : "ipad", 92 | "filename" : "Icon-40.png", 93 | "scale" : "1x" 94 | }, 95 | { 96 | "size" : "40x40", 97 | "idiom" : "ipad", 98 | "filename" : "Icon-40@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "size" : "50x50", 103 | "idiom" : "ipad", 104 | "filename" : "Icon-Small-50.png", 105 | "scale" : "1x" 106 | }, 107 | { 108 | "size" : "50x50", 109 | "idiom" : "ipad", 110 | "filename" : "Icon-Small-50@2x.png", 111 | "scale" : "2x" 112 | }, 113 | { 114 | "size" : "72x72", 115 | "idiom" : "ipad", 116 | "filename" : "Icon-72.png", 117 | "scale" : "1x" 118 | }, 119 | { 120 | "size" : "72x72", 121 | "idiom" : "ipad", 122 | "filename" : "Icon-72@2x.png", 123 | "scale" : "2x" 124 | }, 125 | { 126 | "size" : "76x76", 127 | "idiom" : "ipad", 128 | "filename" : "Icon-76.png", 129 | "scale" : "1x" 130 | }, 131 | { 132 | "size" : "76x76", 133 | "idiom" : "ipad", 134 | "filename" : "Icon-76@2x.png", 135 | "scale" : "2x" 136 | }, 137 | { 138 | "size" : "83.5x83.5", 139 | "idiom" : "ipad", 140 | "filename" : "Icon-83.5@2x.png", 141 | "scale" : "2x" 142 | }, 143 | { 144 | "idiom" : "ios-marketing", 145 | "size" : "1024x1024", 146 | "scale" : "1x" 147 | } 148 | ], 149 | "info" : { 150 | "version" : 1, 151 | "author" : "xcode" 152 | } 153 | } -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 93 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSMicrophoneUsageDescription 24 | $(EXECUTABLE_NAME) would like to access your microphone 25 | UIBackgroundModes 26 | 27 | audio 28 | voip 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/SwiftVoiceQuickstart.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftVoiceQuickstart/TwilioLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/SwiftVoiceQuickstart/TwilioLogo.png -------------------------------------------------------------------------------- /VoiceQuickstart.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /VoiceQuickstart.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VoiceQuickstart.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VoiceQuickstart.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "twilio-voice-ios", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/twilio/twilio-voice-ios", 7 | "state" : { 8 | "revision" : "c6f221be985c76356b6f8168dc4190e0522cd63e", 9 | "version" : "6.10.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /ringtone/ringtone.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/voice-quickstart-ios/d1fa0a409224cd75b51cad78a7935a47952e490f/ringtone/ringtone.wav --------------------------------------------------------------------------------