iOS视频播放全屏效果实现(已兼容iOS13)

概述

最近在做有关音视频的项目,项目中涉及到全屏播放切换的问题,最近研究了一下。在此做个记录,实现全屏效果我目前能够用两种方法实现,一种是让App需要进行全屏的页面随着设备进行旋转,另外一种是把需要全屏的view放到window上面,为window添加旋转动画。在这介绍的是使用window实现全屏功能。

补充说明

由于在iOS13调用setStatusBarOrientation无效,所以做了些调整,已经兼容 iOS13

效果

fullScreen
#import "VideoFullScreenController.h"

static CGFloat AnimationDuration = 0.3;//旋转动画执行时间

@interface VideoFullScreenController ()

@property (nonatomic, nullable, strong) UIView *playerView;//播放器视图
@property (nonatomic, nullable, strong) UIButton *btnFullScreen;
@property (nonatomic, nullable, strong) UIView *playerSuperView;//记录播放器父视图
@property (nonatomic, assign) CGRect playerFrame;//记录播放器原始frame
@property (nonatomic, assign) BOOL isFullScreen;//记录是否全屏

@property (nonatomic, assign) UIInterfaceOrientation lastInterfaceOrientation;//记录最后一次旋转方向
@property (nonatomic, nullable, strong) UIWindow *mainWindow;

@end

@implementation VideoFullScreenController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.playerView addSubview:self.btnFullScreen];
    [self.view addSubview:self.playerView];
    
    if (@available(iOS 13.0, *)) {
        _lastInterfaceOrientation = [UIApplication sharedApplication].windows.firstObject.windowScene.interfaceOrientation;
    } else {
        _lastInterfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
    }
    //开启和监听 设备旋转的通知
    if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    }
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleDeviceOrientationChange:)
                                                name:UIDeviceOrientationDidChangeNotification object:nil];
}

//设备方向改变的处理
- (void)handleDeviceOrientationChange:(NSNotification *)notification{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    switch (deviceOrientation) {
        case UIDeviceOrientationFaceUp:
            NSLog(@"屏幕朝上平躺");
            break;
        case UIDeviceOrientationFaceDown:
            NSLog(@"屏幕朝下平躺");
            break;
        case UIDeviceOrientationUnknown:
            NSLog(@"未知方向");
            break;
        case UIDeviceOrientationLandscapeLeft:
            if (self.isFullScreen) {
                [self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
            }
            
            NSLog(@"屏幕向左横置");
            break;
        case UIDeviceOrientationLandscapeRight:
            if (self.isFullScreen) {
                [self interfaceOrientation:UIInterfaceOrientationLandscapeLeft];
            }
            
            NSLog(@"屏幕向右橫置");
            break;
        case UIDeviceOrientationPortrait:
            NSLog(@"屏幕直立");
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            NSLog(@"屏幕直立,上下顛倒");
            break;
        default:
            NSLog(@"无法辨识");
            break;
    }
}
//最后在dealloc中移除通知 和结束设备旋转的通知
- (void)dealloc{
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    [[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
}

- (BOOL)shouldAutorotate {
    return NO;
}

- (BOOL)prefersStatusBarHidden {
    if (@available(iOS 13.0, *)) {
        return self.isFullScreen;
    }
    return NO;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    
    return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskLandscapeRight;
}



#pragma mark - private method

- (void)fullScreenAction:(UIButton *)sender {
    
    if (self.isFullScreen) {//如果是全屏,点击按钮进入小屏状态
        [self changeToOriginalFrame];
    } else {//不是全屏,点击按钮进入全屏状态
        [self changeToFullScreen];
    }
    
}

- (void)changeToOriginalFrame {
    
    if (!self.isFullScreen) {
        return;
    }

    [UIView animateWithDuration:AnimationDuration animations:^{
        
        
        [self interfaceOrientation:UIInterfaceOrientationPortrait];
        self.playerView.frame = self.playerFrame;
        
    } completion:^(BOOL finished) {
        
        [self.playerView removeFromSuperview];
        
        [self.playerSuperView addSubview:self.playerView];
        self.isFullScreen = NO;
        //调用以下方法后,系统会在合适的时间调用prefersStatusBarHidden方法,控制状态栏的显示和隐藏,可根据自己的产品控制显示逻辑
        [self setNeedsStatusBarAppearanceUpdate];
    }];
    
}

- (void)changeToFullScreen {
    if (self.isFullScreen) {
        return;
    }
    
    //记录播放器视图的父视图和原始frame值,在实际项目中,可能会嵌套子视图,所以播放器的superView有可能不是self.view,所以需要记录父视图
    self.playerSuperView = self.playerView.superview;
    self.playerFrame = self.playerView.frame;
    
    CGRect rectInWindow = [self.playerView convertRect:self.playerView.bounds toView:self.mainWindow];
    [self.playerView removeFromSuperview];
    
    self.playerView.frame = rectInWindow;
    [self.mainWindow addSubview:self.playerView];
    
    //执行旋转动画
    [UIView animateWithDuration:AnimationDuration animations:^{
        
        UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
        if (orientation == UIDeviceOrientationLandscapeRight) {
            [self interfaceOrientation:UIInterfaceOrientationLandscapeLeft];
        } else {
            [self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
        }
        
        self.playerView.bounds = CGRectMake(0, 0, CGRectGetHeight(self.mainWindow.bounds), CGRectGetWidth(self.mainWindow.bounds));
        self.playerView.center = CGPointMake(CGRectGetMidX(self.mainWindow.bounds), CGRectGetMidY(self.mainWindow.bounds));
        
    } completion:^(BOOL finished) {
       
        self.isFullScreen = YES;
        //调用以下方法后,系统会在合适的时间调用prefersStatusBarHidden方法,控制状态栏的显示和隐藏,可根据自己的产品控制显示逻辑
        [self setNeedsStatusBarAppearanceUpdate];
    }];
}

- (void)interfaceOrientation:(UIInterfaceOrientation)orientation {
    if (orientation == UIInterfaceOrientationLandscapeRight || orientation == UIInterfaceOrientationLandscapeLeft) {
        // 设置横屏
        [self setOrientationLandscapeConstraint:orientation];
    } else if (orientation == UIInterfaceOrientationPortrait) {
        // 设置竖屏
        [self setOrientationPortraitConstraint];
    }
}

- (void)setOrientationLandscapeConstraint:(UIInterfaceOrientation)orientation {
    
    [self toOrientation:orientation];
}

- (void)setOrientationPortraitConstraint {
    
    [self toOrientation:UIInterfaceOrientationPortrait];
}

- (void)toOrientation:(UIInterfaceOrientation)orientation {
    // 获取到当前状态条的方向------iOS13已经废弃,所以不能根据状态栏的方向判断是否旋转,手动记录最后一次的旋转方向
//    UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
    // 判断如果当前方向和要旋转的方向一致,那么不做任何操作
    if (self.lastInterfaceOrientation == orientation) { return; }
    
    if (@available(iOS 13.0, *)) {
        //iOS 13已经将setStatusBarOrientation废弃,调用此方法无效
    } else {
        [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];
    }
    self.lastInterfaceOrientation = orientation;
    
    // 获取旋转状态条需要的时间:
    
    [UIView animateWithDuration:AnimationDuration animations:^{
        // 更改了状态条的方向,但是设备方向UIInterfaceOrientation还是正方向的,这就要设置给你播放视频的视图的方向设置旋转
        // 给你的播放视频的view视图设置旋转
        self.playerView.transform = CGAffineTransformIdentity;
        self.playerView.transform = [self getTransformRotationAngleWithOrientation:self.lastInterfaceOrientation];
        // 开始旋转
    } completion:^(BOOL finished) {
        
    }];
}

- (CGAffineTransform)getTransformRotationAngleWithOrientation:(UIInterfaceOrientation)orientation {

    // 根据要进行旋转的方向来计算旋转的角度
    if (orientation == UIInterfaceOrientationPortrait) {
        return CGAffineTransformIdentity;
    } else if (orientation == UIInterfaceOrientationLandscapeLeft){
        return CGAffineTransformMakeRotation(-M_PI_2);
    } else if(orientation == UIInterfaceOrientationLandscapeRight){
        return CGAffineTransformMakeRotation(M_PI_2);
    }
    return CGAffineTransformIdentity;
}

#pragma mark - setter

- (void)setIsFullScreen:(BOOL)isFullScreen {
    _isFullScreen = isFullScreen;
    [self.btnFullScreen setTitle:isFullScreen?@"退出全屏":@"全屏" forState:UIControlStateNormal];
}

#pragma mark - getter

- (UIView *)playerView {
    if (!_playerView) {
        _playerView = [[UIView alloc]init];
        _playerView.backgroundColor = [UIColor redColor];
        
        if (@available(iOS 11.0, *)) {
            _playerView.frame = CGRectMake(0, self.view.safeAreaInsets.top, CGRectGetWidth(self.view.bounds), CGRectGetWidth(self.view.bounds) * 9 / 16.f);
        } else {
            _playerView.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), CGRectGetWidth(self.view.bounds) * 9 / 16.f);
        }
        
    }
    return _playerView;
}

- (UIButton *)btnFullScreen {
    if (!_btnFullScreen) {
        _btnFullScreen = [UIButton buttonWithType:UIButtonTypeCustom];
        [_btnFullScreen setTitle:@"全屏" forState:UIControlStateNormal];
        _btnFullScreen.backgroundColor = [UIColor orangeColor];
        [_btnFullScreen addTarget:self action:@selector(fullScreenAction:) forControlEvents:UIControlEventTouchUpInside];
        _btnFullScreen.frame = CGRectMake(50, 80, 150, 50);
    }
    return _btnFullScreen;
}

- (UIWindow *)mainWindow {
    if (!_mainWindow) {
        if (@available(iOS 13.0, *)) {
            _mainWindow = [UIApplication sharedApplication].windows.firstObject;
        } else {
            _mainWindow = [UIApplication sharedApplication].keyWindow;
        }
    }
    return _mainWindow;
}

@end

结尾

如果你的rootViewController是UIViewController的话,那么用上面的代码实现全屏效果没有问题,如果你的rootViewController是UINavigationController或者UITabBarController的话,那么还要增加两个分类文件。文件内容如下:

一、定义分类UINavigationController+Rotation.h

@implementation UINavigationController (Rotation)

- (BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.topViewController supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.topViewController preferredInterfaceOrientationForPresentation];
}

二、定义分类UITabBarController+Rotation.h

@implementation UITabBarController (Rotation)

- (BOOL)shouldAutorotate {
    return [self.selectedViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.selectedViewController supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}

Demo地址:https://github.com/latacat/iOS_Demos.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 由于美股10月的暴跌加之中期选举,川普有意在贸易站上做些文章,正好亚太经过10月的血洗之后,正在酝酿着一波反弹,所...
    夜如此沉沦阅读 424评论 0 0
  • 这其中的奥秘全都在本文里了。其实说来也很简单,很容易理解,只是您从来未真正从科学角度了解而已。您要知道,孩子的学习...
    铎耀阅读 247评论 0 0
  • C提供的预处理功能主要有以下三种: 1.宏定义:#define 标识符 字符串 例如:# define PI ...
    TG帅阅读 165评论 0 0
  • 最近发生的事情,让我不得不再一次承认,自己是一个遇到问题会逃避的人。可能是表现型人格在作祟——只在乎当下的表现与喜...
    郑在践行阅读 292评论 2 1