main c15f9e6dad16 cached
197 files
721.0 KB
195.5k tokens
1 symbols
1 requests
Download .txt
Showing preview only (784K chars total). Download the full file or copy to clipboard to get everything.
Repository: opentok/opentok-ios-sdk-samples-swift
Branch: main
Commit: c15f9e6dad16
Files: 197
Total size: 721.0 KB

Directory structure:
gitextract_wr9whvu1/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── metrics.yml
├── .gitignore
├── .travis.yml
├── Basic-Video-Chat/
│   ├── Basic-Video-Chat/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Basic-Video-Chat.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Basic-Video-Chat.xcscheme
│   ├── Podfile
│   └── README.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CallKit/
│   ├── CallKitDemo/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── IconMask.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── baseHeroMount.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── CallKitDemo-Bridging-Header.h
│   │   ├── CallKitDemo.entitlements
│   │   ├── Info.plist
│   │   ├── OTDefaultAudioDevice.h
│   │   ├── OTDefaultAudioDevice.m
│   │   ├── ProviderDelegate.swift
│   │   ├── Ringtone.caf
│   │   ├── SpeakerboxCall.swift
│   │   ├── SpeakerboxCallManager.swift
│   │   └── ViewController.swift
│   ├── CallKitDemo.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── CallKitDemo.xcscheme
│   ├── LICENSE
│   ├── Podfile
│   └── README.md
├── CallKit-with-native-OpenTok-support/
│   ├── CallKitDemo/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── IconMask.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── baseHeroMount.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── CallKitDemo.entitlements
│   │   ├── Info.plist
│   │   ├── ProviderDelegate.swift
│   │   ├── Ringtone.caf
│   │   ├── SpeakerboxCall.swift
│   │   ├── SpeakerboxCallManager.swift
│   │   └── ViewController.swift
│   ├── CallKitDemo.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── CallKitDemo.xcscheme
│   ├── LICENSE
│   ├── Podfile
│   ├── README.md
│   └── pu.sh
├── Custom-Audio-Driver/
│   ├── Custom-Audio-Driver/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── DefaultAudioDevice.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Custom-Audio-Driver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Custom-Audio-Driver.xcscheme
│   ├── Podfile
│   └── README.md
├── Custom-Video-Driver/
│   ├── Custom-Video-Driver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Custom-Video-Driver.xcscheme
│   ├── Lets-Build-OTPublisher/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── EAGLVideoRenderer.swift
│   │   ├── ExampleVideoCapture.swift
│   │   ├── ExampleVideoRender.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Podfile
│   └── README.md
├── E2EE-Video-Chat/
│   ├── Basic-Video-Chat/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Basic-Video-Chat.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Basic-Video-Chat.xcscheme
│   ├── Podfile
│   └── README.md
├── FrameMetadata/
│   ├── FrameMetadata/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── FrameMetadata.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── FrameMetadata.xcscheme
│   ├── Podfile
│   └── README.md
├── LICENSE
├── Live-Photo-Capture/
│   ├── Live-Photo-Capture/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ExamplePhotoVideoCapture.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Live-Photo-Capture.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Live-Photo-Capture.xcscheme
│   ├── Podfile
│   └── README.md
├── Media-Transformers/
│   ├── CHANGELOG.md
│   ├── Media-Transformers/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Media-Transformers.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Video-Transformers.xcscheme
│   ├── Podfile
│   └── README.md
├── Multiparty-UICollectionView/
│   ├── Multiparty-UICollectionView/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ChatViewController.swift
│   │   ├── Info.plist
│   │   └── MultipartyLayout.swift
│   ├── Multiparty-UICollectionView.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Multiparty-UICollectionView.xcscheme
│   ├── Podfile
│   └── README.md
├── OpenTokSDKVersion.rb
├── Picture-In-Picture/
│   ├── Lets-Build-OTPublisher/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ExampleVideoRender.swift
│   │   ├── Info.plist
│   │   ├── SampleBufferVideoCallView.swift
│   │   └── ViewController.swift
│   ├── Picture-In-Picture.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Picture-In-Picture.xcscheme
│   ├── Podfile
│   └── README.md
├── README.md
├── Screen-Sharing/
│   ├── Podfile
│   ├── README.md
│   ├── Screen-Sharing/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   ├── ScreenCapturer.swift
│   │   └── ViewController.swift
│   └── Screen-Sharing.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── Screen-Sharing.xcscheme
├── Signals/
│   ├── Podfile
│   ├── README.md
│   ├── Signals/
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── ContentView.swift
│   │   ├── FormView.swift
│   │   ├── MessagesView.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   ├── SignalsApp.swift
│   │   └── VonageVideoSDK.swift
│   └── Signals.xcodeproj/
│       └── project.pbxproj
├── Simple-Multiparty/
│   ├── Podfile
│   ├── README.md
│   ├── Simple-Multiparty/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── Subscriber-Speaker-35.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Subscriber-Speaker-Mute-35.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── TB Bug-30.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── camera-switch_black-33.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── camera_switch-33.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowLeft_disabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowLeft_enabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowRight_disabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowRight_enabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── mic-24.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── mic_muted-24.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── mic_receiving_data-35.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   └── Simple-Multiparty.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── Simple-Multiparty.xcscheme
└── travis_build.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Device (please compete the following information):**
- sessionId, if applicable:
- iOS SDK version: 
- OS and version:

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/workflows/metrics.yml
================================================
name: Aggregit

on:
  schedule:
    - cron: "0 0 * * *"

jobs:
  recordMetrics:
    runs-on: ubuntu-latest
    steps:
    - uses: michaeljolley/aggregit@v1
      with:
        githubToken: ${{ secrets.GITHUB_TOKEN }}
        project_id: ${{ secrets.project_id }}
        private_key: ${{ secrets.private_key }}
        client_email: ${{ secrets.client_email }}
        firebaseDbUrl: ${{ secrets.firebaseDbUrl }}


================================================
FILE: .gitignore
================================================
OpenTok.framework/
OpenTok.framework

# Xcode
.DS_Store
*/build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
profile
*.moved-aside
DerivedData
.idea/
*.hmap
*.xccheckout

#CocoaPods
Pods
Podfile.lock
*.xcworkspacedata
*.xcworkspace


================================================
FILE: .travis.yml
================================================
language: objective-c
osx_image: xcode11.7
before_install:
  - pod repo update > /dev/null
script: ./travis_build.sh 


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  Hello-World
//
//  Created by Roberto Perez Cubero on 11/08/16.
//  Copyright © 2016 tokbox. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
}



================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <animations/>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>NSCameraUsageDescription</key>
	<string></string>
	<key>NSMicrophoneUsageDescription</key>
	<string></string>
</dict>
</plist>


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat/ViewController.swift
================================================
//
//  ViewController.swift
//  Hello-World
//
//  Created by Roberto Perez Cubero on 11/08/16.
//  Copyright © 2016 tokbox. All rights reserved.
//

import UIKit
import OpenTok

// *** Fill the following variables using your own Project info  ***
// ***            https://tokbox.com/account/#/                  ***
// Replace with your OpenTok API key
let kApiKey = ""
// Replace with your generated session ID
let kSessionId = ""
// Replace with your generated token
let kToken = ""

let kWidgetHeight = 240
let kWidgetWidth = 320

class ViewController: UIViewController {
    lazy var session: OTSession = {
        return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)!
    }()
    
    var publisher: OTPublisher?
    var subscriber: OTSubscriber?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        doConnect()
    }
    
    /**
     * Asynchronously begins the session connect process. Some time later, we will
     * expect a delegate method to call us back with the results of this action.
     */
    fileprivate func doConnect() {
        var error: OTError?
        defer {
            processError(error)
        }
        
        session.connect(withToken: kToken, error: &error)
    }
    
    /**
     * Sets up an instance of OTPublisher to use with this session. OTPubilsher
     * binds to the device camera and microphone, and will provide A/V streams
     * to the OpenTok session.
     */
    fileprivate func doPublish() {
        var error: OTError?
        defer {
            processError(error)
        }
        
        let settings = OTPublisherSettings()
        settings.name = UIDevice.current.name
        publisher =  OTPublisher(delegate: self, settings: settings)!

        session.publish(publisher!, error: &error)
        
        if let pubView = publisher!.view {
            pubView.frame = CGRect(x: 0, y: 0, width: kWidgetWidth, height: kWidgetHeight)
            view.addSubview(pubView)
        }
    }
    
    /**
     * Instantiates a subscriber for the given stream and asynchronously begins the
     * process to begin receiving A/V content for this stream. Unlike doPublish,
     * this method does not add the subscriber to the view hierarchy. Instead, we
     * add the subscriber only after it has connected and begins receiving data.
     */
    fileprivate func doSubscribe(_ stream: OTStream) {
        var error: OTError?
        defer {
            processError(error)
        }
        subscriber = OTSubscriber(stream: stream, delegate: self)
        
        session.subscribe(subscriber!, error: &error)
    }
    
    fileprivate func cleanupSubscriber() {
        subscriber?.view?.removeFromSuperview()
        subscriber = nil
    }
    
    fileprivate func cleanupPublisher() {
        publisher!.view?.removeFromSuperview()
        publisher = nil
    }
    
    fileprivate func processError(_ error: OTError?) {
        if let err = error {
            DispatchQueue.main.async {
                let controller = UIAlertController(title: "Error", message: err.localizedDescription, preferredStyle: .alert)
                controller.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
                self.present(controller, animated: true, completion: nil)
            }
        }
    }
}

// MARK: - OTSession delegate callbacks
extension ViewController: OTSessionDelegate {
    func sessionDidConnect(_ session: OTSession) {
        print("Session connected")
        doPublish()
    }
    
    func sessionDidDisconnect(_ session: OTSession) {
        print("Session disconnected")
        cleanupPublisher()
        cleanupSubscriber()
    }
    
    func session(_ session: OTSession, streamCreated stream: OTStream) {
        print("Session streamCreated: \(stream.streamId)")
        if subscriber == nil {
            doSubscribe(stream)
        }
    }
    
    func session(_ session: OTSession, streamDestroyed stream: OTStream) {
        print("Session streamDestroyed: \(stream.streamId)")
        if let subStream = subscriber?.stream, subStream.streamId == stream.streamId {
            cleanupSubscriber()
        }
    }
    
    func session(_ session: OTSession, didFailWithError error: OTError) {
        print("session Failed to connect: \(error.localizedDescription)")
    }
    
}

// MARK: - OTPublisher delegate callbacks
extension ViewController: OTPublisherDelegate {
    func publisher(_ publisher: OTPublisherKit, streamCreated stream: OTStream) {
        print("Publishing")
    }
    
    func publisher(_ publisher: OTPublisherKit, streamDestroyed stream: OTStream) {
        cleanupPublisher()
        if let subStream = subscriber?.stream, subStream.streamId == stream.streamId {
            cleanupSubscriber()
        }
    }
    
    func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
        print("Publisher failed: \(error.localizedDescription)")
    }
}

// MARK: - OTSubscriber delegate callbacks
extension ViewController: OTSubscriberDelegate {
    func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) {
        if let subsView = subscriber?.view {
            subsView.frame = CGRect(x: 0, y: kWidgetHeight, width: kWidgetWidth, height: kWidgetHeight)
            view.addSubview(subsView)
        }
    }
    
    func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
        print("Subscriber failed: \(error.localizedDescription)")
    }
}


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		A05375D71EB1633400645696 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05375CF1EB1633400645696 /* AppDelegate.swift */; };
		A05375D81EB1633400645696 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A05375D01EB1633400645696 /* Assets.xcassets */; };
		A05375D91EB1633400645696 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A05375D11EB1633400645696 /* LaunchScreen.storyboard */; };
		A05375DA1EB1633400645696 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A05375D31EB1633400645696 /* Main.storyboard */; };
		A05375DC1EB1633400645696 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05375D61EB1633400645696 /* ViewController.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		A05375CF1EB1633400645696 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		A05375D01EB1633400645696 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		A05375D21EB1633400645696 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		A05375D41EB1633400645696 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		A05375D51EB1633400645696 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		A05375D61EB1633400645696 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
		F86C649A1D5C7C630081846D /* Basic-Video-Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Basic-Video-Chat.app"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		F86C64971D5C7C630081846D /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		A05375CE1EB1633400645696 /* Basic-Video-Chat */ = {
			isa = PBXGroup;
			children = (
				A05375CF1EB1633400645696 /* AppDelegate.swift */,
				A05375D01EB1633400645696 /* Assets.xcassets */,
				A05375D11EB1633400645696 /* LaunchScreen.storyboard */,
				A05375D31EB1633400645696 /* Main.storyboard */,
				A05375D51EB1633400645696 /* Info.plist */,
				A05375D61EB1633400645696 /* ViewController.swift */,
			);
			path = "Basic-Video-Chat";
			sourceTree = "<group>";
		};
		F86C64911D5C7C630081846D = {
			isa = PBXGroup;
			children = (
				A05375CE1EB1633400645696 /* Basic-Video-Chat */,
				F86C649B1D5C7C630081846D /* Products */,
			);
			sourceTree = "<group>";
		};
		F86C649B1D5C7C630081846D /* Products */ = {
			isa = PBXGroup;
			children = (
				F86C649A1D5C7C630081846D /* Basic-Video-Chat.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		F86C64991D5C7C630081846D /* Basic-Video-Chat */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = F86C64AC1D5C7C630081846D /* Build configuration list for PBXNativeTarget "Basic-Video-Chat" */;
			buildPhases = (
				F86C64961D5C7C630081846D /* Sources */,
				F86C64971D5C7C630081846D /* Frameworks */,
				F86C64981D5C7C630081846D /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = "Basic-Video-Chat";
			productName = "Hello-World";
			productReference = F86C649A1D5C7C630081846D /* Basic-Video-Chat.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		F86C64921D5C7C630081846D /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 0730;
				LastUpgradeCheck = 0930;
				ORGANIZATIONNAME = tokbox;
				TargetAttributes = {
					F86C64991D5C7C630081846D = {
						CreatedOnToolsVersion = 7.3.1;
						DevelopmentTeam = "";
						LastSwiftMigration = 1200;
						ProvisioningStyle = Manual;
					};
				};
			};
			buildConfigurationList = F86C64951D5C7C630081846D /* Build configuration list for PBXProject "Basic-Video-Chat" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = English;
			hasScannedForEncodings = 0;
			knownRegions = (
				English,
				en,
				Base,
			);
			mainGroup = F86C64911D5C7C630081846D;
			productRefGroup = F86C649B1D5C7C630081846D /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				F86C64991D5C7C630081846D /* Basic-Video-Chat */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		F86C64981D5C7C630081846D /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				A05375DA1EB1633400645696 /* Main.storyboard in Resources */,
				A05375D81EB1633400645696 /* Assets.xcassets in Resources */,
				A05375D91EB1633400645696 /* LaunchScreen.storyboard in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		F86C64961D5C7C630081846D /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				A05375DC1EB1633400645696 /* ViewController.swift in Sources */,
				A05375D71EB1633400645696 /* AppDelegate.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		A05375D11EB1633400645696 /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				A05375D21EB1633400645696 /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
		A05375D31EB1633400645696 /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				A05375D41EB1633400645696 /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		F86C64AA1D5C7C630081846D /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				VALID_ARCHS = "arm64 arm64e armv7 armv7s x86_64";
			};
			name = Debug;
		};
		F86C64AB1D5C7C630081846D /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_COMPILATION_MODE = wholemodule;
				VALIDATE_PRODUCT = YES;
				VALID_ARCHS = "arm64 arm64e armv7 armv7s x86_64";
			};
			name = Release;
		};
		F86C64AD1D5C7C630081846D /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = "$(SRCROOT)/Basic-Video-Chat/Info.plist";
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.tokbox.Hello-World";
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE = "";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		F86C64AE1D5C7C630081846D /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = "$(SRCROOT)/Basic-Video-Chat/Info.plist";
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = "com.tokbox.Hello-World";
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		F86C64951D5C7C630081846D /* Build configuration list for PBXProject "Basic-Video-Chat" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				F86C64AA1D5C7C630081846D /* Debug */,
				F86C64AB1D5C7C630081846D /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		F86C64AC1D5C7C630081846D /* Build configuration list for PBXNativeTarget "Basic-Video-Chat" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				F86C64AD1D5C7C630081846D /* Debug */,
				F86C64AE1D5C7C630081846D /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = F86C64921D5C7C630081846D /* Project object */;
}


================================================
FILE: Basic-Video-Chat/Basic-Video-Chat.xcodeproj/xcshareddata/xcschemes/Basic-Video-Chat.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1170"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "F86C64991D5C7C630081846D"
               BuildableName = "Basic-Video-Chat.app"
               BlueprintName = "Basic-Video-Chat"
               ReferencedContainer = "container:Basic-Video-Chat.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "F86C64991D5C7C630081846D"
            BuildableName = "Basic-Video-Chat.app"
            BlueprintName = "Basic-Video-Chat"
            ReferencedContainer = "container:Basic-Video-Chat.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      enableGPUValidationMode = "1"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "F86C64991D5C7C630081846D"
            BuildableName = "Basic-Video-Chat.app"
            BlueprintName = "Basic-Video-Chat"
            ReferencedContainer = "container:Basic-Video-Chat.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "F86C64991D5C7C630081846D"
            BuildableName = "Basic-Video-Chat.app"
            BlueprintName = "Basic-Video-Chat"
            ReferencedContainer = "container:Basic-Video-Chat.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Basic-Video-Chat/Podfile
================================================
require_relative '../OpenTokSDKVersion'

platform :ios, MinIosSdkVersion
use_frameworks!

target 'Basic-Video-Chat' do
  pod 'OTXCFramework', OpenTokSDKVersion
end


================================================
FILE: Basic-Video-Chat/README.md
================================================
Basic Video Chat Sample App
===============================

The Basic-Video-Chat app is a very simple application meant to get a new developer
started using the OpenTok iOS SDK.

Quick Start
-----------

To use this application:

1. Follow the instructions in the [Quick Start](../README.md#quick-start)
   section of the main README file for this repository.

   Among other things, you need to set values for the `kApiKey`, `kSessionId`,
   and `kToken` constants. See [Obtaining OpenTok
   Credentials](../README.md#obtaining-opentok-credentials)
   in the main README file for the repository.

2. When you run the application, it connects to an OpenTok session and
   publishes an audio-video stream from your device to the session.

3. Run the app on a second client. You can do this by deploying the app to an
   iOS device and testing it in the simulator at the same time. Or you can use
   the browser_demo.html file to connect in a browser (see the following
   section).

   When the second client connects, it also publishes a stream to the session,
   and both clients subscribe to (view) each other’s stream.

Application Notes
-----------------

*   Follow the code from the `ViewController.viewDidLoad(_:)` method through
    to the OpenTok callbacks to see how streams are created and handled in
    the OpenTok iOS SDK.

*   By default, all delegate methods from classes in the OpenTok iOS SDK are
    invoked on the main queue. This means that you can directly modify the view
    hierarchy from inside the callback, without any asynchronous callouts.

*   When the main view loads, the ViewController calls the
    `OTSession.initWithApiKey(_:, sessionId:,delegate:)` method to initialize
    a Session object. The app then calls the
    `OTSession.connectWithToken(_:, error:)` to connect to the session. The
    `OTSessionDelegate.sessionDidConnect(_:)` message is sent when the app
    connects to the OpenTok session.

*   The `doPublish()` method of the app initializes a publisher and passes it
    into the `OTSession.publish(_:,error:)` method. This publishes an
    audio-video stream to the session.

*   The `OTSessionDelegate.session(_:,streamCreated:)` message is sent when
    a new stream is created in the session. In response, the
    method calls `OTSubscriber(stream:,delegate:)`,
    passing in the OTStream object. This causes the app to subscribe to the
    stream.

 To add a second publisher (which will display as a subscriber in your emulator), either run the app a second time in an iOS device or use the OpenTok Playground to connect to the session in a supported web browser (Chrome, Firefox, or Internet Explorer 10-11) by following the steps below:

1. Go to [OpenTok Playground](https://tokbox.com/developer/tools/playground) (must be logged into your [Account](https://tokbox.com/account))
2. Select the **Join existing session** tab
3. Copy the session ID you used in your project file and paste it in the **Session ID** input field
4. Click **Join Session**
5. On the next screen, click **Connect**, then click **Publish Stream**
6. You can adjust the Publisher options (not required), then click **Continue** to connect and begin publishing and subscribing


Configuration Notes
-------------------

*   You can test in the iOS Simulator or on a supported iOS device. However, the
    XCode iOS Simulator does not provide access to the camera. When running in
    the iOS Simulator, an OTPublisher object uses a demo video instead of the
    camera.

[1]: https://tokbox.com/account/#/
[2]: https://tokbox.com/developer/sdks/server/


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or
  advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
  address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
devrel@vonage.com.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines

For anyone looking to get involved to this project, we are glad to hear from you. Here are a few types of contributions
that we would be interested in hearing about.

*  Bug fixes
    -  If you find a bug, please first report it using Github Issues.
    -  Issues that have already been identified as a bug will be labelled `bug`.
    -  If you'd like to submit a fix for a bug, send a Pull Request from your own fork and mention the Issue number.
*  New Features
    -  If you'd like to accomplish something in the library that it doesn't already do, describe the problem in a new
       Github Issue.
    -  Issues that have been identified as a feature request will be labelled `enhancement`.
    -  If you'd like to implement the new feature, please wait for feedback from the project maintainers before spending
       too much time writing the code. In some cases, `enhancement`s may not align well with the project objectives at
       the time.
*  Documentation and Miscellaneous
    -  If you think the documentation could be clearer, you've got an alternative
       implementation of something that may have more advantages, or any other change we would still be glad hear about
       it.
       -  If its a trivial change, go ahead and send a Pull Request with the changes you have in mind
       -  If not, open a Github Issue to discuss the idea first.

## Requirements

For a contribution to be accepted:

*  Code must follow existing styling conventions
*  Commit messages must be descriptive. Related issues should be mentioned by number.

If the contribution doesn't meet these criteria, a maintainer will discuss it with you on the Issue. You can still
continue to add more commits to the branch you have sent the Pull Request from.

## How To

1. Fork this repository on GitHub.
1. Clone/fetch your fork to your local development machine.
1. Create a new branch (e.g. `issue-12`, `feat.add_foo`, etc) and check it out.
1. Make your changes and commit them. 
1. Push your new branch to your fork. (e.g. `git push myname issue-12`)
1. Open a Pull Request from your new branch to the original fork's `master` branch.

================================================
FILE: CallKit/CallKitDemo/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  CallKitDemo
//
//  Created by Xi Huang on 6/5/17.
//  Copyright © 2017 Tokbox, Inc. All rights reserved.
//

import UIKit
import PushKit
import CallKit
import OpenTok

let apiKey = ""
let sessionId = ""
let token = ""

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
    let callManager = SpeakerboxCallManager()
    var providerDelegate: ProviderDelegate?

    // Trigger VoIP registration on launch
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        
        providerDelegate = ProviderDelegate(callManager: callManager)
        
        pushRegistry.delegate = self
        pushRegistry.desiredPushTypes = [.voIP]
        
        return true
    }
}

extension AppDelegate: PKPushRegistryDelegate {
    
    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
        print("\(#function) voip token: \(credentials.token)")
        
        let deviceToken = credentials.token.reduce("", {$0 + String(format: "%02X", $1) })
        print("\(#function) token is: \(deviceToken)")
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        
        print("\(#function) incoming voip notfication: \(payload.dictionaryPayload)")
        if let uuidString = payload.dictionaryPayload["UUID"] as? String,
            let handle = payload.dictionaryPayload["handle"] as? String,
            let uuid = UUID(uuidString: uuidString) {
            
            OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance())
                
            // display incoming call UI when receiving incoming voip notification
            let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
            self.displayIncomingCall(uuid: uuid, handle: handle, hasVideo: false) { _ in
                UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
            }
        }
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        print("\(#function) token invalidated")
    }
        
    /// Display the incoming call to the user
    func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        providerDelegate?.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion)
    }
}


================================================
FILE: CallKit/CallKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit/CallKitDemo/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit/CallKitDemo/Assets.xcassets/IconMask.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "filename" : "IconMask-40.png",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "filename" : "IconMask-80.png",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "filename" : "IconMask-120.png",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit/CallKitDemo/Assets.xcassets/baseHeroMount.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x",
      "filename" : "baseHeroMount.png"
    },
    {
      "idiom" : "universal",
      "scale" : "2x",
      "filename" : "baseHeroMount@2x.png"
    },
    {
      "idiom" : "universal",
      "scale" : "3x",
      "filename" : "baseHeroMount@3x.png"
    }
  ],
  "info" : {
    "author" : "zeplin",
    "version" : "1"
  }
}

================================================
FILE: CallKit/CallKitDemo/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: CallKit/CallKitDemo/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="CallKitDemo" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="baseHeroMount" translatesAutoresizingMaskIntoConstraints="NO" id="YYx-UX-emU">
                                <rect key="frame" x="0.0" y="20" width="375" height="647"/>
                            </imageView>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M5B-6m-yHD">
                                <rect key="frame" x="143" y="318.5" width="89" height="30"/>
                                <state key="normal" title="Simulate Call">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="receiveCallLucas:" destination="BYZ-38-t0r" eventType="touchUpInside" id="8EM-RY-UL5"/>
                                </connections>
                            </button>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g8T-fv-bWg">
                                <rect key="frame" x="68" y="356.5" width="239" height="30"/>
                                <fontDescription key="fontDescription" type="system" pointSize="15"/>
                                <state key="normal" title="Simulate Call after 3s(Background)">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="receiveCallLucasAfterThreeSeconds:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cEH-h5-VOU"/>
                                </connections>
                            </button>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9aI-yW-7Tw">
                                <rect key="frame" x="149" y="280.5" width="77" height="30"/>
                                <state key="normal" title="Make a call">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="callButtonPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="eRQ-rc-Kp9"/>
                                </connections>
                            </button>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstAttribute="trailing" secondItem="YYx-UX-emU" secondAttribute="trailing" id="3jK-fK-jae"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="5Go-8s-H2G"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="top" secondItem="9aI-yW-7Tw" secondAttribute="bottom" constant="8" id="D14-vC-v4H"/>
                            <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="YYx-UX-emU" secondAttribute="bottom" id="Fga-mw-vE3"/>
                            <constraint firstItem="g8T-fv-bWg" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="J1U-YB-kys"/>
                            <constraint firstItem="g8T-fv-bWg" firstAttribute="top" secondItem="M5B-6m-yHD" secondAttribute="bottom" constant="8" id="S8a-NF-fyJ"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="TpI-O1-PZx"/>
                            <constraint firstItem="YYx-UX-emU" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="jn7-Kc-DzS"/>
                            <constraint firstItem="YYx-UX-emU" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="myS-Ve-QB6"/>
                            <constraint firstItem="9aI-yW-7Tw" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="rra-Ar-7og"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="callButton" destination="9aI-yW-7Tw" id="Kh9-cg-IdH"/>
                        <outlet property="simulateCallButton" destination="M5B-6m-yHD" id="U2Q-QK-ta3"/>
                        <outlet property="simulateCallButton2" destination="g8T-fv-bWg" id="ABY-0C-tDn"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="32.799999999999997" y="93.103448275862078"/>
        </scene>
    </scenes>
    <resources>
        <image name="baseHeroMount" width="374" height="667"/>
    </resources>
</document>


================================================
FILE: CallKit/CallKitDemo/CallKitDemo-Bridging-Header.h
================================================
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "OTDefaultAudioDevice.h"


================================================
FILE: CallKit/CallKitDemo/CallKitDemo.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>aps-environment</key>
	<string>development</string>
</dict>
</plist>


================================================
FILE: CallKit/CallKitDemo/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLName</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER).url-scheme.dial</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>callkitdemo</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSCameraUsageDescription</key>
	<string>$(PRODUCT_NAME) uses camera</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>$(PRODUCT_NAME) uses microphone</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>audio</string>
		<string>fetch</string>
		<string>remote-notification</string>
		<string>voip</string>
	</array>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
</dict>
</plist>


================================================
FILE: CallKit/CallKitDemo/OTDefaultAudioDevice.h
================================================
//
//  OTAudioDeviceIOSDefault.h
//
//  Copyright (c) 2014 TokBox, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <OpenTok/OpenTok.h>

#define kMixerInputBusCount 2
#define kOutputBus 0
#define kInputBus 1

#define AUDIO_DEVICE_HEADSET     @"AudioSessionManagerDevice_Headset"
#define AUDIO_DEVICE_BLUETOOTH   @"AudioSessionManagerDevice_Bluetooth"
#define AUDIO_DEVICE_SPEAKER     @"AudioSessionManagerDevice_Speaker"


@interface OTDefaultAudioDevice : NSObject <OTAudioDevice>
{
    AudioStreamBasicDescription stream_format;
}

/**
 Returns YES if a wired headset is available.
 */
@property (nonatomic, readonly) BOOL headsetDeviceAvailable;

/**
 Returns YES if a bluetooth device is available.
 */
@property (nonatomic, readonly) BOOL bluetoothDeviceAvailable;

- (BOOL)setAudioBus:(id<OTAudioBus>)audioBus;

/**
 * Audio device lifecycle should live for the duration of the process, and
 * needs to be set before OTSession is initialized.
 *
 * It is not recommended to initialize unique audio device instances.
 */
+ (instancetype)sharedInstance;
+ (instancetype)sharedInstanceWithAudioSession:(AVAudioSession *)audioSession;

- (OTAudioFormat*)captureFormat;
- (OTAudioFormat*)renderFormat;

- (BOOL)renderingIsAvailable;
- (BOOL)initializeRendering;
- (BOOL)renderingIsInitialized;
- (BOOL)captureIsAvailable;
- (BOOL)initializeCapture;
- (BOOL)captureIsInitialized;

- (BOOL)startRendering;
- (BOOL)stopRendering;
- (BOOL)isRendering;
- (BOOL)startCapture;
- (BOOL)stopCapture;
- (BOOL)isCapturing;

- (uint16_t)estimatedRenderDelay;
- (uint16_t)estimatedCaptureDelay;

//desired Audio Route can be bluetooth and headset.
//bluetooth has higher priority of all, next headset, next speaker
- (BOOL)configureAudioSessionWithDesiredAudioRoute:(NSString*)desiredAudioRoute;
- (BOOL)detectCurrentRoute;

- (BOOL)setPlayOutRenderCallback:(AudioUnit)unit;

@end


================================================
FILE: CallKit/CallKitDemo/OTDefaultAudioDevice.m
================================================
//
//  OTDefaultAudioDeviceIOS.m
//
//  Copyright (c) 2014 TokBox, Inc. All rights reserved.
//

#import "OTDefaultAudioDevice.h"
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#include <mach/mach.h>
#include <mach/mach_time.h>


/*
 *  System Versioning Preprocessor Macros
 */

#define SYSTEM_VERSION_EQUAL_TO(v) \
([[[UIDevice currentDevice] systemVersion] compare:v \
options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) \
([[[UIDevice currentDevice] systemVersion] compare:v \
options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \
([[[UIDevice currentDevice] systemVersion] compare:v \
options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) \
([[[UIDevice currentDevice] systemVersion] compare:v \
options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) \
([[[UIDevice currentDevice] systemVersion] compare:v \
options:NSNumericSearch] != NSOrderedDescending)


// Simulator *must* run at 44.1 kHz in order to function properly.
#if (TARGET_IPHONE_SIMULATOR)
#define kSampleRate 44100
#else
#define kSampleRate 48000
#endif

#define OT_ENABLE_AUDIO_DEBUG 0

#if OT_ENABLE_AUDIO_DEBUG
#define OT_AUDIO_DEBUG(fmt, ...) NSLog(fmt, ##__VA_ARGS__)
#else
#define OT_AUDIO_DEBUG(fmt, ...)
#endif

static double kPreferredIOBufferDuration = 0.01;

static mach_timebase_info_data_t info;

static OSStatus recording_cb(void *ref_con,
                             AudioUnitRenderActionFlags *action_flags,
                             const AudioTimeStamp *time_stamp,
                             UInt32 bus_num,
                             UInt32 num_frames,
                             AudioBufferList *data);

static OSStatus playout_cb(void *ref_con,
                           AudioUnitRenderActionFlags *action_flags,
                           const AudioTimeStamp *time_stamp,
                           UInt32 bus_num,
                           UInt32 num_frames,
                           AudioBufferList *data);

@interface OTDefaultAudioDevice ()
- (BOOL) setupAudioUnit:(AudioUnit *)voice_unit playout:(BOOL)isPlayout;
- (void) setupListenerBlocks;
@property (assign) BOOL isAudioSessionSetup;
@end



@implementation OTDefaultAudioDevice
{
    OTAudioFormat *_audioFormat;
    
    AudioUnit recording_voice_unit;
    AudioUnit playout_voice_unit;
    BOOL playing;
    BOOL playout_initialized;
    BOOL recording;
    BOOL recording_initialized;
    BOOL interrupted_playback;
    NSString* _previousAVAudioSessionCategory;
    NSString* avAudioSessionMode;
    double avAudioSessionPreffSampleRate;
    NSInteger avAudioSessionChannels;
    BOOL isRecorderInterrupted;
    BOOL isPlayerInterrupted;
    BOOL areListenerBlocksSetup;
    BOOL _isResetting;
    int _restartRetryCount;
    AVAudioSession* _avAudioSession;
    
    /* synchronize all access to the audio subsystem */
    dispatch_queue_t _safetyQueue;
    
@public
    id _audioBus;
    
    AudioBufferList *buffer_list;
    uint32_t buffer_num_frames;
    uint32_t buffer_size;
    uint32_t _recordingDelay;
    uint32_t _playoutDelay;
    uint32_t _playoutDelayMeasurementCounter;
    uint32_t _recordingDelayHWAndOS;
    uint32_t _recordingDelayMeasurementCounter;
    Float64 _playout_AudioUnitProperty_Latency;
    Float64 _recording_AudioUnitProperty_Latency;
}

#pragma mark - OTAudioDeviceImplementation

- (instancetype)init
{
    self = [super init];
    if (self) {
        _audioFormat = [[OTAudioFormat alloc] init];
        _audioFormat.sampleRate = kSampleRate;
        _audioFormat.numChannels = 1;
        _safetyQueue = dispatch_queue_create("ot-audio-driver",
                                             DISPATCH_QUEUE_SERIAL);
        _restartRetryCount = 0;
    }
    return self;
}

+ (instancetype)sharedInstance {
    static OTDefaultAudioDevice* _sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[OTDefaultAudioDevice alloc] init];
        [_sharedInstance setupAudioSession:nil];
    });
    return _sharedInstance;
}

+ (instancetype)sharedInstanceWithAudioSession:(AVAudioSession *)audioSession
{
    static OTDefaultAudioDevice* _sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[OTDefaultAudioDevice alloc] init];
        [_sharedInstance setupAudioSession:audioSession];
    });
    return _sharedInstance;
}

- (BOOL)setAudioBus:(id<OTAudioBus>)audioBus
{
    _audioBus = audioBus;
    _audioFormat = [[OTAudioFormat alloc] init];
    _audioFormat.sampleRate = kSampleRate;
    _audioFormat.numChannels = 1;
    
    return YES;
}

- (void)dealloc
{
    [self removeObservers];
    [self teardownAudio];
    _audioFormat = nil;
}

- (OTAudioFormat*)captureFormat
{
    return _audioFormat;
}

- (OTAudioFormat*)renderFormat
{
    return _audioFormat;
}

- (BOOL)renderingIsAvailable
{
    return YES;
}

// Audio Unit lifecycle is bound to start/stop cycles, so we don't have much
// to do here.
- (BOOL)initializeRendering
{
    if (playing) {
        return NO;
    }
    if (playout_initialized) {
        return YES;
    }
    playout_initialized = true;
    return YES;
}

- (BOOL)renderingIsInitialized
{
    return playout_initialized;
}

- (BOOL)captureIsAvailable
{
    return YES;
}

// Audio Unit lifecycle is bound to start/stop cycles, so we don't have much
// to do here.
- (BOOL)initializeCapture
{
    if (recording) {
        return NO;
    }
    if (recording_initialized) {
        return YES;
    }
    recording_initialized = true;
    return YES;
}

- (BOOL)captureIsInitialized
{
    return recording_initialized;
}

- (BOOL)startRendering
{
    @synchronized(self) {
        OT_AUDIO_DEBUG(@"startRendering %d", playing);
        
        if (playing) {
            return YES;
        }
        
        playing = YES;
        // Initialize only when playout voice unit is already teardown
        if(playout_voice_unit == NULL)
        {
            if (NO == [self setupAudioUnit:&playout_voice_unit playout:YES]) {
                playing = NO;
                return NO;
            }
        }
        
        OSStatus result = AudioOutputUnitStart(playout_voice_unit);
        if (CheckError(result, @"startRendering.AudioOutputUnitStart")) {
            playing = NO;
        }
        
        return playing;
    }
}

- (BOOL)stopRendering
{
    @synchronized(self) {
        OT_AUDIO_DEBUG(@"stopRendering %d", playing);
        isPlayerInterrupted = NO;
        if (!playing) {
            return YES;
        }
        
        playing = NO;
        
        OSStatus result = AudioOutputUnitStop(playout_voice_unit);
        if (CheckError(result, @"stopRendering.AudioOutputUnitStop")) {
            return NO;
        }
        
        // publisher is already closed
        if (!recording && !_isResetting)
        {
            OT_AUDIO_DEBUG(@"teardownAudio from stopRendering");
            [self teardownAudio];
        }

        return YES;
    }
}

- (BOOL)isRendering
{
    return playing;
}

- (BOOL)startCapture
{
    @synchronized(self) {
        OT_AUDIO_DEBUG(@"startCapture %d", recording);
        
        if (recording) {
            return YES;
        }
        
        recording = YES;
        // Initialize only when recording voice unit is already teardown
        if(recording_voice_unit == NULL)
        {
            if (NO == [self setupAudioUnit:&recording_voice_unit playout:NO]) {
                recording = NO;
                return NO;
            }
        }
        
        OSStatus result = AudioOutputUnitStart(recording_voice_unit);
        if (CheckError(result, @"startCapture.AudioOutputUnitStart")) {
            recording = NO;
        }
        
        return recording;
    }
}

- (BOOL)stopCapture
{
    @synchronized(self) {
        OT_AUDIO_DEBUG(@"stopCapture %d", recording);
        isRecorderInterrupted = NO;
        if (!recording) {
            return YES;
        }
        
        recording = NO;
        
        OSStatus result = AudioOutputUnitStop(recording_voice_unit);
        
        if (CheckError(result, @"stopCapture.AudioOutputUnitStop")) {
            return NO;
        }
        
        [self freeupAudioBuffers];
        
        // subscriber is already closed
        if (!playing && !_isResetting)
        {
            OT_AUDIO_DEBUG(@"teardownAudio from stopCapture");
            [self teardownAudio];
        }
        return YES;
    }
}

- (BOOL)isCapturing
{
    return recording;
}

- (uint16_t)estimatedRenderDelay
{
    return _playoutDelay;
}

- (uint16_t)estimatedCaptureDelay
{
    return _recordingDelay;
}

#pragma mark - AudioSession Setup

static NSString* FormatError(OSStatus error)
{
    uint32_t as_int = CFSwapInt32HostToLittle(error);
    uint8_t* as_char = (uint8_t*) &as_int;
    // see if it appears to be a 4-char-code
    if (isprint(as_char[0]) &&
        isprint(as_char[1]) &&
        isprint(as_char[2]) &&
        isprint(as_char[3]))
    {
        return [NSString stringWithFormat:@"%c%c%c%c",
                as_int >> 24, as_int >> 16, as_int >> 8, as_int];
    }
    else
    {
        // no, format it as an integer
        return [NSString stringWithFormat:@"%d", error];
    }
}

/**
 * @return YES if in error
 */
static bool CheckError(OSStatus error, NSString* function) {
    if (!error) return NO;
    
    NSString* error_string = FormatError(error);
    NSLog(@"ERROR[OpenTok]:Audio device error: %@ returned error: %@",
          function, error_string);
    
    return YES;
}

- (void)checkAndPrintError:(OSStatus)error function:(NSString *)function
{
    CheckError(error,function);
}

- (void)disposePlayoutUnit
{
    if (playout_voice_unit) {
        AudioUnitUninitialize(playout_voice_unit);
        AudioComponentInstanceDispose(playout_voice_unit);
        playout_voice_unit = NULL;
    }
}

- (void)disposeRecordUnit
{
    if (recording_voice_unit) {
        AudioUnitUninitialize(recording_voice_unit);
        AudioComponentInstanceDispose(recording_voice_unit);
        recording_voice_unit = NULL;
    }
}

- (void) teardownAudio
{
    [self disposePlayoutUnit];
    [self disposeRecordUnit];
    [self freeupAudioBuffers];
    
    AVAudioSession *mySession = [AVAudioSession sharedInstance];
    [mySession setCategory:_previousAVAudioSessionCategory error:nil];
    [mySession setMode:avAudioSessionMode error:nil];
    [mySession setPreferredSampleRate: avAudioSessionPreffSampleRate
                                error: nil];
    [mySession setPreferredInputNumberOfChannels:avAudioSessionChannels
                                           error:nil];
    
    self.isAudioSessionSetup = NO;
}

- (void)freeupAudioBuffers
{
    if (buffer_list && buffer_list->mBuffers[0].mData) {
        free(buffer_list->mBuffers[0].mData);
        buffer_list->mBuffers[0].mData = NULL;
    }
    
    if (buffer_list) {
        free(buffer_list);
        buffer_list = NULL;
        buffer_num_frames = 0;
    }
}

- (void) setupAudioSession
{
    AVAudioSession *mySession = [AVAudioSession sharedInstance];
    _previousAVAudioSessionCategory = mySession.category;
    avAudioSessionMode = mySession.mode;
    avAudioSessionPreffSampleRate = mySession.preferredSampleRate;
    avAudioSessionChannels = mySession.inputNumberOfChannels;
    
    [mySession setPreferredSampleRate: kSampleRate error: nil];
    [mySession setPreferredInputNumberOfChannels:1 error:nil];
    [mySession setPreferredIOBufferDuration:kPreferredIOBufferDuration
                                      error:nil];
    
    NSError *error = nil;
    NSUInteger audioOptions = 0;
#if !(TARGET_OS_TV)
    audioOptions |= AVAudioSessionCategoryOptionAllowBluetooth ;
    audioOptions |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    [mySession setCategory:AVAudioSessionCategoryPlayAndRecord
               withOptions:audioOptions
                     error:&error];
#else
    [mySession setCategory:AVAudioSessionCategoryPlayback
               withOptions:audioOptions
                     error:&error];
#endif
    
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
        [mySession setMode:AVAudioSessionModeVideoChat error:nil];
    }
    else {
        [mySession setMode:AVAudioSessionModeVoiceChat error:nil];
    }
    
    if (error)
        OT_AUDIO_DEBUG(@"Audiosession setCategory %@",error);
    
    error = nil;
    
    [self setupListenerBlocks];
    [mySession setActive:YES error:&error];
    
    if (error)
        OT_AUDIO_DEBUG(@"Audiosession setActive %@",error);
    
}

- (void) setupAudioSession:(AVAudioSession *)audioSession
{
    if (self.isAudioSessionSetup) return;
    self.isAudioSessionSetup = YES;
    
    AVAudioSession *mySession = audioSession;
    if (mySession == nil) {
        mySession = [AVAudioSession sharedInstance];
    }
    _previousAVAudioSessionCategory = mySession.category;
    avAudioSessionMode = mySession.mode;
    avAudioSessionPreffSampleRate = mySession.preferredSampleRate;
    avAudioSessionChannels = mySession.inputNumberOfChannels;
    
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
        [mySession setMode:AVAudioSessionModeVideoChat error:nil];
    }
    else {
        [mySession setMode:AVAudioSessionModeVoiceChat error:nil];
    }
    
    [mySession setPreferredSampleRate: kSampleRate error: nil];
    [mySession setPreferredInputNumberOfChannels:1 error:nil];
    [mySession setPreferredIOBufferDuration:kPreferredIOBufferDuration
                                      error:nil];
    
    NSError *error = nil;
    NSUInteger audioOptions = AVAudioSessionCategoryOptionMixWithOthers;
#if !(TARGET_OS_TV)
    audioOptions |= AVAudioSessionCategoryOptionAllowBluetooth ;
    audioOptions |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    [mySession setCategory:AVAudioSessionCategoryPlayAndRecord
               withOptions:audioOptions
                     error:&error];
#else
    [mySession setCategory:AVAudioSessionCategoryPlayback
               withOptions:audioOptions
                     error:&error];
#endif
    
    if (error)
        OT_AUDIO_DEBUG(@"Audiosession setCategory %@",error);
    
    error = nil;
    
    [self setupListenerBlocks];
    [mySession setActive:YES error:&error];
    
    if (error)
        OT_AUDIO_DEBUG(@"Audiosession setActive %@",error);
    
}

- (void)setBluetoothAsPrefferedInputDevice
{
    // Apple's Bug(???) : Audio Interruption Ended notification won't be called
    // for bluetooth devices if we dont set preffered input as bluetooth.
    // Should work for non bluetooth routes/ports too. This makes both input
    // and output to bluetooth device if available.
    NSArray* bluetoothRoutes = @[AVAudioSessionPortBluetoothA2DP,
                                 AVAudioSessionPortBluetoothLE,
                                 AVAudioSessionPortBluetoothHFP];
    NSArray* routes = [[AVAudioSession sharedInstance] availableInputs];
    for (AVAudioSessionPortDescription* route in routes)
    {
        if ([bluetoothRoutes containsObject:route.portType])
        {
            [[AVAudioSession sharedInstance] setPreferredInput:route
                                                         error:nil];
            break;
        }
    }
    
}

#pragma mark - System interruptions and audio route changes

- (void) onInterruptionEvent:(NSNotification *) notification
{
    OT_AUDIO_DEBUG(@"onInterruptionEvent %@",notification);
    
    NSDictionary *interruptionDict = notification.userInfo;
    NSInteger interruptionType =
    [[interruptionDict valueForKey:AVAudioSessionInterruptionTypeKey]
     integerValue];
    
    dispatch_async(_safetyQueue, ^() {
        [self handleInterruptionEvent:interruptionType];
    });
}

- (void) handleInterruptionEvent:(NSInteger) interruptionType
{
    @synchronized(self) {
        OT_AUDIO_DEBUG(@"handleInterruptionEvent %ld",(long)interruptionType);
        switch (interruptionType) {
            case AVAudioSessionInterruptionTypeBegan:
            {
                OT_AUDIO_DEBUG(@"AVAudioSessionInterruptionTypeBegan");
                if(recording)
                {
                    // DONT change the order of the following as
                    // stopCapture sets isRecorderInterrupted to NO
                    [self stopCapture];
                    isRecorderInterrupted = YES;
                }
                if(playing)
                {
                    // DONT change the order of the following as
                    // stopRendering sets isPlayerInterrupted to NO
                    [self stopRendering];
                    isPlayerInterrupted = YES;
                }
            }
                break;
                
            case AVAudioSessionInterruptionTypeEnded:
            {
                OT_AUDIO_DEBUG(@"AVAudioSessionInterruptionTypeEnded");
                // Reconfigure audio session with highest priority device
                [self configureAudioSessionWithDesiredAudioRoute:
                 AUDIO_DEVICE_BLUETOOTH];
                if(isRecorderInterrupted)
                {
                    if([self startCapture] == YES)
                    {
                        isRecorderInterrupted = NO;
                        _restartRetryCount = 0;
                    } else
                    {
                        _restartRetryCount++;
                        if(_restartRetryCount < 3)
                        {
                            dispatch_after(
                                           dispatch_time(
                                                         DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC),
                                           _safetyQueue, ^{
                                               [self handleInterruptionEvent:
                                                AVAudioSessionInterruptionTypeEnded];
                                           });
                        } else // This shouldn't happen!
                        {
                            // When a call get resumed, we get first audio interruption notification from iOS
                            // but the audio fails, since iOS holds the audio session until user presses "Unhold call",
                            // When user press "Unhold call" we post a fake interruption notification from "didActivate" audioSession callback
                            // So for the second time, we need to keep isRecorderInterrupted and isPlayerInterrupted to YES.
                            // The reason for this hack is, we don't want to modify the audio driver much to avoid future audio driver
                            // upgrades from iOS SDK internal driver.
                            //isRecorderInterrupted = NO;
                            //isPlayerInterrupted = NO;
                            _restartRetryCount = 0;
                            NSLog(@"ERROR[OpenTok]:Unable to acquire audio session");
                        }
                        return;
                    }
                }
                
                if(isPlayerInterrupted)
                {
                    isPlayerInterrupted = NO;
                    [self startRendering];
                }
                
            }
                break;
                
            default:
                OT_AUDIO_DEBUG(@"Audio Session Interruption Notification"
                               " case default.");
                break;
        }
    }
}

- (void) onRouteChangeEvent:(NSNotification *) notification
{
    OT_AUDIO_DEBUG(@"onRouteChangeEvent %@",notification);
    dispatch_async(_safetyQueue, ^() {
        [self handleRouteChangeEvent:notification];
    });
}

- (void) handleRouteChangeEvent:(NSNotification *) notification
{
    NSDictionary *interruptionDict = notification.userInfo;
    NSInteger routeChangeReason =
    [[interruptionDict valueForKey:AVAudioSessionRouteChangeReasonKey]
     integerValue];
    
    // We'll receive a routeChangedEvent when the audio unit starts; don't
    // process events we caused internally.
    if (AVAudioSessionRouteChangeReasonRouteConfigurationChange ==
        routeChangeReason)
    {
        return;
    }
    
    if(routeChangeReason == AVAudioSessionRouteChangeReasonOverride ||
       routeChangeReason == AVAudioSessionRouteChangeReasonCategoryChange)
    {
        NSString *oldOutputDeviceName = nil;
        NSString *currentOutputDeviceName = nil;
        
        AVAudioSessionRouteDescription* oldRouteDesc =
        [interruptionDict valueForKey:AVAudioSessionRouteChangePreviousRouteKey];
        NSArray<AVAudioSessionPortDescription*>* outputs =
        [oldRouteDesc outputs];
        
        if(outputs.count > 0)
        {
            AVAudioSessionPortDescription *portDesc =
            (AVAudioSessionPortDescription *)[outputs objectAtIndex:0];
            oldOutputDeviceName = [portDesc portName];
        }
        
        if([[[AVAudioSession sharedInstance] currentRoute] outputs].count > 0)
        {
            currentOutputDeviceName = [[[[[AVAudioSession sharedInstance] currentRoute] outputs]
                                        objectAtIndex:0] portName];
        }
        
        // we need check this because some times we will receive category change
        // with the same device.
        if([oldOutputDeviceName isEqualToString:currentOutputDeviceName] ||
           currentOutputDeviceName == nil ||  oldOutputDeviceName == nil) {
            return;
        }
        OT_AUDIO_DEBUG(@"routeChanged: old=%@ new=%@",
                       oldOutputDeviceName, currentOutputDeviceName);
    }
    
    @synchronized(self) {
        // We've made it here, there's been a legit route change.
        // Restart the audio units with correct sample rate
        _isResetting = YES;
        
        if (recording)
        {
            [self stopCapture];
            [self disposeRecordUnit];
            [self startCapture];
        }
        
        if (playing)
        {
            [self stopRendering];
            [self disposePlayoutUnit];
            [self startRendering];
        }
        
        _isResetting = NO;
    }
    
}

/* When ringer is off, we dont get interruption ended callback
 as mentioned in apple doc : "There is no guarantee that a begin
 interruption will have an end interruption."
 The only caveat here is, some times we get two callbacks from interruption
 handler as well as from here which we handle synchronously with safteyQueue
 */
- (void) appDidBecomeActive:(NSNotification *) notification
{
    OT_AUDIO_DEBUG(@"appDidBecomeActive %@",notification);
    dispatch_async(_safetyQueue, ^{
        [self handleInterruptionEvent:AVAudioSessionInterruptionTypeEnded];
    });
}

- (void) setupListenerBlocks
{
    if(!areListenerBlocksSetup)
    {
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        
        [center addObserver:self
                   selector:@selector(onInterruptionEvent:)
                       name:AVAudioSessionInterruptionNotification object:nil];
        
        [center addObserver:self
                   selector:@selector(onRouteChangeEvent:)
                       name:AVAudioSessionRouteChangeNotification object:nil];
        
        [center addObserver:self
                   selector:@selector(appDidBecomeActive:)
                       name:UIApplicationDidBecomeActiveNotification
                     object:nil];
        
        areListenerBlocksSetup = YES;
    }
}

- (void) removeObservers
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self];
    areListenerBlocksSetup = NO;
}

#pragma mark - native audio callbacks

static void update_recording_delay(OTDefaultAudioDevice* device) {
    
    device->_recordingDelayMeasurementCounter++;
    
    if (device->_recordingDelayMeasurementCounter >= 100) {
        // Update HW and OS delay every second, unlikely to change
        
        device->_recordingDelayHWAndOS = 0;
        
        AVAudioSession *mySession = [AVAudioSession sharedInstance];
        
        // HW input latency
        NSTimeInterval interval = [mySession inputLatency];
        
        device->_recordingDelayHWAndOS += (int)(interval * 1000000);
        
        // HW buffer duration
        interval = [mySession IOBufferDuration];
        device->_recordingDelayHWAndOS += (int)(interval * 1000000);
        
        device->_recordingDelayHWAndOS += (int)(device->_recording_AudioUnitProperty_Latency * 1000000);
        
        // To ms
        device->_recordingDelayHWAndOS =
        (device->_recordingDelayHWAndOS - 500) / 1000;
        
        // Reset counter
        device->_recordingDelayMeasurementCounter = 0;
    }
    
    device->_recordingDelay = device->_recordingDelayHWAndOS;
}

static OSStatus recording_cb(void *ref_con,
                             AudioUnitRenderActionFlags *action_flags,
                             const AudioTimeStamp *time_stamp,
                             UInt32 bus_num,
                             UInt32 num_frames,
                             AudioBufferList *data)
{
    
    OTDefaultAudioDevice *dev = (__bridge OTDefaultAudioDevice*) ref_con;
    
    if (!dev->buffer_list || num_frames > dev->buffer_num_frames)
    {
        if (dev->buffer_list) {
            free(dev->buffer_list->mBuffers[0].mData);
            free(dev->buffer_list);
        }
        
        dev->buffer_list =
        (AudioBufferList*)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer));
        dev->buffer_list->mNumberBuffers = 1;
        dev->buffer_list->mBuffers[0].mNumberChannels = 1;
        
        dev->buffer_list->mBuffers[0].mDataByteSize = num_frames*sizeof(UInt16);
        dev->buffer_list->mBuffers[0].mData = malloc(num_frames*sizeof(UInt16));
        
        dev->buffer_num_frames = num_frames;
        dev->buffer_size = dev->buffer_list->mBuffers[0].mDataByteSize;
    }
    
    OSStatus status;
    status = AudioUnitRender(dev->recording_voice_unit,
                             action_flags,
                             time_stamp,
                             1,
                             num_frames,
                             dev->buffer_list);
    
    if (status != noErr) {
        CheckError(status, @"AudioUnitRender");
    }
    
    if (dev->recording) {
        
        // Some sample code to generate a sine wave instead of use the mic
        //        static double startingFrameCount = 0;
        //        double j = startingFrameCount;
        //        double cycleLength = kSampleRate. / 880.0;
        //        int frame = 0;
        //        for (frame = 0; frame < num_frames; ++frame)
        //        {
        //            int16_t* data = (int16_t*)dev->buffer_list->mBuffers[0].mData;
        //            Float32 sample = (Float32)sin (2 * M_PI * (j / cycleLength));
        //            (data)[frame] = (sample * 32767.0f);
        //            j += 1.0;
        //            if (j > cycleLength)
        //                j -= cycleLength;
        //        }
        //        startingFrameCount = j;
        [dev->_audioBus writeCaptureData:dev->buffer_list->mBuffers[0].mData
                         numberOfSamples:num_frames];
    }
    // some ocassions, AudioUnitRender only renders part of the buffer and then next
    // call to the AudioUnitRender fails with smaller buffer.
    if (dev->buffer_size != dev->buffer_list->mBuffers[0].mDataByteSize)
        dev->buffer_list->mBuffers[0].mDataByteSize = dev->buffer_size;
    
    update_recording_delay(dev);
    
    return noErr;
}

static void update_playout_delay(OTDefaultAudioDevice* device) {
    device->_playoutDelayMeasurementCounter++;
    
    if (device->_playoutDelayMeasurementCounter >= 100) {
        // Update HW and OS delay every second, unlikely to change
        
        device->_playoutDelay = 0;
        
        AVAudioSession *mySession = [AVAudioSession sharedInstance];
        
        // HW output latency
        NSTimeInterval interval = [mySession outputLatency];
        
        device->_playoutDelay += (int)(interval * 1000000);
        
        // HW buffer duration
        interval = [mySession IOBufferDuration];
        device->_playoutDelay += (int)(interval * 1000000);
        
        device->_playoutDelay += (int)(device->_playout_AudioUnitProperty_Latency * 1000000);
        
        // To ms
        device->_playoutDelay = (device->_playoutDelay - 500) / 1000;
        
        // Reset counter
        device->_playoutDelayMeasurementCounter = 0;
    }
}

static OSStatus playout_cb(void *ref_con,
                           AudioUnitRenderActionFlags *action_flags,
                           const AudioTimeStamp *time_stamp,
                           UInt32 bus_num,
                           UInt32 num_frames,
                           AudioBufferList *buffer_list)
{
    OTDefaultAudioDevice *dev = (__bridge OTDefaultAudioDevice*) ref_con;
    
    if (!dev->playing) { return 0; }
    
    uint32_t count =
    [dev->_audioBus readRenderData:buffer_list->mBuffers[0].mData
                   numberOfSamples:num_frames];
    
    if (count != num_frames) {
        //TODO: Not really an error, but conerning. Network issues?
    }
    
    update_playout_delay(dev);
    
    return 0;
}

#pragma mark - BlueTooth

- (BOOL)isBluetoothDevice:(NSString*)portType {
    
    return ([portType isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
            [portType isEqualToString:AVAudioSessionPortBluetoothHFP]);
}


- (BOOL)detectCurrentRoute
{
    // called on startup to initialize the devices that are available...
    OT_AUDIO_DEBUG(@"detect current route");
    
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    
    _headsetDeviceAvailable = _bluetoothDeviceAvailable = NO;
    
    //ios 8.0 complains about Deactivating an audio session that has running
    // I/O. All I/O should be stopped or paused prior to deactivating the audio
    // session. Looks like we can get away by not using the setActive call
    if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"7.0")) {
        // close down our current session...
        [audioSession setActive:NO error:nil];
        
        // start a new audio session. Without activation, the default route will
        // always be (inputs: null, outputs: Speaker)
        [audioSession setActive:YES error:nil];
    }
    
    // Check for current route
    AVAudioSessionRouteDescription *currentRoute = [audioSession currentRoute];
    for (AVAudioSessionPortDescription *output in currentRoute.outputs) {
        if ([[output portType] isEqualToString:AVAudioSessionPortHeadphones]) {
            _headsetDeviceAvailable = YES;
        } else if ([self isBluetoothDevice:[output portType]]) {
            _bluetoothDeviceAvailable = YES;
        }
    }
    
    if (_headsetDeviceAvailable) {
        OT_AUDIO_DEBUG(@"Current route is Headset");
    }
    
    if (_bluetoothDeviceAvailable) {
        OT_AUDIO_DEBUG(@"Current route is Bluetooth");
    }
    
    if(!_bluetoothDeviceAvailable && !_headsetDeviceAvailable) {
        OT_AUDIO_DEBUG(@"Current route is device speaker");
    }
    
    return YES;
}

- (BOOL)configureAudioSessionWithDesiredAudioRoute:(NSString*)desiredAudioRoute
{
    OT_AUDIO_DEBUG(@"configureAudioSessionWithDesiredAudioRoute %@",desiredAudioRoute);
    
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *err;
    
    //ios 8.0 complains about Deactivating an audio session that has running
    // I/O. All I/O should be stopped or paused prior to deactivating the audio
    // session. Looks like we can get away by not using the setActive call
    if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"7.0")) {
        // close down our current session...
        [audioSession setActive:NO error:nil];
    }
    
    if ([AUDIO_DEVICE_BLUETOOTH isEqualToString:desiredAudioRoute]) {
        [self setBluetoothAsPrefferedInputDevice];
    }
    
    if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"7.0")) {
        // Set our session to active...
        if (![audioSession setActive:YES error:&err]) {
            NSLog(@"unable to set audio session active: %@", err);
            return NO;
        }
    }
    
    if ([AUDIO_DEVICE_SPEAKER isEqualToString:desiredAudioRoute]) {
        // replace AudiosessionSetProperty (deprecated from iOS7) with
        // AVAudioSession overrideOutputAudioPort
#if !(TARGET_OS_TV)
        [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
                                        error:&err];
#endif
    } else
    {
        [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone
                                        error:&err];
    }
    
    return YES;
}

- (BOOL)setupAudioUnit:(AudioUnit *)voice_unit playout:(BOOL)isPlayout;
{
    OSStatus result;
    
    mach_timebase_info(&info);
    
    if (!self.isAudioSessionSetup)
    {
        [self setupAudioSession];
        self.isAudioSessionSetup = YES;
    }
    
    UInt32 bytesPerSample = sizeof(SInt16);
    stream_format.mFormatID    = kAudioFormatLinearPCM;
    stream_format.mFormatFlags =
    kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    stream_format.mBytesPerPacket  = bytesPerSample;
    stream_format.mFramesPerPacket = 1;
    stream_format.mBytesPerFrame   = bytesPerSample;
    stream_format.mChannelsPerFrame= 1;
    stream_format.mBitsPerChannel  = 8 * bytesPerSample;
    stream_format.mSampleRate = (Float64) kSampleRate;
    
    AudioComponentDescription audio_unit_description;
    audio_unit_description.componentType = kAudioUnitType_Output;
    
#if !(TARGET_OS_TV)
    audio_unit_description.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
#else
    audio_unit_description.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
    
    audio_unit_description.componentManufacturer = kAudioUnitManufacturer_Apple;
    audio_unit_description.componentFlags = 0;
    audio_unit_description.componentFlagsMask = 0;
    
    AudioComponent found_vpio_unit_ref =
    AudioComponentFindNext(NULL, &audio_unit_description);
    
    result = AudioComponentInstanceNew(found_vpio_unit_ref, voice_unit);
    
    if (CheckError(result, @"setupAudioUnit.AudioComponentInstanceNew")) {
        return NO;
    }
    
    if (!isPlayout)
    {
        UInt32 enable_input = 1;
        AudioUnitSetProperty(*voice_unit, kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Input, kInputBus, &enable_input,
                             sizeof(enable_input));
        AudioUnitSetProperty(*voice_unit, kAudioUnitProperty_StreamFormat,
                             kAudioUnitScope_Output, kInputBus,
                             &stream_format, sizeof (stream_format));
        AURenderCallbackStruct input_callback;
        input_callback.inputProc = recording_cb;
        input_callback.inputProcRefCon = (__bridge void *)(self);
        
        AudioUnitSetProperty(*voice_unit,
                             kAudioOutputUnitProperty_SetInputCallback,
                             kAudioUnitScope_Global, kInputBus, &input_callback,
                             sizeof(input_callback));
        UInt32 flag = 0;
        AudioUnitSetProperty(*voice_unit, kAudioUnitProperty_ShouldAllocateBuffer,
                             kAudioUnitScope_Output, kInputBus, &flag,
                             sizeof(flag));
        // Disable Output on record
        UInt32 enable_output = 0;
        AudioUnitSetProperty(*voice_unit, kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Output, kOutputBus, &enable_output,
                             sizeof(enable_output));
        
    } else
    {
        UInt32 enable_output = 1;
        AudioUnitSetProperty(*voice_unit, kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Output, kOutputBus, &enable_output,
                             sizeof(enable_output));
        AudioUnitSetProperty(*voice_unit, kAudioUnitProperty_StreamFormat,
                             kAudioUnitScope_Input, kOutputBus,
                             &stream_format, sizeof (stream_format));
        // Disable Input on playout
        UInt32 enable_input = 0;
        AudioUnitSetProperty(*voice_unit, kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Input, kInputBus, &enable_input,
                             sizeof(enable_input));
        [self setPlayOutRenderCallback:*voice_unit];
    }
    
    Float64 f64 = 0;
    UInt32 size = sizeof(f64);
    OSStatus latency_result = AudioUnitGetProperty(*voice_unit,
                                                   kAudioUnitProperty_Latency,
                                                   kAudioUnitScope_Global,
                                                   0, &f64, &size);
    if (!isPlayout)
    {
        _recording_AudioUnitProperty_Latency = (0 == latency_result) ? f64 : 0;
    }
    else
    {
        _playout_AudioUnitProperty_Latency = (0 == latency_result) ? f64 : 0;
    }
    
    // Initialize the Voice-Processing I/O unit instance.
    result = AudioUnitInitialize(*voice_unit);
    if (CheckError(result, @"setupAudioUnit.AudioUnitInitialize")) {
        return NO;
    }
    
    [self setBluetoothAsPrefferedInputDevice];
    return YES;
}

- (BOOL)setPlayOutRenderCallback:(AudioUnit)unit
{
    AURenderCallbackStruct render_callback;
    render_callback.inputProc = playout_cb;;
    render_callback.inputProcRefCon = (__bridge void *)(self);
    OSStatus result = AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback,
                                           kAudioUnitScope_Input, kOutputBus, &render_callback,
                                           sizeof(render_callback));
    return (result == 0);
}

@end


================================================
FILE: CallKit/CallKitDemo/ProviderDelegate.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	CallKit provider delegate class, which conforms to CXProviderDelegate protocol
*/

import Foundation
import UIKit
import CallKit
import AVFoundation
import OpenTok

final class ProviderDelegate: NSObject, CXProviderDelegate {

    let callManager: SpeakerboxCallManager
    private let provider: CXProvider

    init(callManager: SpeakerboxCallManager) {
        self.callManager = callManager
        provider = CXProvider(configuration: type(of: self).providerConfiguration)

        super.init()

        provider.setDelegate(self, queue: nil)
    }

    /// The app's provider configuration, representing its CallKit capabilities
    static var providerConfiguration: CXProviderConfiguration {
        let localizedName = NSLocalizedString("CallKitDemo", comment: "Name of application")
        let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)

        providerConfiguration.supportsVideo = false

        providerConfiguration.maximumCallsPerCallGroup = 1

        providerConfiguration.supportedHandleTypes = [.phoneNumber]

        providerConfiguration.iconTemplateImageData = #imageLiteral(resourceName: "IconMask").pngData()

        providerConfiguration.ringtoneSound = "Ringtone.caf"
        
        return providerConfiguration
    }

    // MARK: Incoming Calls

    /// Use CXProvider to report the incoming call to the system
    func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        // Construct a CXCallUpdate describing the incoming call, including the caller.
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
        update.hasVideo = hasVideo

        // pre-heat the AVAudioSession
        //OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance())
        
        // Report the incoming call to the system
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            /*
                Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
                since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
             */
            if error == nil {
                let call = SpeakerboxCall(uuid: uuid)
                call.handle = handle

                self.callManager.addCall(call)
            }
            
            completion?(error as NSError?)
        }
    }

    // MARK: CXProviderDelegate

    func providerDidReset(_ provider: CXProvider) {
        print("Provider did reset")
        /*
            End any ongoing calls if the provider resets, and remove them from the app's list of calls,
            since they are no longer valid.
         */
    }

    var outgoingCall: SpeakerboxCall?
    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        // Create & configure an instance of SpeakerboxCall, the app's model class representing the new outgoing call.
        let call = SpeakerboxCall(uuid: action.callUUID, isOutgoing: true)
        call.handle = action.handle.value

        /*
            Configure the audio session, but do not start call audio here, since it must be done once
            the audio session has been activated by the system after having its priority elevated.
         */
        // https://forums.developer.apple.com/thread/64544
        // we can't configure the audio session here for the case of launching it from locked screen
        // instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
        // please look for  * pre-heat the AVAudioSession *
        configureAudioSession()
        
        /*
            Set callback blocks for significant events in the call's lifecycle, so that the CXProvider may be updated
            to reflect the updated state.
         */
        call.hasStartedConnectingDidChange = { [weak self] in
            self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
        }
        call.hasConnectedDidChange = { [weak self] in
            self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate)
        }

        self.outgoingCall = call
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    var answerCall: SpeakerboxCall?
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        /*
            Configure the audio session, but do not start call audio here, since it must be done once
            the audio session has been activated by the system after having its priority elevated.
         */
        
        // https://forums.developer.apple.com/thread/64544
        // we can't configure the audio session here for the case of launching it from locked screen
        // instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
        // please look for  * pre-heat the AVAudioSession *
        configureAudioSession()

        self.answerCall = call
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Trigger the call to be ended via the underlying network service.
        call.endCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()

        // Remove the ended call from the app's list of calls.
        callManager.removeCall(call)
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Update the SpeakerboxCall's underlying hold state.
        call.isOnHold = action.isOnHold

        // Stop or start audio in response to holding or unholding the call.
        call.isMuted = call.isOnHold

        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }
    
    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        
        call.isMuted = action.isMuted
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
        print("Timed out \(#function)")

        // React to the action timeout if necessary, such as showing an error UI.
    }

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        print("Received \(#function)")
        
        // If we are returning from a hold state
        if answerCall?.hasConnected ?? false {
            //configureAudioSession()
            // See more details on how this works in the OTDefaultAudioDevice.m method handleInterruptionEvent
            sendFakeAudioInterruptionNotificationToStartAudioResources();
            return
        }
        if outgoingCall?.hasConnected ?? false {
            //configureAudioSession()
            // See more details on how this works in the OTDefaultAudioDevice.m method handleInterruptionEvent
            sendFakeAudioInterruptionNotificationToStartAudioResources()
            return
        }
        
        // Start call audio media, now that the audio session has been activated after having its priority boosted.
        outgoingCall?.startCall(withAudioSession: audioSession) { [weak self] success in
            guard let outgoingCall = self?.outgoingCall else { return }
            if success {
                self?.callManager.addCall(outgoingCall)
                self?.outgoingCall?.startAudio()
            } else {
                self?.callManager.end(call: outgoingCall)
            }
        }
        
        answerCall?.answerCall(withAudioSession: audioSession) { success in
            if success {
                self.answerCall?.startAudio()
            }
        }
    }

    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        print("Received \(#function)")

        /*
             Restart any non-call related audio now that the app's audio session has been
             de-activated after having its priority restored to normal.
         */
        if outgoingCall?.isOnHold ?? false || answerCall?.isOnHold ?? false {
            print("Call is on hold. Do not terminate any call")
            return
        }
        
        outgoingCall?.endCall()
        outgoingCall = nil
        answerCall?.endCall()
        answerCall = nil
        callManager.removeAllCalls()
    }
    
    func sendFakeAudioInterruptionNotificationToStartAudioResources() {
        var userInfo = Dictionary<AnyHashable, Any>()
        let interrupttioEndedRaw = AVAudioSession.InterruptionType.ended.rawValue
        userInfo[AVAudioSessionInterruptionTypeKey] = interrupttioEndedRaw
        NotificationCenter.default.post(name: AVAudioSession.interruptionNotification, object: self, userInfo: userInfo)
    }
    
    func configureAudioSession() {
        // See https://forums.developer.apple.com/thread/64544
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(AVAudioSession.Category.playAndRecord, mode: .default)
            try session.setActive(true)
            try session.setMode(AVAudioSession.Mode.voiceChat)
            try session.setPreferredSampleRate(44100.0)
            try session.setPreferredIOBufferDuration(0.005)
        } catch {
            print(error)
        }
    }
}


================================================
FILE: CallKit/CallKitDemo/SpeakerboxCall.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	Model class representing a single call
*/

import Foundation
import OpenTok

final class SpeakerboxCall: NSObject {

    // MARK: Metadata Properties

    let uuid: UUID
    let isOutgoing: Bool
    var handle: String?

    // MARK: Call State Properties

    var connectingDate: Date? {
        didSet {
            stateDidChange?()
            hasStartedConnectingDidChange?()
        }
    }
    var connectDate: Date? {
        didSet {
            stateDidChange?()
            hasConnectedDidChange?()
        }
    }
    var endDate: Date? {
        didSet {
            stateDidChange?()
            hasEndedDidChange?()
        }
    }
    var isOnHold = false {
        didSet {
            publisher?.publishAudio = !isOnHold
            stateDidChange?()
        }
    }
    
    var isMuted = false {
        didSet {
            publisher?.publishAudio = !isMuted
        }
    }

    // MARK: State change callback blocks

    var stateDidChange: (() -> Void)?
    var hasStartedConnectingDidChange: (() -> Void)?
    var hasConnectedDidChange: (() -> Void)?
    var hasEndedDidChange: (() -> Void)?
    var audioChange: (() -> Void)?

    // MARK: Derived Properties

    var hasStartedConnecting: Bool {
        get {
            return connectingDate != nil
        }
        set {
            connectingDate = newValue ? Date() : nil
        }
    }
    var hasConnected: Bool {
        get {
            return connectDate != nil
        }
        set {
            connectDate = newValue ? Date() : nil
        }
    }
    var hasEnded: Bool {
        get {
            return endDate != nil
        }
        set {
            endDate = newValue ? Date() : nil
        }
    }
    var duration: TimeInterval {
        guard let connectDate = connectDate else {
            return 0
        }

        return Date().timeIntervalSince(connectDate)
    }

    // MARK: Initialization

    init(uuid: UUID, isOutgoing: Bool = false) {
        self.uuid = uuid
        self.isOutgoing = isOutgoing
    }

    // MARK: Actions
    var session: OTSession?
    var publisher: OTPublisher?
    var subscriber: OTSubscriber?
    
    var canStartCall: ((Bool) -> Void)?
    func startCall(withAudioSession audioSession: AVAudioSession, completion: ((_ success: Bool) -> Void)?) {
        OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance(with: audioSession))
        if session == nil {
            session = OTSession(apiKey: apiKey, sessionId: sessionId, delegate: self)
        }
        canStartCall = completion
        
        var error: OTError?
        hasStartedConnecting = true
        session?.connect(withToken: token, error: &error)
        if error != nil {
            print(error!)
        }
    }
    
    var canAnswerCall: ((Bool) -> Void)?
    func answerCall(withAudioSession audioSession: AVAudioSession, completion: ((_ success: Bool) -> Void)?) {
        OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance(with: audioSession))
        if session == nil {
            session = OTSession(apiKey: apiKey, sessionId: sessionId, delegate: self)
        }
        
        canAnswerCall = completion
        
        var error: OTError?
        hasStartedConnecting = true
        session?.connect(withToken: token, error: &error)
        if error != nil {
            print(error!)
        }
    }
    
    func startAudio() {
        if publisher == nil {
            let settings = OTPublisherSettings()
            settings.name = UIDevice.current.name
            settings.audioTrack = true
            settings.videoTrack = false
            publisher = OTPublisher.init(delegate: self, settings: settings)
        }
        
        var error: OTError?
        session?.publish(publisher!, error: &error)
        if error != nil {
            print(error!)
        }
    }
    
    func endCall() {
        /*
         Simulate the end taking effect immediately, since
         the example app is not backed by a real network service
         */
        if let publisher = publisher {
            var error: OTError?
            session?.unpublish(publisher, error: &error)
            if error != nil {
                print(error!)
            }
        }
        publisher = nil
        
        if let session = session {
            var error: OTError?
            session.disconnect(&error)
            if error != nil {
                print(error!)
            }
        }
        session = nil
        
        hasEnded = true
    }
}

extension SpeakerboxCall: OTSessionDelegate {
    func sessionDidConnect(_ session: OTSession) {
        print(#function)
        
        hasConnected = true
        canStartCall?(true)
        canAnswerCall?(true)
    }
    
    func sessionDidDisconnect(_ session: OTSession) {
        print(#function)
    }
    
    func sessionDidBeginReconnecting(_ session: OTSession) {
        print(#function)
    }
    
    func sessionDidReconnect(_ session: OTSession) {
        print(#function)
    }
    
    func session(_ session: OTSession, didFailWithError error: OTError) {
        print(#function, error)
        
        hasConnected = false
        canStartCall?(false)
        canAnswerCall?(false)
    }
    
    func session(_ session: OTSession, streamCreated stream: OTStream) {
        print(#function)
        subscriber = OTSubscriber.init(stream: stream, delegate: self)
        subscriber?.subscribeToVideo = false
        if let subscriber = subscriber {
            var error: OTError?
            session.subscribe(subscriber, error: &error)
            if error != nil {
                print(error!)
            }
        }
    }
    
    
    func session(_ session: OTSession, streamDestroyed stream: OTStream) {
        print(#function)
    }
}

extension SpeakerboxCall: OTPublisherDelegate {
    func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
        print(#function)
    }
}

extension SpeakerboxCall: OTSubscriberDelegate {
    func subscriberDidConnect(toStream subscriber: OTSubscriberKit) {
        print(#function)
    }
    
    func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
        print(#function)
    }
}


================================================
FILE: CallKit/CallKitDemo/SpeakerboxCallManager.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	Manager of SpeakerboxCalls, which demonstrates using a CallKit CXCallController to request actions on calls
*/

import UIKit
import CallKit
import OpenTok

final class SpeakerboxCallManager: NSObject {
    
    enum Call: String {
        case start = "startCall"
        case end = "endCall"
        case hold = "holdCall"
    }

    let callController = CXCallController()

    // MARK: Actions

    func startCall(handle: String, video: Bool = false) {        
        let handle = CXHandle(type: .phoneNumber, value: handle)
        let startCallAction = CXStartCallAction(call: UUID(), handle: handle)

        startCallAction.isVideo = video

        let transaction = CXTransaction()
        transaction.addAction(startCallAction)

        requestTransaction(transaction, action: Call.start.rawValue)
    }

    func end(call: SpeakerboxCall) {
        let endCallAction = CXEndCallAction(call: call.uuid)
        let transaction = CXTransaction()
        transaction.addAction(endCallAction)

        requestTransaction(transaction, action: Call.end.rawValue)
    }

    func setHeld(call: SpeakerboxCall, onHold: Bool) {
        let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold)
        let transaction = CXTransaction()
        transaction.addAction(setHeldCallAction)

        requestTransaction(transaction, action: Call.hold.rawValue)
    }

    private func requestTransaction(_ transaction: CXTransaction, action: String = "") {
        callController.request(transaction) { error in
            if let error = error {
                print("Error requesting transaction: \(error)")
            } else {
                print("Requested transaction \(action) successfully")
            }
        }
    }

    // MARK: Call Management

    static let CallsChangedNotification = Notification.Name("CallManagerCallsChangedNotification") 

    private(set) var calls = [SpeakerboxCall]()

    func callWithUUID(uuid: UUID) -> SpeakerboxCall? {
        guard let index = calls.index(where: { $0.uuid == uuid }) else {
            return nil
        }
        return calls[index]
    }

    func addCall(_ call: SpeakerboxCall) {
        calls.append(call)

        call.stateDidChange = { [weak self] in
            self?.postCallsChangedNotification()
        }

        postCallsChangedNotification(userInfo: ["action": Call.start.rawValue])
    }

    func removeCall(_ call: SpeakerboxCall) {
        calls = calls.filter {$0 === call}
        postCallsChangedNotification(userInfo: ["action": Call.end.rawValue])
    }

    func removeAllCalls() {
        calls.removeAll()
        postCallsChangedNotification(userInfo: ["action": Call.end.rawValue])
    }

    private func postCallsChangedNotification(userInfo: [String: Any]? = nil) {
        NotificationCenter.default.post(name: type(of: self).CallsChangedNotification, object: self, userInfo: userInfo)
    }
}


================================================
FILE: CallKit/CallKitDemo/ViewController.swift
================================================
//
//  ViewController.swift
//  CallKitDemo
//
//  Created by Xi Huang on 6/5/17.
//  Copyright © 2017 Tokbox, Inc. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    fileprivate final let displayCaller = "Lucas Huang"
    fileprivate final let makeACallText = "Make a call"
    fileprivate final let unholdCallText = "Unhold Call"
    fileprivate final let simulateIncomingCallText = "Simulate Call"
    fileprivate final let simulateIncomingCallThreeSecondsText = "Simulate Call after 3s(Background)"
    fileprivate final let endCallText = "End call"

    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        NotificationCenter.default.addObserver(self, selector: #selector(handleCallsChangedNotification(notification:)), name: SpeakerboxCallManager.CallsChangedNotification, object: nil)
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        NotificationCenter.default.removeObserver(self)
    }
    
    @IBOutlet weak var callButton: UIButton!
    @IBOutlet weak var simulateCallButton: UIButton!
    @IBOutlet weak var simulateCallButton2: UIButton!
    
    @IBAction func receiveCallLucas(_ sender: UIButton) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        if simulateCallButton.titleLabel?.text == simulateIncomingCallText {
            appdelegate.displayIncomingCall(uuid: UUID(), handle: displayCaller)
            sender.setTitle(endCallText, for: .normal)
            sender.setTitleColor(.red, for: .normal)
            callButton.isEnabled = false
            simulateCallButton2.isEnabled = false
        }
        else {
            endCall()
            sender.setTitle(simulateIncomingCallText, for: .normal)
            sender.setTitleColor(.white, for: .normal)
            callButton.isEnabled = true
            simulateCallButton2.isEnabled = true
        }
    }
    
    @IBAction func receiveCallLucasAfterThreeSeconds(_ sender: UIButton) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        if sender.titleLabel?.text == simulateIncomingCallThreeSecondsText {
            
            let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                appdelegate.displayIncomingCall(uuid: UUID(), handle: "Lucas Huang", hasVideo: false) { _ in
                    UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
                }
            }
            sender.setTitle(endCallText, for: .normal)
            sender.setTitleColor(.red, for: .normal)
            callButton.isEnabled = false
            simulateCallButton.isEnabled = false
        }
        else {
            endCall()
            sender.setTitle(simulateIncomingCallThreeSecondsText, for: .normal)
            sender.setTitleColor(.white, for: .normal)
            callButton.isEnabled = true
            simulateCallButton.isEnabled = true
        }
    }
    
    @IBAction func callButtonPressed(_ sender: UIButton) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        if sender.titleLabel?.text == makeACallText {
            appdelegate.callManager.startCall(handle: displayCaller)
            sender.setTitle(endCallText, for: .normal)
            sender.setTitleColor(.red, for: .normal)
            simulateCallButton.isEnabled = false
            simulateCallButton2.isEnabled = false
        } else if sender.titleLabel?.text == unholdCallText { // This state set when user receives another call
            appdelegate.callManager.setHeld(call: appdelegate.callManager.calls[0], onHold: false)
        }
        else {
            endCall()
            sender.setTitle(makeACallText, for: .normal)
            sender.setTitleColor(.white, for: .normal)
            simulateCallButton.isEnabled = true
            simulateCallButton2.isEnabled = true
        }
    }
    
    @objc func handleCallsChangedNotification(notification: NSNotification) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }

        if (appdelegate.callManager.calls.count > 0)
        {
            let call = appdelegate.callManager.calls[0]
            if call.isOnHold {
                callButton.setTitle(unholdCallText, for: .normal)
            } else if call.session != nil {
                callButton.setTitle(endCallText, for: .normal)
                callButton.setTitleColor(.red, for: .normal)
            }
            
            if let action = notification.userInfo?["action"] as? String, action == SpeakerboxCallManager.Call.end.rawValue {
                callButton.setTitle(makeACallText, for: .normal)
                callButton.setTitleColor(.white, for: .normal)
                callButton.isEnabled = true
                simulateCallButton.setTitle(simulateIncomingCallText, for: .normal)
                simulateCallButton.setTitleColor(.white, for: .normal)
                simulateCallButton.isEnabled = true
                simulateCallButton2.setTitle(simulateIncomingCallThreeSecondsText, for: .normal)
                simulateCallButton2.setTitleColor(.white, for: .normal)
                simulateCallButton2.isEnabled = true
            }
        }
    }
    
    fileprivate func endCall() {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        /*
         End any ongoing calls if the provider resets, and remove them from the app's list of calls,
         since they are no longer valid.
         */
        for call in appdelegate.callManager.calls {
            appdelegate.callManager.end(call: call)
        }
    }
}


================================================
FILE: CallKit/CallKitDemo.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		A00A84251F3A772400B2862E /* OTDefaultAudioDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = A00A84221F3A772400B2862E /* OTDefaultAudioDevice.m */; };
		A04D825E1EEB1E3E00EBA4CA /* SpeakerboxCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04D825C1EEB1E3E00EBA4CA /* SpeakerboxCall.swift */; };
		A04D825F1EEB1E3E00EBA4CA /* SpeakerboxCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04D825D1EEB1E3E00EBA4CA /* SpeakerboxCallManager.swift */; };
		A04D82611EEB1EB000EBA4CA /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04D82601EEB1EB000EBA4CA /* ProviderDelegate.swift */; };
		A062C6241EE5FAA200FD64A3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A062C6231EE5FAA200FD64A3 /* AppDelegate.swift */; };
		A062C6261EE5FAA200FD64A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A062C6251EE5FAA200FD64A3 /* ViewController.swift */; };
		A062C6291EE5FAA200FD64A3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A062C6271EE5FAA200FD64A3 /* Main.storyboard */; };
		A062C62B1EE5FAA200FD64A3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A062C62A1EE5FAA200FD64A3 /* Assets.xcassets */; };
		A062C62E1EE5FAA200FD64A3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A062C62C1EE5FAA200FD64A3 /* LaunchScreen.storyboard */; };
		A0B080C81EF9BD4D0082691D /* Ringtone.caf in Resources */ = {isa = PBXBuildFile; fileRef = A0B080C71EF9BD4A0082691D /* Ringtone.caf */; };
		A0F2087A1EEF442E00104C6C /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = A0F208791EEF442E00104C6C /* Podfile */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		A00A84201F3A772400B2862E /* CallKitDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CallKitDemo-Bridging-Header.h"; sourceTree = "<group>"; };
		A00A84211F3A772400B2862E /* OTDefaultAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTDefaultAudioDevice.h; sourceTree = "<group>"; };
		A00A84221F3A772400B2862E /* OTDefaultAudioDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTDefaultAudioDevice.m; sourceTree = "<group>"; };
		A04D825C1EEB1E3E00EBA4CA /* SpeakerboxCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeakerboxCall.swift; sourceTree = "<group>"; };
		A04D825D1EEB1E3E00EBA4CA /* SpeakerboxCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeakerboxCallManager.swift; sourceTree = "<group>"; };
		A04D82601EEB1EB000EBA4CA /* ProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = "<group>"; };
		A062C6201EE5FAA200FD64A3 /* CallKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CallKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
		A062C6231EE5FAA200FD64A3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		A062C6251EE5FAA200FD64A3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
		A062C6281EE5FAA200FD64A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		A062C62A1EE5FAA200FD64A3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		A062C62D1EE5FAA200FD64A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		A062C62F1EE5FAA200FD64A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		A0B080C71EF9BD4A0082691D /* Ringtone.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Ringtone.caf; sourceTree = "<group>"; };
		A0F208791EEF442E00104C6C /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
		A0F2087B1EEF449800104C6C /* CallKitDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CallKitDemo.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		A062C61D1EE5FAA200FD64A3 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		A062C6171EE5FAA200FD64A3 = {
			isa = PBXGroup;
			children = (
				A0F208791EEF442E00104C6C /* Podfile */,
				A062C6221EE5FAA200FD64A3 /* CallKitDemo */,
				A062C6211EE5FAA200FD64A3 /* Products */,
			);
			sourceTree = "<group>";
		};
		A062C6211EE5FAA200FD64A3 /* Products */ = {
			isa = PBXGroup;
			children = (
				A062C6201EE5FAA200FD64A3 /* CallKitDemo.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		A062C6221EE5FAA200FD64A3 /* CallKitDemo */ = {
			isa = PBXGroup;
			children = (
				A00A84211F3A772400B2862E /* OTDefaultAudioDevice.h */,
				A00A84221F3A772400B2862E /* OTDefaultAudioDevice.m */,
				A0B080C71EF9BD4A0082691D /* Ringtone.caf */,
				A0F2087B1EEF449800104C6C /* CallKitDemo.entitlements */,
				A062C6231EE5FAA200FD64A3 /* AppDelegate.swift */,
				A04D82601EEB1EB000EBA4CA /* ProviderDelegate.swift */,
				A062C6251EE5FAA200FD64A3 /* ViewController.swift */,
				A04D825C1EEB1E3E00EBA4CA /* SpeakerboxCall.swift */,
				A04D825D1EEB1E3E00EBA4CA /* SpeakerboxCallManager.swift */,
				A062C6271EE5FAA200FD64A3 /* Main.storyboard */,
				A062C62A1EE5FAA200FD64A3 /* Assets.xcassets */,
				A062C62C1EE5FAA200FD64A3 /* LaunchScreen.storyboard */,
				A062C62F1EE5FAA200FD64A3 /* Info.plist */,
				A00A84201F3A772400B2862E /* CallKitDemo-Bridging-Header.h */,
			);
			path = CallKitDemo;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		A062C61F1EE5FAA200FD64A3 /* CallKitDemo */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = A062C6321EE5FAA200FD64A3 /* Build configuration list for PBXNativeTarget "CallKitDemo" */;
			buildPhases = (
				A062C61C1EE5FAA200FD64A3 /* Sources */,
				A062C61D1EE5FAA200FD64A3 /* Frameworks */,
				A062C61E1EE5FAA200FD64A3 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = CallKitDemo;
			productName = CallKitDemo;
			productReference = A062C6201EE5FAA200FD64A3 /* CallKitDemo.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		A062C6181EE5FAA200FD64A3 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 0830;
				LastUpgradeCheck = 0900;
				ORGANIZATIONNAME = "Tokbox, Inc.";
				TargetAttributes = {
					A062C61F1EE5FAA200FD64A3 = {
						CreatedOnToolsVersion = 8.3.2;
						LastSwiftMigration = 0830;
						ProvisioningStyle = Manual;
						SystemCapabilities = {
							com.apple.BackgroundModes = {
								enabled = 1;
							};
							com.apple.Push = {
								enabled = 1;
							};
						};
					};
				};
			};
			buildConfigurationList = A062C61B1EE5FAA200FD64A3 /* Build configuration list for PBXProject "CallKitDemo" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = English;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = A062C6171EE5FAA200FD64A3;
			productRefGroup = A062C6211EE5FAA200FD64A3 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				A062C61F1EE5FAA200FD64A3 /* CallKitDemo */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		A062C61E1EE5FAA200FD64A3 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				A0B080C81EF9BD4D0082691D /* Ringtone.caf in Resources */,
				A062C62E1EE5FAA200FD64A3 /* LaunchScreen.storyboard in Resources */,
				A062C62B1EE5FAA200FD64A3 /* Assets.xcassets in Resources */,
				A0F2087A1EEF442E00104C6C /* Podfile in Resources */,
				A062C6291EE5FAA200FD64A3 /* Main.storyboard in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		A062C61C1EE5FAA200FD64A3 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				A062C6261EE5FAA200FD64A3 /* ViewController.swift in Sources */,
				A04D825F1EEB1E3E00EBA4CA /* SpeakerboxCallManager.swift in Sources */,
				A062C6241EE5FAA200FD64A3 /* AppDelegate.swift in Sources */,
				A00A84251F3A772400B2862E /* OTDefaultAudioDevice.m in Sources */,
				A04D825E1EEB1E3E00EBA4CA /* SpeakerboxCall.swift in Sources */,
				A04D82611EEB1EB000EBA4CA /* ProviderDelegate.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		A062C6271EE5FAA200FD64A3 /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				A062C6281EE5FAA200FD64A3 /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
		A062C62C1EE5FAA200FD64A3 /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				A062C62D1EE5FAA200FD64A3 /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		A062C6301EE5FAA200FD64A3 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 10.3;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		A062C6311EE5FAA200FD64A3 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 10.3;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		A062C6331EE5FAA200FD64A3 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_ENTITLEMENTS = CallKitDemo/CallKitDemo.entitlements;
				CODE_SIGN_STYLE = Manual;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = CallKitDemo/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = com.tokbox.CallKitDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_OBJC_BRIDGING_HEADER = "CallKitDemo/CallKitDemo-Bridging-Header.h";
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 4.2;
			};
			name = Debug;
		};
		A062C6341EE5FAA200FD64A3 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_ENTITLEMENTS = CallKitDemo/CallKitDemo.entitlements;
				CODE_SIGN_STYLE = Manual;
				DEVELOPMENT_TEAM = "";
				INFOPLIST_FILE = CallKitDemo/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = com.tokbox.CallKitDemo;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_OBJC_BRIDGING_HEADER = "CallKitDemo/CallKitDemo-Bridging-Header.h";
				SWIFT_VERSION = 4.2;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		A062C61B1EE5FAA200FD64A3 /* Build configuration list for PBXProject "CallKitDemo" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				A062C6301EE5FAA200FD64A3 /* Debug */,
				A062C6311EE5FAA200FD64A3 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		A062C6321EE5FAA200FD64A3 /* Build configuration list for PBXNativeTarget "CallKitDemo" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				A062C6331EE5FAA200FD64A3 /* Debug */,
				A062C6341EE5FAA200FD64A3 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = A062C6181EE5FAA200FD64A3 /* Project object */;
}


================================================
FILE: CallKit/CallKitDemo.xcodeproj/xcshareddata/xcschemes/CallKitDemo.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0900"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "A062C61F1EE5FAA200FD64A3"
               BuildableName = "CallKitDemo.app"
               BlueprintName = "CallKitDemo"
               ReferencedContainer = "container:CallKitDemo.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "A062C61F1EE5FAA200FD64A3"
            BuildableName = "CallKitDemo.app"
            BlueprintName = "CallKitDemo"
            ReferencedContainer = "container:CallKitDemo.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      enableGPUValidationMode = "1"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "A062C61F1EE5FAA200FD64A3"
            BuildableName = "CallKitDemo.app"
            BlueprintName = "CallKitDemo"
            ReferencedContainer = "container:CallKitDemo.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "A062C61F1EE5FAA200FD64A3"
            BuildableName = "CallKitDemo.app"
            BlueprintName = "CallKitDemo"
            ReferencedContainer = "container:CallKitDemo.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: CallKit/LICENSE
================================================
MIT License

Copyright (c) 2017 OpenTok

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: CallKit/Podfile
================================================
require_relative '../OpenTokSDKVersion'

platform :ios, MinIosSdkVersion
use_frameworks!

target 'CallKitDemo' do
  pod 'OTXCFramework', OpenTokSDKVersion
end


================================================
FILE: CallKit/README.md
================================================
![logo](./tokbox-logo.png)



## CallKit Integration with OpenTok
A sample app to demonstrate how to integrate the [CallKit](https://developer.apple.com/documentation/callkit) into OpenTok iOS SDK. This sample app is built based on the [SpeakerBox](https://developer.apple.com/library/content/samplecode/Speakerbox/Introduction/Intro.html) app from [WWDC 2016 CallKit Session](https://developer.apple.com/videos/play/wwdc2016/230/)

### Install the project files

Use CocoaPods to install the project files and dependencies.

1. Install CocoaPods as described in [CocoaPods Getting Started](https://guides.cocoapods.org/using/getting-started.html#getting-started).
1. In Terminal, `cd` to your project directory and type `pod install`. (Sometimes, `pod update` is magical)
1. Reopen your project in Xcode using the new `*.xcworkspace` file.

### Configure and build the app

Configure the sample app code. Then, build and run the app.

1. The application **requires** values for **API Key**, **Session ID**, and **Token**. In the sample, you can get these values at the [OpenTok Developer Dashboard](https://dashboard.tokbox.com/). For production deployment, you must generate the **Session ID** and **Token** values using one of the [OpenTok Server SDKs](https://tokbox.com/developer/sdks/server/).

1. Replace the following empty strings with the corresponding **API Key**, **Session ID**, and **Token** values in `AppDelegate.swift`:
    ```swift
      let apiKey = ""
      let sessionId = ""
      let token = ""
    ```

1. Use Xcode to build and run the app on an iOS simulator or device.

### Exploring the sample app

![demo](./demo.png)

  1. **Make a call**: 

The iOS system boosts the call priority of the app. Then, the app starts publishing to OpenTok platform. You won't notice any differences until you go to the home screen. Two ways to verify:
  - A badge in home screen indicating an ongoing VoIP call.
  - An incoming native phone call will not interrupt the current VoIP call, instead it shows the option menu.
  
  ***You will need a device to test the followings***

  2. **Simulate an incoming call**

The native incoming call screen appears. Upon acceptance, the iOS system opens the app and boosts the call priority. Then, the app starts publishing to OpenTok platform.

![unlock1](./unlock1.png) ---> ![unlock2](./unlock2.png)

  3. **Simulate an incoming call after 3s(Background)** (After clicking the button, please lock your cell phone to test this scenario.)

The system wakes up your cell phone by making a native calling screen appear. Upon acceptance (a slider is shown instead of two buttons for the locked screen), the phone stays locked and boosts the call priority. Then, the app (which runs in the background during that time) starts publishing to OpenTok platform. 

![lock1](./lock1.png) ---> ![lock2](./lock2.png)

  4. **Without simulation, use a push server or [NWPusher](https://github.com/noodlewerk/NWPusher) to call**

This requires a few more steps to test:

    - create your certificate
    - configure your push notification backend or NWPusher
    - locate your device token for testing (launch the app and get it from the console)
    - send a remote notification from your backend or NWPusher


**Notice**: You might want to use [OpenTok.js Sample App](https://github.com/opentok/opentok-web-samples/tree/master/Basic%20Video%20Chat) to test the sample app together.

### Exploring the codes

A `CXProvider` object is responsible for reporting out-of-band notifications that occur to the system. To create one, you first need to initialize a `CXProviderConfiguration` object, which encapsulates the behaviors and capabilities of calls, to pass on to the `CXProvider` initializer. In order to receive telephony events of the provider, the provider needs to specify an object conforming to the `CXProviderDelegate` protocol.

```swift
// create a provider configuration
let localizedName = NSLocalizedString("CallKitDemo", comment: "Name of application")
let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(#imageLiteral(resourceName: "IconMask"))
providerConfiguration.ringtoneSound = "Ringtone.caf"

// set up a provider
provider = CXProvider(configuration: providerConfiguration)
provider.setDelegate(self, queue: nil)
```

The `CXProviderDelegate` protocol defines events of the telephony provider (`CXProvider`) such as the call starting, the call being put on hold, or the provider’s audio session is activated.

```swift
// MARK: CXProviderDelegate
func providerDidReset(_ provider: CXProvider) {
    print("Provider did reset")
}

func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
    print("Provider performs the start call action")

    /*
      Configure the audio session, but do not start call audio here, since it must be done once
      the audio session has been activated by the system after having its priority elevated.
    */
}

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    print("Provider performs the answer call action")

    /*
      Configure the audio session, but do not start call audio here, since it must be done once
      the audio session has been activated by the system after having its priority elevated.
    */
}

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
    print("Provider performs the end call action")

    // Trigger the call to be ended via the underlying network service.
}

func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
    print("Provider performs the hold call action")
}

func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
    print("Provider performs the mute call action")
}
``` 

The following methods indicate whether your VoIP call has been successfully priority boosted or recovered.

```swift
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
    print("Timed out \(#function)")
    // React to the action timeout if necessary, such as showing an error UI.
}

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    // Start call audio media, now that the audio session has been activated after having its priority boosted.
}

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
    /*
        Restart any non-call related audio now that the app's audio session has been
        de-activated after having its priority restored to normal.
    */
}
```

Let's explore how to make a call and answer a call on behalf of a user. To do that, we need a `CXCallController` object to interact with the system. 

The `CXCallController` object takes a `CXTransaction` object to request a telephony action (which will later trigger delegate methods above if succeed). To specify a telephony action in a transaction, you need to create your desired action object and associate them with the transaction. Each telephony action has a corresponding `CXAction` class such as `CXEndCallAction` for ending a call, `CXSetHeldCallAction` for setting a call on hold. 

Once you have it all ready, invoke the `request(_:completion:)` by passing a ready transaction object. Here's how you start a call:

```swift
// create a CXAction
let startCallAction = CXStartCallAction(call: UUID(), handle: CXHandle(type: .phoneNumber, value: handle))

// create a transaction
let transaction = CXTransaction()
transaction.addAction(startCallAction)

// create a label
let action = "startCall"

callController.request(transaction) { error in
    if let error = error {
        print("Error requesting transaction: \(error)")
    } else {
        print("Requested transaction \(action) successfully")
    }
}
```

As for answering a call, the `CallKit` framework provides a convenient API to present a native calling UI like the screen-shot below. By invoking `reportNewIncomingCall(with:update:completion:)` on the provider, you will have the same experience as receiving a native phone call. Often, this piece of code works with VoIP remote notification to make calls to a device/person like WhatsApp, WeChat, and Messenger etc.

```swift
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)

// Report the incoming call to the system
provider.reportNewIncomingCall(with: uuid, update: update) { error in
    /*
      Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
      since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
    */
}
```

![call](./call.jpeg)


### A glitch

There is a small [issue](https://forums.developer.apple.com/thread/64544) when accepting a call from a locked screen. The underlying audio session does not get activated propertly inside the CallKit framework. Apple's engineers propose a workaround by setting up the audio session as early as possible to make the case work out temporarily: 

```
then a workaround would be to configure your app's audio session (call `configureAudioSession()`) earlier in your app's lifecycle, before the `-provider:performAnswerCallAction:` method is invoked.
```


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  CallKitDemo
//
//  Created by Xi Huang on 6/5/17.
//  Copyright © 2017 Tokbox, Inc. All rights reserved.
//

import UIKit
import PushKit
import CallKit
import OpenTok

let apiKey = ""
let sessionId = ""
let token = ""

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
    let callManager = SpeakerboxCallManager()
    var providerDelegate: ProviderDelegate?

    // Trigger VoIP registration on launch
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        let sessionManager = OTAudioDeviceManager.currentAudioSessionManager()
        sessionManager?.enableCallingServicesMode()
        
        providerDelegate = ProviderDelegate(callManager: callManager, sessionManager: sessionManager)
        
        pushRegistry.delegate = self
        pushRegistry.desiredPushTypes = [.voIP]
        
        return true
    }
}

extension AppDelegate: PKPushRegistryDelegate {
    
    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
        print("\(#function) voip token: \(credentials.token)")
        
        let deviceToken = credentials.token.reduce("", {$0 + String(format: "%02X", $1) })
        print("\(#function) token is: \(deviceToken)")
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        
        print("\(#function) incoming voip notfication: \(payload.dictionaryPayload)")
        if let uuidString = payload.dictionaryPayload["UUID"] as? String,
            let handle = payload.dictionaryPayload["handle"] as? String,
            let uuid = UUID(uuidString: uuidString) {
                            
            // display incoming call UI when receiving incoming voip notification
            let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
            self.displayIncomingCall(uuid: uuid, handle: handle, hasVideo: false) { _ in
                UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
            }
        }
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        print("\(#function) token invalidated")
    }
        
    /// Display the incoming call to the user
    func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        providerDelegate?.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion)
    }
}


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "20x20",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "29x29",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "40x40",
      "scale" : "3x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "2x"
    },
    {
      "idiom" : "iphone",
      "size" : "60x60",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/IconMask.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "filename" : "IconMask-40.png",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "filename" : "IconMask-80.png",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "filename" : "IconMask-120.png",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/baseHeroMount.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x",
      "filename" : "baseHeroMount.png"
    },
    {
      "idiom" : "universal",
      "scale" : "2x",
      "filename" : "baseHeroMount@2x.png"
    },
    {
      "idiom" : "universal",
      "scale" : "3x",
      "filename" : "baseHeroMount@3x.png"
    }
  ],
  "info" : {
    "author" : "zeplin",
    "version" : "1"
  }
}

================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="CallKitDemo" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="baseHeroMount" translatesAutoresizingMaskIntoConstraints="NO" id="YYx-UX-emU">
                                <rect key="frame" x="0.0" y="20" width="375" height="647"/>
                            </imageView>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M5B-6m-yHD">
                                <rect key="frame" x="143" y="318.5" width="89" height="30"/>
                                <state key="normal" title="Simulate Call">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="receiveCallLucas:" destination="BYZ-38-t0r" eventType="touchUpInside" id="8EM-RY-UL5"/>
                                </connections>
                            </button>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g8T-fv-bWg">
                                <rect key="frame" x="68" y="356.5" width="239" height="30"/>
                                <fontDescription key="fontDescription" type="system" pointSize="15"/>
                                <state key="normal" title="Simulate Call after 3s(Background)">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="receiveCallLucasAfterThreeSeconds:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cEH-h5-VOU"/>
                                </connections>
                            </button>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9aI-yW-7Tw">
                                <rect key="frame" x="149" y="280.5" width="77" height="30"/>
                                <state key="normal" title="Make a call">
                                    <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <state key="disabled">
                                    <color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                </state>
                                <connections>
                                    <action selector="callButtonPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="eRQ-rc-Kp9"/>
                                </connections>
                            </button>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstAttribute="trailing" secondItem="YYx-UX-emU" secondAttribute="trailing" id="3jK-fK-jae"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="5Go-8s-H2G"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="top" secondItem="9aI-yW-7Tw" secondAttribute="bottom" constant="8" id="D14-vC-v4H"/>
                            <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="YYx-UX-emU" secondAttribute="bottom" id="Fga-mw-vE3"/>
                            <constraint firstItem="g8T-fv-bWg" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="J1U-YB-kys"/>
                            <constraint firstItem="g8T-fv-bWg" firstAttribute="top" secondItem="M5B-6m-yHD" secondAttribute="bottom" constant="8" id="S8a-NF-fyJ"/>
                            <constraint firstItem="M5B-6m-yHD" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="TpI-O1-PZx"/>
                            <constraint firstItem="YYx-UX-emU" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="jn7-Kc-DzS"/>
                            <constraint firstItem="YYx-UX-emU" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="myS-Ve-QB6"/>
                            <constraint firstItem="9aI-yW-7Tw" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="rra-Ar-7og"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="callButton" destination="9aI-yW-7Tw" id="Kh9-cg-IdH"/>
                        <outlet property="simulateCallButton" destination="M5B-6m-yHD" id="U2Q-QK-ta3"/>
                        <outlet property="simulateCallButton2" destination="g8T-fv-bWg" id="ABY-0C-tDn"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="32.799999999999997" y="93.103448275862078"/>
        </scene>
    </scenes>
    <resources>
        <image name="baseHeroMount" width="374" height="667"/>
    </resources>
</document>


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/CallKitDemo.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>aps-environment</key>
	<string>development</string>
</dict>
</plist>


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLName</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER).url-scheme.dial</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>callkitdemo</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSCameraUsageDescription</key>
	<string>$(PRODUCT_NAME) uses camera</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>$(PRODUCT_NAME) uses microphone</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>audio</string>
		<string>fetch</string>
		<string>remote-notification</string>
		<string>voip</string>
	</array>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
</dict>
</plist>


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/ProviderDelegate.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	CallKit provider delegate class, which conforms to CXProviderDelegate protocol
*/

import Foundation
import UIKit
import CallKit
import AVFoundation
import OpenTok
import Combine

final class ProviderDelegate: NSObject, CXProviderDelegate {

    let callManager: SpeakerboxCallManager
    private let provider: CXProvider
    private let sessionManager: OTAudioSessionManager?
    let muteSubject = PassthroughSubject<Bool, Never>()
    private var cancellable: AnyCancellable?

    init(callManager: SpeakerboxCallManager, sessionManager: OTAudioSessionManager?) {
        self.callManager = callManager
        self.sessionManager = sessionManager
        provider = CXProvider(configuration: type(of: self).providerConfiguration)

        super.init()

        provider.setDelegate(self, queue: nil)

        setupMuteThrottle()
    }
    
    /// The app's provider configuration, representing its CallKit capabilities
    static var providerConfiguration: CXProviderConfiguration {
        let localizedName = NSLocalizedString("CallKitDemo", comment: "Name of application")
        let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)

        providerConfiguration.supportsVideo = false
        
        providerConfiguration.maximumCallsPerCallGroup = 1
        
        providerConfiguration.maximumCallGroups = 1
        
        providerConfiguration.supportedHandleTypes = [.phoneNumber]

        providerConfiguration.iconTemplateImageData = #imageLiteral(resourceName: "IconMask").pngData()

        providerConfiguration.ringtoneSound = "Ringtone.caf"
        
        return providerConfiguration
    }

    // MARK: Incoming Calls

    /// Use CXProvider to report the incoming call to the system
    func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        // Construct a CXCallUpdate describing the incoming call, including the caller.
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
        update.hasVideo = hasVideo
        update.supportsHolding = true
        
        // pre-heat the AVAudioSession
        // Report the incoming call to the system
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            /*
                Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
                since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
             */
            if error == nil {
                let call = SpeakerboxCall(uuid: uuid)
                call.handle = handle

                self.callManager.addCall(call)
            }
            
            completion?(error as NSError?)
        }
    }

    // MARK: CXProviderDelegate

    func providerDidBegin(_ provider: CXProvider) {
        print("Provider did begin")
    }
    
    func providerDidReset(_ provider: CXProvider) {
        print("Provider did reset")
        /*
            End any ongoing calls if the provider resets, and remove them from the app's list of calls,
            since they are no longer valid.
         */
    }

    var outgoingCall: SpeakerboxCall?
    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        print("Received perform CXStartCallAction \(action.callUUID)")
        // Create & configure an instance of SpeakerboxCall, the app's model class representing the new outgoing call.
        let call = SpeakerboxCall(uuid: action.callUUID, isOutgoing: true)
        call.handle = action.handle.value

        /*
            Configure the audio session, but do not start call audio here, since it must be done once
            the audio session has been activated by the system after having its priority elevated.
         */
        // https://forums.developer.apple.com/thread/64544
        // we can't configure the audio session here for the case of launching it from locked screen
        // instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
        // please look for  * pre-heat the AVAudioSession *
        sessionManager?.preconfigureAudioSessionForCall(withMode: .voiceChat)
        
        /*
            Set callback blocks for significant events in the call's lifecycle, so that the CXProvider may be updated
            to reflect the updated state.
         */
        call.hasStartedConnectingDidChange = { [weak self] in
            self?.setupHold(to: call.uuid)
            self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
        }
        call.hasConnectedDidChange = { [weak self] in
            self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate)
        }
        call.callDidEnd = { [weak self] reason in
            self?.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason)
        }
        self.outgoingCall = call
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    var answerCall: SpeakerboxCall?
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        print("Received perform CXAnswerCallAction")
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        /*
            Configure the audio session, but do not start call audio here, since it must be done once
            the audio session has been activated by the system after having its priority elevated.
         */
        
        // https://forums.developer.apple.com/thread/64544
        // we can't configure the audio session here for the case of launching it from locked screen
        // instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
        // please look for  * pre-heat the AVAudioSession *
        sessionManager?.preconfigureAudioSessionForCall(withMode: .voiceChat)
        
        self.answerCall = call
        
        call.callDidEnd = { [weak self] reason in
            self?.provider.reportCall(with: call.uuid, endedAt: Date(), reason: reason)
        }
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        print("Received perform CXEndCallAction")
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Trigger the call to be ended via the underlying network service.
        call.endCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()

        call.stateDidChange = nil
        call.hasStartedConnectingDidChange = nil
        call.hasConnectedDidChange = nil
        call.hasEndedDidChange = nil
        call.audioChange = nil
        call.callDidEnd = nil
        
        // Remove the ended call from the app's list of calls.
        callManager.removeCall(call)
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        print("Received perform CXSetHeldCallAction \(action.isOnHold)")
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Update the SpeakerboxCall's underlying hold state.
        call.isOnHold = action.isOnHold

        // Stop or start audio in response to holding or unholding the call.
        updateMuteState(call.isOnHold)
        
        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }
    
    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
        print("Received perform CXSetMutedCallAction \(action.isMuted)")
        
        updateMuteState(action.isMuted)
        
        action.fulfill()
    }

    func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
        print("Timed out \(#function)")

        // React to the action timeout if necessary, such as showing an error UI.
    }

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        print("Received didActivate")
        sessionManager?.audioSessionDidActivate(audioSession)
        
        // If we are returning from a hold state
        if answerCall?.hasConnected ?? false {
            return
        }
        if outgoingCall?.hasConnected ?? false {
            return
        }
        
        // Start call audio media, now that the audio session has been activated after having its priority boosted.
        outgoingCall?.startCall(withAudioSession: audioSession) { [weak self] success in
            guard let outgoingCall = self?.outgoingCall else { return }
            if success {
                self?.callManager.addCall(outgoingCall)
                self?.outgoingCall?.startAudio()
            } else {
                self?.callManager.end(call: outgoingCall)
            }
        }
        
        answerCall?.answerCall(withAudioSession: audioSession) { success in
            if success {
                self.answerCall?.startAudio()
            }
        }
    }

    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        print("Received didDeactivate")
        sessionManager?.audioSessionDidDeactivate(audioSession)
        
        /*
             Restart any non-call related audio now that the app's audio session has been
             de-activated after having its priority restored to normal.
         */
        if outgoingCall?.isOnHold ?? false || answerCall?.isOnHold ?? false {
            print("Call is on hold. Do not terminate any call")
            return
        }
        
        outgoingCall?.endCall()
        outgoingCall = nil
        answerCall?.endCall()
        answerCall = nil
        callManager.removeAllCalls()
    }
    
    // MARK: Helpers
    
    func setupHold(to callUUID: UUID) {
        let update = CXCallUpdate()
        update.supportsHolding = true
        update.hasVideo = false
        provider.reportCall(with: callUUID, updated: update)
    }
    
    func setupMuteThrottle() {
        cancellable = muteSubject
            .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
            .sink { [weak self] isMuted in
                self?.applyMuteState(isMuted)
            }
    }
    
    func updateMuteState(_ isMuted: Bool) {
        muteSubject.send(isMuted)
    }
    
    private func applyMuteState(_ isMuted: Bool) {
        print("applyMuteState \(isMuted)")
        answerCall?.isMuted = isMuted
        outgoingCall?.isMuted = isMuted
    }
}


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/SpeakerboxCall.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	Model class representing a single call
*/

import Foundation
import OpenTok
import CallKit

final class SpeakerboxCall: NSObject {

    // MARK: Metadata Properties

    let uuid: UUID
    let isOutgoing: Bool
    var handle: String?

    // MARK: Call State Properties

    var connectingDate: Date? {
        didSet {
            stateDidChange?()
            hasStartedConnectingDidChange?()
        }
    }
    var connectDate: Date? {
        didSet {
            stateDidChange?()
            hasConnectedDidChange?()
        }
    }
    var endDate: Date? {
        didSet {
            stateDidChange?()
            hasEndedDidChange?()
        }
    }
    var isOnHold = false {
        didSet {
            stateDidChange?()
        }
    }
    
    var isMuted = false {
        didSet {
            publisher?.publishAudio = !isMuted
        }
    }

    // MARK: State change callback blocks

    var stateDidChange: (() -> Void)?
    var hasStartedConnectingDidChange: (() -> Void)?
    var hasConnectedDidChange: (() -> Void)?
    var hasEndedDidChange: (() -> Void)?
    var audioChange: (() -> Void)?
    var callDidEnd: ((CXCallEndedReason) -> Void)?
    
    // MARK: Derived Properties

    var hasStartedConnecting: Bool {
        get {
            return connectingDate != nil
        }
        set {
            connectingDate = newValue ? Date() : nil
        }
    }
    var hasConnected: Bool {
        get {
            return connectDate != nil
        }
        set {
            connectDate = newValue ? Date() : nil
        }
    }
    var hasEnded: Bool {
        get {
            return endDate != nil
        }
        set {
            endDate = newValue ? Date() : nil
        }
    }
    var duration: TimeInterval {
        guard let connectDate = connectDate else {
            return 0
        }

        return Date().timeIntervalSince(connectDate)
    }

    // MARK: Initialization

    init(uuid: UUID, isOutgoing: Bool = false) {
        self.uuid = uuid
        self.isOutgoing = isOutgoing
    }

    // MARK: Actions
    var session: OTSession?
    var publisher: OTPublisher?
    var subscriber: OTSubscriber?
    
    func assertSessionParams() {
        assert(!apiKey.isEmpty, "Empty API key, session will not be instantiated")
        assert(!sessionId.isEmpty, "Empty Session ID, session will not be instantiated")
        assert(!token.isEmpty, "Empty token, session will not connect")
    }
    
    var canStartCall: ((Bool) -> Void)?
    func startCall(withAudioSession audioSession: AVAudioSession, completion: ((_ success: Bool) -> Void)?) {
        if session == nil {
            assertSessionParams()
            session = OTSession(apiKey: apiKey, sessionId: sessionId, delegate: self)
        }
        canStartCall = completion
        
        var error: OTError?
        hasStartedConnecting = true
        session?.connect(withToken: token, error: &error)
        if let error = error {
            print(error)
            callDidEnd?(.failed)
        }
    }
    
    var canAnswerCall: ((Bool) -> Void)?
    func answerCall(withAudioSession audioSession: AVAudioSession, completion: ((_ success: Bool) -> Void)?) {
        if session == nil {
            assertSessionParams()
            session = OTSession(apiKey: apiKey, sessionId: sessionId, delegate: self)
        }
        
        canAnswerCall = completion
        
        var error: OTError?
        hasStartedConnecting = true
        session?.connect(withToken: token, error: &error)
        if let error = error {
            print(error)
            callDidEnd?(.failed)
        }
    }
    
    func startAudio() {
        if publisher == nil {
            let settings = OTPublisherSettings()
            settings.name = UIDevice.current.name
            settings.audioTrack = true
            settings.videoTrack = false
            publisher = OTPublisher(delegate: self, settings: settings)
        }
        
        var error: OTError?
        session?.publish(publisher!, error: &error)
        if let error = error {
            print(error)
            
            if let session = session {
                var error: OTError?
                session.disconnect(&error)
                if let error = error {
                    print(error)
                }
            }
            
            callDidEnd?(.failed)
        }
    }
    
    func endCall() {
        /*
         Simulate the end taking effect immediately, since
         the example app is not backed by a real network service
         */
        if let publisher = publisher {
            var error: OTError?
            session?.unpublish(publisher, error: &error)
            if error != nil {
                print(error!)
            }
        }
        publisher = nil
        
        if let session = session {
            var error: OTError?
            session.disconnect(&error)
            if let error = error {
                print(error)
            }
        }
        session = nil
        
        hasEnded = true
    }
}

extension SpeakerboxCall: OTSessionDelegate {
    func sessionDidConnect(_ session: OTSession) {
        print(#function)
        
        hasConnected = true
        canStartCall?(true)
        canAnswerCall?(true)
    }
    
    func sessionDidDisconnect(_ session: OTSession) {
        print(#function)
    }
    
    func sessionDidBeginReconnecting(_ session: OTSession) {
        print(#function)
    }
    
    func sessionDidReconnect(_ session: OTSession) {
        print(#function)
    }
    
    func session(_ session: OTSession, didFailWithError error: OTError) {
        print(#function, error)
        
        hasConnected = false
        canStartCall?(false)
        canAnswerCall?(false)
    }
    
    func session(_ session: OTSession, streamCreated stream: OTStream) {
        print(#function)
        subscriber = OTSubscriber.init(stream: stream, delegate: self)
        subscriber?.subscribeToVideo = false
        if let subscriber = subscriber {
            var error: OTError?
            session.subscribe(subscriber, error: &error)
            if error != nil {
                print(error!)
            }
        }
    }
    
    
    func session(_ session: OTSession, streamDestroyed stream: OTStream) {
        print(#function)
    }
}

extension SpeakerboxCall: OTPublisherDelegate {
    func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
        print(#function)
        callDidEnd?(.failed)
    }
}

extension SpeakerboxCall: OTSubscriberDelegate {
    func subscriberDidConnect(toStream subscriber: OTSubscriberKit) {
        print(#function)
    }
    
    func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
        print(#function)
        callDidEnd?(.failed)
    }
}


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/SpeakerboxCallManager.swift
================================================
/*
	Copyright (C) 2016 Apple Inc. All Rights Reserved.
	See LICENSE.txt for this sample’s licensing information
	
	Abstract:
	Manager of SpeakerboxCalls, which demonstrates using a CallKit CXCallController to request actions on calls
*/

import UIKit
import CallKit
import OpenTok

final class SpeakerboxCallManager: NSObject {
    
    enum Call: String {
        case start = "startCall"
        case end = "endCall"
        case hold = "holdCall"
    }

    let callController = CXCallController()

    // MARK: Actions

    func startCall(handle: String, video: Bool = false) {
        print("SpeakerboxCallManager: startCall")
        let handle = CXHandle(type: .phoneNumber, value: handle)
        let startCallAction = CXStartCallAction(call: UUID(), handle: handle)

        startCallAction.isVideo = video

        let transaction = CXTransaction()
        transaction.addAction(startCallAction)

        requestTransaction(transaction, action: Call.start.rawValue)
    }

    func end(call: SpeakerboxCall) {
        print("SpeakerboxCallManager: end")
        let endCallAction = CXEndCallAction(call: call.uuid)
        let transaction = CXTransaction()
        transaction.addAction(endCallAction)

        requestTransaction(transaction, action: Call.end.rawValue)
    }

    func setHeld(call: SpeakerboxCall, onHold: Bool) {
        print("SpeakerboxCallManager: setHeld \(onHold)")
        let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold)
        let transaction = CXTransaction()
        transaction.addAction(setHeldCallAction)

        requestTransaction(transaction, action: Call.hold.rawValue)
    }

    private func requestTransaction(_ transaction: CXTransaction, action: String = "") {
        callController.request(transaction) { error in
            if let error = error {
                print("Error requesting transaction: \(error)")
            } else {
                print("Requested transaction \(action) successfully")
            }
        }
    }

    // MARK: Call Management

    static let CallsChangedNotification = Notification.Name("CallManagerCallsChangedNotification") 

    private(set) var calls = [SpeakerboxCall]()

    func callWithUUID(uuid: UUID) -> SpeakerboxCall? {
        guard let index = calls.index(where: { $0.uuid == uuid }) else {
            return nil
        }
        return calls[index]
    }

    func addCall(_ call: SpeakerboxCall) {
        calls.append(call)

        call.stateDidChange = { [weak self] in
            self?.postCallsChangedNotification()
        }

        postCallsChangedNotification(userInfo: ["action": Call.start.rawValue])
    }

    func removeCall(_ call: SpeakerboxCall) {
        calls = calls.filter {$0 === call}
        postCallsChangedNotification(userInfo: ["action": Call.end.rawValue])
    }

    func removeAllCalls() {
        calls.removeAll()
        postCallsChangedNotification(userInfo: ["action": Call.end.rawValue])
    }

    private func postCallsChangedNotification(userInfo: [String: Any]? = nil) {
        NotificationCenter.default.post(name: type(of: self).CallsChangedNotification, object: self, userInfo: userInfo)
    }
}


================================================
FILE: CallKit-with-native-OpenTok-support/CallKitDemo/ViewController.swift
================================================
//
//  ViewController.swift
//  CallKitDemo
//
//  Created by Xi Huang on 6/5/17.
//  Copyright © 2017 Tokbox, Inc. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    fileprivate final let displayCaller = "Lucas Huang"
    fileprivate final let makeACallText = "Make a call"
    fileprivate final let unholdCallText = "Unhold Call"
    fileprivate final let simulateIncomingCallText = "Simulate Call"
    fileprivate final let simulateIncomingCallThreeSecondsText = "Simulate Call after 3s(Background)"
    fileprivate final let endCallText = "End call"

    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        NotificationCenter.default.addObserver(self, selector: #selector(handleCallsChangedNotification(notification:)), name: SpeakerboxCallManager.CallsChangedNotification, object: nil)
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        NotificationCenter.default.removeObserver(self)
    }
    
    @IBOutlet weak var callButton: UIButton!
    @IBOutlet weak var simulateCallButton: UIButton!
    @IBOutlet weak var simulateCallButton2: UIButton!
    
    @IBAction func receiveCallLucas(_ sender: UIButton) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        if simulateCallButton.titleLabel?.text == simulateIncomingCallText {
            appdelegate.displayIncomingCall(uuid: UUID(), handle: displayCaller)
            sender.setTitle(endCallText, for: .normal)
            sender.setTitleColor(.red, for: .normal)
            callButton.isEnabled = false
            simulateCallButton2.isEnabled = false
        }
        else {
            endCall()
            sender.setTitle(simulateIncomingCallText, for: .normal)
            sender.setTitleColor(.white, for: .normal)
            callButton.isEnabled = true
            simulateCallButton2.isEnabled = true
        }
    }
    
    @IBAction func receiveCallLucasAfterThreeSeconds(_ sender: UIButton) {
        guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else {
            
            print("appdelegate is missing")
            return
        }
        
        if sender.titleLabel?.text == simulateIncomingCallThreeSecondsText {
            
            let backgroundTaskIdentifier = UIApplication.shared.beginBackg
Download .txt
gitextract_wr9whvu1/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── metrics.yml
├── .gitignore
├── .travis.yml
├── Basic-Video-Chat/
│   ├── Basic-Video-Chat/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Basic-Video-Chat.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Basic-Video-Chat.xcscheme
│   ├── Podfile
│   └── README.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CallKit/
│   ├── CallKitDemo/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── IconMask.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── baseHeroMount.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── CallKitDemo-Bridging-Header.h
│   │   ├── CallKitDemo.entitlements
│   │   ├── Info.plist
│   │   ├── OTDefaultAudioDevice.h
│   │   ├── OTDefaultAudioDevice.m
│   │   ├── ProviderDelegate.swift
│   │   ├── Ringtone.caf
│   │   ├── SpeakerboxCall.swift
│   │   ├── SpeakerboxCallManager.swift
│   │   └── ViewController.swift
│   ├── CallKitDemo.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── CallKitDemo.xcscheme
│   ├── LICENSE
│   ├── Podfile
│   └── README.md
├── CallKit-with-native-OpenTok-support/
│   ├── CallKitDemo/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── IconMask.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── baseHeroMount.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── CallKitDemo.entitlements
│   │   ├── Info.plist
│   │   ├── ProviderDelegate.swift
│   │   ├── Ringtone.caf
│   │   ├── SpeakerboxCall.swift
│   │   ├── SpeakerboxCallManager.swift
│   │   └── ViewController.swift
│   ├── CallKitDemo.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── CallKitDemo.xcscheme
│   ├── LICENSE
│   ├── Podfile
│   ├── README.md
│   └── pu.sh
├── Custom-Audio-Driver/
│   ├── Custom-Audio-Driver/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── DefaultAudioDevice.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Custom-Audio-Driver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Custom-Audio-Driver.xcscheme
│   ├── Podfile
│   └── README.md
├── Custom-Video-Driver/
│   ├── Custom-Video-Driver.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Custom-Video-Driver.xcscheme
│   ├── Lets-Build-OTPublisher/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── EAGLVideoRenderer.swift
│   │   ├── ExampleVideoCapture.swift
│   │   ├── ExampleVideoRender.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Podfile
│   └── README.md
├── E2EE-Video-Chat/
│   ├── Basic-Video-Chat/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Basic-Video-Chat.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Basic-Video-Chat.xcscheme
│   ├── Podfile
│   └── README.md
├── FrameMetadata/
│   ├── FrameMetadata/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── FrameMetadata.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── FrameMetadata.xcscheme
│   ├── Podfile
│   └── README.md
├── LICENSE
├── Live-Photo-Capture/
│   ├── Live-Photo-Capture/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ExamplePhotoVideoCapture.swift
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Live-Photo-Capture.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Live-Photo-Capture.xcscheme
│   ├── Podfile
│   └── README.md
├── Media-Transformers/
│   ├── CHANGELOG.md
│   ├── Media-Transformers/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   ├── Media-Transformers.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Video-Transformers.xcscheme
│   ├── Podfile
│   └── README.md
├── Multiparty-UICollectionView/
│   ├── Multiparty-UICollectionView/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ChatViewController.swift
│   │   ├── Info.plist
│   │   └── MultipartyLayout.swift
│   ├── Multiparty-UICollectionView.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Multiparty-UICollectionView.xcscheme
│   ├── Podfile
│   └── README.md
├── OpenTokSDKVersion.rb
├── Picture-In-Picture/
│   ├── Lets-Build-OTPublisher/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── ExampleVideoRender.swift
│   │   ├── Info.plist
│   │   ├── SampleBufferVideoCallView.swift
│   │   └── ViewController.swift
│   ├── Picture-In-Picture.xcodeproj/
│   │   ├── project.pbxproj
│   │   └── xcshareddata/
│   │       └── xcschemes/
│   │           └── Picture-In-Picture.xcscheme
│   ├── Podfile
│   └── README.md
├── README.md
├── Screen-Sharing/
│   ├── Podfile
│   ├── README.md
│   ├── Screen-Sharing/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   └── AppIcon.appiconset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   ├── ScreenCapturer.swift
│   │   └── ViewController.swift
│   └── Screen-Sharing.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── Screen-Sharing.xcscheme
├── Signals/
│   ├── Podfile
│   ├── README.md
│   ├── Signals/
│   │   ├── Assets.xcassets/
│   │   │   ├── AccentColor.colorset/
│   │   │   │   └── Contents.json
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── ContentView.swift
│   │   ├── FormView.swift
│   │   ├── MessagesView.swift
│   │   ├── Preview Content/
│   │   │   └── Preview Assets.xcassets/
│   │   │       └── Contents.json
│   │   ├── SignalsApp.swift
│   │   └── VonageVideoSDK.swift
│   └── Signals.xcodeproj/
│       └── project.pbxproj
├── Simple-Multiparty/
│   ├── Podfile
│   ├── README.md
│   ├── Simple-Multiparty/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── Subscriber-Speaker-35.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── Subscriber-Speaker-Mute-35.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── TB Bug-30.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── camera-switch_black-33.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── camera_switch-33.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowLeft_disabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowLeft_enabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowRight_disabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── icon_arrowRight_enabled-28.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── mic-24.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── mic_muted-24.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── mic_receiving_data-35.imageset/
│   │   │       └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   └── ViewController.swift
│   └── Simple-Multiparty.xcodeproj/
│       ├── project.pbxproj
│       └── xcshareddata/
│           └── xcschemes/
│               └── Simple-Multiparty.xcscheme
└── travis_build.sh
Download .txt
SYMBOL INDEX (1 symbols across 1 files)

FILE: CallKit/CallKitDemo/OTDefaultAudioDevice.h
  function interface (line 19) | interface OTDefaultAudioDevice : NSObject <OTAudioDevice>
Condensed preview — 197 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (802K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 585,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/workflows/metrics.yml",
    "chars": 413,
    "preview": "name: Aggregit\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  recordMetrics:\n    runs-on: ubuntu-latest\n    steps:\n  "
  },
  {
    "path": ".gitignore",
    "chars": 320,
    "preview": "OpenTok.framework/\nOpenTok.framework\n\n# Xcode\n.DS_Store\n*/build/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n"
  },
  {
    "path": ".travis.yml",
    "chars": 118,
    "preview": "language: objective-c\nosx_image: xcode11.7\nbefore_install:\n  - pod repo update > /dev/null\nscript: ./travis_build.sh \n"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/AppDelegate.swift",
    "chars": 439,
    "preview": "//\n//  AppDelegate.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tokbox"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/Base.lproj/Main.storyboard",
    "chars": 1581,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/Info.plist",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat/ViewController.swift",
    "chars": 5504,
    "preview": "//\n//  ViewController.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tok"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat.xcodeproj/project.pbxproj",
    "chars": 12126,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Basic-Video-Chat/Basic-Video-Chat.xcodeproj/xcshareddata/xcschemes/Basic-Video-Chat.xcscheme",
    "chars": 3341,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1170\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Basic-Video-Chat/Podfile",
    "chars": 164,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Basic-Video-Chat' do\n "
  },
  {
    "path": "Basic-Video-Chat/README.md",
    "chars": 3589,
    "preview": "Basic Video Chat Sample App\n===============================\n\nThe Basic-Video-Chat app is a very simple application meant"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5217,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2161,
    "preview": "# Contributing Guidelines\n\nFor anyone looking to get involved to this project, we are glad to hear from you. Here are a "
  },
  {
    "path": "CallKit/CallKitDemo/AppDelegate.swift",
    "chars": 2708,
    "preview": "//\n//  AppDelegate.swift\n//  CallKitDemo\n//\n//  Created by Xi Huang on 6/5/17.\n//  Copyright © 2017 Tokbox, Inc. All rig"
  },
  {
    "path": "CallKit/CallKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "CallKit/CallKitDemo/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "CallKit/CallKitDemo/Assets.xcassets/IconMask.imageset/Contents.json",
    "chars": 385,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"IconMask-40.png\",\n      \"scale\" : \"1x\"\n    },\n  "
  },
  {
    "path": "CallKit/CallKitDemo/Assets.xcassets/baseHeroMount.imageset/Contents.json",
    "chars": 399,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\",\n      \"filename\" : \"baseHeroMount.png\"\n    },\n"
  },
  {
    "path": "CallKit/CallKitDemo/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "CallKit/CallKitDemo/Base.lproj/Main.storyboard",
    "chars": 7867,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "CallKit/CallKitDemo/CallKitDemo-Bridging-Header.h",
    "chars": 137,
    "preview": "//\n//  Use this file to import your target's public headers that you would like to expose to Swift.\n//\n\n#import \"OTDefau"
  },
  {
    "path": "CallKit/CallKitDemo/CallKitDemo.entitlements",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "CallKit/CallKitDemo/Info.plist",
    "chars": 1686,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "CallKit/CallKitDemo/OTDefaultAudioDevice.h",
    "chars": 1892,
    "preview": "//\n//  OTAudioDeviceIOSDefault.h\n//\n//  Copyright (c) 2014 TokBox, Inc. All rights reserved.\n//\n\n#import <Foundation/Fou"
  },
  {
    "path": "CallKit/CallKitDemo/OTDefaultAudioDevice.m",
    "chars": 38093,
    "preview": "//\n//  OTDefaultAudioDeviceIOS.m\n//\n//  Copyright (c) 2014 TokBox, Inc. All rights reserved.\n//\n\n#import \"OTDefaultAudio"
  },
  {
    "path": "CallKit/CallKitDemo/ProviderDelegate.swift",
    "chars": 10736,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit/CallKitDemo/SpeakerboxCall.swift",
    "chars": 6364,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit/CallKitDemo/SpeakerboxCallManager.swift",
    "chars": 3036,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit/CallKitDemo/ViewController.swift",
    "chars": 6315,
    "preview": "//\n//  ViewController.swift\n//  CallKitDemo\n//\n//  Created by Xi Huang on 6/5/17.\n//  Copyright © 2017 Tokbox, Inc. All "
  },
  {
    "path": "CallKit/CallKitDemo.xcodeproj/project.pbxproj",
    "chars": 16264,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "CallKit/CallKitDemo.xcodeproj/xcshareddata/xcschemes/CallKitDemo.xcscheme",
    "chars": 3387,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "CallKit/LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2017 OpenTok\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "CallKit/Podfile",
    "chars": 159,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'CallKitDemo' do\n  pod "
  },
  {
    "path": "CallKit/README.md",
    "chars": 9510,
    "preview": "![logo](./tokbox-logo.png)\n\n\n\n## CallKit Integration with OpenTok\nA sample app to demonstrate how to integrate the [Call"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/AppDelegate.swift",
    "chars": 2774,
    "preview": "//\n//  AppDelegate.swift\n//  CallKitDemo\n//\n//  Created by Xi Huang on 6/5/17.\n//  Copyright © 2017 Tokbox, Inc. All rig"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/IconMask.imageset/Contents.json",
    "chars": 385,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"IconMask-40.png\",\n      \"scale\" : \"1x\"\n    },\n  "
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Assets.xcassets/baseHeroMount.imageset/Contents.json",
    "chars": 399,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\",\n      \"filename\" : \"baseHeroMount.png\"\n    },\n"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Base.lproj/Main.storyboard",
    "chars": 7867,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/CallKitDemo.entitlements",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/Info.plist",
    "chars": 1686,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/ProviderDelegate.swift",
    "chars": 11371,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/SpeakerboxCall.swift",
    "chars": 6956,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/SpeakerboxCallManager.swift",
    "chars": 3180,
    "preview": "/*\n\tCopyright (C) 2016 Apple Inc. All Rights Reserved.\n\tSee LICENSE.txt for this sample’s licensing information\n\t\n\tAbstr"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo/ViewController.swift",
    "chars": 6315,
    "preview": "//\n//  ViewController.swift\n//  CallKitDemo\n//\n//  Created by Xi Huang on 6/5/17.\n//  Copyright © 2017 Tokbox, Inc. All "
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo.xcodeproj/project.pbxproj",
    "chars": 23118,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 60;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/CallKitDemo.xcodeproj/xcshareddata/xcschemes/CallKitDemo.xcscheme",
    "chars": 3387,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "CallKit-with-native-OpenTok-support/LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2017 OpenTok\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/Podfile",
    "chars": 159,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'CallKitDemo' do\n  pod "
  },
  {
    "path": "CallKit-with-native-OpenTok-support/README.md",
    "chars": 10851,
    "preview": "![logo](./tokbox-logo.png)\n\n\n\n## CallKit Integration with OpenTok\nA sample app to demonstrate how to integrate the [Call"
  },
  {
    "path": "CallKit-with-native-OpenTok-support/pu.sh",
    "chars": 1273,
    "preview": "#!/bin/bash\n\nPAYLOAD=\"\"\n\nuuid=$(uuidgen | tr '[:upper:]' '[:lower:]')\n\nif [ -z \"$1\" ]\n  then\n    PAYLOAD=\"{\\\"aps\\\":{\\\"co"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/AppDelegate.swift",
    "chars": 832,
    "preview": "//\n//  AppDelegate.swift\n//  4.Custom-Audio-Driver\n//\n//  Created by Roberto Perez Cubero on 21/09/2016.\n//  Copyright ©"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 848,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/Base.lproj/Main.storyboard",
    "chars": 1695,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/DefaultAudioDevice.swift",
    "chars": 25157,
    "preview": "//\n//  DefaultAudioDevice.swift\n//  4.Custom-Audio-Driver\n//\n//  Created by Roberto Perez Cubero on 21/09/2016.\n//  Copy"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/Info.plist",
    "chars": 1268,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver/ViewController.swift",
    "chars": 4939,
    "preview": "//\n//  ViewController.swift\n//  Custom-Audio-Driver\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © "
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver.xcodeproj/project.pbxproj",
    "chars": 13392,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Custom-Audio-Driver/Custom-Audio-Driver.xcodeproj/xcshareddata/xcschemes/Custom-Audio-Driver.xcscheme",
    "chars": 3377,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Custom-Audio-Driver/Podfile",
    "chars": 167,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Custom-Audio-Driver' d"
  },
  {
    "path": "Custom-Audio-Driver/README.md",
    "chars": 2165,
    "preview": "Custom Audio Driver Sample App\n================================\n\nThis project implements a controller nearly identical t"
  },
  {
    "path": "Custom-Video-Driver/Custom-Video-Driver.xcodeproj/project.pbxproj",
    "chars": 14035,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Custom-Video-Driver/Custom-Video-Driver.xcodeproj/xcshareddata/xcschemes/Custom-Video-Driver.xcscheme",
    "chars": 3377,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/AppDelegate.swift",
    "chars": 904,
    "preview": "//\n//  AppDelegate.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © "
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 585,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/Base.lproj/Main.storyboard",
    "chars": 3729,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/EAGLVideoRenderer.swift",
    "chars": 14101,
    "preview": "//\n//  EAGLVideoRenderer.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 16/08/16.\n//  Copyri"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/ExampleVideoCapture.swift",
    "chars": 11678,
    "preview": "//\n//  ExampleVideoCapture.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copy"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/ExampleVideoRender.swift",
    "chars": 6616,
    "preview": "//\n//  ExampleVideoRender.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyr"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/Info.plist",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Custom-Video-Driver/Lets-Build-OTPublisher/ViewController.swift",
    "chars": 6171,
    "preview": "//\n//  ViewController.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright"
  },
  {
    "path": "Custom-Video-Driver/Podfile",
    "chars": 167,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Custom-Video-Driver' d"
  },
  {
    "path": "Custom-Video-Driver/README.md",
    "chars": 3503,
    "preview": "Custom Video Driver Sample App\n==================================\n\nThis project uses the custom video driver features in"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/AppDelegate.swift",
    "chars": 439,
    "preview": "//\n//  AppDelegate.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tokbox"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/Base.lproj/Main.storyboard",
    "chars": 1581,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/Info.plist",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat/ViewController.swift",
    "chars": 5842,
    "preview": "//\n//  ViewController.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tok"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat.xcodeproj/project.pbxproj",
    "chars": 16018,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "E2EE-Video-Chat/Basic-Video-Chat.xcodeproj/xcshareddata/xcschemes/Basic-Video-Chat.xcscheme",
    "chars": 3341,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1170\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "E2EE-Video-Chat/Podfile",
    "chars": 164,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Basic-Video-Chat' do\n "
  },
  {
    "path": "E2EE-Video-Chat/README.md",
    "chars": 3234,
    "preview": "E2EE Basic Video Chat Sample App\n===============================\n\nThe E2EE Basic-Video-Chat app is a very simple applica"
  },
  {
    "path": "FrameMetadata/FrameMetadata/AppDelegate.swift",
    "chars": 904,
    "preview": "//\n//  AppDelegate.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © "
  },
  {
    "path": "FrameMetadata/FrameMetadata/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 585,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "FrameMetadata/FrameMetadata/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "FrameMetadata/FrameMetadata/Base.lproj/Main.storyboard",
    "chars": 5058,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "FrameMetadata/FrameMetadata/Info.plist",
    "chars": 1385,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "FrameMetadata/FrameMetadata/ViewController.swift",
    "chars": 7733,
    "preview": "//\n//  ViewController.swift\n//  Lets-Build-OTPublisher\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright"
  },
  {
    "path": "FrameMetadata/FrameMetadata.xcodeproj/project.pbxproj",
    "chars": 14417,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "FrameMetadata/FrameMetadata.xcodeproj/xcshareddata/xcschemes/FrameMetadata.xcscheme",
    "chars": 3305,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "FrameMetadata/Podfile",
    "chars": 161,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'FrameMetadata' do\n  po"
  },
  {
    "path": "FrameMetadata/README.md",
    "chars": 2974,
    "preview": "Frame Metadata\n==================================\n\nThis project shows how to set metadata (limited to 32 bytes) to a vid"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2013-2016 TokBox, Inc.\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/AppDelegate.swift",
    "chars": 899,
    "preview": "//\n//  AppDelegate.swift\n//  Live-Photo-Capture\n//\n//  Created by Roberto Perez Cubero on 23/08/16.\n//  Copyright © 2016"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 585,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/Base.lproj/Main.storyboard",
    "chars": 1654,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/ExamplePhotoVideoCapture.swift",
    "chars": 3051,
    "preview": "//\n//  ExamplePhotoVideoCapture.swift\n//  Live-Photo-Capture\n//\n//  Created by Roberto Perez Cubero on 23/08/16.\n//  Cop"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/Info.plist",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture/ViewController.swift",
    "chars": 4662,
    "preview": "//\n//  ViewController.swift\n//  Live-Photo-Capture\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture.xcodeproj/project.pbxproj",
    "chars": 15185,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Live-Photo-Capture/Live-Photo-Capture.xcodeproj/xcshareddata/xcschemes/Live-Photo-Capture.xcscheme",
    "chars": 3365,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Live-Photo-Capture/Podfile",
    "chars": 166,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Live-Photo-Capture' do"
  },
  {
    "path": "Live-Photo-Capture/README.md",
    "chars": 2641,
    "preview": "Live Photo Capture Sample App\n================================\n\nThis project extends some of the material we presented i"
  },
  {
    "path": "Media-Transformers/CHANGELOG.md",
    "chars": 617,
    "preview": "# Video Transformers Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## 2.27.2\n\n### Add"
  },
  {
    "path": "Media-Transformers/Media-Transformers/AppDelegate.swift",
    "chars": 439,
    "preview": "//\n//  AppDelegate.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tokbox"
  },
  {
    "path": "Media-Transformers/Media-Transformers/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 849,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Media-Transformers/Media-Transformers/Base.lproj/LaunchScreen.storyboard",
    "chars": 1813,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Media-Transformers/Media-Transformers/Base.lproj/Main.storyboard",
    "chars": 1860,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Media-Transformers/Media-Transformers/Info.plist",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Media-Transformers/Media-Transformers/ViewController.swift",
    "chars": 10707,
    "preview": "//\n//  ViewController.swift\n//  Hello-World\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 tok"
  },
  {
    "path": "Media-Transformers/Media-Transformers.xcodeproj/project.pbxproj",
    "chars": 12796,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Media-Transformers/Media-Transformers.xcodeproj/xcshareddata/xcschemes/Video-Transformers.xcscheme",
    "chars": 3365,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1170\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Media-Transformers/Podfile",
    "chars": 227,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Media-Transformers' do"
  },
  {
    "path": "Media-Transformers/README.md",
    "chars": 7329,
    "preview": "Video Transformers\n======================\n\nThe Video Transformers app is a very simple application created on top of Bas"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/AppDelegate.swift",
    "chars": 842,
    "preview": "//\n//  AppDelegate.swift\n//  7.Multiparty-UICollectionView\n//\n//  Created by Roberto Perez Cubero on 17/04/2017.\n//  Cop"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/Base.lproj/Main.storyboard",
    "chars": 3121,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/ChatViewController.swift",
    "chars": 5853,
    "preview": "//\n//  ViewController.swift\n//  7.Multiparty-UICollectionView\n//\n//  Created by Roberto Perez Cubero on 17/04/2017.\n//  "
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/Info.plist",
    "chars": 1157,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView/MultipartyLayout.swift",
    "chars": 6178,
    "preview": "//\n//  MultipartyLayout.swift\n//  7.Multiparty-UICollectionView\n//\n//  Created by Roberto Perez Cubero on 17/04/2017.\n//"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView.xcodeproj/project.pbxproj",
    "chars": 12990,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Multiparty-UICollectionView/Multiparty-UICollectionView.xcodeproj/xcshareddata/xcschemes/Multiparty-UICollectionView.xcscheme",
    "chars": 3579,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0930\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Multiparty-UICollectionView/Podfile",
    "chars": 175,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Multiparty-UICollectio"
  },
  {
    "path": "Multiparty-UICollectionView/README.md",
    "chars": 2708,
    "preview": "Multiparty UICollectionView Sample App\n========================================\n\nIf you plan to build a multiparty app, "
  },
  {
    "path": "OpenTokSDKVersion.rb",
    "chars": 55,
    "preview": "OpenTokSDKVersion = '2.32.1'\nMinIosSdkVersion = '15.0'\n"
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/AppDelegate.swift",
    "chars": 746,
    "preview": "import UIKit\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n\n\n  "
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 849,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/Base.lproj/LaunchScreen.storyboard",
    "chars": 1664,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/Base.lproj/Main.storyboard",
    "chars": 4852,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/ExampleVideoRender.swift",
    "chars": 9788,
    "preview": "import OpenTok\nimport GLKit\nimport Foundation\nimport Accelerate\nimport UIKit\n\nclass Accelerater{\n    var infoYpCbCrToARG"
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/Info.plist",
    "chars": 1450,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/SampleBufferVideoCallView.swift",
    "chars": 279,
    "preview": "import UIKit\nimport AVKit\n\nclass SampleBufferVideoCallView: UIView {\n    override class var layerClass: AnyClass {\n     "
  },
  {
    "path": "Picture-In-Picture/Lets-Build-OTPublisher/ViewController.swift",
    "chars": 7832,
    "preview": "import UIKit\nimport OpenTok\nimport AVKit\n\nlet kWidgetRatio: CGFloat = 1.333\n\n// *** Fill the following variables using y"
  },
  {
    "path": "Picture-In-Picture/Picture-In-Picture.xcodeproj/project.pbxproj",
    "chars": 18864,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Picture-In-Picture/Picture-In-Picture.xcodeproj/xcshareddata/xcschemes/Picture-In-Picture.xcscheme",
    "chars": 3365,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Picture-In-Picture/Podfile",
    "chars": 166,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Picture-In-Picture' do"
  },
  {
    "path": "Picture-In-Picture/README.md",
    "chars": 1589,
    "preview": "Picture In Picture Sample App\n==================================\n\nThis project uses the custom video render features in "
  },
  {
    "path": "README.md",
    "chars": 4743,
    "preview": ":warning: **This repository has been deprecated in favour of the [Vonage iOS samples](https://github.com/Vonage/vonage-v"
  },
  {
    "path": "Screen-Sharing/Podfile",
    "chars": 162,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Screen-Sharing' do\n  p"
  },
  {
    "path": "Screen-Sharing/README.md",
    "chars": 3961,
    "preview": "Screen Sharing Sample App\n=========================\n\nThis project shows how to use OpenTok iOS SDK to publish a stream t"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/AppDelegate.swift",
    "chars": 827,
    "preview": "//\n//  AppDelegate.swift\n//  5.Screen-Sharing\n//\n//  Created by Roberto Perez Cubero on 23/09/2016.\n//  Copyright © 2016"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 585,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/Base.lproj/Main.storyboard",
    "chars": 3763,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/Info.plist",
    "chars": 1268,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/ScreenCapturer.swift",
    "chars": 10121,
    "preview": "//\n//  ScreenCapturer.swift\n//  5.Screen-Sharing\n//\n//  Created by Roberto Perez Cubero on 23/09/2016.\n//  Copyright © 2"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing/ViewController.swift",
    "chars": 5056,
    "preview": "//\n//  ViewController.swift\n//  Screen-Sharing\n//\n//  Created by Roberto Perez Cubero on 11/08/16.\n//  Copyright © 2016 "
  },
  {
    "path": "Screen-Sharing/Screen-Sharing.xcodeproj/project.pbxproj",
    "chars": 13643,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Screen-Sharing/Screen-Sharing.xcodeproj/xcshareddata/xcschemes/Screen-Sharing.xcscheme",
    "chars": 3317,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Signals/Podfile",
    "chars": 155,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Signals' do\n  pod 'OTX"
  },
  {
    "path": "Signals/README.md",
    "chars": 3430,
    "preview": "Signaling Sample App\n===============================\n\nThe Signaling app is a very simple application meant to get a new "
  },
  {
    "path": "Signals/Signals/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "Signals/Signals/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 177,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"i"
  },
  {
    "path": "Signals/Signals/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Signals/Signals/ContentView.swift",
    "chars": 1959,
    "preview": "//\n//  ContentView.swift\n//  Signals\n//\n//  Created by Jaideep Shah on 2/9/23.\n//\n\nimport SwiftUI\nstruct ContentView {\n "
  },
  {
    "path": "Signals/Signals/FormView.swift",
    "chars": 4144,
    "preview": "//\n//  FormView.swift\n//\n//  Created by Jaideep Shah on 2/9/23.\n//\n\nimport SwiftUI\n\nstruct FormView {\n    @EnvironmentOb"
  },
  {
    "path": "Signals/Signals/MessagesView.swift",
    "chars": 2568,
    "preview": "//\n//  MessagesView.swift\n//  \n//\n//  Created by Jaideep Shah on 2/9/23.\n//\n\nimport SwiftUI\n\n\n\nstruct MessagesView {\n   "
  },
  {
    "path": "Signals/Signals/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Signals/Signals/SignalsApp.swift",
    "chars": 222,
    "preview": "//\n//  SignalsApp.swift\n//  Signals\n//\n//  Created by Jaideep Shah on 2/9/23.\n//\n\nimport SwiftUI\n\n@main\nstruct SignalsAp"
  },
  {
    "path": "Signals/Signals/VonageVideoSDK.swift",
    "chars": 6612,
    "preview": "//\n//  VonageVideoSDK.swift\n//  Signals\n//\n//  Created by Jaideep Shah on 2/15/23.\n//\n\nimport UIKit\nimport OpenTok\n\n\n\nle"
  },
  {
    "path": "Signals/Signals.xcodeproj/project.pbxproj",
    "chars": 14477,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Simple-Multiparty/Podfile",
    "chars": 165,
    "preview": "require_relative '../OpenTokSDKVersion'\n\nplatform :ios, MinIosSdkVersion\nuse_frameworks!\n\ntarget 'Simple-Multiparty' do\n"
  },
  {
    "path": "Simple-Multiparty/README.md",
    "chars": 2625,
    "preview": "Simple Multiparty Sample App\n==============================\n\nPrevious samples subscribe to only one stream. In a multipa"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/AppDelegate.swift",
    "chars": 824,
    "preview": "//\n//  AppDelegate.swift\n//  6.Multi-Party\n//\n//  Created by Roberto Perez Cubero on 27/09/2016.\n//  Copyright © 2016 to"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 753,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"20x20\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/Subscriber-Speaker-35.imageset/Contents.json",
    "chars": 369,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"Subscriber-Speaker-35.png\",\n      \"scale\" : \"1x\""
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/Subscriber-Speaker-Mute-35.imageset/Contents.json",
    "chars": 379,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"Subscriber-Speaker-Mute-35.png\",\n      \"scale\" :"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/TB Bug-30.imageset/Contents.json",
    "chars": 345,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"TB Bug-30.png\",\n      \"scale\" : \"1x\"\n    },\n    "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/camera-switch_black-33.imageset/Contents.json",
    "chars": 371,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"camera-switch_black-33.png\",\n      \"scale\" : \"1x"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/camera_switch-33.imageset/Contents.json",
    "chars": 359,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"camera_switch-33.png\",\n      \"scale\" : \"1x\"\n    "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/icon_arrowLeft_disabled-28.imageset/Contents.json",
    "chars": 379,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"icon_arrowLeft_disabled-28.png\",\n      \"scale\" :"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/icon_arrowLeft_enabled-28.imageset/Contents.json",
    "chars": 377,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"icon_arrowLeft_enabled-28.png\",\n      \"scale\" : "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/icon_arrowRight_disabled-28.imageset/Contents.json",
    "chars": 381,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"icon_arrowRight_disabled-28.png\",\n      \"scale\" "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/icon_arrowRight_enabled-28.imageset/Contents.json",
    "chars": 379,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"icon_arrowRight_enabled-28.png\",\n      \"scale\" :"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/mic-24.imageset/Contents.json",
    "chars": 339,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"mic-24.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/mic_muted-24.imageset/Contents.json",
    "chars": 351,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"mic_muted-24.png\",\n      \"scale\" : \"1x\"\n    },\n "
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Assets.xcassets/mic_receiving_data-35.imageset/Contents.json",
    "chars": 369,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"mic_receiving_data-35.png\",\n      \"scale\" : \"1x\""
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Base.lproj/LaunchScreen.storyboard",
    "chars": 1740,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Base.lproj/Main.storyboard",
    "chars": 17253,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/Info.plist",
    "chars": 1268,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty/ViewController.swift",
    "chars": 8695,
    "preview": "//\n//  ViewController.swift\n//  6.Multi-Party\n//\n//  Created by Roberto Perez Cubero on 27/09/2016.\n//  Copyright © 2016"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty.xcodeproj/project.pbxproj",
    "chars": 12477,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Simple-Multiparty/Simple-Multiparty.xcodeproj/xcshareddata/xcschemes/Simple-Multiparty.xcscheme",
    "chars": 3353,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1200\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "travis_build.sh",
    "chars": 1673,
    "preview": "#!/bin/sh\n\nset -e\n\ncd Basic-Video-Chat/\npod install\nxcodebuild -workspace Basic-Video-Chat.xcworkspace  -scheme Basic-Vi"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the opentok/opentok-ios-sdk-samples-swift GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 197 files (721.0 KB), approximately 195.5k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!