Closed mouli457 closed 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
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)")
}
}
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
}
}
}
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.
@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
I am facing as show in image.Please suggest sample code for latest twilio video configuration.