diff --git a/Basic-Video-Chat/Basic-Video-Chat/OTPublisherSync.swift b/Basic-Video-Chat/Basic-Video-Chat/OTPublisherSync.swift new file mode 100644 index 0000000..ce42fa1 --- /dev/null +++ b/Basic-Video-Chat/Basic-Video-Chat/OTPublisherSync.swift @@ -0,0 +1,98 @@ +// +// OTPublisherSync.swift +// Basic-Video-Chat +// +// Created by Jaideep Shah on 8/30/24. +// Copyright © 2024 tokbox. All rights reserved. +// + + + +import Foundation +import OpenTok +import ObjectiveC + + +extension OTPublisherKit { + + // Define the unique key for associated objects + private static var associatedObjectHandle: UInt8 = 0 + + // Method to set the associated object (e.g., wrapper) + private func setAssociatedWrapper(_ wrapper: PublisherKitDelegateWrapper?) { + objc_setAssociatedObject(self, &Self.associatedObjectHandle, wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + // Method to get the associated object + private func associatedWrapper() -> PublisherKitDelegateWrapper? { + return objc_getAssociatedObject(self, &Self.associatedObjectHandle) as? PublisherKitDelegateWrapper + } +} + + + +extension OTPublisherKit { + // Internal wrapper class to handle the Objective-C callbacks + private class PublisherKitDelegateWrapper: NSObject, OTPublisherDelegate { + let continuation: CheckedContinuation + + init(continuation: CheckedContinuation) { + self.continuation = continuation + } + + func publisher(_ publisher: OTPublisherKit, streamCreated stream: OTStream) { + print("Wrapper Publishing") + continuation.resume(returning: stream) + } + + func publisher(_ publisher: OTPublisherKit, streamDestroyed stream: OTStream) { + print("Wrapper Publishing stream destroyed") + continuation.resume(returning: stream) + } + + func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { + print("Wrapper Publisher failed: \(error.localizedDescription)") + continuation.resume(throwing: error) + } + } + + + // Async function to wait for a stream to be created + func waitForStreamCreated() async throws -> OTStream { + try await withCheckedThrowingContinuation { continuation in + let wrapper = PublisherKitDelegateWrapper(continuation: continuation) + setAssociatedWrapper(wrapper) + self.delegate = wrapper + } + } + + // Async function to wait for a stream to be destroyed + //TODO: Make sure stream destroyed do not overlap between diff pubs + func waitForStreamDestroyed(completion: @escaping (Result) -> Void) { + Task { + do { + let stream = try await withCheckedThrowingContinuation { continuation in + let wrapper = PublisherKitDelegateWrapper(continuation: continuation) + self.delegate = wrapper + setAssociatedWrapper(nil) + } + completion(.success(stream)) + } catch { + completion(.failure(error)) + } + } + } +} + +class OTPublisherSync : OTPublisher { + // Custom initializer + init?(settings: OTPublisherSettings) { + super.init(delegate: nil, settings: OTPublisherSettings()) + } + + // Hide the inherited initializer by providing an empty implementation + @available(*, unavailable, message: "Use custom initialization instead.") + override init(delegate: OTPublisherKitDelegate?, settings: OTPublisherSettings?) { + fatalError("This initializer is not available. Use the custom initializer.") + } +} diff --git a/Basic-Video-Chat/Basic-Video-Chat/ViewController.swift b/Basic-Video-Chat/Basic-Video-Chat/ViewController.swift index b6918a9..c33a019 100644 --- a/Basic-Video-Chat/Basic-Video-Chat/ViewController.swift +++ b/Basic-Video-Chat/Basic-Video-Chat/ViewController.swift @@ -12,11 +12,11 @@ import OpenTok // *** Fill the following variables using your own Project info *** // *** https://tokbox.com/account/#/ *** // Replace with your OpenTok API key -let kApiKey = "" +let kApiKey = "28415832" // Replace with your generated session ID -let kSessionId = "" +let kSessionId = "2_MX4yODQxNTgzMn5-MTcyNTA0ODQ5ODQzN35ueTZwWTl2WFAwM1AvMi82YUpPd2wzTkp-fn4" // Replace with your generated token -let kToken = "" +let kToken = "T1==cGFydG5lcl9pZD0yODQxNTgzMiZzaWc9NGE0MDRiZjU4M2E4ODVlZTY5YWViZTFjMGEwZTliOWE1MDk0NDdiMzpzZXNzaW9uX2lkPTJfTVg0eU9EUXhOVGd6TW41LU1UY3lOVEEwT0RRNU9EUXpOMzV1ZVRad1dUbDJXRkF3TTFBdk1pODJZVXBQZDJ3elRrcC1mbjQmY3JlYXRlX3RpbWU9MTcyNTA0ODQ5OCZub25jZT0wLjE3NTUxMTYzNjkwMjkzMTI2JnJvbGU9bW9kZXJhdG9yJmV4cGlyZV90aW1lPTE3Mjc2NDA0OTgmaW5pdGlhbF9sYXlvdXRfY2xhc3NfbGlzdD0=" let kWidgetHeight = 240 let kWidgetWidth = 320 @@ -26,7 +26,7 @@ class ViewController: UIViewController { return OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self)! }() - var publisher: OTPublisher? + var publisher: OTPublisherSync? var subscriber: OTSubscriber? override func viewDidLoad() { @@ -53,6 +53,26 @@ class ViewController: UIViewController { * binds to the device camera and microphone, and will provide A/V streams * to the OpenTok session. */ + func capturePublishingLifeCycle() async { + do { + let createdStream = try await publisher!.waitForStreamCreated() + print("Stream created after await: \(createdStream)") + session.unpublish(publisher!, error: nil) + // After stream creation, you might want to wait for stream destruction + + publisher!.waitForStreamDestroyed { result in + switch result { + case .success(let stream): + print("Stream destroyed after await: \(stream)") + case .failure(let error): + print("Error occurred after await: \(error)") + } + } + } catch { + print("Error occurred after await: \(error)") + } + } + fileprivate func doPublish() { var error: OTError? defer { @@ -61,7 +81,7 @@ class ViewController: UIViewController { let settings = OTPublisherSettings() settings.name = UIDevice.current.name - publisher = OTPublisher(delegate: self, settings: settings)! + publisher = OTPublisherSync(settings: settings) session.publish(publisher!, error: &error) @@ -69,6 +89,9 @@ class ViewController: UIViewController { pubView.frame = CGRect(x: 0, y: 0, width: kWidgetWidth, height: kWidgetHeight) view.addSubview(pubView) } + Task { + await self.capturePublishingLifeCycle() + } } /**