Skip to content

Instantly share code, notes, and snippets.

@serjooo
Last active December 19, 2017 15:19
Show Gist options
  • Save serjooo/d5d7c4009a4a9327d7dda315bc42c86b to your computer and use it in GitHub Desktop.
Save serjooo/d5d7c4009a4a9327d7dda315bc42c86b to your computer and use it in GitHub Desktop.
TwilioVideoManager Swift
import Foundation
import UIKit
import TwilioVideo
class TwilioVideoManager: NSObject {
//MARK: Static instance to class
public static let shared = TwilioVideoManager()
//MARK: TwilioVideo SDK components
fileprivate var room: TVIRoom?
fileprivate var camera: TVICameraCapturer?
fileprivate var localVideoTrack: TVILocalVideoTrack?
fileprivate var localAudioTrack: TVILocalAudioTrack?
fileprivate var remoteParticipant: TVIRemoteParticipant?
fileprivate var remoteView: TVIVideoView?
fileprivate var previewView: TVIVideoView?
fileprivate lazy var cameraSource = TVICameraCaptureSource.frontCamera
//MARK: Private variables
weak var delegate: TwilioVideoManagerDelegate?
//MARK: Public helper methods
public func setupCamera(previewView: TVIVideoView, remoteView: TVIVideoView? = nil, delegate: TwilioVideoManagerDelegate? = nil) {
self.previewView = previewView
self.remoteView = remoteView
self.delegate = delegate
self.camera = TVICameraCapturer(source: cameraSource, delegate: self)
let videoConstraints = TVIVideoConstraints.init { (builder) in
builder.maxSize = TVIVideoConstraintsSize960x540
builder.minSize = TVIVideoConstraintsSize960x540
builder.aspectRatio = TVIAspectRatio16x9
}
localVideoTrack = TVILocalVideoTrack.init(capturer: camera!, enabled: true, constraints: videoConstraints, name: "Camera")
localAudioTrack = TVILocalAudioTrack.init(options: nil, enabled: true, name: "Microphone")
guard let localVideoTrack = localVideoTrack else {
print("Failed to create video Track")
return
}
guard let localAudioTrack = localAudioTrack else {
print("Failed to create audio track")
return
}
localVideoTrack.addRenderer(previewView)
localAudioTrack.isEnabled = true
}
public func stopCameraPreview() {
self.previewView?.removeFromSuperview()
self.previewView = nil
}
//MARK: Private helper methods
fileprivate func cleanupRemoteParticipant() {
guard let remoteParticipant = self.remoteParticipant else {
self.remoteParticipant = nil
return
}
if remoteParticipant.videoTracks.count > 0 {
guard let remoteVideoTrack = remoteParticipant.remoteVideoTracks[0].remoteTrack, let remoteView = remoteView else {return}
remoteVideoTrack.removeRenderer(remoteView)
//WARNING: make sure you have to remove the remote view from the superview
// self.remoteView.removeFromSuperview()
// self.remoteView = nil
}
self.remoteParticipant = nil
}
//MARK: Public Methods
public func flipCamera() {
cameraSource = cameraSource == .frontCamera ? .backCameraWide : .frontCamera
self.camera?.selectSource(cameraSource)
}
public func toggleMic() -> Bool {
guard let localAudioTrack = localAudioTrack else {return false}
localAudioTrack.isEnabled = !localAudioTrack.isEnabled
return localAudioTrack.isEnabled
}
public func connect(roomName: String, accessToken: String) {
guard let localVideoTrack = localVideoTrack else {
print("Haven't initialized local video Track")
return
}
guard let localAudioTrack = localAudioTrack else {
print("Haven't initialized local audio Track")
return
}
// Preparing the connect options with the access token that we fetched (or hardcoded).
let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
builder.audioTracks = [localAudioTrack]
builder.videoTracks = [localVideoTrack]
if let preferredAudioCodec = Settings.shared.audioCodec {
builder.preferredAudioCodecs = [preferredAudioCodec.rawValue]
}
if let preferredVideoCodec = Settings.shared.videoCodec {
builder.preferredVideoCodecs = [preferredVideoCodec.rawValue]
}
if let encodingParameters = Settings.shared.getEncodingParameters() {
builder.encodingParameters = encodingParameters
}
// The name of the Room where the Client will attempt to connect to. Please note that if you pass an empty
// Room `name`, the Client will create one for you. You can get the name or sid from any connected Room.
builder.roomName = roomName
}
// Connect to the Room using the options we provided.
room = TwilioVideo.connect(with: connectOptions, delegate: self)
}
}
// MARK: TVICameraCapturerDelegate
extension TwilioVideoManager: TVICameraCapturerDelegate {
func cameraCapturer(_ capturer: TVICameraCapturer, didStartWith source: TVICameraCaptureSource) {
guard let previewView = previewView else {return}
previewView.shouldMirror = (source == .frontCamera)
}
}
// MARK: TVIRoomDelegate
extension TwilioVideoManager: TVIRoomDelegate {
func didConnect(to room: TVIRoom) {
//Maybe add a protocol that it connected?
if (room.remoteParticipants.count > 0) {
self.remoteParticipant = room.remoteParticipants[0]
self.remoteParticipant?.delegate = self
delegate?.didConnect?()
}
}
func room(_ room: TVIRoom, didDisconnectWithError error: Error?) {
//Maybe add a protocol that it disconnected?
self.cleanupRemoteParticipant()
self.room = nil
delegate?.didDisconnect?()
}
func room(_ room: TVIRoom, didFailToConnectWithError error: Error) {
//Maybe add a protocol that it failed to connect with some error?
self.room = nil
delegate?.didFailToConnectWithError?(error)
}
func room(_ room: TVIRoom, participantDidConnect participant: TVIRemoteParticipant) {
if (self.remoteParticipant == nil) {
self.remoteParticipant = participant
self.remoteParticipant?.delegate = self
delegate?.didConnect?()
}
}
func room(_ room: TVIRoom, participantDidDisconnect participant: TVIRemoteParticipant) {
//Maybe add a protocol that it disconnected?
if (self.remoteParticipant == participant) {
cleanupRemoteParticipant()
delegate?.didDisconnect?()
}
}
}
// MARK: TVIRemoteParticipantDelegate
extension TwilioVideoManager: TVIRemoteParticipantDelegate {
func remoteParticipant(_ participant: TVIRemoteParticipant, publishedVideoTrack publication: TVIRemoteVideoTrackPublication) {
// Remote Participant has offered to share the video Track.
delegate?.remoteParticipantDidPublishVideoTrack?(true)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, unpublishedVideoTrack publication: TVIRemoteVideoTrackPublication) {
// Remote Participant has stopped sharing the video Track.
delegate?.remoteParticipantDidPublishVideoTrack?(false)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, publishedAudioTrack publication: TVIRemoteAudioTrackPublication) {
// Remote Participant has offered to share the audio Track.
delegate?.remoteParticipantDidPublishAudioTrack?(true)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, unpublishedAudioTrack publication: TVIRemoteAudioTrackPublication) {
// Remote Participant has stopped sharing the audio Track.
delegate?.remoteParticipantDidPublishAudioTrack?(false)
}
func subscribed(to videoTrack: TVIRemoteVideoTrack, publication: TVIRemoteVideoTrackPublication, for participant: TVIRemoteParticipant) {
// We are subscribed to the remote Participant's audio Track. We will start receiving the
// remote Participant's video frames now.
if (self.remoteParticipant == participant) {
if let remoteView = remoteView {
videoTrack.addRenderer(remoteView)
}
delegate?.didSubscribeToVideoTrack?(true)
}
}
func unsubscribed(from videoTrack: TVIRemoteVideoTrack, publication: TVIRemoteVideoTrackPublication, for participant: TVIRemoteParticipant) {
// We are unsubscribed from the remote Participant's video Track. We will no longer receive the
// remote Participant's video.
if (self.remoteParticipant == participant) {
//Should I remove from superview and make it nil? Isn't removing the renderer enough?
if let remoteView = remoteView {
videoTrack.removeRenderer(remoteView)
}
delegate?.didSubscribeToVideoTrack?(false)
// self.remoteView?.removeFromSuperview()
// self.remoteView = nil
}
}
func subscribed(to audioTrack: TVIRemoteAudioTrack, publication: TVIRemoteAudioTrackPublication, for participant: TVIRemoteParticipant) {
// We are subscribed to the remote Participant's audio Track. We will start receiving the
// remote Participant's audio now.
delegate?.didSubscribeToAudioTrack?(true)
}
func unsubscribed(from audioTrack: TVIRemoteAudioTrack, publication: TVIRemoteAudioTrackPublication, for participant: TVIRemoteParticipant) {
// We are unsubscribed from the remote Participant's audio Track. We will no longer receive the
// remote Participant's audio.
delegate?.didSubscribeToAudioTrack?(false)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, enabledVideoTrack publication: TVIRemoteVideoTrackPublication) {
//logMessage(messageText: "Participant \(participant.identity) enabled \(publication.trackName) video track")
delegate?.didEnableVideoTrack?(true)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, disabledVideoTrack publication: TVIRemoteVideoTrackPublication) {
//logMessage(messageText: "Participant \(participant.identity) disabled \(publication.trackName) video track")
delegate?.didEnableVideoTrack?(false)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, enabledAudioTrack publication: TVIRemoteAudioTrackPublication) {
//logMessage(messageText: "Participant \(participant.identity) enabled \(publication.trackName) audio track")
delegate?.didEnableAudioTrack?(true)
}
func remoteParticipant(_ participant: TVIRemoteParticipant, disabledAudioTrack publication: TVIRemoteAudioTrackPublication) {
//logMessage(messageText: "Participant \(participant.identity) disabled \(publication.trackName) audio track")
delegate?.didEnableAudioTrack?(false)
}
}
//MARK: TwilioVideoManager Protocol
@objc protocol TwilioVideoManagerDelegate: NSObjectProtocol {
@objc optional func didConnect()
@objc optional func didDisconnect()
@objc optional func didFailToConnectWithError(_ error: Error)
@objc optional func remoteParticipantDidPublishVideoTrack(_ didPublishVideoTrack: Bool)
@objc optional func remoteParticipantDidPublishAudioTrack(_ didPublishAudioTrack: Bool)
@objc optional func didSubscribeToVideoTrack(_ didSubscribeVideoTrack: Bool)
@objc optional func didSubscribeToAudioTrack(_ didSubscribeAudioTrack: Bool)
@objc optional func didEnableVideoTrack(_ didEnableVideoTrack: Bool)
@objc optional func didEnableAudioTrack(_ didEnableAudioTrack: Bool)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment