### Set Custom Audio Device in OpenTok iOS SDK (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Custom-Audio-Driver/README.md This snippet demonstrates how to set a custom audio device before initializing OpenTok objects. It requires the DefaultAudioDevice class and the OTSDK. Ensure this setup occurs before any OTSession or OTPublisher is instantiated. Failure to set a device will result in the default driver being used. ```swift let customAudioDevice = DefaultAudioDevice.sharedInstance OTAudioDeviceManager.setAudioDevice(customAudioDevice) ``` -------------------------------- ### Request Call Start Action via CXCallController in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md Initiates a call using CXCallController by creating a CXStartCallAction with a unique call identifier and handle. This action is then added to a CXTransaction, which is subsequently requested from the call controller. Error handling is included for the transaction request. ```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") } } ``` -------------------------------- ### Handle CXProvider Delegate Callbacks Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit-with-native-OpenTok-support/README.md Implements the `CXProviderDelegate` protocol to respond to CallKit events such as starting, answering, ending, holding, or muting calls. Notifies the OpenTok SDK about audio session activation and deactivation stages. ```swift let sessionManager = OTAudioDeviceManager.currentAudioSessionManager() // MARK: CXProviderDelegate func providerDidReset(_ provider: CXProvider) { print("Provider did reset") } func provider(_ provider: CXProvider, perform action: CXStartCallAction) { print("Provider performs the start call action") // Pre-heating stage for outgoing calls sessionManager?.preconfigureAudioSessionForCall(withMode: .videoChat) } func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { print("Provider performs the answer call action") // Pre-heating stage for incoming calls sessionManager?.preconfigureAudioSessionForCall(withMode: .videoChat) } 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") // You may want to mute/unmute the publisher here } func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { print("Provider performs the mute call action") // You may want to mute/unmute the publisher here } ``` -------------------------------- ### Implement CXProviderDelegate Methods in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md Implements the CXProviderDelegate protocol to handle various telephony events from the CXProvider. This includes actions for resetting the provider, performing start, answer, end, and set hold actions for calls, along with actions for muting calls. ```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") } ``` -------------------------------- ### Creating an OTVideoFrame from a Screenshot in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Demonstrates the process of capturing a screenshot of a UIView, resizing and padding it, and then consuming it as a video frame for publishing. This is part of the custom video capturer implementation for screen sharing. ```swift let screen = self.screenShoot() let padded = self.resizeAndPad(image: screen) self.consume(frame: padded) ``` -------------------------------- ### Consuming and Publishing a Video Frame in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Prepares an `OTVideoFrame` with timestamp, format, and pixel data, then consumes it using the `videoCaptureConsumer`. This method is central to the custom video capturer, enabling the transmission of captured screen content. ```swift let timeStamp = mach_absolute_time() let time = CMTime(seconds: Double(timeStamp), preferredTimescale: 1000) let ref = pixelBuffer(fromCGImage: frame) CVPixelBufferLockBaseAddress(ref, CVPixelBufferLockFlags(rawValue: 0)) videoFrame?.timestamp = time videoFrame?.format.estimatedCaptureDelay = 100 videoFrame?.orientation = .up videoFrame?.clearPlanes() videoFrame?.planes.addPointer(CVPixelBufferGetBaseAddress(ref)) videoCaptureConsumer.consumeFrame(videoFrame) ``` -------------------------------- ### Configure and Set Up CXProvider in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md Initializes a CXProviderConfiguration with application-specific details like localized name, video support, maximum calls, supported handle types, icon, and ringtone. It then creates a CXProvider with this configuration and sets the delegate to receive telephony events. ```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) ``` -------------------------------- ### Swift: ViewController Controls for Publisher Audio and Camera Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Simple-Multiparty/README.md Demonstrates how to implement user interface controls within the ViewController for managing the publisher's stream. Specifically, it shows how to toggle the publisher's audio on and off by invoking an IBAction that updates the publisher's publishAudio property and changes the associated mute button's image. ```swift @IBAction func muteMicAction(_ sender: AnyObject) { publisher.publishAudio = !publisher.publishAudio let buttonImage: UIImage = { if !publisher.publishAudio { return #imageLiteral(resourceName: "mic_muted-24") } else { return #imageLiteral(resourceName: "mic-24") } }() muteMicButton.setImage(buttonImage, for: .normal) } ``` -------------------------------- ### Configuring Video Format for OpenTok iOS Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Initializes an `OTVideoFormat` object, specifying the pixel format for the video stream. This is used within the custom video capturer to define the characteristics of the video frames being captured. ```swift let format = OTVideoFormat() format.pixelFormat = .argb ``` -------------------------------- ### Basic Video Chat - One-to-One Communication in Swift Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt Initializes and connects to an OpenTok session, publishes local audio/video, and subscribes to remote streams. Requires OpenTok API key, session ID, and token. Demonstrates session connection, publishing, and subscribing delegates. ```swift import UIKit import OpenTok let kApiKey = "your-api-key" let kSessionId = "your-session-id" let kToken = "your-token" class ViewController: UIViewController { lazy var session: OTSession = { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! // Force unwrap for simplicity in example }() var publisher: OTPublisher? var subscriber: OTSubscriber? override func viewDidLoad() { super.viewDidLoad() // Call super first doConnect() } fileprivate func doConnect() { var error: OTError? // Use var for error defer { processError(error) } // Defer error processing session.connect(withToken: kToken, error: &error) } fileprivate func doPublish() { var error: OTError? // Use var for error defer { processError(error) } // Defer error processing let settings = OTPublisherSettings() settings.name = UIDevice.current.name publisher = OTPublisher(delegate: self, settings: settings)! // Force unwrap for simplicity session.publish(publisher!, error: &error) // Add publisher view if available if let pubView = publisher!.view { // Use ! for simplicity in example pubView.frame = CGRect(x: 0, y: 0, width: 320, height: 240) view.addSubview(pubView) } } fileprivate func doSubscribe(_ stream: OTStream) { var error: OTError? // Use var for error defer { processError(error) } // Defer error processing subscriber = OTSubscriber(stream: stream, delegate: self) session.subscribe(subscriber!, error: &error) } fileprivate func processError(_ error: OTError?) { if let err = error { DispatchQueue.main.async { // Ensure UI updates on main thread 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: - OTSessionDelegate extension ViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("Session connected") doPublish() } func session(_ session: OTSession, streamCreated stream: OTStream) { print("Session streamCreated: (stream.streamId)") if subscriber == nil { // Only subscribe if no subscriber exists doSubscribe(stream) } } func session(_ session: OTSession, streamDestroyed stream: OTStream) { print("Session streamDestroyed: (stream.streamId)") subscriber?.view?.removeFromSuperview() subscriber = nil } func session(_ session: OTSession, didFailWithError error: OTError) { print("Session failed to connect: (error.localizedDescription)") processError(error) // Process the session failure error } } // MARK: - OTPublisherDelegate extension ViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, streamCreated stream: OTStream) { print("Publishing") } func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("Publisher failed: (error.localizedDescription)") processError(error) // Process publisher failure error } } // MARK: - OTSubscriberDelegate extension ViewController: OTSubscriberDelegate { func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) { if let subsView = subscriber?.view { // Safely unwrap subscriber view subsView.frame = CGRect(x: 0, y: 240, width: 320, height: 240) view.addSubview(subsView) } } func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) { print("Subscriber failed: (error.localizedDescription)") processError(error) // Process subscriber failure error } } ``` -------------------------------- ### Publishing a Screen Stream with OpenTok iOS Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Initiates the process of publishing a screen stream using OpenTok iOS SDK. It configures publisher settings, sets the video type to screen, and assigns a custom screen capturer to the publisher. An error handling mechanism is included using `defer`. ```swift func doPublish() { defer { process(error: error) } let settings = OTPublisherSettings() settings.name = UIDevice.current.name publisher = OTPublisher(delegate: self, settings: settings) publisher?.videoType = .screen publisher?.audioFallbackEnabled = false capturer = ScreenCapturer(withView: view) publisher?.videoCapture = capturer!.videoCapture() var error: OTError? = nil session.publish(publisher!, error: &error) } ``` -------------------------------- ### Replace API Key, Session ID, and Token in ViewController.swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/README.md This snippet shows how to replace placeholder strings in the ViewController.swift file with your OpenTok API key, session ID, and token. Ensure these values are obtained from your OpenTok account for authentication and session establishment. This is a crucial step for running the sample applications. ```swift // *** 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 = "" ``` -------------------------------- ### Apply Custom Video Transformers in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Media-Transformers/README.md This Swift code demonstrates how to initialize and apply custom video transformers to an OpenTok publisher. It involves creating an instance of the custom transformer class, wrapping it in an OTVideoTransformer object, and assigning an array of these transformers to the publisher's videoTransformers property. ```swift // Create an instance of CustomTransformer var logoTransformer: CustomTransformer = CustomTransformer() ... // Assuming 'publisher' is an initialized OTPublisherKit instance // Create custom transformer guard let myCustomTransformer = OTVideoTransformer(name: "logo", transformer: logoTransformer) else { return } var myVideoTransformers = [OTVideoTransformer]() myVideoTransformers.append(myCustomTransformer) // Set video transformers to publisher video stream publisher.videoTransformers = myVideoTransformers ``` -------------------------------- ### Publish Screen Content with Custom Capturer (Swift) Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This Swift code snippet demonstrates how to publish screen content using a custom `ScreenCapturer`. The capturer captures a specified UI view, renders it as a video stream, and sends it to the OpenTok session. It requires the OpenTok SDK, UIKit, and proper session and token credentials. The video type is set to `.screen` and audio fallback is disabled. ```swift import UIKit import OpenTok let kApiKey = "your-api-key" let kSessionId = "your-session-id" let kToken = "your-token" class ViewController: UIViewController { lazy var session: OTSession = { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! }() var publisher: OTPublisher? var subscriber: OTSubscriber? var capturer: ScreenCapturer? override func viewDidLoad() { super.viewDidLoad() doConnect() } private func doConnect() { var error: OTError? defer { process(error: error) } session.connect(withToken: kToken, error: &error) } fileprivate func doPublish() { var error: OTError? = nil defer { process(error: error) } let settings = OTPublisherSettings() settings.name = UIDevice.current.name publisher = OTPublisher(delegate: self, settings: settings) publisher?.videoType = .screen publisher?.audioFallbackEnabled = false capturer = ScreenCapturer(withView: view) publisher?.videoCapture = capturer publisher?.videoCapture?.videoContentHint = .text session.publish(publisher!, error: &error) } fileprivate func process(error err: OTError?) { if let e = err { showAlert(errorStr: e.localizedDescription) } } fileprivate func showAlert(errorStr err: String) { DispatchQueue.main.async { let controller = UIAlertController(title: "Error", message: err, preferredStyle: .alert) controller.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) self.present(controller, animated: true, completion: nil) } } } extension ViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("Session connected") doPublish() } func session(_ session: OTSession, streamCreated stream: OTStream) { print("Session streamCreated: (stream.streamId)") var error: OTError? subscriber = OTSubscriber(stream: stream, delegate: self) session.subscribe(subscriber!, error: &error) } } extension ViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("Publisher failed: (error.localizedDescription)") } } extension ViewController: OTSubscriberDelegate { func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) { print("Subscriber connected") } } class ScreenCapturer: NSObject, OTVideoCapture { var videoContentHint: OTVideoContentHint var videoCaptureConsumer: OTVideoCaptureConsumer? fileprivate let captureView: UIView fileprivate let captureQueue = DispatchQueue(label: "ot-screen-capture") fileprivate var timer: DispatchSourceTimer fileprivate var capturing: Bool = false init(withView: UIView) { self.videoContentHint = .none captureView = withView timer = DispatchSource.makeTimerSource(flags: .strict, queue: captureQueue) } func initCapture() { timer.setEventHandler { DispatchQueue.main.async { let screen = self.screenShoot() let padded = self.resizeAndPad(image: screen) self.consume(frame: padded) } } timer.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.milliseconds(100)) timer.resume() } func start() -> Int32 { capturing = true return 0 } func stop() -> Int32 { capturing = false return 0 } fileprivate func screenShoot() -> UIImage { UIGraphicsBeginImageContextWithOptions(captureView.bounds.size, false, 0.0) captureView.drawHierarchy(in: captureView.bounds, afterScreenUpdates: false) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } } ``` -------------------------------- ### Swift: Receive and Process Custom Metadata from Video Frames Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This snippet demonstrates how to receive video frames with attached metadata and process it. It's implemented within the `ExampleVideoRenderDelegate` protocol. The code checks if metadata exists, decodes it as a UTF-8 string, and updates a `UILabel` on the main thread. ```swift import UIKit import OpenTok class ViewController: UIViewController { // ... (session, dateFormatter, publisher, subscriber setup) @IBOutlet weak var metadataLabel: UILabel! // ... (other methods) } extension ViewController: ExampleVideoRenderDelegate { func renderer(_ renderer: ExampleVideoRender, didReceiveFrame videoFrame: OTVideoFrame) { guard let metadata = videoFrame.metadata, let timestamp = String(data: metadata, encoding: .utf8) else { print("Receiving video frame without metadata attached") return } DispatchQueue.main.async { self.metadataLabel.text = timestamp print("Receiving video frame metadata", timestamp) } } } // Assume ExampleVideoRender class is defined elsewhere and implements the ExampleVideoRenderDelegate protocol. protocol ExampleVideoRenderDelegate: AnyObject { func renderer(_ renderer: ExampleVideoRender, didReceiveFrame videoFrame: OTVideoFrame) } class ExampleVideoRender { weak var delegate: ExampleVideoRenderDelegate? // ... implementation to render video frames func renderOutput(...) { // When a frame is received for rendering: // self.delegate?.renderer(self, didReceiveFrame: videoFrame) } } ``` -------------------------------- ### Receive Signal using OTSessionDelegate Callback (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Signals/README.md Provides the implementation for the `OTSessionDelegate` callback used to receive signals sent between participants in an OpenTok session. This method is invoked when a signal is received, providing the signal type, sender's connection, and the signal's string data. ```swift func session(_ session: OTSession, receivedSignalType type: String?, from connection: OTConnection?, with string: String?) { .. } ``` -------------------------------- ### Setting Custom Video Capturer in OpenTok iOS Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Assigns a custom video capturer to the `OTPublisherKit` object. This allows the SDK to use a non-camera video source, such as a screen capture, for publishing the video stream. ```swift publisher?.videoCapture = capturer!.videoCapture() ``` -------------------------------- ### Setting Video Type to Screen in OpenTok iOS Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Screen-Sharing/README.md Configures the `OTPublisherKit` to publish a stream optimized for screen sharing. Setting `videoType` to `.screen` enables specific encoding optimizations and may disable features like audio fallback. ```swift publisher?.videoType = .screen ``` -------------------------------- ### Configure OpenTok API Credentials in AppDelegate.swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md This snippet shows how to configure the essential OpenTok API Key, Session ID, and Token within the AppDelegate.swift file. These values are crucial for initializing and connecting to the OpenTok platform. Ensure these are replaced with valid credentials obtained from the OpenTok Developer Dashboard or generated via an OpenTok Server SDK. ```swift let apiKey = "" let sessionId = "" let token = "" ``` -------------------------------- ### Implement End-to-End Encryption in OpenTok Swift Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This snippet demonstrates how to enable end-to-end encryption for a video chat session using the OpenTok Swift SDK. It involves initializing the OpenTok session and publisher, setting a shared encryption secret using `session.setEncryptionSecret()`, and then connecting to the session and publishing the local video stream. Ensure the `kEncryptionSecret` is securely shared between participants. ```swift import UIKit import OpenTok let kApiKey = "your-api-key" let kSessionId = "your-session-id" let kToken = "your-token" let kEncryptionSecret = "your-encryption-secret" class ViewController: UIViewController { lazy var session: OTSession = { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! }() lazy var publisher: OTPublisher = { let settings = OTPublisherSettings() settings.name = UIDevice.current.name return OTPublisher(delegate: self, settings: settings)! }() var subscriber: OTSubscriber? = nil override func viewDidLoad() { super.viewDidLoad() setEncryptionSecret() doConnect() } fileprivate func setEncryptionSecret() { var error: OTError? defer { processError(error) } session.setEncryptionSecret(kEncryptionSecret, error: &error) } fileprivate func doConnect() { var error: OTError? defer { processError(error) } session.connect(withToken: kToken, error: &error) } fileprivate func doPublish() { var error: OTError? defer { processError(error) } session.publish(publisher, error: &error) if let pubView = publisher.view { pubView.frame = CGRect(x: 0, y: 0, width: 320, height: 240) view.addSubview(pubView) } } 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) } } } } extension ViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("Session connected") doPublish() } func session(_ session: OTSession, streamCreated stream: OTStream) { print("Session streamCreated: \(stream.streamId)") if subscriber == nil { var error: OTError? subscriber = OTSubscriber(stream: stream, delegate: self) session.subscribe(subscriber!, error: &error) } } } ``` -------------------------------- ### Handle OpenTok Subscriber Connection and Errors in Swift Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt Implement methods to manage the connection status of an OpenTok subscriber. This includes logging successful connections and handling/logging any errors that occur during the subscription process. Dependencies include the OpenTok SDK. ```swift func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) { print("Subscriber connected") reloadCollectionView() } func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) { print("Subscriber failed: (error.localizedDescription)") } ``` -------------------------------- ### Send Signal using OpenTok Session Object (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Signals/README.md Demonstrates how to send a signal with a specified type and string data to a connection within an OpenTok session. It supports an optional parameter to retry sending after a reconnect. The error handling in this method is silent. ```swift session.signal(withType: type , string: data, connection:c.getOTConnection(), error: nil) ``` ```swift session.signal(withType: type , string: data, connection:c.getOTConnection(), retryAfterReconnect: retryAfterConnect, error: nil) ``` -------------------------------- ### Swift: Attach Custom Metadata to Video Frames Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This snippet shows how to attach custom metadata (up to 32 bytes) to video frames before publishing. It uses a `DateFormatter` to create a timestamp string and converts it to `Data` to be set as metadata using `videoFrame.setMetadata`. Error handling for the metadata setting is included. ```swift import UIKit import OpenTok class ViewController: UIViewController { // ... (session, dateFormatter, publisher, subscriber setup) fileprivate func setTimestampToVideoFrame(_ videoFrame: OTVideoFrame?) { guard let videoFrame = videoFrame else { return } let timestamp = self.dateFormatter.string(from: Date()) let metadata = Data(timestamp.utf8) var error: OTError? videoFrame.setMetadata(metadata, error: &error) if let error = error { print(error) } } // This method is called by the video capture func finishPreparingFrame(_ videoFrame: OTVideoFrame?) { guard let videoFrame = videoFrame else { return } setTimestampToVideoFrame(videoFrame) } // ... (other methods and delegates) } // Assume ExampleVideoCapture and ExampleVideoRender classes handle frame capture and rendering respectively, // and call finishPreparingFrame on the delegate when a frame is ready. protocol FrameCapturerMetadataDelegate: AnyObject { func finishPreparingFrame(_ videoFrame: OTVideoFrame?) } protocol ExampleVideoRenderDelegate: AnyObject { func renderer(_ renderer: ExampleVideoRender, didReceiveFrame videoFrame: OTVideoFrame) } class ExampleVideoCapture { weak var delegate: FrameCapturerMetadataDelegate? // ... implementation to capture video frames func captureOutput(...) { // When a frame is ready: // self.delegate?.finishPreparingFrame(videoFrame) } } class ExampleVideoRender { weak var delegate: ExampleVideoRenderDelegate? // ... implementation to render video frames func renderOutput(...) { // When a frame is received for rendering: // self.delegate?.renderer(self, didReceiveFrame: videoFrame) } } ``` -------------------------------- ### Preconfigure Audio Session for CallKit Events Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit-with-native-OpenTok-support/README.md Prepares the audio session for incoming or outgoing calls managed by CallKit. Uses `preconfigureAudioSessionForCall` with either `.voiceChat` for audio-only or `.videoChat` for video conferencing modes. ```swift sessionManager?.preconfigureAudioSessionForCall(withMode: .voiceChat) or sessionManager?.preconfigureAudioSessionForCall(withMode: .videoChat) ``` -------------------------------- ### Simulate Incoming Call with pu.sh Script Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit-with-native-OpenTok-support/README.md This shell script is used to simulate an incoming VoIP call to the iOS device. It requires specific parameters related to your Apple Developer account and the application's bundle ID and VoIP push token. This script facilitates testing the CallKit integration by triggering the native call screen and testing background call handling. ```shell #!/bin/bash # Parameters to modify: TEAMID="YOUR_TEAM_ID" KEYID="YOUR_KEY_ID" SECRET="~/path/to/your/AuthKey_XXXXXXXXX.p8" BUNDLEID="com.your.app.voip" DEVICETOKEN="YOUR_DEVICE_TOKEN" # ... (rest of the script for generating JWT and sending APNs payload) ``` -------------------------------- ### Multiparty Video Chat with UICollectionView in Swift Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This Swift code implements a multiparty video chat feature using the OpenTok SDK. It utilizes UICollectionView to manage and display multiple subscriber video streams in a dynamic grid layout. The code handles session connection, publishing the local stream, subscribing to remote streams, and updating the UI as participants join or leave. Dependencies include the OpenTok SDK and UIKit. ```swift import UIKit import OpenTok let kApiKey = "your-api-key" let kSessionId = "your-session-id" let kToken = "your-token" class ViewController: UIViewController { @IBOutlet var collectionView: UICollectionView! var subscribers: [IndexPath: OTSubscriber] = [:] lazy var session: OTSession = { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! }() lazy var publisher: OTPublisher = { let settings = OTPublisherSettings() settings.name = UIDevice.current.name return OTPublisher(delegate: self, settings: settings)! }() var error: OTError? override func viewDidLoad() { super.viewDidLoad() session.connect(withToken: kToken, error: &error) } override func viewDidAppear(_ animated: Bool) { guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } layout.itemSize = CGSize(width: collectionView.bounds.size.width / 2, height: collectionView.bounds.size.height / 2) } @IBAction func swapCameraAction(_ sender: AnyObject) { if publisher.cameraPosition == .front { publisher.cameraPosition = .back } else { publisher.cameraPosition = .front } } @IBAction func muteMicAction(_ sender: AnyObject) { publisher.publishAudio = !publisher.publishAudio } @IBAction func endCallAction(_ sender: AnyObject) { session.disconnect(&error) } func doPublish() { if let pubView = publisher.view { let publisherDimensions = CGSize(width: view.bounds.size.width / 4, height: view.bounds.size.height / 6) pubView.frame = CGRect(origin: CGPoint(x: collectionView.bounds.size.width - publisherDimensions.width, y: collectionView.bounds.size.height - publisherDimensions.height + collectionView.frame.origin.y), size: publisherDimensions) view.addSubview(pubView) } session.publish(publisher, error: &error) } func doSubscribe(to stream: OTStream) { if let subscriber = OTSubscriber(stream: stream, delegate: self) { let indexPath = IndexPath(item: subscribers.count, section: 0) subscribers[indexPath] = subscriber session.subscribe(subscriber, error: &error) reloadCollectionView() } } func findSubscriber(byStreamId id: String) -> (IndexPath, OTSubscriber)? { for entry in subscribers { if let stream = entry.value.stream, stream.streamId == id { return (entry.key, entry.value) } } return nil } func reloadCollectionView() { collectionView.isHidden = subscribers.count == 0 collectionView.reloadData() } } extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return subscribers.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "subscriberCell", for: indexPath) as! SubscriberCollectionCell cell.subscriber = subscribers[indexPath] return cell } } extension ViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("Session connected") doPublish() } func session(_ session: OTSession, streamCreated stream: OTStream) { print("Session streamCreated: (stream.streamId)") if subscribers.count == 4 { print("Sorry this sample only supports up to 4 subscribers") return } doSubscribe(to: stream) } func session(_ session: OTSession, streamDestroyed stream: OTStream) { print("Session streamDestroyed: (stream.streamId)") guard let (index, subscriber) = findSubscriber(byStreamId: stream.streamId) else { return } subscriber.view?.removeFromSuperview() subscribers.removeValue(forKey: index) reloadCollectionView() } func sessionDidDisconnect(_ session: OTSession) { print("Session disconnected") subscribers.removeAll() reloadCollectionView() } } extension ViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("Publisher failed: (error.localizedDescription)") } } extension ViewController: OTSubscriberDelegate { // Add OTSubscriberDelegate methods here if needed } ``` -------------------------------- ### Implement Custom Video Transformer in Swift Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Media-Transformers/README.md This Swift code defines a class that implements the OTCustomVideoTransformer protocol to apply custom transformations to video frames. It includes a helper method for resizing images and the core transform method that overlays a logo onto the video frame. Ensure the logo image is added to your project's assets. ```swift class CustomTransformer: NSObject, OTCustomVideoTransformer { func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage? { UIGraphicsBeginImageContextWithOptions(size, false, 0.0) image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } func transform(_ videoFrame: OTVideoFrame) { if let image = UIImage(named: "Vonage_Logo.png") { let yPlaneData = videoFrame.getPlaneBinaryData(0) let videoWidth = Int(videoFrame.format?.imageWidth ?? 0) let videoHeight = Int(videoFrame.format?.imageHeight ?? 0) // Calculate the desired size of the image let desiredWidth = CGFloat(videoWidth) / 8 // Adjust this value as needed let desiredHeight = image.size.height * (desiredWidth / image.size.width) // Resize the image to the desired size if let resizedImage = resizeImage(image, to: CGSize(width: desiredWidth, height: desiredHeight)) { let yPlane = yPlaneData // Create a CGContext from the Y plane guard let context = CGContext(data: yPlane, width: videoWidth, height: videoHeight, bitsPerComponent: 8, bytesPerRow: videoWidth, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return } // Location of the image (in this case right bottom corner) let x = CGFloat(videoWidth) * 4/5 let y = CGFloat(videoHeight) * 1/5 // Draw the resized image on top of the Y plane let rect = CGRect(x: x, y: y, width: desiredWidth, height: desiredHeight) context.draw(resizedImage.cgImage!, in: rect) } } } } ``` -------------------------------- ### Send and Receive Signals in OpenTok Swift Source: https://context7.com/opentok/opentok-ios-sdk-samples-swift/llms.txt This Swift code implements the OpenTok signaling functionality. It includes methods to connect to a session, send signals to all participants or specific connections, and handle incoming signals. It relies on the OpenTok SDK and UIKit. Incoming signals are processed in the `receivedSignalType` delegate method, and outgoing signals are sent using the `signal` method. ```swift import UIKit import OpenTok let kApiKey = "your-api-key" let kSessionId = "your-session-id" let kToken = "your-token" class VonageVideoSDK: NSObject, ObservableObject { @Published var isSessionConnected = false @Published var messages: [SignalMessage] = [] lazy var session: OTSession = { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! }() override init() { super.init() var error: OTError? session.connect(withToken: kToken, error: &error) if let error = error { print("Session creation error (error.description)") } } func sendSignalToAll(type: String?, data: String?) { guard let type = type, let data = data, type.isValidSignal() == true && data.isValidSignal() == true else { return } session.signal(withType: type, string: data, connection: nil, error: nil) addMessage(connection: nil, type: type, data: data, outgoing: true) } func sendSignalToConnection(connection: OTConnection, type: String?, data: String?, retryAfterConnect: Bool) { guard let type = type, let data = data, type.isValidSignal() == true && data.isValidSignal() == true else { return } if retryAfterConnect == true { session.signal(withType: type, string: data, connection: connection, error: nil) } else { session.signal(withType: type, string: data, connection: connection, retryAfterReconnect: retryAfterConnect, error: nil) } addMessage(connection: connection.connectionId, type: type, data: data, outgoing: true) } private func addMessage(connection: String?, type: String, data: String, outgoing: Bool) { messages.insert(SignalMessage(connId: connection, type: type, content: data, outgoing: outgoing), at: 0) } func closeAll() { session.disconnect(nil) } } extension VonageVideoSDK: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { isSessionConnected = true } func session(_ session: OTSession, connectionCreated connection: OTConnection) { print("Connection created: (connection.connectionId)") } func session(_ session: OTSession, receivedSignalType type: String?, from connection: OTConnection?, with string: String?) { if let string = string, let type = type, let c = connection?.connectionId { addMessage(connection: c, type: type, data: string, outgoing: false) } } func session(_ session: OTSession, didFailWithError error: OTError) { print("Session Failed to connect: (error.localizedDescription)") } } struct SignalMessage: Identifiable { let id = UUID() var connId: String? var type: String var content: String var outgoing: Bool } extension String { func isValidSignal() -> Bool { return self.count <= 128 && self.range(of: "[^a-zA-Z0-9-_~\s]", options: .regularExpression) == nil } } ``` -------------------------------- ### String Extension for Base64 Encoding and Signal Validation (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/Signals/README.md A Swift `String` extension providing utility functions for Base64 encoding/decoding and validating signal data. The `isValidSignal` function checks if the string adheres to the allowed character set and length limits for OpenTok signals. `toBase64` and `fromBase64` facilitate data transfer when special characters are needed. ```swift extension String { func fromBase64() -> String? { guard let data = Data(base64Encoded: self) else { return nil } return String(data: data, encoding: .ascii) } func toBase64() -> String { return Data(self.utf8).base64EncodedString() } func isValidSignal() -> Bool { return self.count <= 128 && self.range(of: "[^a-zA-Z0-9-_~\\s]", options: .regularExpression) == nil } ... } ``` -------------------------------- ### Workaround for CallKit Audio Session Glitch (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md Provides a workaround for a CallKit issue where the audio session may not activate properly when accepting a call from a locked screen. The solution involves configuring the app's audio session earlier in the app's lifecycle, before the call answer action is performed. ```swift configureAudioSession() ``` -------------------------------- ### Report Incoming Call with CallKit (Swift) Source: https://github.com/opentok/opentok-ios-sdk-samples-swift/blob/main/CallKit/README.md Constructs and reports a new incoming call to CallKit. It uses CXCallUpdate to define call details and CXHandle for the caller's information. The reportNewIncomingCall method handles the system's response, with a callback to conditionally update the app's call list. ```swift let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .phoneNumber, value: handle) 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. */ } ```