Add AirPlay support

This commit is contained in:
xzzpig
2024-11-29 12:04:08 +08:00
parent b1785b1fa5
commit 589ca19e4f
16 changed files with 408 additions and 34 deletions
@@ -617,6 +617,7 @@ static NSMutableSet* hostList;
}
- (void) prepareToStreamApp:(TemporaryApp *)app {
[self updateResolutionAccordingly];
self.revealViewController.isStreaming = true; // tell the revealViewController streaming is started.
_streamConfig = [[StreamConfiguration alloc] init];
_streamConfig.host = app.host.activeAddress;
@@ -634,9 +635,14 @@ static NSMutableSet* hostList;
_streamConfig.frameRate = [streamSettings.framerate intValue];
if (@available(iOS 10.3, *)) {
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
NSInteger maximumFramesPerSecond = window.screen.maximumFramesPerSecond;
if(UIScreen.screens.count > 1 && streamSettings.externalDisplayMode.intValue == 1){ //AirPlaying
maximumFramesPerSecond = UIScreen.screens.lastObject.maximumFramesPerSecond;
}
// Don't stream more FPS than the display can show
if (_streamConfig.frameRate > [UIScreen mainScreen].maximumFramesPerSecond) {
_streamConfig.frameRate = (int)[UIScreen mainScreen].maximumFramesPerSecond;
if (_streamConfig.frameRate > maximumFramesPerSecond) {
_streamConfig.frameRate = (int)maximumFramesPerSecond;
Log(LOG_W, @"Clamping FPS to maximum refresh rate: %d", _streamConfig.frameRate);
}
}
@@ -1171,7 +1177,7 @@ static NSMutableSet* hostList;
//if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height)
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
[self updateResolutionAccordingly];
//[self updateResolutionAccordingly];
// SettingsViewController* settingsViewController = (SettingsViewController*)[self.revealViewController rearViewController];
// [settingsViewController updateResolutionTable];
@@ -1219,17 +1225,17 @@ static NSMutableSet* hostList;
}
// this will also be called back when device orientation changes
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
double delayInSeconds = 0.7;
// Convert the delay into a dispatch_time_t value
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// Perform some task after the delay
dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay
[self updateResolutionAccordingly];
});
}
//- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
//
// double delayInSeconds = 0.7;
// // Convert the delay into a dispatch_time_t value
// dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// // Perform some task after the delay
// dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay
// [self updateResolutionAccordingly];
// });
//}
-(void) updateResolutionAccordingly {
DataManager* dataMan = [[DataManager alloc] init];
@@ -1240,6 +1246,13 @@ static NSMutableSet* hostList;
CGFloat appWindowHeight = CGRectGetHeight(window.frame) * screenScale;
CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]);
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
if(currentSettings.externalDisplayMode.intValue == 1 && UIScreen.screens.count > 1){
CGRect bounds = [UIScreen.screens.lastObject bounds];
screenScale = [UIScreen.screens.lastObject scale];
appWindowWidth = bounds.size.width * screenScale;
appWindowHeight = bounds.size.height * screenScale;
}
bool needSwap = false;
@@ -1398,7 +1411,7 @@ static NSMutableSet* hostList;
object: nil];
//[self simulateSettingsButtonPress]; //force reload resolution table in the setting
//[self simulateSettingsButtonPress];
[self updateResolutionAccordingly];
//[self updateResolutionAccordingly];
}
@@ -57,6 +57,7 @@
@property (strong, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC;
@property (nonatomic, strong) MainFrameViewController *mainFrameViewController;
@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
@@ -222,6 +222,9 @@ BOOL isCustomResolution(CGSize res) {
return true;
}
- (bool)isAirPlayEnabled{
return [self.externalDisplayModeSelector selectedSegmentIndex] == 1;
}
- (void)updateResolutionTable{
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
@@ -229,6 +232,12 @@ BOOL isCustomResolution(CGSize res) {
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
CGFloat appWindowWidth = window.frame.size.width * screenScale;
CGFloat appWindowHeight = window.frame.size.height * screenScale;
if([self isAirPlayEnabled] && UIScreen.screens.count > 1){
CGRect bounds = [UIScreen.screens.lastObject bounds];
screenScale = [UIScreen.screens.lastObject scale];
appWindowWidth = bounds.size.width * screenScale;
appWindowHeight = bounds.size.height * screenScale;
}
bool needSwapWidthAndHeight = appWindowWidth > appWindowHeight;
resolutionTable[4] = CGSizeMake(safeAreaWidth, appWindowHeight);
@@ -248,7 +257,7 @@ BOOL isCustomResolution(CGSize res) {
// this will also be called back when device orientation changes
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
double delayInSeconds = 1.0;
double delayInSeconds = 0.2;
// Convert the delay into a dispatch_time_t value
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
// Perform some task after the delay
@@ -549,6 +558,8 @@ BOOL isCustomResolution(CGSize res) {
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen;
}
[self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue];
}
- (void)slideToSettingsScreenEdgeChanged{
@@ -1055,6 +1066,7 @@ BOOL isCustomResolution(CGSize res) {
BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1;
BOOL allowPortrait = [self.allowPortraitSelector selectedSegmentIndex] == 1;
NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex];
NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex];
[dataMan saveSettingsWithBitrate:_bitrate
framerate:framerate
height:height
@@ -1085,7 +1097,8 @@ BOOL isCustomResolution(CGSize res) {
touchMode:touchMode
statsOverlay:statsOverlay
allowPortrait:allowPortrait
resolutionSelected:resolutionSelected];
resolutionSelected:resolutionSelected
externalDisplayMode:externalDisplayMode];
}
- (void)didReceiveMemoryWarning {
@@ -54,6 +54,10 @@
UIScrollView *_scrollView;
BOOL _userIsInteracting;
CGSize _keyboardSize;
UIWindow *_extWindow;
UIView *_renderView;
UIWindow *_deviceWindow;
dispatch_block_t _delayedRemoveExtScreen;
#if !TARGET_OS_TV
CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer;
CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer;
@@ -162,6 +166,8 @@
[self->_streamView reloadOnScreenControlsRealtimeWith:(ControllerSupport*)_controllerSupport
andConfig:(StreamConfiguration*)_streamConfig]; //reload OSC here.
[self->_streamView reloadOnScreenButtonViews]; //reload keyboard buttons here. the keyboard button view will be added to the streamframe view instead streamview, the highest layer, which saves a lot of reengineering
[self reloadAirPlayConfig];
//reconfig statsOverlay
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
@@ -179,6 +185,29 @@
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_deviceWindow = self.view.window;
if (UIScreen.screens.count > 1 && [self isAirPlayEnabled]) {
[self prepExtScreen:UIScreen.screens.lastObject];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self.view insertSubview:self->_renderView atIndex:0];
});
}
// check to see if external screen is connected/disconnected
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(extScreenDidConnect:)
name: UIScreenDidConnectNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(extScreenDidDisconnect:)
name: UIScreenDidDisconnectNotification
object: nil];
#if !TARGET_OS_TV
[[self revealViewController] setPrimaryViewController:self];
@@ -239,7 +268,9 @@
_controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig delegate:self];
_inactivityTimer = nil;
_renderView = (StreamView*)[[UIView alloc] initWithFrame:self.view.frame];
_streamView = [[StreamView alloc] initWithFrame:self.view.frame];
_renderView.bounds = _streamView.bounds;
//[_streamView setupStreamView:_controllerSupport interactionDelegate:self config:self.streamConfig];
[self reConfigStreamViewRealtime]; // call this method again to make sure all gestures are configured & added to the superview(self.view), including the gestures added from inside the streamview.
@@ -281,7 +312,7 @@
_tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9);
_streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig
renderView:_streamView
renderView:_renderView
connectionCallbacks:self];
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperation:_streamMan];
@@ -449,8 +480,86 @@
_statsUpdateTimer = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
_extWindow = nil;
}
// External Screen connected
- (void)extScreenDidConnect:(NSNotification *)notification {
Log(LOG_I, @"External Screen Connected");
dispatch_async(dispatch_get_main_queue(), ^{
[self prepExtScreen:notification.object];
});
}
// External Screen disconnected
- (void)extScreenDidDisconnect:(NSNotification *)notification {
Log(LOG_I, @"External Screen Disconnected");
if(UIScreen.screens.count < 2)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self removeExtScreen];
});
}
}
- (BOOL) isAirPlaying{
return _extWindow != nil && _extWindow.hidden == NO;
}
- (BOOL) isAirPlayEnabled{
return _settings.externalDisplayMode.intValue == 1;
}
- (void) reloadAirPlayConfig{
if (UIScreen.screens.count == 1){return;}
if (![self isAirPlaying] && [self isAirPlayEnabled]){
[self prepExtScreen:UIScreen.screens.lastObject];
}else if ([self isAirPlaying] && ![self isAirPlayEnabled]){
[self removeExtScreen];
}
}
// Prepare Screen
- (void)prepExtScreen:(UIScreen*)extScreen {
Log(LOG_I, @"Preparing External Screen");
if(![self isAirPlayEnabled]){
return;
}
CGRect frame = extScreen.bounds;
extScreen.overscanCompensation = UIScreenOverscanCompensationNone;
if(_extWindow==nil){
_extWindow = [[UIWindow alloc] initWithFrame:frame];
}
_extWindow.screen = extScreen;
_renderView.bounds = frame;
_renderView.frame = frame;
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:@"ScreenChanged" object:self];
[_extWindow addSubview:_renderView];
_extWindow.hidden = NO;
}
- (void)removeExtScreen {
Log(LOG_I, @"Removing External Screen");
_extWindow.hidden = YES;
[self handleViewResize];
[self.view insertSubview:_renderView atIndex:0];
}
- (void) handleViewResize{
_streamView.bounds = _deviceWindow.bounds;
_streamView.frame = _deviceWindow.frame;
if(![self isAirPlaying]){
_renderView.bounds = _deviceWindow.bounds;
_renderView.frame = _deviceWindow.frame;
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:@"ScreenChanged" object:self];
}
[self reConfigStreamViewRealtime];
}
// This will fire if the user opens control center or gets a low battery message
- (void)applicationWillResignActive:(NSNotification *)notification {
if (_inactivityTimer != nil) {
@@ -523,6 +632,7 @@
// the first frame of video.
self->_stageLabel.hidden = YES;
self->_tipLabel.hidden = YES;
self->_spinner.hidden = YES;
[self->_streamView showOnScreenControls];
@@ -864,7 +974,17 @@
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
Log(LOG_I, @"View size changed, terminating stream");
// [self returnToMainFrame];
double delayInSeconds = 0.2;
if (_delayedRemoveExtScreen) {
dispatch_block_cancel(_delayedRemoveExtScreen);
}
dispatch_block_t block = dispatch_block_create(0, ^{
[self handleViewResize];
});
_delayedRemoveExtScreen = block;
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), block);
}
@end