8 三分屏

8.1 概述

三分屏是一种支持 ppt 或 pdf 文档与视频同步播放的双窗口播放模式。使用一大一小两个播放窗口,布局灵活,能自由切换主屏播放内容,支持为 ppt 或 pdf 文档的每一页设定播放时间点来实现自动翻页功能,支持点击课件目录上的课件进行播放进度跳转,支持下载到本机离线播放。为客户更丰富的授课场景提供了可能。

8.2 快速集成

8.2.1 SDK 版本

如果想使用点播的三分屏功能,请将 PolyvVodSDK 升级到 2.6.5 以上,Podfile 设置如下:

pod 'PolyvVodSDK', '~> 2.6.5'

8.2.2 开源代码

其次,大部分代码我们在 demo 项目的文件夹 PolyvOpenSourceModule/PPT 中提供,请把 demo 的代码更新到版本 2.6.5 以上,然后把这个文件夹下的代码拖到你的项目中。该文件夹下的文件目录如下:

└── PPT ├── Controller │ ├── PLVPPTBaseViewController.h │ ├── PLVPPTBaseViewController.m │ ├── PLVPPTBaseViewControllerInternal.h │ ├── PLVPPTVideoViewController.h │ ├── PLVPPTVideoViewController.m │ ├── PLVPPTViewController.h │ └── PLVPPTViewController.m └── View ├── PLVFloatingView.h ├── PLVFloatingView.m ├── PLVPPTActionView.h ├── PLVPPTActionView.m ├── PLVPPTActionViewCell.h ├── PLVPPTActionViewCell.m ├── PLVPPTControllerSkinView.h ├── PLVPPTControllerSkinView.m ├── PLVPPTFailView.h ├── PLVPPTFailView.m ├── PLVPPTLoadFailAlertView.h ├── PLVPPTLoadFailAlertView.m ├── PLVPPTSkinProgressView.h └── PLVPPTSkinProgressView.m

8.2.3 demo 示例

在 demo 项目中 Classes 路径下,我们提供了三分屏播放页 PLVPPTSimpleDetailController,可使用该页面进行三分屏播放。使用代码如下:

PLVPPTSimpleDetailController *vctrl = [[PLVPPTSimpleDetailController alloc] init];
vctrl.vid = @"准备播放的视频 vid";
vctrl.isOffline = NO;
[self.navigationController pushViewController:vctrl animated:YES];

属性 isOffline 默认为 NO,表示调用接口获取视频资源,没有网络时即使本地有缓存也无法播放,如果设置为 YES,则从本地获取视频资源,没有网络时只要本地有缓存就可以播放。

8.2.4 项目配置

由于 ppt 文档的图片链接使用 http 协议,需要在项目的 Info.plist 文件中的 App Transport Security Settings / Exception Domains 中增加 Key-Value 如下:

<key>doc.polyv.net</key>
  <dict>
    <key>NSExceptionAllowsInsecureHTTPLoads</key>
    <true/>
  </dict>

效果如下图所示:

8.3 视频播放器

8.3.1 三分屏开关

点播 SDK 中的视频播放器 PLVVodPlayerViewController 增加布尔属性 enablePPT,表示是否启动 ppt 功能,默认为 NO,此时无论该视频有无 ppt 均不显示。可在 PLVPPTVideoViewController.m 的方法 -player 进行设置,demo 中的代码如下:

- (PLVVodSkinPlayerController *)player {
    if (!_player){
        _player = [[PLVVodSkinPlayerController alloc] init];
        _player.enablePPT = YES;
        _player.enableBackgroundPlayback = YES;
        _player.autoplay = YES;
        _player.enableAd = YES;
    }
    return _player;
}

除了设置 enablePPT,还可以在此处进行播放器的其他属性设置,譬如是否允许后台播放enableBackgroundPlayback、是否自动播放 autoplay、是否开启广告 enableAd 等,更多参数详见点播文档 4 视频播放 - 4.5 播放器配置

8.3.2 播放器皮肤

demo 中开源组件 PolyvOpenSourceModule 中提供的播放器皮肤 PLVVodPlayerSkin 新增【开/关三分屏小窗】按钮、全屏时新增【课件】按钮。点击【开/关三分屏小窗】可以打开或关闭小窗,点击【课件】按钮可在全屏时弹出课件目录列表。开源组件中播放器皮肤新增按钮代码如下:

/// 竖屏播放器皮肤
@interface PLVVodShrinkscreenView : UIView

@property (weak, nonatomic) IBOutlet UIButton *subScreenButton; // 关闭三分屏按钮

@end
/// 全屏播放器皮肤
@interface PLVVodFullscreenView : UIView

@property (weak, nonatomic) IBOutlet UIButton *subScreenButton; // 关闭三分屏按钮
@property (weak, nonatomic) IBOutlet UIButton *pptCatalogButton; // 显示课件目录按钮

@end

按钮的显示与隐藏在 PLVVodPlayerSkin.m 中进行控制。只有当 enablePPT 为YES,且播放的音/视频包含 ppt 或 pdf 文档,这两个按钮才会显示。

8.3.3 播放进度回调

PLVVodSkinPlayerController 新增播放器回调,代码如下:

@interface PLVVodSkinPlayerController : PLVVodPlayerViewController

// 播放进度回调
@property (nonatomic, copy) void (^playbackTimeHandler)(NSTimeInterval currentPlaybackTime);

@end

三分屏功能将通过这个回调,实现文档与视频播放的同步。PLVPPTBaseViewController 中的代码示例如下:

__weak typeof(self) weakSelf = self;
self.videoController.player.playbackTimeHandler = ^(NSTimeInterval currentPlaybackTime) {
      [weakSelf.pptController playAtCurrentSecond:(int)currentPlaybackTime];
};

其中,self.videoController.player 为 PLVVodSkinPlayerController 的实例,weakSelf.pptController 为下文即将提到的 ppt 播放器。

8.3.4 播放器容器

PLVPPTVideoViewController 是播放器的视图容器,用于配置视频播放器、控制播放器皮肤、视频播放业务逻辑代码等,PLVPPTBaseViewController 中的代码示例如下:

#import "PLVPPTBaseViewController.h"
#import "PLVPPTVideoViewController.h"
#import <PLVVodSDK/PLVVodSDK.h>

@interface PLVPPTBaseViewController ()<
PLVPPTVideoViewControllerProtocol
>

@property (nonatomic, strong) UIView *mainView; // 大屏视图
@property (nonatomic, strong) PLVPPTVideoViewController *videoController;

@end

@implementation PLVPPTBaseViewController

#pragma mark - Life Cycle
  
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.mainView];
    [self.mainView addSubview:self.videoController.view]; // 添加播放器视图到大屏视图
}

- (void)dealloc{
    _videoController.delegate = nil;
}

#pragma mark - Getter & Setter

- (PLVPPTVideoViewController *)videoController {
    if (!_videoController){
        _videoController = [[PLVPPTVideoViewController alloc] init];
        _videoController.delegate = self;
    }
    return _videoController;
}

#pragma mark - PLVPPTVideoViewControllerProtocol

- (void)videoWithVid:(NSString *)vid title:(NSString *)title hasPPT:(BOOL)hasPPT localPlay:(BOOL)localPlay {
// 视频播放回调,获取到视频资源后调用
// vid:当前播放视频的 vid
// title:当前播放视频的标题
// hasPPT:当前播放视频是否包含文档
// localPlay:播放资源是否为本地缓存,YES 为是,NO 为否
}

- (PLVVodPlaybackMode)currenPlaybackMode {
// 当前播放音视频模式:
// PLVVodPlaybackModeDefault
// PLVVodPlaybackModeVideo
// PLVVodPlaybackModeAudio
}

@end

“8.3.2 播放器皮肤” 一节中提到的新增按钮的响应事件,也可以在 PLVPPTBaseViewController.m 中进行设置:

- (PLVPPTVideoViewController *)videoController {
    if (!_videoController){
        _videoController = [[PLVPPTVideoViewController alloc] init];
        _videoController.delegate = self;
        
        __weak typeof(self) weakSelf = self;
        _videoController.closeSubscreenButtonActionHandler = ^{
            // 打开/关闭小屏
        };
        _videoController.pptCatalogButtonActionHandler = ^{
            // 打开课件列表
        };
    }
    return _videoController;
}

8.4 PPT 播放器

demo 中的开源组件 PolyvOpenSourceModule 提供了 PPT 播放器 PLVPPTViewController 用于播放 ppt 或 pdf 文档,并支持后台自定义文档翻页的时间点,实现文档与视频同步播放。

8.4.1 PPT 加载

PPT 播放器简单使用示例如下:

self.pptController = [[PLVPPTViewController alloc] init];
self.pptController.ppt = ppt;

其中属性 pptPLVVodPPT 模型,表示一个文档数据模型。使用 PLVVodPPT 的以下方法获取 ppt 文档在线数据(离线数据的获取见 8.7):

// 获取在线数据
+ (void)requestPPTWithVid:(NSString *)vid completion:(void (^)(PLVVodPPT * _Nullable ppt, NSError * _Nullable error))completion;

PLVPPTBaseViewController.m 的代码示例如下:

- (void)getPPTJson {
    [PLVVodPPT requestPPTWithVid:self.vid completion:^(PLVVodPPT * _Nullable ppt, NSError * _Nullable error) {
        if (error == nil && ppt) {
            // 获取 ppt 数据成功
        } else {
            // 获取 ppt 数据失败
        }
    }];
}

8.4.2 PPT 翻页

PPT 播放器提供了以下两个方法,实现文档的翻页:

@interface PLVPPTViewController : UIViewController

 /**
 将文档切换到特定播放时间点的特定页
 @param second 当前视频播放时间点,单位:秒
 */
- (void)playAtCurrentSecond:(NSInteger)second;

/**
 将文档切换到特定页
 @param index 文档的第 index 页
 */
- (void)playPPTAtIndex:(NSInteger)index;

@end

PLVPPTBaseViewController 中,方法 -playAtCurrentSecond: 在视频播放进度回调中调用,实现与视频播放的同步。方法 -playPPTAtIndex: 用于手动选择播放某一页文档时调用。

8.4.3 PPT 播放器皮肤

PPT 播放器 PLVPPTViewController 提供了简单的皮肤,一个是加载失败时的文本显示“暂无课件”,一个是加载中的 loading 控件,还有一个是下载课件时的下载进度控件。这几个皮肤可通过以下四个方法进行控制:

@interface PLVPPTViewController (PLVPPTSkin)

/**
 开始加载 ppt
 */
- (void)startLoading;

 /**
 加载 ppt 失败
 */
- (void)loadPPTFail;

/**
 开始下载 ppt
 */
- (void)startDownloading;

/**
 下载 ppt 进度变化
 @param progress ppt 下载进度
 */
- (void)setDownloadProgress:(CGFloat)progress;

@end

加载/下载 ppt 成功时,即 ppt 属性被赋值(非 nil)时,这些控件会自动隐藏,无需调用任何接口。

8.5 三分屏小窗

demo 中的开源组件 PolyvOpenSourceModule 提供了自定义 UIView 的子类 PLVFloatingView 作为三分屏播放时的(悬浮)小窗。

小窗大小固定,由类方法 +viewSize 定义:

+ (CGSize)viewSize {
    return CGSizeMake(125, 70);
}

提供协议 PLVFloatingViewProtocol 和代理方法 -tapAtFloatingView: 用于响应窗口点击事件:

@protocol PLVFloatingViewProtocol <NSObject>

- (void)tapAtFloatingView:(PLVFloatingView *)floatingView;

@end

三分屏小窗 PLVFloatingView 支持通过手势拖动改变窗口位置,如果想固定位置,只需将 PLVFloatingView.m 文件中的以下几行代码注释掉即可:

@implementation PLVFloatingView
  
  - (instancetype)init {
    self = [super init];
    if (self) {
      
      ……
        
      // 如果想固定窗口,注释掉下面这两行代码  
      UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)];
      [self addGestureRecognizer:panGestureRecognizer];
    }
    return self;
}

@end

8.6 三分屏播放页

demo 中的开源组件 PolyvOpenSourceModule 提供了 PLVPPTBaseViewController 作为三分屏播放页。

PLVPPTBaseViewController 提供对外属性 vidisOfflineplaybackMode

@interface PLVPPTBaseViewController : UIViewController

/**  
 播放视频的 vid
 */
@property (nonatomic, copy) NSString *vid;

/**
  是否离线播放,默认为 NO
 YES: 从本地获取视频资源,没有网络时只要本地有缓存就可以播放
 NO: 调用接口获取视频资源,没有网络时即使本地有缓存也无法播放
 */
@property (nonatomic, assign) BOOL isOffline;

/**
  播放模式:默认、视频、音频三种
 在线播放时会自动设置播放模式
 离线时需根据本地资源类型手动设置播放模式
 */
@property (nonatomic, assign) PLVVodPlaybackMode playbackMode;

@end

demo 中的 PLVPPTSimpleDetailControllerPLVPPTBaseViewController 的子类,三分屏等业务逻辑代码都封装在父类中,使用 PLVPPTSimpleDetailController 进行视频播放的代码示例如下:

PLVPPTSimpleDetailController *vctrl = [[PLVPPTSimpleDetailController alloc] init];
vctrl.vid = vid;
[self.navigationController pushViewController:vctrl animated:YES];

PLVPPTBaseViewController 还提供了以下几个空方法,供子类覆写:

// 获取课件异常时,会执行这个方法,子类需要时可覆写
- (void)getPPTFail;

// ppt 的值更新时,获得 ppt 模型,或者置 nil 会执行这个方法,子类需要时可覆写
- (void)getPPTSuccess;

// 横竖屏切换时会执行这个方法,子类需要时可覆写
- (void)interfaceOrientationDidChange;

文件 PLVPPTBaseViewControllerInternal.h 定义了子类可见的其他属性跟方法。

8.7 下载器

SDK 2.6.5 之后的下载器 PLVVodDownloadManager 支持三分屏模式的视频、文档打包下载。调用方法不变:

PLVVodVideo *video;
[[PLVVodDownloadManager sharedManager] downloadVideo:video];

视频模型 PLVVodVideo 新增属性 hasPPTppt_linkPLVVodDownloadManager 会在下载视频时,根据该属性判断是否需要下载 ppt 文档,从属性 ppt_link 获取文档的下载链接。

下载器 PLVVodDownloadManager 新增 ppt 下载接口 -downloadPPTWithVideo:completion:

/**
 下载PPT 文件
 @param video PLVVodVideo 视频对象
 */
- (void)downloadPPTWithVideo:(PLVVodVideo *)video completion:(void(^)(PLVVodDownloadInfo *info))completion;

可通过这个接口单独下载某个视频对应的 ppt。

下载成功后,通过类 PLVVodPPT 提供的方法 -requestCachePPTWithVid:completion: 获取离线 ppt 文档数据:

+ (void)requestCachePPTWithVid:(NSString *)vid completion:(void (^)(PLVVodPPT * _Nullable ppt, NSError * _Nullable error))completion;

PLVPPTBaseViewController.m 还提供了 ppt 的下载进度回调、下载状态变化回调示例,示例代码如下:

@implementation PLVPPTBaseViewController (PPT)
  
- (void)downloadPPT {
    PLVVodVideo *video = self.video;
    [[PLVVodDownloadManager sharedManager] downloadPPTWithVideo:video completion:^(PLVVodDownloadInfo *info) {
        [self handlePPTDownload:info];
    }];
}

- (void)handlePPTDownload:(PLVVodDownloadInfo *)info {
    __weak typeof(self) weakSelf = self;
    PLVVodDownloadInfo *downloadInfo = info;
    downloadInfo.progressDidChangeBlock = ^(PLVVodDownloadInfo *info) {
        NSLog(@"下载进度:%f", info.progress);
    };
    
    downloadInfo.stateDidChangeBlock = ^(PLVVodDownloadInfo *info) {
        if (info.state == PLVVodDownloadStateSuccess) {
          NSLog(@"下载成功");
        } else if (info.state == PLVVodDownloadStateFailed) {
          NSLog(@"下载失败");
        }
    };
}

@end

Last updated