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.
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 callinghd_init
. This is a bit mind-bending, but it's how swizzling works: at the time we execute that piece of codehd_init
has been replaced by the originalinit
, 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 videoMediaTrack
. Given this, you can - for example - hide the video on the other client's side using theonmuted
event.