twilio / video-quickstart-ios

Twilio Video Quickstart for iOS
https://www.twilio.com/docs/api/video
MIT License
460 stars 177 forks source link

My video and opponent video is not displaying #526

Closed mouli457 closed 4 years ago

mouli457 commented 4 years ago

I am facing as show in image.Please suggest sample code for latest twilio video configuration.

Screenshot 2020-07-17 at 3 14 02 PM

paynerc commented 4 years ago

@mouli457,

You can use the TVIVideoTrackPublication.videoTrack method to get properly typed TVIVideoTrack objects.

Let me know if you have any further questions.

Ryan

mouli457 commented 4 years ago

Hai @paynerc

My issue is when opponent was connected only audio is coming and showing error as shown in above question.My complete code shown below(passing access token).Please correct my code.

 import UIKit
import TwilioVideo

class ViewController: UIViewController {

// MARK:- View Controller Members

// Configure access token manually for testing, if desired! Create one manually in the console
// at https://www.twilio.com/console/video/runtime/testing-tools
var accessToken = "TWILIO_ACCESS_TOKEN"

// Configure remote URL to fetch token from
var tokenUrl = "http://localhost:8000/token.php"
var roomName = String()

// Video SDK components
var room: TVIRoom?
var camera: TVICameraSource?
var localVideoTrack: TVIVideoTrack?
var localAudioTrack: TVILocalAudioTrack?
var remoteParticipant: TVIParticipant?
var remoteView: TVIVideoView?

// MARK:- UI Element Outlets and handles

// `VideoView` created from a storyboard

@IBOutlet weak var previewView: TVIVideoView!
@IBOutlet weak var connectButton: UIButton!
@IBOutlet weak var disconnectButton: UIButton!
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var roomTextField: UITextField!
@IBOutlet weak var roomLine: UIView!
@IBOutlet weak var roomLabel: UILabel!
@IBOutlet weak var micButton: UIButton!
deinit {
    // We are done with camera
    if let camera = self.camera {
        camera.stopCapture()
        self.camera = nil
    }
}

// MARK:- UIViewController
override func viewDidLoad() {
    super.viewDidLoad()

    self.title = "QuickStart"
    self.messageLabel.adjustsFontSizeToFitWidth = true;
    self.messageLabel.minimumScaleFactor = 0.75;

    if PlatformUtils.isSimulator {
        self.previewView.removeFromSuperview()
    } else {
        // Preview our local camera track in the local video preview view.
        self.startPreview()
     } 

    // Disconnect and mic button will be displayed when the Client is connected to a Room.
    self.disconnectButton.isHidden = true
    self.micButton.isHidden = true

    self.roomTextField.autocapitalizationType = .none
    self.roomTextField.delegate = self

    let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
    self.view.addGestureRecognizer(tap)

}

override var prefersHomeIndicatorAutoHidden: Bool {
    return self.room != nil
}

func setupRemoteVideoView() {
    // Creating `VideoView` programmatically
    self.remoteView = TVIVideoView(frame: CGRect.zero, delegate: self)

    self.view.insertSubview(self.remoteView!, at: 0)

    // `VideoView` supports scaleToFill, scaleAspectFill and scaleAspectFit
    // scaleAspectFit is the default mode when you create `VideoView` programmatically.
    self.remoteView!.contentMode = .scaleAspectFit;

    let centerX = NSLayoutConstraint(item: self.remoteView!,
                                     attribute: NSLayoutConstraint.Attribute.centerX,
                                     relatedBy: NSLayoutConstraint.Relation.equal,
                                     toItem: self.view,
                                     attribute: NSLayoutConstraint.Attribute.centerX,
                                     multiplier: 1,
                                     constant: 0);
    self.view.addConstraint(centerX)
    let centerY = NSLayoutConstraint(item: self.remoteView!,
                                     attribute: NSLayoutConstraint.Attribute.centerY,
                                     relatedBy: NSLayoutConstraint.Relation.equal,
                                     toItem: self.view,
                                     attribute: NSLayoutConstraint.Attribute.centerY,
                                     multiplier: 1,
                                     constant: 0);
    self.view.addConstraint(centerY)
    let width = NSLayoutConstraint(item: self.remoteView!,
                                   attribute: NSLayoutConstraint.Attribute.width,
                                   relatedBy: NSLayoutConstraint.Relation.equal,
                                   toItem: self.view,
                                   attribute: NSLayoutConstraint.Attribute.width,
                                   multiplier: 1,
                                   constant: 0);
    self.view.addConstraint(width)
    let height = NSLayoutConstraint(item: self.remoteView!,
                                    attribute: NSLayoutConstraint.Attribute.height,
                                    relatedBy: NSLayoutConstraint.Relation.equal,
                                    toItem: self.view,
                                    attribute: NSLayoutConstraint.Attribute.height,
                                    multiplier: 1,
                                    constant: 0);
    self.view.addConstraint(height)
}

// MARK:- IBActions
@IBAction func connect(sender: AnyObject) {
   // Configure access token either from server or manually.
        // If the default wasn't changed, try fetching from server.
        if (accessToken == "TWILIO_ACCESS_TOKEN") {
            do {
                accessToken = try TokenUtils.fetchToken(url: tokenUrl)
            } catch {
                let message = "Failed to fetch access token"
                logMessage(messageText: message)
                return
            }
        }

        // Prepare local media which we will share with Room Participants.
        self.prepareLocalMedia()

        // Preparing the connect options with the access token that we fetched (or hardcoded).
        let connectOptions = TVIConnectOptions(token: accessToken) { (builder) in

            // Use the local media that we prepared earlier.
            builder.audioTracks = [TVIAudioTrack]() as! [TVILocalAudioTrack]
            builder.videoTracks = [TVIVideoTrack]() as! [TVILocalVideoTrack]

            // 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 = self.roomTextField.text
        }

        // Connect to the Room using the options we provided.
    room = TwilioVideo.connect(with: connectOptions, delegate: self)

        logMessage(messageText: "Attempting to connect to room \(String(describing: self.roomTextField.text))")

        self.showRoomUI(inRoom: true)
        self.dismissKeyboard()
    }
@IBAction func disconnect(sender: AnyObject) {
    self.room!.disconnect()
    logMessage(messageText: "Attempting to disconnect from room \(room!.name)")
}

@IBAction func toggleMic(sender: AnyObject) {
    if (self.localAudioTrack != nil) {
        self.localAudioTrack?.isEnabled = !(self.localAudioTrack?.isEnabled)!

        // Update the button title
        if (self.localAudioTrack?.isEnabled == true) {
            self.micButton.setTitle("Mute", for: .normal)
        } else {
            self.micButton.setTitle("Unmute", for: .normal)
        }
    }
}

// MARK:- Private
func startPreview() {
    if PlatformUtils.isSimulator {
        return
    }

    let frontCamera = TVICameraSource.captureDevice(for: .front)
    let backCamera = TVICameraSource.captureDevice(for: .back)

    if (frontCamera != nil || backCamera != nil) {

        let options = TVICameraSourceOptions { (builder) in
            // To support building with Xcode 10.x.
            #if XCODE_1100
            if #available(iOS 13.0, *) {
                // Track UIWindowScene events for the key window's scene.
                // The example app disables multi-window support in the .plist (see UIApplicationSceneManifestKey).
                builder.orientationTracker = UserInterfaceTracker(scene: UIApplication.shared.keyWindow!.windowScene!)
            }
            #endif
        }
        // Preview our local camera track in the local video preview view.
        camera = TVICameraSource(options: options, delegate: (self as TVICameraSourceDelegate))
        localVideoTrack = TVILocalVideoTrack(source: camera!, enabled: true, name: "Camera")

        // Add renderer to video track for local preview
        localVideoTrack!.addRenderer(self.previewView)
        logMessage(messageText: "Video track created")

        if (frontCamera != nil && backCamera != nil) {
            // We will flip camera on tap.
            let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.flipCamera))
            self.previewView.addGestureRecognizer(tap)
        }

        camera!.startCapture(with: frontCamera != nil ? frontCamera! : backCamera!) { (captureDevice, videoFormat, error) in
            if let error = error {
                self.logMessage(messageText: "Capture failed with error.\ncode = \((error as NSError).code) error = \(error.localizedDescription)")
            } else {
                self.previewView.shouldMirror = (captureDevice.position == .front)
            }
        }
    }
    else {
        self.logMessage(messageText:"No front or back capture device found!")
    }
}

@objc func flipCamera() {
    var newDevice: AVCaptureDevice?

    if let camera = self.camera, let captureDevice = camera.device {
        if captureDevice.position == .front {
            newDevice = TVICameraSource.captureDevice(for: .back)
        } else {
            newDevice = TVICameraSource.captureDevice(for: .front)
        }

        if let newDevice = newDevice {
            camera.select(newDevice) { (captureDevice, videoFormat, error) in
                if let error = error {
                    self.logMessage(messageText: "Error selecting capture device.\ncode = \((error as NSError).code) error = \(error.localizedDescription)")
                } else {
                    self.previewView.shouldMirror = (captureDevice.position == .front)
                }
            }
        }
    }
}

  func prepareLocalMedia() {

    // We will share local audio and video when we connect to the Room.

    // Create an audio track.
    if (localAudioTrack == nil) {
        localAudioTrack = TVILocalAudioTrack(options: nil, enabled: true, name: "Microphone")

        if (localAudioTrack == nil) {
            logMessage(messageText: "Failed to create audio track")
        }
    }

    // Create a video track which captures from the camera.
    if (localVideoTrack == nil) {
        self.startPreview()
    }
    }

// Update our UI based upon if we are in a Room or not
func showRoomUI(inRoom: Bool) {
    self.connectButton.isHidden = inRoom
    self.roomTextField.isHidden = inRoom
    self.roomLine.isHidden = inRoom
    self.roomLabel.isHidden = inRoom
    self.micButton.isHidden = !inRoom
    self.disconnectButton.isHidden = !inRoom
    self.navigationController?.setNavigationBarHidden(inRoom, animated: true)
    UIApplication.shared.isIdleTimerDisabled = inRoom

    // Show / hide the automatic home indicator on modern iPhones.
    self.setNeedsUpdateOfHomeIndicatorAutoHidden()
}

@objc func dismissKeyboard() {
    if (self.roomTextField.isFirstResponder) {
        self.roomTextField.resignFirstResponder()
    }
}

func logMessage(messageText: String) {
    NSLog(messageText)
    messageLabel.text = messageText
}

func renderRemoteParticipant(participant : TVIParticipant) -> Bool {
    // This example renders the first subscribed RemoteVideoTrack from the RemoteParticipant.
    let videoPublications = participant.videoTracks
    for publication in videoPublications {
        if let subscribedVideoTrack = publication.track,
            publication.isTrackEnabled {
            setupRemoteVideoView()
            subscribedVideoTrack.addRenderer(self.remoteView!)
            self.remoteParticipant = participant
            return true
        }
    }
    return false
}

func renderRemoteParticipants(participants : Array<TVIParticipant>) {
    for participant in participants {
        // Find the first renderable track.
        if participant.videoTracks.count > 0,
            renderRemoteParticipant(participant: participant) {
            break
        }
    }
}

func cleanupRemoteParticipant() {
    if self.remoteParticipant != nil {
        self.remoteView?.removeFromSuperview()
        self.remoteView = nil
        self.remoteParticipant = nil
    }
}
}

  // MARK:- UITextFieldDelegate
 extension ViewController : UITextFieldDelegate {
   func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    self.connect(sender: textField)
    return true
  }
  }

  // MARK:- RoomDelegate
  extension ViewController : TVIRoomDelegate {
    func roomDidConnect(room: TVIRoom) {
    logMessage(messageText: "Connected to room \(room.name) as \(room.localParticipant?.identity ?? "")")

    // This example only renders 1 RemoteVideoTrack at a time. Listen for all events to decide which track to render.
    for remoteParticipant in room.remoteParticipants {
        remoteParticipant.delegate = self
    }
    }

func roomDidDisconnect(room: TVIRoom, error: Error?) {
    logMessage(messageText: "Disconnected from room \(room.name), error = \(String(describing: error))")

    self.cleanupRemoteParticipant()
    self.room = nil

    self.showRoomUI(inRoom: false)
    }

func roomDidFailToConnect(room: TVIRoom, error: Error) {
    logMessage(messageText: "Failed to connect to room with error = \(String(describing: error))")
    self.room = nil

    self.showRoomUI(inRoom: false)
    }

func roomIsReconnecting(room: TVIRoom, error: Error) {
    logMessage(messageText: "Reconnecting to room \(room.name), error = \(String(describing: error))")
}

func roomDidReconnect(room: TVIRoom) {
    logMessage(messageText: "Reconnected to room \(room.name)")
}

func participantDidConnect(room: TVIRoom, participant: TVIParticipant) {
    // Listen for events from all Participants to decide which RemoteVideoTrack to render.

    logMessage(messageText: "Participant \(participant.identity) connected with \(participant.audioTracks.count) audio and \(participant.videoTracks.count) video tracks")
}

func participantDidDisconnect(room: TVIRoom, participant: TVIParticipant) {
    logMessage(messageText: "Room \(room.name), Participant \(participant.identity) disconnected")

    // Nothing to do in this example. Subscription events are used to add/remove renderers.
}

}

  // MARK:- RemoteParticipantDelegate
  extension ViewController : TVIRemoteParticipantDelegate {

func remoteParticipantDidPublishVideoTrack(participant: TVIParticipant, publication: TVIVideoTrackPublication) {
    // Remote Participant has offered to share the video Track.

    logMessage(messageText: "Participant \(participant.identity) published \(publication.trackName) video track")
}

func remoteParticipantDidUnpublishVideoTrack(participant: TVIParticipant, publication: TVIVideoTrackPublication) {
    // Remote Participant has stopped sharing the video Track.

    logMessage(messageText: "Participant \(participant.identity) unpublished \(publication.trackName) video track")
}

func remoteParticipantDidPublishAudioTrack(participant: TVIParticipant, publication: TVIAudioTrackPublication) {
    // Remote Participant has offered to share the audio Track.

    logMessage(messageText: "Participant \(participant.identity) published \(publication.trackName) audio track")
}

func remoteParticipantDidUnpublishAudioTrack(participant: TVIParticipant, publication: TVIAudioTrackPublication) {
    // Remote Participant has stopped sharing the audio Track.

    logMessage(messageText: "Participant \(participant.identity) unpublished \(publication.trackName) audio track")
}

func didSubscribeToVideoTrack(videoTrack: TVIVideoTrack, publication: TVIVideoTrackPublication, participant: TVIParticipant) {
    // The LocalParticipant is subscribed to the RemoteParticipant's video Track. Frames will begin to arrive now.

    logMessage(messageText: "Subscribed to \(publication.trackName) video track for Participant \(participant.identity)")

    if (self.remoteParticipant == nil) {
        _ = renderRemoteParticipant(participant: participant)
    }
}

func didUnsubscribeFromVideoTrack(videoTrack: TVIVideoTrack, publication: TVIVideoTrackPublication, participant: TVIParticipant) {
    // We are unsubscribed from the remote Participant's video Track. We will no longer receive the
    // remote Participant's video.

    logMessage(messageText: "Unsubscribed from \(publication.trackName) video track for Participant \(participant.identity)")

    if self.remoteParticipant == participant {
        cleanupRemoteParticipant()

        // Find another Participant video to render, if possible.
        if var remainingParticipants = room?.remoteParticipants,
            let index = remainingParticipants.firstIndex(of: participant as! TVIRemoteParticipant) {
            remainingParticipants.remove(at: index)
            renderRemoteParticipants(participants: remainingParticipants)
        }
    }
    }

func didSubscribeToAudioTrack(audioTrack: TVIAudioTrack, publication: TVIAudioTrackPublication, participant: TVIParticipant) {
    // We are subscribed to the remote Participant's audio Track. We will start receiving the
    // remote Participant's audio now.

    logMessage(messageText: "Subscribed to \(publication.trackName) audio track for Participant \(participant.identity)")
}

func didUnsubscribeFromAudioTrack(audioTrack: TVIAudioTrack, publication: TVIAudioTrackPublication, participant: TVIParticipant) {
    // We are unsubscribed from the remote Participant's audio Track. We will no longer receive the
    // remote Participant's audio.
    logMessage(messageText: "Unsubscribed from \(publication.trackName) audio track for Participant \(participant.identity)")
}

func remoteParticipantDidEnableVideoTrack(participant: TVIParticipant, publication: TVIVideoTrackPublication) {
    logMessage(messageText: "Participant \(participant.identity) enabled \(publication.trackName) video track")
}

func remoteParticipantDidDisableVideoTrack(participant: TVIParticipant, publication: TVIVideoTrackPublication) {
    logMessage(messageText: "Participant \(participant.identity) disabled \(publication.trackName) video track")
}

func remoteParticipantDidEnableAudioTrack(participant: TVIParticipant, publication: TVIAudioTrackPublication) {
    logMessage(messageText: "Participant \(participant.identity) enabled \(publication.trackName) audio track")
}

func remoteParticipantDidDisableAudioTrack(participant: TVIParticipant, publication: TVIAudioTrackPublication) {
    logMessage(messageText: "Participant \(participant.identity) disabled \(publication.trackName) audio track")
}

func didFailToSubscribeToAudioTrack(publication: TVIAudioTrackPublication, error: Error, participant: TVIParticipant) {
    logMessage(messageText: "FailedToSubscribe \(publication.trackName) audio track, error = \(String(describing: error))")
}

func didFailToSubscribeToVideoTrack(publication: TVIVideoTrackPublication, error: Error, participant: TVIParticipant) {
    logMessage(messageText: "FailedToSubscribe \(publication.trackName) video track, error = \(String(describing: error))")
}
}

 // MARK:- VideoViewDelegate
extension ViewController : TVIVideoViewDelegate {
func videoViewDimensionsDidChange(view: TVIVideoView, dimensions: CMVideoDimensions) {
    self.view.setNeedsLayout()
}
}

 // MARK:- CameraSourceDelegate
 extension ViewController : TVICameraSourceDelegate {
func cameraSourceDidFail(source: TVICameraSource, error: Error) {
    logMessage(messageText: "Camera source failed with error: \(error.localizedDescription)")
}
}
paynerc commented 4 years ago

First question that I have is what version of TwilioVideo are you using? From the code it would appear to be 2.x as we have removed the need for the TVI prefix with our classes in Swift starting with 3.0.0.

The methods defined in RemoteParticipantDelegte all pass in instances of RemoteParticipant and not the base class Participant. That is the first thing I am seeing here. If you fix that, then I would make the following changes:

func renderRemoteParticipant(participant : RemoteParticipant) -> Bool {
    // This example renders the first subscribed RemoteVideoTrack from the RemoteParticipant.
    let videoPublications = participant.remoteVideoTracks
    for publication in videoPublications {
        if let subscribedVideoTrack = publication.remoteTrack,
            publication.isTrackEnabled {
            setupRemoteVideoView()
            subscribedVideoTrack.addRenderer(self.remoteView!)
            self.remoteParticipant = participant
            return true
        }
    }
    return false
}

func renderRemoteParticipants(participants : Array<RemoteParticipant>) {
    for participant in participants {
        // Find the first renderable track.
        if participant.videoTracks.count > 0,
            renderRemoteParticipant(participant: participant) {
            break
        }
    }
}
mouli457 commented 4 years ago

Actually,My problem is I install TwilioVideo through pods and I use example code in ViewController.swift but i got so many errors.Then I change some keywords with TVI prefix.All errors are fixed but my question issue not fixed and I comment that line and run my project,It's work but opponent video is not displaying.I am using (3.2.5)version.Please tell me pod name which is used in GitHub example or send me latest example code.Thanks in advance.

paynerc commented 4 years ago

@mouli457,

I see that you have opened issue #530 so I am assuming that you have gotten things to install and are no longer stuck with this issue. I am going to close this ticket and we can continue to work through #53

Ryan