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

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

前言


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


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上,所以当我按下全屏按钮之时就出现下面的问题 😓😓- -!

1.png

解决方案:修改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、顶部导航栏问题


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

1.png

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

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


文章关联


关于播放器其他相关文章

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

开发播放器框架之边下边播边存方案分享


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

相关文章
|
数据库 容器
Flutter笔记:滚动之-无限滚动与动态加载的实现
Flutter笔记:滚动之-无限滚动与动态加载的实现
819 0
|
存储 设计模式 Dart
Flutter笔记:getX库中的GetView中间件
Flutter笔记:getX库中的GetView中间件
785 0
Flutter ListView懒加载(滑动不加载,停止滑动加载)
前言:为了更好的减小网络的带宽,使得列表更加流畅,我们需要了解懒加载,也称延迟加载。关于上一章的登录界面,各位属实难为我了,我也在求ui小姐姐,各位点点赞给我点动力吧~
|
11月前
|
存储 安全 Java
Git历史数据清理:运用BFG删除敏感信息。
通过使用BFG Repo-Cleaner,你可以确保你的Git历史记录自由于敏感数据,而不需要为手动清理每一个提交而烦恼。不仅如此,BFG的效率极高,对于大型的存储库和长期的提交历史,其表现出色,即使在处理数万个提交和GB级别的数据时,也能快速和有效的清理Git历史。
296 34
|
存储 设计模式 Dart
Flutter笔记:GetX模块中不使用 Get.put 怎么办
依赖注入(Dependency Injection,对于很多真的就是简单的局部共享状态的场景,自己实现单例我个人感觉反而更好。首先,你不需要集中于创建代码初期就从各个模块中导入你的各个控制器,也不需要预先在应用初始化时就创建它们的实例,从而将实例添加到GetX依赖中进行管理。这使得mian文件中的代码更加简洁。如果某个局部状态控制器被移除,你也不需要回到mian文件中来对代码进行改动,只需要删除不用的部分。其次,在Dart语言中,为面向对象的单例实现提供了很方便的支持,仅仅三个小步骤就可以实现严格管理单例。
363 1
|
前端开发 UED 开发者
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
【4月更文挑战第30天】Flutter开发中,优化列表和滚动视图至关重要。本文介绍了几种优化方法:1) 使用`ListView.builder`和`GridView.builder`实现懒加载;2) 复用子组件以减少实例创建;3) 利用`CustomScrollView`和`Slivers`提升滚动性能;4) 通过`NotificationListener`监听滚动事件;5) 使用`KeepAlive`保持列表项状态。掌握这些技巧能提升应用性能和用户体验。
395 1
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
|
算法 大数据 量子技术
探索未来:量子计算在解决复杂问题中的潜力与挑战
本文深入探讨了量子计算技术如何为解决传统计算机无法有效处理的复杂问题开辟新路径。通过分析量子计算的原理、当前发展状况以及面临的主要技术挑战,文章揭示了量子计算在未来可能带来的革命性变化,并讨论了其对科学研究、密码学和大数据处理等领域的潜在影响。
|
Shell Linux C语言
CentOS完美升级gcc方案
CentOS完美升级gcc方案
3430 0
|
传感器 Java Android开发
Android 中屏幕进行横屏显示和竖屏显示的方法
Android 中屏幕进行横屏显示和竖屏显示的方法
1384 0
重识Flutter 用于解决复杂滑动视窗问题的Slivers - part1
在日常的开发工作中,仅仅使用ListView、ListView.builder等这样的滑动组件就能满足大部分的业务需求,但在碰到较为复杂的滑动页面时,我认为Slivers可以帮你更简单的实现。
重识Flutter  用于解决复杂滑动视窗问题的Slivers - part1