Search code examples
iossinch

How to disable video in Sinch


I'm implementing a video chat using Sinch, but I can't find a way to disable the outgoing video once the call has started.

Is this documented somewhere?

And if it's not supported by the SDK, is there a way (even an hackish one) to "intercept" the video stream and prevent it to be sent.


Solution

  • Apparently there's no official way to do this in the SDK, but here's a hack that works as expected.

    Long story short, with some runtime "magic" we can intercept the AVCaptureSessoin initiated by Sinch and control it.

    Here's how:

    extension AVCaptureSession {
      public override class func initialize() {
        struct Static {
          static var token: dispatch_once_t = 0
        }
    
        // make sure this isn't a subclass
        if self !== AVCaptureSession.self {
          return
        }
    
        dispatch_once(&Static.token) {
          let originalSelector = #selector(AVCaptureSession.init)
          let swizzledSelector = #selector(AVCaptureSession.hd_init)
    
          let originalMethod = class_getInstanceMethod(self, originalSelector)
          let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
    
          let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
    
          if didAddMethod {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
          } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
          }
        }
      }
    
      @nonobjc static var hd_currentSession: AVCaptureSession? = nil
    
      func hd_init() {
        hd_init() // at runtime this is the original `init` implementation
        AVCaptureSession.hd_currentSession = self
      }
    
    }
    

    A brief explanation: the first part is just basic runtime swizzling of two methods, nothing specific to this implementation.

    Namely, we're replacing the implementation of AVCaptureSession.init with our own AVCaptureSession.hd_init (hd is just any prefix to avoid potential clashing of method names).

    NOTE

    In hd_init we're calling hd_init. This is a bit mind-bending, but it's how swizzling works: at the time we execute that piece of code hd_init has been replaced by the original init, so we're actually calling the original implementation, which is what we want.

    Our custom implementation of init has just the purpose of storing a static reference to the instance, which we'll refer to as hd_currentSession.

    And we're done!

    Now from anywhere in the app we can get a reference to the current AVCaptureSession and stop it / start it at will.

    For instance

    func toggleVideo() {
      if let session = AVCapture.hd_currentSession {
        if session.running {
          session.stopRunning()
        } else {
          session.startRunning()
        }
      }  
    }
    

    At any time after the Sinch video stream has started, we can simply call toggleVideo()

    CAVEAT

    Stopping the capture session will cause the video to "freeze" to the last frame. I haven't worked around this issue on iOS side, but I've noticed that on the Javascript API you get a "muted" event on the video MediaTrack. Given this, you can - for example - hide the video on the other client's side using the onmuted event.