I am using Twilio Video QuickStart, Swift, while I was able to make a video call using the example, but I couldn't make it work when I bundled all of it into one class and used in my app's viewController.
Here is what I did:
I've put all the Twilio Code in one class VideoCall as shown below, so that I could use the Twilio Video in whichever Controller I need, by just calling VideoCall(myViewController: self) as shown below.
While the below code creates both views (videoView & subVideoView) and renders local video in subVideoView as intended, it comes up with an Error when an incoming call is received
TwilioVideo:[Platform]:Fatal runtime capture error: Error
Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be
completed"
However, I don't receive this error, when I put all the variables and functions into the class mainViewController instead of a separate class VideoCall, but this means I would be duplicating the code if I need to use Twilio Video in other View Controllers.
More info on Twilio class TVIVideoView which says
A TVIVideoView draws video frames from a TVIVideoTrack.
TVIVideoView should only be used on the application's main thread.
Subclassing TVIVideoView is not supported.
UIViewContentModeScaleToFill, UIViewContentModeScaleAspectFill and
UIViewContentModeScaleAspectFit are the only supported content modes.
Please advice how I could get around this issue.
class mainViewController : UIViewController{
//Declare variable in my viewController
var videoCall : VideoCall!
// And create an instance
fun viewDidAppear(){
videoCall = VideoCall(myViewController: self)
}
}
class VideoCall: NSObject {
// Declared variables for Twilio Video
var subVideoView : TVIVideoView!
var videoView : TVIVideoView!
var viewController : UIViewController
var accessToken = "TWILIO_ACCESS_TOKEN"
// Configure remote URL to fetch token from
var tokenUrl = "https://testapp.com/getTwilioVideoAccessToken"
// Video SDK components
var room: TVIRoom?
var camera: TVICameraCapturer?
var localVideoTrack: TVILocalVideoTrack?
var localAudioTrack: TVILocalAudioTrack?
var participant: TVIParticipant?
var remoteView: TVIVideoView?
// CallKit components
let callKitProvider : CXProvider
let callKitCallController : CXCallController
var callKitCompletionHandler: ((Bool)->Swift.Void?)? = nil
var callConfiguration : CXProviderConfiguration
init(myViewController: UIViewController) {
viewController = myViewController
let callConfiguration = CXProviderConfiguration(localizedName: "Video Call")
callConfiguration.maximumCallGroups = 1
callConfiguration.maximumCallsPerCallGroup = 1
callConfiguration.supportsVideo = true
print("configuration: \(callConfiguration)")
if let callKitIcon = UIImage(named: "iconMask80") {
callConfiguration.iconTemplateImageData = UIImagePNGRepresentation(callKitIcon)
}
callKitProvider = CXProvider(configuration: callConfiguration)
callKitCallController = CXCallController()
super.init()
subVideoView = TVIVideoView.init(frame: CGRect.zero, delegate: self)
videoView = TVIVideoView.init(frame: CGRect.zero, delegate: self)
callKitProvider.setDelegate(self, queue: nil)
setup()
}
deinit {
// CallKit has an odd API contract where the developer must call invalidate or the CXProvider is leaked.
callKitProvider.invalidate()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(){
// The following is done here
// a. videoView and subVideoView are made subViews of window, so that they are overlaid on top of the viewController
// b. Setting size and position of videoView and subVideoView
}
}
extension VideoCall{
/*
Has all the callKit methods and Twilio methods, same as the example app but with these changes
a.the remoteView in the example app is replaced with videoView and screamed on initialisation, unlike Twilio Video example where it is created when connected to the room and cleared after disconnecting.
b. previewView is replaced with subVideoView.
*/
}
Related
I’ve been using the agora SDK (audio only) for a while now, and I’m very pleased! Users can enter rooms of max 8 people and talk to each other. Now, I’m supposed to add a video feature, so they can enable their video stream at any time they want.
I’ve added the AgoraRtcEngine_iOS 3.7.0 pod and this is how I initialize the agora engine:
agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: AppKeys.agoraAppId, delegate: self)
agoraKit?.setChannelProfile(.liveBroadcasting)
agoraKit?.setClientRole(.broadcaster)
agoraKit?.enableVideo()
agoraKit?.muteLocalVideoStream(true)
agoraKit?.muteLocalAudioStream(true)
agoraKit?.enableAudioVolumeIndication(1000, smooth: 3, report_vad: true)
This way, when a user joins a room, they have muted their audio and video streams, so they can enable it whenever they’re ready. The thing is, I’m using a UICollectionView to present the broadcasters. (everyone's user cell is visible at all times, so no reuse takes place, but collection view reloads happen constantly)
This is part of the cell setup (cellForItemAt), that handles the video view using a delegate:
private func setupVideoView(uid: UInt, isOfCurrentUser: Bool) {
videoView.frame.size = avatarBackgroundView.frame.size
videoView.layer.cornerRadius = avatarBackgroundView.layer.cornerRadius
if isOfCurrentUser {
delegate?.localVideoSetupWasRequested(videoView: videoView)
} else {
delegate?.remoteVideoSetupWasRequested(uid: uid, videoView: videoView)
}
}
And this is the conformance to the protocol:
extension RealTimeAudioAgoraService: NewVoiceRoomCellsDelegate {
func localVideoSetupWasRequested(videoView: UIView) {
guard !enabledVideoUIds.contains(currentUserRTCServiceId) else { return }
let videoCanvas = setUpVideoView(uid: currentUserRTCServiceId, videoView: videoView)
agoraKit?.setupLocalVideo(videoCanvas)
}
func remoteVideoSetupWasRequested(uid: UInt, videoView: UIView) {
guard !enabledVideoUIds.contains(uid) else { return }
let videoCanvas = setUpVideoView(uid: uid, videoView: videoView)
agoraKit?.setupRemoteVideo(videoCanvas)
}
private func setUpVideoView(uid: UInt, videoView: UIView) -> AgoraRtcVideoCanvas {
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = videoView
videoCanvas.renderMode = .hidden
// ------------------------------------
enabledVideoUIds.append(uid)
// ------------------------------------
return videoCanvas
}
}
As you can see, I keep track of all the uids I have enabled the video canvas for, so I do it only once for each user. The thing is something messes up the UI (see attached video) and even users with muted videos show the video canvas of others. It’s like the video canvas of each user is being cycled over every other user.
Any help will be much appreciated!
I have several different ViewControllers set up in my project. The first one acts as a 'landing' page and and on pressing a button the view is directed to a following ViewController.
There are various buttons and labels on there that I want to use to provide information and run a method. The buttons all work ok, but I have followed every tutorial I can to get the labels to update based on a method, and I can't seem to get them to do so.
I know the methods are called correctly as I have put print statements in there to check.
Basic idea is, program plays a series of beeps separated by a delay (bleep_time), this changes each level (bleep_level) and there are several steps (bleep_step) in each level. Ive simplified the arrays containing this data to save space.
I have successfully created a number of tutorial projects and the labels all update correctly, but they only use one ViewController.
Is the issue I am facing due to using 2 ViewControllers?
ViewController 1
import UIKit
class Bleep_Test_Menu: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
View Controller 2
import UIKit
import AVFoundation
class Bleep_Test_Level_1: UIViewController {
// Global Variables
var audioPlayer: AVAudioPlayer?
// Bleep test delays
var bleep_time = [1,2,3,4]
// Levels of bleep test
var bleep_level = [1,2,3,4]
// Level Label
#IBOutlet weak var bleepTestLevel1Level: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// Function to run repeated bleeps
#IBAction func bleepTestLevel1Start(_ sender: Any) {
var i = 0
tracker = false
let length = bleep_time.count
while i < length {
var bleeplevel = bleep_level[i]
bleepTestLevel1Level.text = "\(bleeplevel)"
playSaveSound()
sleep(UInt32(bleep_time[i]))
i += 1
}
}
// Function to play sounds
func playSaveSound(){
let path = Bundle.main.path(forResource: "Sound1.wav", ofType: nil)!
let url = URL(fileURLWithPath: path)
do {
//create your audioPlayer in your parent class as a property
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
} catch {
print("couldn't load the file")
}
}
}
The only error I keep getting in the console is this;
"Tests[53616:891478] - changing property contentsGravity in transform-only layer, will have no effect"
But I can't seem to find anything that relates to my issue.
I am creating a macOS app on Xcode 9.2 and i can't figure out why after the views hierarchy is created in viewDidLoad(), i can't use anymore the IBOutlet which is referencing the view in the StoryBoard.
I have found similar question, where they suggest to save the view that i want to update, as a variable, when i am in viewDidLoad() so i can use it later; if so what is the advantage of using an IBOutlet?
Can someone pls explain how to update in this case the content of the PDFView outside viewDidLoad() without getting nil?
The AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Properties
var filePath: URL?
var result: Int?
var fileObject: Data?
// MARK: - Outlets
#IBOutlet weak var openFileMenuItem: NSMenuItem!
// MARK: - App Life Cycle
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
// MARK: - My Functions
#IBAction func openFile(_ sender: NSMenuItem) {
//Get the window of the app
var window = NSApplication.shared.mainWindow!
// Create NSOpenPanel and setting properties
let panel = NSOpenPanel()
panel.title = "Select file"
panel.allowsMultipleSelection = false;
panel.canChooseDirectories = false;
panel.canCreateDirectories = false;
panel.canChooseFiles = true;
// Filtering file extension
let fileTypes: [String] = ["pdf"]
panel.allowedFileTypes = fileTypes
// Showing OpenPanel to the user for selecting the file
panel.beginSheetModal(for: window) {
(result) in
if result.rawValue == NSFileHandlingPanelOKButton {
// Getting url of the file
self.filePath = panel.urls[0]
print(self.filePath)
// Creating Data Object of the file for creating later the PDFDocument in the controller
do {
if let fileData = self.filePath {
self.fileObject = try Data.init(contentsOf: self.filePath!)
print("\(self.fileObject) ")
}
} catch let error as NSError {
print("Error with file Data Object: \(error)")
}
// Getting the mainViewController
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "mainViewControllerID")) as! MainViewController
// Appdelegate call function of mainViewController to update the content of PDFView
mainController.showPdfDocument(fileData: self.fileObject!)
}
}
}
}
The MainViewController:
import Foundation
import Quartz
class MainViewController: NSViewController {
// MARK: - Property
var pdfdocument: PDFDocument?
// MARK: - Outlets
#IBOutlet var mainView: NSView!
#IBOutlet var pdfView: PDFView!
// MARK: - App Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: - My Functions
func showPdfDocument(fileData: Data) {
print("appdelegate calling showPdfDocument on mainViewController " )
pdfdocument = PDFDocument(data: fileData)
pdfView.document = pdfdocument
}
}
The Error I Am Getting:
fatal error: unexpectedly found nil while unwrapping an Optional value
I solved the problem with the current code in the AppDelegate. Basically i am using the contentViewController to update the PDFView but i don't know if this is a good practice because i should update the PDFView with its own controller and not with the one of the window.
in the openFile function of the AppleDelegate:
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainViewController = NSApplication.shared.mainWindow?.contentViewController as! MainViewController
mainViewController.showPdfDocument(fileData: self.fileObject!)
Reading from the Apple documentation at this link (https://developer.apple.com/documentation/appkit/nswindow/1419615-contentviewcontroller) it says 'Directly assigning a contentView value clears out the root view controller.'. Should I programmatically assign my mainView to the contentView to automatically set my mainViewController as the initial controller?
I have also found that i can set my controller as the initial controller from IB as you can see form this image:
The ideal thing i want to do is to leave the contentViewController as the initial viewController (the default setting in IB) and then instantiate my mainViewController to update the pdfView. The problem is that with instantiation, the new viewController has everything to nil(IBOutlet, vars etc..). Is it the downcasting from contentViewController to mainViewController a good approach to achieve this task?
I am a beginner in iOS development, and I am following one tutorial using firebase database to make a simple chat app. Actually I am confused with the use of viewDidLoad method.
Here is the screenshot of the app: https://ibb.co/gqD4Tw
I don't understand why retrieveMessage() method is put on viewDidLoad when I want to send data (chat message) to firebase database, I used sendButtonPressed() method (which is an IBAction) and when I want to retrieve data from the database, I use retrieveMessage().
The retrieveMessage() method is called on viewDidLoad, as far as I know the viewDidLoad method is called only once after the view is loaded into memory. We usually use it for initial setup.
So, if viewDidLoad is called only once in initial setup, why the retrieveMessage() method can retrieve all the message that I have sent to my own database over and over again, after I send message data to the database ?
I don't understand why retrieveMessage() method is put on viewDidLoad below is the simplified code:
class ChatViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var messageArray = [Message]()
#IBOutlet var messageTextfield: UITextField!
#IBOutlet var messageTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//Set as the delegate and datasource :
messageTableView.delegate = self
messageTableView.dataSource = self
//the delegate of the text field:
messageTextfield.delegate = self
retrieveMessage()
///////////////////////////////////////////
//MARK: - Send & Recieve from Firebase
#IBAction func sendPressed(_ sender: AnyObject) {
// Send the message to Firebase and save it in our database
let messageDB = FIRDatabase.database().reference().child("message")
let messageDictionary = ["MessageBody":messageTextfield.text!, "Sender": FIRAuth.auth()?.currentUser?.email]
messageDB.childByAutoId().setValue(messageDictionary) {
(error,ref) in
if error != nil {
print(error!)
} else {
self.messageTextfield.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextfield.text = ""
}
}
}
//Create the retrieveMessages method :
func retrieveMessage () {
let messageDB = FIRDatabase.database().reference().child("message")
messageDB.observe(.childAdded, with: { (snapshot) in
let snapshotValue = snapshot.value as! [String:String]
let text = snapshotValue["MessageBody"]!
let sender = snapshotValue["Sender"]!
let message = Message()
message.messsageBody = text
message.sender = sender
self.messageArray.append(message)
self.messageTableView.reloadData()
})
}
}
viewDidLoad method is called only once in ViewController lifecycle.
The reason retrieveMessage() is called in viewDidLoad because it's adding observer to start listening for received and sent message. Once you receive or send message then this block(observer) is called and
then adding that text in array self.messageArray.append(message) and updating tableview.
viewDidLoad gets called only once but the firebase functions starts a listener, working in background and syncronizeing data.
Its called in viewDidLoad because it tells -> When this view loads, start listening for messages.
ViewDidLoad() is only called upon initializing the ViewController.
If you want to have a function called every time the user looks at the VC again (e.g. after a segue back from another VC) you can just use ViewDidAppear().
It is also called when ViewDidLoad() is called.
Im trying to call protocol delegate in an additional class. The first class (ViewController) works but the second one I have implemented it in doesn't show any data. I added it with the autofill option so its not giving errors. It just doesn't do anything.
sender class
#objc protocol BWWalkthroughViewControllerDelegate{
#objc optional func walkthroughPageDidChange(pageNumber:Int) // Called when current page changes
}
#objc class BWWalkthroughViewController: UIViewController, UIScrollViewDelegate, ViewControllerDelegate {
// MARK: - Public properties -
weak var delegate:BWWalkthroughViewControllerDelegate?
var currentPage:Int{ // The index of the current page (readonly)
get{
let page = Int((scrollview.contentOffset.x / view.bounds.size.width))
return page
}
}
// MARK: - Private properties -
let scrollview:UIScrollView!
var controllers:[UIViewController]!
var lastViewConstraint:NSArray?
var shouldCancelTimer = false
var aSound: AVAudioPlayer!
var isForSound: AVAudioPlayer!
var alligatorSound: AVAudioPlayer!
var audioPlayer = AVAudioPlayer?()
var error : NSError?
var soundTrack2 = AVAudioPlayer?()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var audioPlayerAnimalSound = AVAudioPlayer?()
var audioPlayerAlphabetSound = AVAudioPlayer?()
var audioPlayerFullPhraze = AVAudioPlayer?()
var audioPlayerPhonics = AVAudioPlayer?()
Code removed to save space carries on:
/**
Update the UI to reflect the current walkthrough situation
**/
private func updateUI(){
// Get the current page
pageControl?.currentPage = currentPage
// Notify delegate about the new page
delegate?.walkthroughPageDidChange?(currentPage)
}
receiver class
class BWWalkthroughPageViewController: UIViewController, BWWalkthroughPage, ViewControllerDelegate, BWWalkthroughViewControllerDelegate {
Function in second Class.
func walkthroughPageDidChange(pageNumber: Int) {
println("current page 2 \(pageNumber)")
}
walkthroughPageDidChange does work in the viewController class however. Please can you help me see what is wrong?
Your weak var delegate:BWWalkthroughViewControllerDelegate? is an optional variable and it is not assigned anywhere in your presented code, hence it will be nil. You have to instantiate it somewhere for it to work.
A good way to do so is:
class BWWalkthroughViewController {
let bwWalkthroughPageViewController = BWWalkthroughPageViewController()
var bwWalkthroughViewControllerDelegate : BWWalkthroughViewControllerDelegate!
init() {
bwWalkthroughViewControllerDelegate = bwWalkthroughPageViewController as BWWalkthroughViewControllerDelegate
}
}
A thing to note is that it is often good practice to name the delegates with meaningful names. The keyword "delegate" is often used for many classes and is restricted. A more verbose name will eliminate the chance of overriding an original delegate and will help identify each one if you have more than one.