VOIP & Video Consultation
VoipConfig Struct
For VOIP consultation, the config data will be in voipConfig, and for VIDEO it will be in videoConfig. They share the same data structure.
struct VoipConfig {
var id: Int?
var consultationId: Int?
var apiKey: String?
var callId: String?
var token: String?
}
VOIP and Video consultations are handled similarly. The primary difference is enabling video for Video consultations and disabling it for VOIP consultations.
Frameworks
We use OpenTok (Vonage Video API) to handle the VOIP/Video session. You need to import both AltibbiTelehealth and OpenTok.
import UIKit
import AltibbiTelehealth
import OpenTok
Static Properties
In your view controller, declare properties to manage the session, publisher, and subscriber.
// The Consultation data object
var consultationInfo: Consultation?
// The OpenTok session
lazy var session: OTSession? = {
let config = (consultationInfo?.medium == "voip") ? consultationInfo?.voipConfig : consultationInfo?.videoConfig
guard let apiKey = config?.apiKey, let callId = config?.callId else { return nil }
return OTSession(apiKey: apiKey, sessionId: callId, delegate: self)
}()
// The OpenTok Publisher
lazy var publisher: OTPublisher? = {
let settings = OTPublisherSettings()
settings.name = UIDevice.current.name
if consultationInfo?.medium == "voip" {
settings.videoTrack = false
}
return OTPublisher(delegate: self, settings: settings)
}()
// The OpenTok Subscriber
var subscriber: OTSubscriber?
VOIP/Video Call Actions
Manage common call actions like muting, switching cameras, and ending the call.
// Handle Mute/Unmute
@IBAction func muteButtonTapped(_ sender: UIButton) {
publisher?.publishAudio = !(publisher?.publishAudio ?? true)
let title = (publisher?.publishAudio ?? true) ? "Mute" : "Unmute"
sender.setTitle(title, for: .normal)
}
// Handle Camera Source
@IBAction func switchCameraButtonTapped(_ sender: UIButton) {
publisher?.cameraPosition = (publisher?.cameraPosition == .front) ? .back : .front
}
// Handle Enable/Disable Video
@IBAction func toggleVideoButtonTapped(_ sender: UIButton) {
publisher?.publishVideo = !(publisher?.publishVideo ?? true)
}
// Handle End Call
@IBAction func endCallButtonTapped(_ sender: UIButton) {
var error: OTError?
session?.disconnect(&error)
if let consultationId = consultationInfo?.consultationId {
ApiService.cancelConsultation(id: consultationId) { (cancelled, failure, error) in
// Handle cancellation callback
}
}
}
Connection Implementation
Call doConnect() in your viewDidLoad() or equivalent setup method.
fileprivate func doConnect() {
var error: OTError?
let token = (consultationInfo?.medium == "voip") ? consultationInfo?.voipConfig?.token : consultationInfo?.videoConfig?.token
session?.connect(withToken: token ?? "", error: &error)
if let error = error {
print("Connection error: \(error.localizedDescription)")
}
}
fileprivate func doPublish() {
guard let session = session, let publisher = publisher else { return }
var error: OTError?
session.publish(publisher, error: &error)
if let pubView = publisher.view {
// Adjust frame and add to your view hierarchy
pubView.frame = CGRect(x: 0, y: 0, width: 320, height: 240)
self.view.addSubview(pubView)
}
}
fileprivate func doSubscribe(_ stream: OTStream) {
var error: OTError?
subscriber = OTSubscriber(stream: stream, delegate: self)
session?.subscribe(subscriber!, error: &error)
}
OpenTok Delegates
Implement the delegates to handle session connectivity, streaming, and publisher/subscriber events.
extension YourViewController: OTSessionDelegate {
func sessionDidConnect(_ session: OTSession) {
doPublish()
}
func sessionDidDisconnect(_ session: OTSession) {
print("Session disconnected")
}
func session(_ session: OTSession, streamCreated stream: OTStream) {
if subscriber == nil {
doSubscribe(stream)
}
}
func session(_ session: OTSession, streamDestroyed stream: OTStream) {
if subscriber?.stream?.streamId == stream.streamId {
subscriber?.view?.removeFromSuperview()
subscriber = nil
}
}
func session(_ session: OTSession, didFailWithError error: OTError) {
print("Session failed: \(error.localizedDescription)")
}
}
extension YourViewController: OTPublisherDelegate {
func publisher(_ publisher: OTPublisherKit, streamCreated stream: OTStream) {
print("Publishing started")
}
func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
print("Publisher failed: \(error.localizedDescription)")
}
}
extension YourViewController: OTSubscriberDelegate {
func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) {
if let subsView = subscriber?.view {
subsView.frame = CGRect(x: 0, y: 240, width: 320, height: 240)
self.view.addSubview(subsView)
}
}
func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
print("Subscriber failed: \(error.localizedDescription)")
}
}