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

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

前言


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


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];
    }
};

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


文章关联


关于播放器其他相关文章

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

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


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

相关文章
|
6月前
|
移动开发 Android开发 HTML5
uniapp视频播放功能
uniapp视频播放功能
709 0
|
14小时前
|
前端开发 API
鸿蒙next版开发:相机开发-预览(ArkTS)
在HarmonyOS 5.0中,使用ArkTS进行相机预览是核心功能之一。本文详细介绍了如何使用ArkTS实现相机预览,包括导入相机接口、创建Surface、获取相机输出能力、创建会话并开始预览,以及监听预览输出状态等步骤,并提供了代码示例。通过本文,读者可以掌握在HarmonyOS 5.0中使用ArkTS进行相机预览的基本方法。
15 6
|
7天前
|
开发工具 UED 容器
Flutter&鸿蒙next 实现长按录音按钮及动画特效
本文介绍了如何在 Flutter 中实现一个带有动画效果的长按录音按钮。通过使用 `GestureDetector` 监听长按手势,结合 `AnimatedContainer` 和 `AnimationController` 实现按钮的动画效果,以及 `flutter_sound` 插件完成录音功能。文章详细讲解了功能需求、实现思路和代码实现,帮助读者逐步掌握这一实用功能的开发方法。
82 5
|
9天前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
61 1
|
6月前
|
XML Java Android开发
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
251 0
|
6月前
uniapp制作录音播放功能
uniapp制作录音播放功能
214 0
|
6月前
|
存储
uniapp录音功能和音频播放功能制作
uniapp录音功能和音频播放功能制作
392 0
|
iOS开发
IOS滚动不流畅问题
IOS滚动不流畅问题
|
搜索推荐
Flutter 使用自定义转场动效提升页面跳转体验
本篇带你如何完成个性化的页面切换。Flutter本身提供了不少预定义的转场动画,可以设计多种多样的转场动画实现个性化的转场效果。
777 0
Flutter 使用自定义转场动效提升页面跳转体验
|
移动开发 前端开发 JavaScript
移动端 H5图片裁剪插件,内置简单手势操作
移动端 H5图片裁剪插件,内置简单手势操作
移动端 H5图片裁剪插件,内置简单手势操作