Sample project not running in iOS

Hey @vp-ecommerce,

In your screenshot, there is a view that contains a person avatar and a black background. When you see this, this means that the video stream is working and displaying properly, however that user has no active camera and needs to turn on their video. Are you seeing this for the remote user as well? Or is that for the local user?

Thanks!
Michael

Hi @Michael_Condon
That view is for remote user. For local user, nothing is displaying. For local user it gives Failed to show local user video.

Hey @vp-ecommerce,

Try this sample code. I just tested it and it seemed to work fine. The first view shows the local user’s video, the second view is the remote user, and the third view is the active video. You need to supply a JWT and meeting ID/Password. Then start a meeting on another device. Run the app then click the join meeting button, and it should display a custom UI, with working video streams.

CustomUIViewController:

//
//  CustomMeetingUIViewController.swift
//  MeetingSDKCustomUI
//
//  Created by Michael Condon on 8/23/21.
//

import Foundation
import MobileRTC

protocol CustomMeetingUIViewControllerDelegate: NSObject {
    
    func userWasAdmittedFromTheWaitingRoom()
}

class CustomMeetingUIViewController: UIViewController {
    
    weak var delegate: CustomMeetingUIViewControllerDelegate?
    
    var screenWidth: CGFloat {
        return view.frame.width
    }
    
    var screenHeight: CGFloat {
        return view.frame.height
    }
    
    lazy var localUserView: MobileRTCVideoView = {
        let videoView = MobileRTCVideoView(frame: CGRect(x: 15.0, y: 0.0, width: screenWidth - 30, height: (screenHeight / 4)))
        videoView.setVideoAspect(MobileRTCVideoAspect_PanAndScan)
        
        return videoView
    }()
    
    lazy var remoteUserView: MobileRTCVideoView = {
        let videoView = MobileRTCVideoView(frame: CGRect(x: 15.0, y: ((screenHeight / 4) + 15), width: screenWidth - 30, height: (screenHeight / 4)))
        videoView.setVideoAspect(MobileRTCVideoAspect_PanAndScan)
        
        return videoView
    }()
    
    lazy var activeVideoView: MobileRTCActiveVideoView = {
        let videoView = MobileRTCActiveVideoView(frame: CGRect(x: 15.0, y: (((screenHeight / 4) * 2) + 30), width: screenWidth - 30, height: (screenHeight / 4)))
        videoView.setVideoAspect(MobileRTCVideoAspect_PanAndScan)
        
        return videoView
    }()
    
    lazy var buttonContainerScrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.alwaysBounceHorizontal = true
        
        return scrollView
    }()
    
    lazy var buttonStackViewView: UIStackView = {
        let stackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.alignment = .center
        stackView.distribution = .fillProportionally
        stackView.axis = .horizontal
        stackView.spacing = 5
        
        return stackView
    }()
    
    lazy var leaveMeetingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Leave Meeting", for: .normal)
        button.addAction(UIAction(handler: { _ in
            self.dismiss(animated: true, completion: nil)
        }), for: .touchUpInside)
        
        return button
    }()
    
    lazy var toggleMuteAudioButton: UIButton = {
        let button = UIButton(type: .system)
        let title = (MobileRTC.shared().getMeetingService()?.isMyAudioMuted() ?? true) ? "Unmute" : "Mute"
        button.setTitle(title, for: .normal)
        button.addTarget(self, action: #selector(toggleMuteAudio), for: .touchUpInside)
        
        return button
    }()
    
    lazy var toggleMuteVideoButton: UIButton = {
        let button = UIButton(type: .system)
        let title = (MobileRTC.shared().getMeetingService()?.isSendingMyVideo() ?? false) ? "Stop Video" : "Start Video"
        button.setTitle(title, for: .normal)
        button.addTarget(self, action: #selector(toggleMuteVideo), for: .touchUpInside)
        
        return button
    }()
    
    override func viewDidAppear(_ animated: Bool) {
        view.backgroundColor = .black
        
        setupVideoViews()
        setupButtons()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        MobileRTC.shared().getMeetingService()?.leaveMeeting(with: .leave)
    }
    
    func setupVideoViews() {
        guard let localUserID = MobileRTC.shared().getMeetingService()?.myselfUserID() else { return }
        
        view.addSubview(localUserView)
        localUserView.showAttendeeVideo(withUserID: localUserID)
        
        if let firstRemoteUserID = MobileRTC.shared().getMeetingService()?.getInMeetingUserList()?.first(where: { UInt(truncating: $0) != localUserID }) {
            view.addSubview(remoteUserView)
            remoteUserView.showAttendeeVideo(withUserID: UInt(truncating: firstRemoteUserID))
        }
        
        view.addSubview(activeVideoView)
    }
    
    func setupButtons() {
        view.addSubview(buttonContainerScrollView)
        buttonContainerScrollView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        buttonContainerScrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        buttonContainerScrollView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        buttonContainerScrollView.leadingAnchor.constraint(equalTo:view.leadingAnchor).isActive = true
        buttonContainerScrollView.trailingAnchor.constraint(equalTo:view.trailingAnchor).isActive = true
        
        buttonContainerScrollView.addSubview(buttonStackViewView)
        buttonStackViewView.leadingAnchor.constraint(equalTo: buttonContainerScrollView.leadingAnchor).isActive = true
        buttonStackViewView.trailingAnchor.constraint(equalTo: buttonContainerScrollView.trailingAnchor).isActive = true
        buttonStackViewView.topAnchor.constraint(equalTo: buttonContainerScrollView.topAnchor).isActive = true
        buttonStackViewView.bottomAnchor.constraint(equalTo: buttonContainerScrollView.bottomAnchor).isActive = true
        buttonStackViewView.widthAnchor.constraint(greaterThanOrEqualTo: buttonContainerScrollView.widthAnchor).isActive = true
        
        buttonStackViewView.addArrangedSubview(leaveMeetingButton)
        buttonStackViewView.addArrangedSubview(toggleMuteAudioButton)
        buttonStackViewView.addArrangedSubview(toggleMuteVideoButton)
    }
    
    func updateViews() {
        guard let meetingService = MobileRTC.shared().getMeetingService() else { return }
        let localUserID = meetingService.myselfUserID()
        localUserView.showAttendeeVideo(withUserID: localUserID)

        if let firstRemoteUserID = meetingService.getInMeetingUserList()?.first(where: { UInt(truncating: $0) != localUserID }) {
            remoteUserView.showAttendeeVideo(withUserID: UInt(truncating: firstRemoteUserID))
        }
        
        toggleMuteAudioButton.setTitle(meetingService.isMyAudioMuted() ? "Unmute" : "Mute", for: .normal)
        toggleMuteVideoButton.setTitle(meetingService.isSendingMyVideo() ? "Stop Video" : "Start Video", for: .normal)
    }
    
    @objc func toggleMuteAudio() {
        guard let meetingService = MobileRTC.shared().getMeetingService() else { return }
        
        if meetingService.isMyAudioMuted() {
            meetingService.muteMyAudio(false)
        } else {
            meetingService.muteMyAudio(true)
        }
    }
    
    @objc func toggleMuteVideo() {
        guard let meetingService = MobileRTC.shared().getMeetingService() else { return }
        
        if meetingService.isSendingMyVideo() {
            meetingService.muteMyVideo(true)
        } else {
            meetingService.muteMyVideo(false)
        }
    }
}

extension CustomMeetingUIViewController: MobileRTCMeetingServiceDelegate {
    
    func onJBHWaiting(with cmd: JBHCmd) {
        switch cmd {
        case .show:
            print("Joined before host.")
            showAlert(with: "Joined before host.", and: "Wait for the host to start the meeting.")
        case .hide:
            print("Hide join before host message.")
        @unknown default:
            print("Unexpected error.")
        }
    }
    
    func onWaitingRoomStatusChange(_ needWaiting: Bool) {
        if needWaiting {
            print("User joined waiting room.")
            showAlert(with: "User now in waiting room.", and: "User needs to be admitted by host or leave.", action: UIAlertAction(title: "Leave", style: .default, handler: { _ in
                MobileRTC.shared().getMeetingService()?.leaveMeeting(with: .leave)
            }))
        } else {
            print("User is entering meeting.")
            delegate?.userWasAdmittedFromTheWaitingRoom()
        }
    }
    
    func onMeetingError(_ error: MobileRTCMeetError, message: String?) {
        switch error {
        case .inAnotherMeeting:
            print("User is already in another meeting.")
        case .meetingNotExist:
            print("Meeting does not exist")
        case .invalidArguments:
            print("One or more of the join meeting params was invalid.")
        case .passwordError:
            print("Incorrect meeting password.")
        case .success:
            print("Meeting operation was successful.")
        default:
            print("Meeting error: \(error) \(message ?? "")")
        }
    }
    
    func onMeetingStateChange(_ state: MobileRTCMeetingState) {
        switch state {
        case .connecting:
            print("Meeting State: connecting...")
        case .ended:
            print("Meeting State: ended.")
        case .failed:
            print("Meeting State: failed.")
        case .reconnecting:
            print("Meeting State: reconnecting...")
        case .inWaitingRoom:
            print("Meeting State: in waiting room.")
        default:
            break
        }
    }
    
    func onMeetingEndedReason(_ reason: MobileRTCMeetingEndReason) {
        switch reason {
        case .connectBroken:
            print("Meeting ended due to lost connection.")
        case .endByHost:
            print("Meeting was ended by the host.")
        case .freeMeetingTimeout:
            print("Meeting ended due to free meeting limit being reached.")
        case .selfLeave:
            print("User left meeting.")
        case .removedByHost:
            print("User was removed by host.")
        default:
            print("Meeting ended with reason: \(reason)")
        }
    }
    
    func onSubscribeUserFail(_ errorCode: Int, size: Int, userId: UInt) {
        print("Failed to subscribe to user video. Error: \(errorCode)")
    }
}

extension CustomMeetingUIViewController: MobileRTCVideoServiceDelegate {
    func onSinkMeetingActiveVideo(_ userID: UInt) {
        print("Active video status changed.")
    }
    
    func onSinkMeetingVideoStatusChange(_ userID: UInt) {
        print("User video status changed: \(userID)")
    }
    
    func onMyVideoStateChange() {
        print("Local user's video status changed.")
        updateViews()
    }
    
    func onSinkMeetingVideoStatusChange(_ userID: UInt, videoStatus: MobileRTC_VideoStatus) {
        print("User video status changed: \(userID)")
        updateViews()
    }
    
    func onSpotlightVideoChange(_ on: Bool) {
        print("Spotlight status changed.")
    }
    
    func onSinkMeetingPreviewStopped() {
        print("Meeting preview ended.")
    }
    
    func onSinkMeetingActiveVideo(forDeck userID: UInt) {
        print("Active video user changed")
    }
    
    func onSinkMeetingVideoQualityChanged(_ qality: MobileRTCNetworkQuality, userID: UInt) {
        print("Video quality changed for user: \(userID)")
    }
    
    func onSinkMeetingVideoRequestUnmute(byHost completion: @escaping (Bool) -> Void) {
        print("User was asked to start video by host")
    }
    
    func onSinkMeetingShowMinimizeMeetingOrBackZoomUI(_ state: MobileRTCMinimizeMeetingState) {
        // Only for default UI.
        print("Meeting minimization was toggled. ")
    }
}

Main View Controller:

//
//  ViewController.swift
//  MeetingSDKCustomUI
//
//  Created by Michael Condon on 8/23/21.
//

import UIKit
import MobileRTC

class ViewController: UIViewController {

    lazy var joinMeetingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Join meeting", for: .normal)
        button.addTarget(self, action: #selector(joinMeetingButtonPressed(_:)), for: .touchUpInside)
        button.isEnabled = false
        button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        return button
    }()
    
    lazy var customMeetingUIViewController: CustomMeetingUIViewController = {
        let vc = CustomMeetingUIViewController()
        vc.delegate = self
        return vc
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        joinMeetingButton.center.x = view.center.x
        joinMeetingButton.center.y = view.center.y
        view.addSubview(joinMeetingButton)
        
        let sdkInitContext = MobileRTCSDKInitContext()
        sdkInitContext.appGroupId = ""
        sdkInitContext.domain = "https://zoom.us"
        sdkInitContext.enableLog = true
        
        if MobileRTC.shared().initialize(sdkInitContext) {
            if let authService = MobileRTC.shared().getAuthService() {
                authService.delegate = self
                
                authService.jwtToken = ""
            
                authService.sdkAuth()
            }
        }
    }
    
    func joinMeeting() {
        guard let meetingService = MobileRTC.shared().getMeetingService(),
              let meetingSettings = MobileRTC.shared().getMeetingSettings() else { return }
        
        meetingService.delegate = customMeetingUIViewController
        meetingService.customizedUImeetingDelegate = self
        
        meetingSettings.enableCustomMeeting = true
        
        let joinParams = MobileRTCMeetingJoinParam()
        joinParams.meetingNumber = ""
        joinParams.password = ""
        let joinMeetingReturnValue = meetingService.joinMeeting(with: joinParams)
        
        switch joinMeetingReturnValue {
        case .success:
            print("Joining meeting.")
        case .inAnotherMeeting:
            print("User is in another meeting.")
            showAlert(with: "User is in another meeting.")
        default:
            print("Error joining meeting: \(joinMeetingReturnValue.rawValue)")
        }
    }

    @objc func joinMeetingButtonPressed(_ sender: UIButton) {
        joinMeeting()
    }
}

extension ViewController: MobileRTCAuthDelegate {
    
    func onMobileRTCAuthReturn(_ returnValue: MobileRTCAuthError) {
        switch returnValue {
        case .success:
            print("SDK authed successfully.")
            
            joinMeetingButton.isEnabled = true
        default:
            print("Error authing SDK: \(returnValue.rawValue)")
        }
    }
}

extension ViewController: MobileRTCCustomizedUIMeetingDelegate {
    
    func onInitMeetingView() {
        
    }
    
    func onDestroyMeetingView() {
        
    }
}

extension ViewController: CustomMeetingUIViewControllerDelegate {
    
    func userWasAdmittedFromTheWaitingRoom() {
        DispatchQueue.main.async { [weak self] in
            if let strongSelf = self {
                strongSelf.present(strongSelf.customMeetingUIViewController, animated: true, completion: nil)
            }
        }
    }
}

extension UIViewController {
    
    func showAlert(with title: String, and message: String? = nil, action: UIAlertAction? = nil) {
        DispatchQueue.main.async {
            let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
            
            if let action = action {
                alertController.addAction(action)
            }
            
            let keyWindow = UIApplication.shared.connectedScenes
                    .filter({$0.activationState == .foregroundActive})
                    .compactMap({$0 as? UIWindowScene})
                    .first?.windows
                    .filter({$0.isKeyWindow}).first
            var rootViewController = keyWindow?.rootViewController
            if let navigationController = rootViewController as? UINavigationController {
                rootViewController = navigationController.viewControllers.first
            }
            
            
            rootViewController?.present(alertController, animated: true, completion: nil)
        }
    }
}

AppDelegate:

//
//  AppDelegate.swift
//  MeetingSDKCustomUI
//
//  Created by Michael Condon on 8/23/21.
//

import UIKit
import MobileRTC

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
    func applicationWillResignActive(_ application: UIApplication) {
        MobileRTC.shared().appWillResignActive()
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        MobileRTC.shared().appDidBecomeActive()
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        MobileRTC.shared().appDidEnterBackgroud()
    }
    
    func applicationWillTerminate(_ application: UIApplication) {
        MobileRTC.shared().appWillTerminate()
    }
}

Thanks!
Michael

Hi @Michael_Condon
I have implemented the above code. Video is streaming properly. But audio is not working (Unable to hear the remote side person voice and I am also not audible to him too).

Please tell me what am i doing wrong?

Hey @vp-ecommerce,

Has the remote user started their audio and unmuted themself? If the remote user is also using custom ui you have to start the audio and unmute the user manually.

Thanks!
Michael

No, the remote side user is using the default UI. Remote user is using the zoom application on mac.

When I join from the zoom application on iOS. It’s working fine.

Hey @vp-ecommerce,

Try calling this code after you join the meeting:

MobileRTC.shared().getMeetingService()?.connectMyAudio(true)

Also make sure you have added the camera and microphone permissions in your info.plist.

Thanks!
Michael

Hi @Michael_Condon
Thanks for the reply. It’s now working in the sample project that I have downloaded from github.

But I am facing a new issue. Now I am implementing this in my own project.

When I called a line, I am getting an error. The line is meetingService.joinMeeting(with: joinMeetingParameters)
And the error is like as follows:

**First throw call stack:**

**(0x18da31344 0x18d746cc0 0x18d930110 0x191b89194 0x18da358a0 0x18da37e10 0x106b08ff8 0x18d989824 0x18d989874 0x18d988b64 0x18d988818 0x18d902058 0x18d988158 0x106925dac 0x107147064 0x107127d1c 0x107120e1c 0x10711fa90 0x1071215a8 0x1072b61b8 0x106ac09e8 0x102fba8f0 0x102fba630 0x102fba698 0x1914e7748 0x1914ec2fc 0x1914ec6e8 0x1914483a8 0x19145c87c 0x19145dc1c 0x191440900 0x19202b2bc 0x19467c978 0x19467cdb8 0x19468f21c 0x1945d3e10 0x1945fe8c4 0x191b83f24 0x18d9ac11c 0x18d9a6e4c 0x18d9a72dc 0x18d9a6bc8 0x197d885cc 0x191b59744 0x1c4b84f58 0x103086ea8 0x103086e20 0x10308a3a4 0x18d823384)**

**libc++abi.dylib: terminating with uncaught exception of type NSException**

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GUL_HelloLyfe_Doctor.AppDelegate-1BFA3FBD-F480-4AA8-B0A5-E6F069DB056F window]: unrecognized selector sent to instance 0x282058060'

terminating with uncaught exception of type NSException

I have implemented the sdk successfully using this https://marketplace.zoom.us/docs/sdk/native-sdks/iOS/getting-started/integration

Please provide me the solution. This would be very great.

Thanks
@vp-ecommerce

Hi @Michael_Condon,
I have removed SceneDelegate.swift and all stuffs related to UIScene from my project. Then It’s working fine in my project.

Thanks for your support Michael.

Hey @vp-ecommerce,

Awesome! I am happy to hear you found the solution :slight_smile:

Please let us know if you have any other questions.

Thanks!
Michael

Hi @Michael_Condon
When first time I join the meeting, asking for a name in a popup. How to handle this programmatically. So that user will not have to enter the name manually.

Sorry for late reply. Somehow this has skipped. Today I am looking old chats and found your question.

They don’t have the project in swift yet. But you can follow the github link:

This contains both ObjectiveC example and Swift as well.

Hi @Michael_Condon
I am waiting for your reply. How to set the participant(my) name before join the meeting. So that no popup would come and ask to enter the name manually.

Hey @vp-ecommerce,

Before your call startMeeting or joinMeeting you can set the name of the participant with the username parameter in your joinParams/startParm:
https://marketplacefront.zoom.us/sdk/meeting/ios/interface_mobile_r_t_c_meeting_join_param.html#a2a04b796978e3c58c76f03aef807215c

Thanks!
Michael