Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate if iOS sdk callbacks can be made "async" to support Swift concurrency #221

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions Basic-Video-Chat/Basic-Video-Chat/OTPublisherSync.swift
Original file line number Diff line number Diff line change
@@ -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<OTStream, Error>

init(continuation: CheckedContinuation<OTStream, Error>) {
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<OTStream, Error>) -> 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.")
}
}
33 changes: 28 additions & 5 deletions Basic-Video-Chat/Basic-Video-Chat/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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 {
Expand All @@ -61,14 +81,17 @@ 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)

if let pubView = publisher!.view {
pubView.frame = CGRect(x: 0, y: 0, width: kWidgetWidth, height: kWidgetHeight)
view.addSubview(pubView)
}
Task {
await self.capturePublishingLifeCycle()
}
}

/**
Expand Down