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

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

前言


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


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

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


文章关联


关于播放器其他相关文章

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

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


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

相关文章
|
7月前
|
移动开发 Android开发 HTML5
uniapp视频播放功能
uniapp视频播放功能
752 0
|
27天前
|
开发工具 UED 容器
Flutter&鸿蒙next 实现长按录音按钮及动画特效
本文介绍了如何在 Flutter 中实现一个带有动画效果的长按录音按钮。通过使用 `GestureDetector` 监听长按手势,结合 `AnimatedContainer` 和 `AnimationController` 实现按钮的动画效果,以及 `flutter_sound` 插件完成录音功能。文章详细讲解了功能需求、实现思路和代码实现,帮助读者逐步掌握这一实用功能的开发方法。
101 5
|
3月前
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
127 3
|
7月前
|
Android开发
播放器相关功能
播放器相关功能
64 1
|
7月前
|
XML Java Android开发
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
Android App开发音量调节中实现拖动条和滑动条和音频管理器AudioManager讲解及实战(超详细 附源码和演示视频)
266 0
|
API
uniapp播放音频
uniapp播放音频
158 0
|
移动开发 前端开发 JavaScript
移动端 H5图片裁剪插件,内置简单手势操作
移动端 H5图片裁剪插件,内置简单手势操作
移动端 H5图片裁剪插件,内置简单手势操作
|
缓存 开发工具 Android开发
产品百科 |使用阿里云播放器实现全屏秒播
阿里云播放器快速实现全屏秒播主要通过三个手段实现该需求:首帧图和播放的首帧画面一致 + 播放器预加载 + 边播边缓存。本方案在 WiFi 环境下可以做到平均 300 毫秒左右的起播速度。
产品百科 |使用阿里云播放器实现全屏秒播
|
XML 缓存 编解码
01.视频播放器框架介绍
01.视频播放器框架介绍
2804 0
01.视频播放器框架介绍
阿里云移动端播放器高级功能---UI播放器
用户想要实现一个视频播放除了要集成我们播放器SDK之外,还需要做一些UI交互的东西。要实现一个完整的视频播放还是需要做很多工作的。那么我们提供了一套完整的UI播放器,用户可以非常快速的集成进去来实现完整的播放器功能。
5763 0