My question is: how to decrease the lag on the secodary screen when using Airplay mirroring?
To illustrate what I am doing, here is some code. To use it, create a new iOS project (single view, Swift). Then copy-paste the following code in ViewController.swift, replacing all that is there by default.
Make sure you have an Apple TV to mirror to, and enable mirroring to it on your iPhone / iPad.
Tap the i-Device's screen to stop the framecounter and observe the latency between iOS device and television. I was filming an iPhone 5 with the television in the background using the slo-mo function of the iPhone 6 Plus to get a good indication of the absolute latency.
The thing is, playing Real Racing 2 on the same device, same network, same Apple TV, same TV, works very very well.
Could there be less lag when using OpenGL / Metal?
The endgoal here is:
I have an array of bytes and I want to draw these on the secondary screen. As this is going to be a game, low-latency in response to touches is required.
Here is the code:
import UIKit
class ViewController: UIViewController, MyViewDelegate
{
var myView = MyView(frame: CGRectZero) // allow responding to touches directly
var pause = false // when MyView is touched, this is flipped
var totalTime = 0 // incremented each frame, displayed on both screens
var secondScreenLabel = UILabel(frame: CGRectZero)
var primaryScreenLabel = UILabel(frame: CGRectZero)
var secondaryWindow = UIWindow(frame: CGRectZero)
var _secondaryScreen : UIScreen?
func secondaryDisplayLinkFired(displaylink : CADisplayLink)
{
// if not paused, the counter gets incremented and the value
// is displayed on the labels in both screens
if (!self.pause)
{
self.totalTime++
self.secondScreenLabel.text = "\(self.totalTime)"
self.secondScreenLabel.sizeToFit()
self.secondScreenLabel.center = self.secondaryWindow.center
self.primaryScreenLabel.text = "\(self.totalTime)"
self.primaryScreenLabel.sizeToFit()
self.primaryScreenLabel.center = self.view.center
}
}
func myViewDidBeginTouches(window: MyView, touches: Set<NSObject>)
{
// textColors get changed for extra visibility
self.primaryScreenLabel.textColor = UIColor.redColor()
self.secondScreenLabel.textColor = UIColor.redColor()
// this flip will cause the labels to not change anymore
self.pause = true
}
func myViewDidEndTouches(window: MyView, touches: Set<NSObject>)
{
// reset the colors
self.primaryScreenLabel.textColor = UIColor.lightGrayColor()
self.secondScreenLabel.textColor = UIColor.lightGrayColor()
// this flip will cause the labels to change their values again
self.pause = false
}
func secondaryScreen () -> UIScreen?
{
// find the first screen that is not the mainscreen.
// We hold on to it, so that calling self.secondaryScreen
// many times should not be too slow
if (nil == _secondaryScreen)
{
let screens = UIScreen.screens()
// no more then one screen means there is no
// secondary screen
if (screens.count > 1)
{
for aScreen in screens
{
let screen = UIScreen.mainScreen() as UIScreen
if (aScreen as! UIScreen != screen)
{
_secondaryScreen = aScreen as? UIScreen
break
}
}
}
}
return _secondaryScreen
}
override func viewDidLoad()
{
super.viewDidLoad()
// setup the secondary screen if it is already available
if (nil != self.secondaryScreen())
{
self.screenDidConnect(nil)
}
else
{
self.screenDidDisconnect(nil)
}
// in a real application, self needs to be removed as observer
// in e.g. deinit
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("screenDidConnect:"), name: UIScreenDidConnectNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("screenDidDisconnect:"), name: UIScreenDidDisconnectNotification, object: nil)
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
// use myView to capture touches
myView.frame = self.view.bounds
myView.delegate = self
myView.backgroundColor = UIColor.blackColor()
self.view.addSubview(myView)
// a label to display the frame count
self.primaryScreenLabel.text = "\(totalTime)"
self.primaryScreenLabel.userInteractionEnabled = false
self.primaryScreenLabel.textColor = UIColor.lightGrayColor()
self.primaryScreenLabel.font = UIFont.systemFontOfSize(100)
self.primaryScreenLabel.sizeToFit()
self.primaryScreenLabel.center = self.view.center
self.view.addSubview(self.primaryScreenLabel)
}
func screenDidConnect(notification : NSNotification?)
{
if let secondScreen = self.secondaryScreen()
{
// see MainViewController.m in sample code at:
// https://developer.apple.com/library/ios/samplecode/GLAirplay/Introduction/Intro.html
self.secondaryWindow.frame = secondScreen.bounds
self.secondaryWindow.backgroundColor = UIColor.blackColor()
self.secondaryWindow.makeKeyAndVisible()
self.secondaryWindow.screen = secondScreen
self.secondaryWindow .addSubview(self.secondScreenLabel)
self.secondScreenLabel.text = "\(totalTime)"
self.secondScreenLabel.textColor = UIColor.lightGrayColor()
self.secondScreenLabel.font = UIFont.systemFontOfSize(400)
self.secondScreenLabel.sizeToFit()
self.secondScreenLabel.center = self.secondaryWindow.center
if let secondaryScreenDisplayLink = secondScreen.displayLinkWithTarget(self, selector: Selector("secondaryDisplayLinkFired:"))
{
secondaryScreenDisplayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
else
{
print("WARNING: could not get a displaylink")
}
}
else
{
print("PROGRAMMER ERROR: screenDidConnect: called but there is no second screen")
}
}
func screenDidDisconnect(notification : NSNotification?)
{
// in a real application, this should be implemented properly
}
}
protocol MyViewDelegate
{
func myViewDidBeginTouches(view : MyView, touches : Set<NSObject>)
func myViewDidEndTouches(view : MyView, touches : Set<NSObject>)
}
class MyView : UIView
{
var delegate : MyViewDelegate?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
{
self.delegate?.myViewDidBeginTouches(self, touches: touches)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent)
{
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!)
{
self.delegate?.myViewDidEndTouches(self, touches: touches)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent)
{
self.delegate?.myViewDidEndTouches(self, touches: touches)
}
}