开发播放器框架之全屏处理

1,745 阅读4分钟

前言

  • 最近在写播放器壳子,里面涉及到全屏/半屏转换,这里分享一下当中遇见的问题和处理思路,感兴趣的老哥可以去下载玩玩,觉得好用有帮助还请点个小星星!!!

Demo地址:KJPlayerDemo


效果预览,

思路分享

目前我所知道的关于全屏就是以下几种处理方案:

  • 原生页面旋转,强制旋转设备旋转,播放器所在控制器旋转为横屏状态

  • 播放器承载的View旋转,使用UIView的transform属性旋转90度,其实这个并非真正的横屏,系统菜单栏和系统控件等还是保持原先的竖屏状态

baseView.transform = CGAffineTransformMakeRotation(M_PI_2);
  • 旋转View + 横屏Window,这种方式就解决第二种没有旋转系统控件的问题

第三种思路方案

1、存储frame,后面切回小屏时刻使用

static CGRect _originalFrame;
+ (CGRect)originalFrame{
    return _originalFrame;
}
+ (void)setOriginalFrame:(CGRect)originalFrame{
    _originalFrame = originalFrame;
}

2、旋转状态栏方向

id<KJPlayerRotateAppDelegate> delegate = (id<KJPlayerRotateAppDelegate>)[[UIApplication sharedApplication] delegate];
NSAssert([delegate conformsToProtocol:@protocol(KJPlayerRotateAppDelegate)], @"Please see the usage documentation!!!");
[delegate kj_transmitCurrentRotateOrientation:UIInterfaceOrientationMaskLandscape];

KJRotateViewController *vc = [[KJRotateViewController alloc] init];
vc.interfaceOrientationMask = UIInterfaceOrientationMaskLandscape;
UIWindow *videoWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
videoWindow.rootViewController = vc;

这里配置ViewController来支持旋转的方向,调用Window的rootViewcontroller的supportedInterfaceOrientationsshouldAutorotate方向来确定当前页面支持的方向

@implementation KJRotateViewController
- (BOOL)shouldAutorotate{
    return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return self.interfaceOrientationMask;
}
@end

3、旋转View

baseView.transform = CGAffineTransformMakeRotation(M_PI_2);
baseView.bounds = [UIScreen mainScreen].bounds;
baseView.center = baseView.superview.center;
[baseView setValue:@(YES) forKey:@"isFullScreen"];

这里有个细节处理,就是我们在旋转动画完成之后,需要将屏幕固定为竖屏方向

id<KJPlayerRotateAppDelegate> delegate = (id<KJPlayerRotateAppDelegate>)[[UIApplication sharedApplication] delegate];
[delegate kj_transmitCurrentRotateOrientation:UIInterfaceOrientationMaskPortrait];

到此全屏模式思路就差不多都出来了,具体需要注意的地方就是需要Appdelegate当中实现KJPlayerRotateAppDelegate

参考文档,iOS端一次视频全屏需求的实现

我已将之封装成工具

直接贴出实现代码

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
/* 必须在Appdelegate当中实现该协议 */
@protocol KJPlayerRotateAppDelegate <NSObject>
/* 传递当前旋转方向 */
- (void)kj_transmitCurrentRotateOrientation:(UIInterfaceOrientationMask)rotateOrientation;

@end
@class KJBasePlayerView;
@interface KJRotateManager : NSObject
/* 切换到全屏 */
+ (void)kj_rotateFullScreenBasePlayerView:(UIView*)baseView;
/* 切换到小屏 */
+ (void)kj_rotateSmallScreenBasePlayerView:(UIView*)baseView;
/* 切换到浮窗屏 */
+ (void)kj_rotateFloatingWindowBasePlayerView:(UIView*)baseView;

@end

NS_ASSUME_NONNULL_END
#import "KJRotateManager.h"
#define kRotate_KeyWindow \
({UIWindow *window;\
if (@available(iOS 13.0, *)) {\
window = [UIApplication sharedApplication].windows.firstObject;\
}else{\
window = [UIApplication sharedApplication].keyWindow;\
}\
window;})
//旋转中间控制器
@interface KJRotateViewController : UIViewController
@property (nonatomic, assign) UIInterfaceOrientationMask interfaceOrientationMask;
@end
@implementation KJRotateViewController
- (BOOL)shouldAutorotate{
    return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return self.interfaceOrientationMask;
}
@end

/* ************************* 黄金分割线 ***************************/

@interface KJRotateManager ()
@property(nonatomic,assign,class)CGRect originalFrame;
@end
@implementation KJRotateManager
/* 切换到全屏 */
+ (void)kj_rotateFullScreenBasePlayerView:(UIView*)baseView{
    self.originalFrame = baseView.frame;
    UIColor *temp = baseView.superview.backgroundColor;
    baseView.superview.backgroundColor = UIColor.blackColor;
    baseView.layer.zPosition = 1;
    id<KJPlayerRotateAppDelegate> delegate = (id<KJPlayerRotateAppDelegate>)[[UIApplication sharedApplication] delegate];
    NSAssert([delegate conformsToProtocol:@protocol(KJPlayerRotateAppDelegate)], @"Please see the usage documentation!!!");
    [delegate kj_transmitCurrentRotateOrientation:UIInterfaceOrientationMaskLandscape];

    KJRotateViewController *vc = [[KJRotateViewController alloc] init];
    vc.interfaceOrientationMask = UIInterfaceOrientationMaskLandscape;
    UIWindow *videoWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    videoWindow.rootViewController = vc;

    [UIView animateWithDuration:0.3f animations:^{
        baseView.transform = CGAffineTransformMakeRotation(M_PI_2);
        baseView.bounds = [UIScreen mainScreen].bounds;
        baseView.center = baseView.superview.center;
        [baseView setValue:@(YES) forKey:@"isFullScreen"];
    } completion:^(BOOL finished) {
        baseView.superview.backgroundColor = temp;
        id<KJPlayerRotateAppDelegate> delegate = (id<KJPlayerRotateAppDelegate>)[[UIApplication sharedApplication] delegate];
        [delegate kj_transmitCurrentRotateOrientation:UIInterfaceOrientationMaskPortrait];
    }];
}
/* 切换到小屏 */
+ (void)kj_rotateSmallScreenBasePlayerView:(UIView*)baseView{
    baseView.layer.zPosition = 0;
    id<KJPlayerRotateAppDelegate> delegate = (id<KJPlayerRotateAppDelegate>)[[UIApplication sharedApplication] delegate];
    NSAssert([delegate conformsToProtocol:@protocol(KJPlayerRotateAppDelegate)], @"Please see the usage documentation!!!");
    [delegate kj_transmitCurrentRotateOrientation:UIInterfaceOrientationMaskPortrait];
    
    KJRotateViewController *vc = [[KJRotateViewController alloc] init];
    vc.interfaceOrientationMask = UIInterfaceOrientationMaskPortrait;
    UIWindow *videoWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    videoWindow.rootViewController = vc;
    baseView.transform = CGAffineTransformIdentity;
    baseView.frame = self.originalFrame;
    [baseView setValue:@(NO) forKey:@"isFullScreen"];
}
/* 切换到浮窗屏 */
+ (void)kj_rotateFloatingWindowBasePlayerView:(UIView*)baseView{
    // TODO:
}

#pragma mark - getter/setter
static CGRect _originalFrame;
+ (CGRect)originalFrame{
    return _originalFrame;
}
+ (void)setOriginalFrame:(CGRect)originalFrame{
    _originalFrame = originalFrame;
}
+ (UIViewController*)topViewController{
    UIViewController *result = nil;
    UIWindow * window = kRotate_KeyWindow;
    if (window.windowLevel != UIWindowLevelNormal){
        NSArray *windows = [[UIApplication sharedApplication] windows];
        for(UIWindow * tmpWin in windows){
            if (tmpWin.windowLevel == UIWindowLevelNormal){
                window = tmpWin;
                break;
            }
        }
    }
    UIViewController *vc = window.rootViewController;
    while (vc.presentedViewController) {
        vc = vc.presentedViewController;
    }
    if ([vc isKindOfClass:[UITabBarController class]]){
        UITabBarController * tabbar = (UITabBarController *)vc;
        UINavigationController * nav = (UINavigationController *)tabbar.viewControllers[tabbar.selectedIndex];
        result = nav.childViewControllers.lastObject;
    }else if ([vc isKindOfClass:[UINavigationController class]]){
        UIViewController * nav = (UIViewController *)vc;
        result = nav.childViewControllers.lastObject;
    }else{
        result = vc;
    }
    return result;
}

@end

到此,关于全屏模式思路介绍的差不多了,至于详细信息,我Dmeo里面写的也很详细,感兴趣的朋友可以去下载 Demo地址:KJPlayerDemo

使用注意事项

关于全屏/半屏配置信息

请在Appdelegate当中实现该协议KJPlayerRotateAppDelegate

@interface AppDelegate ()<KJPlayerRotateAppDelegate>
@property(nonatomic,assign) UIInterfaceOrientationMask rotateOrientation;
@end

@implementation AppDelegate
/* 传递当前旋转方向 */
- (void)kj_transmitCurrentRotateOrientation:(UIInterfaceOrientationMask)rotateOrientation{
    self.rotateOrientation = rotateOrientation;
}
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
    if (self.rotateOrientation) {
        return self.rotateOrientation;
    }else{
        return UIInterfaceOrientationMaskPortrait;
    }
}
@end

八阿哥总结

果然代码都是经不起测试调试的,一如既往的又出现bug

1、视图层次显示问题

控件KJBasePlayerView是优先于后面的按钮Add在self.view上,所以当我按下全屏按钮之时就出现下面的问题 😓😓- -!

解决方案:修改KJBasePlayerViewzPosition属性,正常设置的控件对应的layer.zPosition默认都是0,所以只需要将控件的layer.zPosition设置为1就解决

- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.layer.zPosition = 1;
        [self kj_initializeConfiguration];
    }
    return self;
}

2、顶部导航栏问题

现在还有一个问题就是上面的导航栏,这个我目前能想到的笨办法就是,我不是有把当前全屏/半屏状态通过回调返出来嘛,然后在回调当中去控制隐藏/显示

PLAYER_WEAKSELF;
self.basePlayerView.kVideoChangeScreenState = ^(KJPlayerVideoScreenState state) {
    if (state == KJPlayerVideoScreenStateFullScreen) {
        [weakself.navigationController setNavigationBarHidden:YES animated:YES];
    }else{
        [weakself.navigationController setNavigationBarHidden:NO animated:YES];
    }
};

不知哪位大神或者有更好更方便的解决方式,欢迎指点一下,谢谢~😁

文章关联

关于播放器其他相关文章

后续该播放器壳子我会慢慢补充完善,老哥觉得好用还请帮我点个**小星星**传送门