diff --git a/README.md b/README.md index c19307a..dad9cc6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ## Get started with Voice on iOS: * [Quickstart](#quickstart) - Run the quickstart app +* [New Features](#new-features) - New features in 3.X * [Migration Guide](#migration-guide) - Migrating from 2.X to 3.X * [Access Tokens](#access-tokens) - Using access tokens * [Managing Audio Interruptions](#managing-audio-interruptions) - Managing audio interruptions @@ -225,10 +226,155 @@ There are number of techniques you can use to ensure that access token expiry is - 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. - Prefetch the access token whenever the `UIApplication`, or `UIViewController` associated with an outgoing call is created. +## New Features +Voice iOS 3.X has a number of new features listed below: + +1. [WebRTC](#webrtc) +2. [Custom Parameters](#custom-parameters) +3. [Call Ringing APIs](#call-ringing-apis) +4. [Media Stats](#media-stats) +5. [Audio Device APIs](#audio-device-apis) + * [Default Audio Device](#default-audio-device) + * [Custom Audio Device](#custom-audio-device) + +#### WebRTC +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. + +#### Custom Parameters +Custom Parameters is now supported in `TVOCallInvite`. `TVOCallInvite.customParamaters` returns a `NSDictionary` of custom parameters sent from the caller side to the callee. + +Pass custom parameters in TwiML: + +```.xml + + + + + bob + + + + + +``` + +`callInvite.customParameters` returns a dictionary of key-value pairs passed in the TwiML: + +```.objc +{ + "caller_first_name" = "alice"; + "caller_last_name" = "smith"; +} +``` + +#### Call Ringing APIs +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`. + +These changes are added as follows: + +```.objc +// TVOCall.h +typedef NS_ENUM(NSUInteger, TVOCallState) { + TVOCallStateConnecting = 0, ///< The Call is connecting. + TVOCallStateRinging, ///< The Call is ringing. + TVOCallStateConnected, ///< The Call is connected. + TVOCallStateDisconnected ///< The Call is disconnected. +}; + +// TVOCallDelegate.h +@protocol TVOCallDelegate + +@optional +- (void)callDidStartRinging:(nonnull TVOCall *)call; + +@end +``` + +#### Media Stats +In Voice iOS 3.X SDK you can now access media stats in a Call using the `[TVOCall getStatsWithBlock:]` method. + +```.objc +[call getStatsWithBlock:^(NSArray *statsReports) { + for (TVOStatsReport *report in statsReports) { + NSArray *localAudioTracks = report.localAudioTrackStats; + TVOLocalAudioTrackStats *localAudioTrackStats = localAudioTracks[0]; + NSArray *remoteAudioTracks = report.remoteAudioTrackStats; + TVORemoteAudioTrackStats *remoteAudioTrackStats = remoteAudioTracks[0]; + + NSLog(@"Local Audio Track - audio level: %lu, packets sent: %lu", localAudioTrackStats.audioLevel, localAudioTrackStats.packetsSent); + NSLog(@"Remote Audio Track - audio level: %lu, packets received: %lu", remoteAudioTrackStats.audioLevel, remoteAudioTrackStats.packetsReceived); + } +} +``` +### Audio Device APIs + +#### Default Audio Device +In Voice iOS 3.X SDK, `TVODefaultAudioDevice` is used as the default device for rendering and capturing audio. + +An example of using `TVODefaultAudioDevice` to change the audio route from receiver to the speaker in a live call: + +```.objc +// The Voice SDK uses TVODefaultAudioDevice by default. +// ... connect to a Call. The `TVODefaultAudioDevice` is configured to route audio to the receiver by default. + +TVODefaultAudioDevice *audioDevice = (TVODefaultAudioDevice *)TwilioVoice.audioDevice; + +audioDevice.block = ^ { + // We will execute `kDefaultAVAudioSessionConfigurationBlock` first. + kDefaultAVAudioSessionConfigurationBlock(); + + if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]) { + NSLog(@"AVAudiosession overrideOutputAudioPort %@",error); + } +}; +audioDevice.block(); +``` + +#### Custom Audio Device +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`. + +Connecting to a Call using the `AVAudioSessionCategoryPlayback` category: + +```.objc +id audioDevice = [TVODefaultAudioDevice audioDeviceWithBlock:^ { + + // Execute the `kDefaultAVAudioSessionConfigurationBlock` first. + kDefaultAVAudioSessionConfigurationBlock(); + + // Overwrite the category to `playback` + AVAudioSession *session = [AVAudioSession sharedInstance]; + NSError *error = nil; + if (![session setCategory:AVAudioSessionCategoryPlayback + mode:AVAudioSessionModeVoiceChat + options:AVAudioSessionCategoryOptionAllowBluetooth + error:&error]) { + NSLog(@"AVAudioSession setCategory:options:mode:error: %@",error); + } +}]; + +TwilioVoice.audioDevice = audioDevice; + +TVOCall *call = [TwilioVoice connectWithOptions:connectOptions delegate:self]; +``` + ## Migration Guide -This section describes API changes and additions to ease migration from Voice iOS 2.X to Voice iOS 3.X. Each section provides code snippets to assist in transitioning to the new API. +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. + +1. [Making a Call](#making-a-call) +2. [TVOCallInvite Changes](#tvocallinvite-changes) +3. [Specifying a Media Region](#specifying-a-media-region) +4. [TVOConnectOptions & TVOAcceptOptions](#tvoconnectoptions-and-tvoacceptoptions) +5. [Media Establishment & Connectivity](#media-establishment-and-connectivity) +6. [CallKit](#callkit) -### TVOCallInvite Changes +#### Making a Call +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:]`. + +```.objc +TVOCall *call = [TwilioVoice connectWithAccessToken:token delegate:self]; +``` + +#### TVOCallInvite Changes 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. ```.objc @@ -252,7 +398,16 @@ didReceiveIncomingPushWithPayload:(PKPushPayload *)payload 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`. -### Specifying a media region +In Voice iOS 2.X passing a `cancel` notification into `[TwilioVoice handleNotification:delegate:]` would not raise a callback in the following two cases: + +- This callee accepted the call +- This callee rejected the call + +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:]`. + +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. + +#### Specifying a media region 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: ```.objc @@ -267,7 +422,7 @@ TVOAcceptOptions *options = [TVOAcceptOptions optionsWithCallInvite:callInvite }]; ``` -### TVOConnectOptions & TVOAcceptOptions +#### TVOConnectOptions & TVOAcceptOptions 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: ```.objc @@ -290,30 +445,7 @@ TVOAcceptOptions *options = [TVOAcceptOptions optionsWithCallInvite:callInvite self.call = [callInvite acceptWithOptions:options delegate:self]; ``` -### Ringing -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`. - -These changes are added as follows: - -```.objc -// TVOCall.h -typedef NS_ENUM(NSUInteger, TVOCallState) { - TVOCallStateConnecting = 0, ///< The Call is connecting. - TVOCallStateRinging, ///< The Call is ringing. - TVOCallStateConnected, ///< The Call is connected. - TVOCallStateDisconnected ///< The Call is disconnected. -}; - -// TVOCallDelegate.h -@protocol TVOCallDelegate - -@optional -- (void)callDidStartRinging:(nonnull TVOCall *)call; - -@end -``` - -### Media Establishment & Connectivity +#### Media Establishment & Connectivity 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: ```.objc @@ -346,32 +478,7 @@ TVOAcceptOptions *options = [TVOAcceptOptions optionsWithCallInvite:callInvite }]; ``` -### Audio Device -The Voice iOS 3.X SDK introduces audio device APIs. - -#### TVODefaultAudioDevide -In Voice iOS 3.X SDK, `TVODefaultAudioDevice` is used as the default device for rendering and capturing audio. - -An example of using `TVODefaultAudioDevice` to change the audio route from receiver to the speaker in a live call: - -```.objc -// The Voice SDK uses TVODefaultAudioDevice by default. -// ... connect to a Call. The `TVODefaultAudioDevice` is configured to route audio to the receiver by default. - -TVODefaultAudioDevice *audioDevice = (TVODefaultAudioDevice *)TwilioVoice.audioDevice; - -audioDevice.block = ^ { - // We will execute `kDefaultAVAudioSessionConfigurationBlock` first. - kDefaultAVAudioSessionConfigurationBlock(); - - if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]) { - NSLog(@"AVAudiosession overrideOutputAudioPort %@",error); - } -}; -audioDevice.block(); -``` - -#### CallKit +#### CallKit 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. An example of managing the `TVODefaultAudioDevice` while connecting a CallKit Call: @@ -426,77 +533,6 @@ An example of managing the `TVODefaultAudioDevice` while connecting a CallKit Ca See [CallKit Example](https://github.com/twilio/voice-quickstart-objc/blob/3.x/ObjCVoiceCallKitQuickstart/ViewController.m) for the complete implementation. -#### TVOAudioDevice -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`. - -Connecting to a Call using the `AVAudioSessionCategoryPlayback` category: - -```.objc -id audioDevice = [TVODefaultAudioDevice audioDeviceWithBlock:^ { - - // Execute the `kDefaultAVAudioSessionConfigurationBlock` first. - kDefaultAVAudioSessionConfigurationBlock(); - - // Overwrite the category to `playback` - AVAudioSession *session = [AVAudioSession sharedInstance]; - NSError *error = nil; - if (![session setCategory:AVAudioSessionCategoryPlayback - mode:AVAudioSessionModeVoiceChat - options:AVAudioSessionCategoryOptionAllowBluetooth - error:&error]) { - NSLog(@"AVAudioSession setCategory:options:mode:error: %@",error); - } -}]; - -TwilioVoice.audioDevice = audioDevice; - -TVOCall *call = [TwilioVoice connectWithOptions:connectOptions delegate:self]; -``` - -### Custom Parameters -Custom Parameters is now supported in `TVOCallInvite`. `TVOCallInvite.customParamaters` returns a `NSDictionary` of custom parameters sent from the caller side to the callee. - -Pass custom parameters in TwiML: - -```.xml - - - - - bob - - - - - -``` - -`callInvite.customParameters` returns a dictionary of key-value pairs passed in the TwiML: - -```.objc -{ - "caller_first_name" = "alice"; - "caller_last_name" = "smith"; -} -``` - -### Media Stats -In Voice iOS 3.X SDK you can now access media stats in a Call using the `[TVOCall getStatsWithBlock:]` method. - -```.objc -[call getStatsWithBlock:^(NSArray *statsReports) { - for (TVOStatsReport *report in statsReports) { - NSArray *localAudioTracks = report.localAudioTrackStats; - TVOLocalAudioTrackStats *localAudioTrackStats = localAudioTracks[0]; - NSArray *remoteAudioTracks = report.remoteAudioTrackStats; - TVORemoteAudioTrackStats *remoteAudioTrackStats = remoteAudioTracks[0]; - - NSLog(@"Local Audio Track - audio level: %lu, packets sent: %lu", localAudioTrackStats.audioLevel, localAudioTrackStats.packetsSent); - NSLog(@"Remote Audio Track - audio level: %lu, packets received: %lu", remoteAudioTrackStats.audioLevel, remoteAudioTrackStats.packetsReceived); - } -} -``` - ## Managing Audio Interruptions 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.