|
|
#import "SuperPlayerView.h"
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
#import "SuperPlayer.h"
|
|
|
#import "SuperPlayerControlViewDelegate.h"
|
|
|
#import "J2Obj.h"
|
|
|
#import "SuperPlayerView+Private.h"
|
|
|
#import "DataReport.h"
|
|
|
#import "TXCUrl.h"
|
|
|
#import "StrUtils.h"
|
|
|
#import "UIView+Fade.h"
|
|
|
#import "TXBitrateItemHelper.h"
|
|
|
#import "UIView+MMLayout.h"
|
|
|
#import "SPDefaultControlView.h"
|
|
|
#import "SuperPlayerModelInternal.h"
|
|
|
#import "NSString+URL.h"
|
|
|
// TODO: 处理头部引用
|
|
|
#import "TXAudioCustomProcessDelegate.h"
|
|
|
#import "TXAudioRawDataDelegate.h"
|
|
|
#import "TXBitrateItem.h"
|
|
|
#import "TXImageSprite.h"
|
|
|
#import "TXLiteAVCode.h"
|
|
|
#import "TXLiveAudioSessionDelegate.h"
|
|
|
#import "TXLiveBase.h"
|
|
|
#import "TXLivePlayConfig.h"
|
|
|
#import "TXLivePlayListener.h"
|
|
|
#import "TXLivePlayer.h"
|
|
|
#import "TXLiveRecordListener.h"
|
|
|
#import "TXLiveRecordTypeDef.h"
|
|
|
#import "TXLiveSDKEventDef.h"
|
|
|
#import "TXLiveSDKTypeDef.h"
|
|
|
#import "TXPlayerAuthParams.h"
|
|
|
#ifdef ENABLE_UGC
|
|
|
#import "TXUGCBase.h"
|
|
|
#import "TXUGCPartsManager.h"
|
|
|
#import "TXUGCRecord.h"
|
|
|
#import "TXUGCRecordListener.h"
|
|
|
#import "TXUGCRecordTypeDef.h"
|
|
|
#endif
|
|
|
#import "TXVideoCustomProcessDelegate.h"
|
|
|
#import "TXVodPlayConfig.h"
|
|
|
#import "TXVodPlayListener.h"
|
|
|
#import "TXVodPlayer.h"
|
|
|
|
|
|
static UISlider * _volumeSlider;
|
|
|
|
|
|
#define CellPlayerFatherViewTag 200
|
|
|
#define SUPPORT_PARAM_MAJOR_VERSION (8)
|
|
|
#define SUPPORT_PARAM_MINOR_VERSION (2)
|
|
|
|
|
|
//忽略编译器的警告
|
|
|
#pragma clang diagnostic push
|
|
|
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation SuperPlayerView {
|
|
|
UIView *_fullScreenBlackView;
|
|
|
SuperPlayerControlView *_controlView;
|
|
|
NSURLSessionTask *_currentLoadingTask;
|
|
|
}
|
|
|
|
|
|
|
|
|
#pragma mark - life Cycle
|
|
|
|
|
|
/**
|
|
|
* 代码初始化调用此方法
|
|
|
*/
|
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
|
self = [super initWithFrame:frame];
|
|
|
if (self) { [self initializeThePlayer]; }
|
|
|
return self;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* storyboard、xib加载playerView会调用此方法
|
|
|
*/
|
|
|
- (void)awakeFromNib {
|
|
|
[super awakeFromNib];
|
|
|
[self initializeThePlayer];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 初始化player
|
|
|
*/
|
|
|
- (void)initializeThePlayer {
|
|
|
LOG_ME;
|
|
|
self.netWatcher = [[NetWatcher alloc] init];
|
|
|
|
|
|
CGRect frame = CGRectMake(0, -100, 10, 0);
|
|
|
self.volumeView = [[MPVolumeView alloc] initWithFrame:frame];
|
|
|
[self.volumeView sizeToFit];
|
|
|
for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
|
|
|
if (!window.isHidden) {
|
|
|
[window addSubview:self.volumeView];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_fullScreenBlackView = [UIView new];
|
|
|
_fullScreenBlackView.backgroundColor = [UIColor blackColor];
|
|
|
|
|
|
// 单例slider
|
|
|
_volumeSlider = nil;
|
|
|
for (UIView *view in [self.volumeView subviews]){
|
|
|
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
|
|
|
_volumeSlider = (UISlider *)view;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_playerConfig = [[SuperPlayerViewConfig alloc] init];
|
|
|
// 添加通知
|
|
|
[self addNotifications];
|
|
|
// 添加手势
|
|
|
[self createGesture];
|
|
|
|
|
|
self.autoPlay = YES;
|
|
|
}
|
|
|
|
|
|
- (void)dealloc {
|
|
|
LOG_ME;
|
|
|
// 移除通知
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
|
|
|
|
|
[self reportPlay];
|
|
|
[self.netWatcher stopWatch];
|
|
|
[self.volumeView removeFromSuperview];
|
|
|
}
|
|
|
|
|
|
#pragma mark - 观察者、通知
|
|
|
|
|
|
/**
|
|
|
* 添加观察者、通知
|
|
|
*/
|
|
|
- (void)addNotifications {
|
|
|
// app退到后台
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
|
|
|
// app进入前台
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
|
|
|
|
// 监测设备方向
|
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
selector:@selector(onDeviceOrientationChange)
|
|
|
name:UIDeviceOrientationDidChangeNotification
|
|
|
object:nil];
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
selector:@selector(onStatusBarOrientationChange)
|
|
|
name:UIApplicationDidChangeStatusBarOrientationNotification
|
|
|
object:nil];
|
|
|
}
|
|
|
|
|
|
#pragma mark - layoutSubviews
|
|
|
|
|
|
- (void)layoutSubviews {
|
|
|
[super layoutSubviews];
|
|
|
if (self.subviews.count > 0) {
|
|
|
UIView *innerView = self.subviews[0];
|
|
|
if ([innerView isKindOfClass:NSClassFromString(@"TXIJKSDLGLView")] ||
|
|
|
[innerView isKindOfClass:NSClassFromString(@"TXCAVPlayerView")]) {
|
|
|
innerView.frame = self.bounds;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#pragma mark - Public Method
|
|
|
|
|
|
- (void)playWithModel:(SuperPlayerModel *)playerModel {
|
|
|
LOG_ME;
|
|
|
self.isShiftPlayback = NO;
|
|
|
self.imageSprite = nil;
|
|
|
self.originalDuration = 0;
|
|
|
[self reportPlay];
|
|
|
self.reportTime = [NSDate date];
|
|
|
[self _removeOldPlayer];
|
|
|
[self _playWithModel:playerModel];
|
|
|
self.coverImageView.alpha = 1;
|
|
|
self.maskView.hidden = YES;
|
|
|
self.repeatBtn.hidden = YES;
|
|
|
self.repeatBackBtn.hidden = YES;
|
|
|
// 播放时添加监听
|
|
|
[self addNotifications];
|
|
|
}
|
|
|
|
|
|
- (void)reloadModel {
|
|
|
SuperPlayerModel *model = _playerModel;
|
|
|
if (model) {
|
|
|
[self resetPlayer];
|
|
|
[self _playWithModel:_playerModel];
|
|
|
[self addNotifications];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)_playWithModel:(SuperPlayerModel *)playerModel {
|
|
|
[_currentLoadingTask cancel];
|
|
|
_currentLoadingTask = nil;
|
|
|
|
|
|
_playerModel = playerModel;
|
|
|
|
|
|
[self pause];
|
|
|
|
|
|
NSString *videoURL = playerModel.playingDefinitionUrl;
|
|
|
if (videoURL != nil) {
|
|
|
[self configTXPlayer];
|
|
|
} else if (playerModel.videoId || playerModel.videoIdV2) {
|
|
|
self.isLive = NO;
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
|
_currentLoadingTask = [_playerModel requestWithCompletion:
|
|
|
^(NSError *error,SuperPlayerModel *model) {
|
|
|
if (error) {
|
|
|
[weakSelf _onModelLoadFailed:model error:error];
|
|
|
} else {
|
|
|
weakSelf.imageSprite = model.imageSprite;
|
|
|
weakSelf.keyFrameDescList = model.keyFrameDescList;
|
|
|
weakSelf.originalDuration = model.originalDuration;
|
|
|
[weakSelf _onModelLoadSucceed:model];
|
|
|
}
|
|
|
}];
|
|
|
return;
|
|
|
} else {
|
|
|
NSLog(@"无播放地址");
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)_onModelLoadSucceed:(SuperPlayerModel *)model {
|
|
|
if (model == _playerModel) {
|
|
|
[self _playWithModel:_playerModel];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)_onModelLoadFailed:(SuperPlayerModel *)model error:(NSError *)error {
|
|
|
if (model != _playerModel) {
|
|
|
return;
|
|
|
}
|
|
|
// error 错误信息
|
|
|
[self showMiddleBtnMsg:kStrLoadFaildRetry withAction:ActionRetry];
|
|
|
[self.spinner stopAnimating];
|
|
|
NSLog(@"Load play model failed. fileID: %@, error: %@",
|
|
|
_playerModel.videoId.fileId, error);
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) {
|
|
|
NSString *message = [NSString stringWithFormat:@"网络请求失败 %d %@",
|
|
|
(int)error.code, error.localizedDescription];
|
|
|
[self.delegate superPlayerError:self
|
|
|
errCode:(int)error.code
|
|
|
errMessage:message];
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* player添加到fatherView上
|
|
|
*/
|
|
|
- (void)addPlayerToFatherView:(UIView *)view {
|
|
|
[self removeFromSuperview];
|
|
|
if (view) {
|
|
|
[view addSubview:self];
|
|
|
[self mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.edges.mas_offset(UIEdgeInsetsZero);
|
|
|
}];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)setFatherView:(UIView *)fatherView {
|
|
|
if (fatherView != _fatherView) {
|
|
|
[self addPlayerToFatherView:fatherView];
|
|
|
}
|
|
|
_fatherView = fatherView;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 重置player
|
|
|
*/
|
|
|
- (void)resetPlayer {
|
|
|
LOG_ME;
|
|
|
// 移除通知
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
// 暂停
|
|
|
[self pause];
|
|
|
|
|
|
[self.vodPlayer stopPlay];
|
|
|
[self.vodPlayer removeVideoWidget];
|
|
|
self.vodPlayer = nil;
|
|
|
|
|
|
[self.livePlayer stopPlay];
|
|
|
[self.livePlayer removeVideoWidget];
|
|
|
self.livePlayer = nil;
|
|
|
|
|
|
[self reportPlay];
|
|
|
|
|
|
self.state = StateStopped;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 播放
|
|
|
*/
|
|
|
- (void)resume {
|
|
|
LOG_ME;
|
|
|
[self.controlView setPlayState:YES];
|
|
|
self.isPauseByUser = NO;
|
|
|
self.state = StatePlaying;
|
|
|
if (self.isLive) {
|
|
|
[_livePlayer resume];
|
|
|
} else {
|
|
|
[_vodPlayer resume];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 暂停
|
|
|
*/
|
|
|
- (void)pause {
|
|
|
LOG_ME;
|
|
|
if (!self.isLoaded)
|
|
|
return;
|
|
|
[self.controlView setPlayState:NO];
|
|
|
self.isPauseByUser = YES;
|
|
|
self.state = StatePause;
|
|
|
if (self.isLive) {
|
|
|
[_livePlayer pause];
|
|
|
} else {
|
|
|
[_vodPlayer pause];
|
|
|
}
|
|
|
}
|
|
|
#pragma mark - Control View Configuration
|
|
|
- (void)resetControlViewWithLive:(BOOL)isLive
|
|
|
shiftPlayback:(BOOL)isShiftPlayback
|
|
|
isPlaying:(BOOL)isPlaying
|
|
|
{
|
|
|
[_controlView resetWithResolutionNames:self.playerModel.playDefinitions
|
|
|
currentResolutionIndex:self.playerModel.playingDefinitionIndex
|
|
|
isLive:isLive
|
|
|
isTimeShifting:isShiftPlayback
|
|
|
isPlaying:isPlaying];
|
|
|
}
|
|
|
|
|
|
#pragma mark - Private Method
|
|
|
- (BOOL)isSupportAppendParam {
|
|
|
NSString* version = [TXLiveBase getSDKVersionStr];
|
|
|
NSArray* vers = [version componentsSeparatedByString:@"."];
|
|
|
if (vers.count <= 1) {
|
|
|
return NO;
|
|
|
}
|
|
|
NSInteger majorVer = [vers[0] integerValue]?:0;
|
|
|
NSInteger minorVer = [vers[1] integerValue]?:0;
|
|
|
return majorVer >= SUPPORT_PARAM_MAJOR_VERSION && minorVer >= SUPPORT_PARAM_MINOR_VERSION;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置Player相关参数
|
|
|
*/
|
|
|
- (void)configTXPlayer {
|
|
|
LOG_ME;
|
|
|
self.backgroundColor = [UIColor blackColor];
|
|
|
|
|
|
if (_playerConfig.enableLog) {
|
|
|
[TXLiveBase setLogLevel:LOGLEVEL_DEBUG];
|
|
|
[TXLiveBase sharedInstance].delegate = self;
|
|
|
[TXLiveBase setConsoleEnabled:YES];
|
|
|
}
|
|
|
|
|
|
[self.vodPlayer stopPlay];
|
|
|
[self.vodPlayer removeVideoWidget];
|
|
|
[self.livePlayer stopPlay];
|
|
|
[self.livePlayer removeVideoWidget];
|
|
|
|
|
|
self.liveProgressTime = self.maxLiveProgressTime = 0;
|
|
|
|
|
|
int liveType = [self livePlayerType];
|
|
|
if (liveType >= 0) {
|
|
|
self.isLive = YES;
|
|
|
} else {
|
|
|
self.isLive = NO;
|
|
|
}
|
|
|
self.isLoaded = NO;
|
|
|
|
|
|
self.netWatcher.playerModel = self.playerModel;
|
|
|
//时移播放需要原始分辨率播放流地址
|
|
|
// if (self.playerModel.playingDefinition == nil) {
|
|
|
// self.playerModel.playingDefinition = self.netWatcher.adviseDefinition;
|
|
|
// }
|
|
|
NSString *videoURL = self.playerModel.playingDefinitionUrl;
|
|
|
// 时移
|
|
|
[TXLiveBase setAppID:[NSString stringWithFormat:@"%ld", _playerModel.appId]];
|
|
|
if (self.isLive) {
|
|
|
if (!self.livePlayer) {
|
|
|
self.livePlayer = [[TXLivePlayer alloc] init];
|
|
|
self.livePlayer.delegate = self;
|
|
|
}
|
|
|
TXLivePlayConfig *config = [[TXLivePlayConfig alloc] init];
|
|
|
config.bAutoAdjustCacheTime = NO;
|
|
|
config.maxAutoAdjustCacheTime = 5.0f;
|
|
|
config.minAutoAdjustCacheTime = 5.0f;
|
|
|
config.headers = self.playerConfig.headers;
|
|
|
[self.livePlayer setConfig:config];
|
|
|
|
|
|
|
|
|
self.livePlayer.enableHWAcceleration = self.playerConfig.hwAcceleration;
|
|
|
[self.livePlayer startPlay:videoURL type:liveType];
|
|
|
TXCUrl *curl = [[TXCUrl alloc] initWithString:videoURL];
|
|
|
[self.livePlayer prepareLiveSeek:self.playerConfig.playShiftDomain bizId:curl.bizid];
|
|
|
[self.livePlayer setMute:self.playerConfig.mute];
|
|
|
[self.livePlayer setRenderMode:self.playerConfig.renderMode];
|
|
|
} else {
|
|
|
if (!self.vodPlayer) {
|
|
|
self.vodPlayer = [[TXVodPlayer alloc] init];
|
|
|
self.vodPlayer.vodDelegate = self;
|
|
|
}
|
|
|
|
|
|
TXVodPlayConfig *config = [[TXVodPlayConfig alloc] init];
|
|
|
config.smoothSwitchBitrate = YES;
|
|
|
if (self.playerConfig.maxCacheItem) {
|
|
|
// https://github.com/tencentyun/SuperPlayer_iOS/issues/64
|
|
|
config.cacheFolderPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/TXCache"];
|
|
|
config.maxCacheItems = (int)self.playerConfig.maxCacheItem;
|
|
|
}
|
|
|
config.progressInterval = 0.02;
|
|
|
self.vodPlayer.token = self.playerModel.drmToken;
|
|
|
// if (_playerModel.videoId.version == FileIdV3) {
|
|
|
// if ([_playerModel.drmType isEqualToString:kDrmType_FairPlay]) {
|
|
|
// config.certificate = self.playerModel.certificate;
|
|
|
// self.vodPlayer.token = self.playerModel.token;
|
|
|
// NSLog(@"FairPlay播放");
|
|
|
// } else if ([_playerModel.drmType isEqualToString:kDrmType_SimpleAES]) {
|
|
|
// self.vodPlayer.token = self.playerModel.token;
|
|
|
// NSLog(@"SimpleAES播放");
|
|
|
// } else {
|
|
|
// // 降级播放
|
|
|
// self.vodPlayer.token = nil;
|
|
|
// }
|
|
|
// } else if (_playerModel.token) {
|
|
|
// if (self.playerModel.certificate) {
|
|
|
// config.certificate = self.playerModel.certificate;
|
|
|
// }
|
|
|
// self.vodPlayer.token = self.playerModel.token;
|
|
|
// } else {
|
|
|
// self.vodPlayer.token = nil;
|
|
|
// }
|
|
|
|
|
|
config.headers = self.playerConfig.headers;
|
|
|
|
|
|
[self.vodPlayer setConfig:config];
|
|
|
|
|
|
self.vodPlayer.enableHWAcceleration = self.playerConfig.hwAcceleration;
|
|
|
[self.vodPlayer setStartTime:self.startTime]; self.startTime = 0;
|
|
|
|
|
|
NSString *appParameter = [NSString stringWithFormat:@"spappid=%ld",self.playerModel.appId];
|
|
|
NSString *fileidParameter = [NSString stringWithFormat:@"spfileid=%@",self.playerModel.videoId.fileId];
|
|
|
NSString *drmtypeParameter = [NSString stringWithFormat:@"spdrmtype=%@",
|
|
|
self.playerModel.drmType == SPDrmTypeSimpleAES ? @"SimpleAES" : @"plain"];
|
|
|
NSString *vodParamVideoUrl = [NSString appendParameter:appParameter WithOriginUrl:videoURL];
|
|
|
vodParamVideoUrl = [NSString appendParameter:fileidParameter WithOriginUrl:vodParamVideoUrl];
|
|
|
vodParamVideoUrl = [NSString appendParameter:drmtypeParameter WithOriginUrl:vodParamVideoUrl];
|
|
|
|
|
|
[self.vodPlayer startPlay:([self isSupportAppendParam] ? vodParamVideoUrl : videoURL)];
|
|
|
[self.vodPlayer setBitrateIndex:self.playerModel.playingDefinitionIndex];
|
|
|
|
|
|
[self.vodPlayer setRate:self.playerConfig.playRate];
|
|
|
[self.vodPlayer setMirror:self.playerConfig.mirror];
|
|
|
[self.vodPlayer setMute:self.playerConfig.mute];
|
|
|
[self.vodPlayer setRenderMode:self.playerConfig.renderMode];
|
|
|
[self.vodPlayer setLoop:self.loop];
|
|
|
}
|
|
|
[self.netWatcher startWatch];
|
|
|
__weak SuperPlayerView *weakSelf = self;
|
|
|
[self.netWatcher setNotifyTipsBlock:^(NSString *msg) {
|
|
|
SuperPlayerView *strongSelf = weakSelf;
|
|
|
if (strongSelf) {
|
|
|
[strongSelf showMiddleBtnMsg:msg withAction:ActionSwitch];
|
|
|
[strongSelf.middleBlackBtn fadeOut:2];
|
|
|
}
|
|
|
}];
|
|
|
self.state = StateBuffering;
|
|
|
self.isPauseByUser = NO;
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:self.autoPlay];
|
|
|
self.controlView.playerConfig = self.playerConfig;
|
|
|
self.maskView.hidden = YES;
|
|
|
self.repeatBtn.hidden = YES;
|
|
|
self.repeatBackBtn.hidden = YES;
|
|
|
self.playDidEnd = NO;
|
|
|
[self.middleBlackBtn fadeOut:0.1];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建手势
|
|
|
*/
|
|
|
- (void)createGesture {
|
|
|
// 单击
|
|
|
self.singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleTapAction:)];
|
|
|
self.singleTap.delegate = self;
|
|
|
self.singleTap.numberOfTouchesRequired = 1; //手指数
|
|
|
self.singleTap.numberOfTapsRequired = 1;
|
|
|
[self addGestureRecognizer:self.singleTap];
|
|
|
|
|
|
// 双击(播放/暂停)
|
|
|
self.doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapAction:)];
|
|
|
self.doubleTap.delegate = self;
|
|
|
self.doubleTap.numberOfTouchesRequired = 1; //手指数
|
|
|
self.doubleTap.numberOfTapsRequired = 2;
|
|
|
[self addGestureRecognizer:self.doubleTap];
|
|
|
|
|
|
// 解决点击当前view时候响应其他控件事件
|
|
|
[self.singleTap setDelaysTouchesBegan:YES];
|
|
|
[self.doubleTap setDelaysTouchesBegan:YES];
|
|
|
// 双击失败响应单击事件
|
|
|
[self.singleTap requireGestureRecognizerToFail:self.doubleTap];
|
|
|
|
|
|
// 加载完成后,再添加平移手势
|
|
|
// 添加平移手势,用来控制音量、亮度、快进快退
|
|
|
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panDirection:)];
|
|
|
panRecognizer.delegate = self;
|
|
|
[panRecognizer setMaximumNumberOfTouches:1];
|
|
|
[panRecognizer setDelaysTouchesBegan:YES];
|
|
|
[panRecognizer setDelaysTouchesEnded:YES];
|
|
|
[panRecognizer setCancelsTouchesInView:YES];
|
|
|
[self addGestureRecognizer:panRecognizer];
|
|
|
}
|
|
|
|
|
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
|
|
|
|
|
}
|
|
|
|
|
|
#pragma mark - KVO
|
|
|
|
|
|
/**
|
|
|
* 设置横屏的约束
|
|
|
*/
|
|
|
- (void)setOrientationLandscapeConstraint:(UIInterfaceOrientation)orientation {
|
|
|
_isFullScreen = YES;
|
|
|
// [self _switchToLayoutStyle:orientation];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置竖屏的约束
|
|
|
*/
|
|
|
- (void)setOrientationPortraitConstraint {
|
|
|
|
|
|
[self addPlayerToFatherView:self.fatherView];
|
|
|
_isFullScreen = NO;
|
|
|
// [self _switchToLayoutStyle:UIInterfaceOrientationPortrait];
|
|
|
}
|
|
|
|
|
|
- (UIDeviceOrientation)_orientationForFullScreen:(BOOL)fullScreen {
|
|
|
UIDeviceOrientation targetOrientation = [UIDevice currentDevice].orientation;
|
|
|
if (fullScreen) {
|
|
|
if (!UIDeviceOrientationIsLandscape(targetOrientation)) {
|
|
|
targetOrientation = UIDeviceOrientationLandscapeLeft;
|
|
|
}
|
|
|
} else {
|
|
|
if (!UIDeviceOrientationIsPortrait(targetOrientation)) {
|
|
|
targetOrientation = UIDeviceOrientationPortrait;
|
|
|
}
|
|
|
// targetOrientation = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation;
|
|
|
}
|
|
|
return targetOrientation;
|
|
|
}
|
|
|
|
|
|
- (void)_switchToFullScreen:(BOOL)fullScreen {
|
|
|
if (_isFullScreen == fullScreen) {
|
|
|
return;
|
|
|
}
|
|
|
_isFullScreen = fullScreen;
|
|
|
[self.fatherView.viewController setNeedsStatusBarAppearanceUpdate];
|
|
|
|
|
|
UIDeviceOrientation targetOrientation = [self _orientationForFullScreen:fullScreen];// [UIDevice currentDevice].orientation;
|
|
|
|
|
|
if (fullScreen) {
|
|
|
[self removeFromSuperview];
|
|
|
[[UIApplication sharedApplication].keyWindow addSubview:_fullScreenBlackView];
|
|
|
[_fullScreenBlackView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.width.equalTo(@(ScreenHeight));
|
|
|
make.height.equalTo(@(ScreenWidth));
|
|
|
make.center.equalTo([UIApplication sharedApplication].keyWindow);
|
|
|
}];
|
|
|
|
|
|
[[UIApplication sharedApplication].keyWindow addSubview:self];
|
|
|
[self mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
if (IsIPhoneX) {
|
|
|
make.width.equalTo(@(ScreenHeight - self.mm_safeAreaTopGap * 2));
|
|
|
} else {
|
|
|
make.width.equalTo(@(ScreenHeight));
|
|
|
}
|
|
|
make.height.equalTo(@(ScreenWidth));
|
|
|
make.center.equalTo([UIApplication sharedApplication].keyWindow);
|
|
|
}];
|
|
|
[self.superview setNeedsLayout];
|
|
|
} else {
|
|
|
[_fullScreenBlackView removeFromSuperview];
|
|
|
[self addPlayerToFatherView:self.fatherView];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)_switchToLayoutStyle:(SuperPlayerLayoutStyle)style {
|
|
|
// 获取到当前状态条的方向
|
|
|
|
|
|
// UIInterfaceOrientation currentOrientation = [UIDevice currentDevice].orientation;
|
|
|
// 判断如果当前方向和要旋转的方向一致,那么不做任何操作
|
|
|
// if (currentOrientation == orientation) { return; }
|
|
|
|
|
|
// 根据要旋转的方向,使用Masonry重新修改限制
|
|
|
if (style == SuperPlayerLayoutStyleFullScreen) {//
|
|
|
// 这个地方加判断是为了从全屏的一侧,直接到全屏的另一侧不用修改限制,否则会出错;
|
|
|
if (_layoutStyle != SuperPlayerLayoutStyleFullScreen) { //UIInterfaceOrientationIsPortrait(currentOrientation)) {
|
|
|
[self removeFromSuperview];
|
|
|
[[UIApplication sharedApplication].keyWindow addSubview:_fullScreenBlackView];
|
|
|
[_fullScreenBlackView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.width.equalTo(@(ScreenHeight));
|
|
|
make.height.equalTo(@(ScreenWidth));
|
|
|
make.center.equalTo([UIApplication sharedApplication].keyWindow);
|
|
|
}];
|
|
|
|
|
|
[[UIApplication sharedApplication].keyWindow addSubview:self];
|
|
|
[self mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
if (IsIPhoneX) {
|
|
|
make.width.equalTo(@(ScreenHeight - self.mm_safeAreaTopGap * 2));
|
|
|
} else {
|
|
|
make.width.equalTo(@(ScreenHeight));
|
|
|
}
|
|
|
make.height.equalTo(@(ScreenWidth));
|
|
|
make.center.equalTo([UIApplication sharedApplication].keyWindow);
|
|
|
}];
|
|
|
}
|
|
|
} else {
|
|
|
[_fullScreenBlackView removeFromSuperview];
|
|
|
}
|
|
|
self.controlView.compact = style == SuperPlayerLayoutStyleCompact;
|
|
|
|
|
|
[[UIApplication sharedApplication].keyWindow layoutIfNeeded];
|
|
|
|
|
|
|
|
|
// iOS6.0之后,设置状态条的方法能使用的前提是shouldAutorotate为NO,也就是说这个视图控制器内,旋转要关掉;
|
|
|
// 也就是说在实现这个方法的时候-(BOOL)shouldAutorotate返回值要为NO
|
|
|
/*
|
|
|
[[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];
|
|
|
[UIView beginAnimations:nil context:nil];
|
|
|
[UIView setAnimationDuration:0.3];
|
|
|
// 更改了状态条的方向,但是设备方向UIInterfaceOrientation还是正方向的,这就要设置给你播放视频的视图的方向设置旋转
|
|
|
// 给你的播放视频的view视图设置旋转
|
|
|
self.transform = CGAffineTransformIdentity;
|
|
|
self.transform = [self getTransformRotationAngleOfOrientation:[UIDevice currentDevice].orientation];
|
|
|
|
|
|
_fullScreenContainerView.transform = self.transform;
|
|
|
// 开始旋转
|
|
|
[UIView commitAnimations];
|
|
|
|
|
|
[self.fatherView.mm_viewController setNeedsStatusBarAppearanceUpdate];
|
|
|
_layoutStyle = style;
|
|
|
*/
|
|
|
}
|
|
|
|
|
|
- (void)_adjustTransform:(UIDeviceOrientation)orientation {
|
|
|
|
|
|
[UIView beginAnimations:nil context:nil];
|
|
|
[UIView setAnimationDuration:0.3];
|
|
|
|
|
|
self.transform = [self getTransformRotationAngleOfOrientation:orientation];
|
|
|
_fullScreenBlackView.transform = self.transform;
|
|
|
[UIView commitAnimations];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取变换的旋转角度
|
|
|
*
|
|
|
* @return 变换矩阵
|
|
|
*/
|
|
|
- (CGAffineTransform)getTransformRotationAngleOfOrientation:(UIDeviceOrientation)orientation {
|
|
|
// 状态条的方向已经设置过,所以这个就是你想要旋转的方向
|
|
|
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
|
|
|
if (interfaceOrientation == (UIInterfaceOrientation)orientation) {
|
|
|
return CGAffineTransformIdentity;
|
|
|
}
|
|
|
// 根据要进行旋转的方向来计算旋转的角度
|
|
|
if (orientation == UIInterfaceOrientationPortrait) {
|
|
|
return CGAffineTransformIdentity;
|
|
|
} else if (orientation == UIInterfaceOrientationLandscapeLeft){
|
|
|
return CGAffineTransformMakeRotation(-M_PI_2);
|
|
|
} else if(orientation == UIInterfaceOrientationLandscapeRight){
|
|
|
return CGAffineTransformMakeRotation(M_PI_2);
|
|
|
}
|
|
|
return CGAffineTransformIdentity;
|
|
|
}
|
|
|
|
|
|
#pragma mark 屏幕转屏相关
|
|
|
|
|
|
/**
|
|
|
* 屏幕转屏
|
|
|
*
|
|
|
* @param orientation 屏幕方向
|
|
|
*/
|
|
|
- (void)interfaceOrientation:(UIInterfaceOrientation)orientation {
|
|
|
if (orientation == UIInterfaceOrientationLandscapeRight || orientation == UIInterfaceOrientationLandscapeLeft) {
|
|
|
// 设置横屏
|
|
|
[self setOrientationLandscapeConstraint:orientation];
|
|
|
} else if (orientation == UIInterfaceOrientationPortrait) {
|
|
|
// 设置竖屏
|
|
|
[self setOrientationPortraitConstraint];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (SuperPlayerLayoutStyle)defaultStyleForDeviceOrientation:(UIDeviceOrientation)orientation {
|
|
|
if (UIDeviceOrientationIsPortrait(orientation)) {
|
|
|
return SuperPlayerLayoutStyleCompact;
|
|
|
} else {
|
|
|
return SuperPlayerLayoutStyleFullScreen;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#pragma mark - Action
|
|
|
|
|
|
/**
|
|
|
* 轻拍方法
|
|
|
*
|
|
|
* @param gesture UITapGestureRecognizer
|
|
|
*/
|
|
|
- (void)singleTapAction:(UIGestureRecognizer *)gesture {
|
|
|
if (gesture.state == UIGestureRecognizerStateRecognized) {
|
|
|
|
|
|
if (self.playDidEnd) {
|
|
|
return;
|
|
|
}
|
|
|
if (SuperPlayerWindowShared.isShowing)
|
|
|
return;
|
|
|
|
|
|
if (self.controlView.hidden) {
|
|
|
[[self.controlView fadeShow] fadeOut:5];
|
|
|
} else {
|
|
|
[self.controlView fadeOut:0.2];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 双击播放/暂停
|
|
|
*
|
|
|
* @param gesture UITapGestureRecognizer
|
|
|
*/
|
|
|
- (void)doubleTapAction:(UIGestureRecognizer *)gesture {
|
|
|
if (self.playDidEnd) { return; }
|
|
|
// 显示控制层
|
|
|
[self.controlView fadeShow];
|
|
|
if (self.isPauseByUser) {
|
|
|
[self resume];
|
|
|
} else {
|
|
|
[self pause];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 全屏 */
|
|
|
- (void)setFullScreen:(BOOL)fullScreen {
|
|
|
|
|
|
if (_isFullScreen != fullScreen) {
|
|
|
[self _adjustTransform:[self _orientationForFullScreen:fullScreen]];
|
|
|
[self _switchToFullScreen:fullScreen];
|
|
|
[self _switchToLayoutStyle:fullScreen ? SuperPlayerLayoutStyleFullScreen : SuperPlayerLayoutStyleCompact];
|
|
|
}
|
|
|
_isFullScreen = fullScreen;
|
|
|
/*
|
|
|
self.controlView.compact = !_isFullScreen;
|
|
|
if (fullScreen) {
|
|
|
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
|
|
|
if (orientation == UIDeviceOrientationLandscapeRight) {
|
|
|
[self interfaceOrientation:UIInterfaceOrientationLandscapeLeft];
|
|
|
} else {
|
|
|
[self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
|
|
|
}
|
|
|
} else {
|
|
|
[self setOrientationPortraitConstraint];
|
|
|
[self interfaceOrientation:UIInterfaceOrientationPortrait];
|
|
|
}
|
|
|
*/
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 播放完了
|
|
|
*
|
|
|
*/
|
|
|
- (void)moviePlayDidEnd {
|
|
|
self.state = StateStopped;
|
|
|
self.playDidEnd = YES;
|
|
|
// 播放结束隐藏
|
|
|
if (SuperPlayerWindowShared.isShowing) {
|
|
|
[SuperPlayerWindowShared hide];
|
|
|
[self resetPlayer];
|
|
|
}
|
|
|
[self.controlView fadeOut:0.2];
|
|
|
[self fastViewUnavaliable];
|
|
|
[self.netWatcher stopWatch];
|
|
|
self.maskView.hidden = NO;
|
|
|
self.repeatBtn.hidden = NO;
|
|
|
self.repeatBackBtn.hidden = NO;
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerDidEnd:)]) {
|
|
|
[self.delegate superPlayerDidEnd:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#pragma mark - UIKit Notifications
|
|
|
|
|
|
/**
|
|
|
* 应用退到后台
|
|
|
*/
|
|
|
- (void)appDidEnterBackground:(NSNotification *)notify {
|
|
|
NSLog(@"appDidEnterBackground");
|
|
|
self.didEnterBackground = YES;
|
|
|
if (self.isLive) {
|
|
|
return;
|
|
|
}
|
|
|
if (!self.isPauseByUser && (self.state != StateStopped && self.state != StateFailed)) {
|
|
|
[_vodPlayer pause];
|
|
|
self.state = StatePause;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 应用进入前台
|
|
|
*/
|
|
|
- (void)appDidEnterPlayground:(NSNotification *)notify {
|
|
|
NSLog(@"appDidEnterPlayground");
|
|
|
self.didEnterBackground = NO;
|
|
|
if (self.isLive) {
|
|
|
return;
|
|
|
}
|
|
|
if (!self.isPauseByUser && (self.state != StateStopped && self.state != StateFailed)) {
|
|
|
self.state = StatePlaying;
|
|
|
[_vodPlayer resume];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 状态条变化通知(在前台播放才去处理)
|
|
|
- (void)onStatusBarOrientationChange {
|
|
|
[self onDeviceOrientationChange];
|
|
|
return;
|
|
|
if (!self.didEnterBackground) {
|
|
|
UIInterfaceOrientation orientation = (UIInterfaceOrientation)[UIDevice currentDevice].orientation;
|
|
|
SuperPlayerLayoutStyle style = [self defaultStyleForDeviceOrientation:orientation];
|
|
|
// [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];
|
|
|
if ([UIApplication sharedApplication].statusBarOrientation != orientation) {
|
|
|
[self _adjustTransform:(UIInterfaceOrientation)[UIDevice currentDevice].orientation];
|
|
|
}
|
|
|
[self _switchToFullScreen:style == SuperPlayerLayoutStyleFullScreen];
|
|
|
[self _switchToLayoutStyle:style];
|
|
|
/* // 获取到当前状态条的方向
|
|
|
UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
|
|
|
if (currentOrientation == UIInterfaceOrientationPortrait) {
|
|
|
[self setOrientationPortraitConstraint];
|
|
|
} else {
|
|
|
[self _switchToLayoutStyle:style];
|
|
|
|
|
|
if (currentOrientation == UIInterfaceOrientationLandscapeRight) {
|
|
|
[self _switchToLayoutStyle:style];
|
|
|
} else if (currentOrientation == UIDeviceOrientationLandscapeLeft){
|
|
|
[self _switchToLayoutStyle:UIInterfaceOrientationLandscapeLeft];
|
|
|
}
|
|
|
}
|
|
|
*/
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 屏幕方向发生变化会调用这里
|
|
|
*/
|
|
|
- (void)onDeviceOrientationChange {
|
|
|
if (!self.isLoaded) { return; }
|
|
|
if (self.isLockScreen) { return; }
|
|
|
if (self.didEnterBackground) { return; };
|
|
|
if (SuperPlayerWindowShared.isShowing) { return; }
|
|
|
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
|
|
|
if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown) {
|
|
|
return;
|
|
|
}
|
|
|
SuperPlayerLayoutStyle style = [self defaultStyleForDeviceOrientation:[UIDevice currentDevice].orientation];
|
|
|
|
|
|
BOOL shouldFullScreen = UIDeviceOrientationIsLandscape(orientation);
|
|
|
[self _switchToFullScreen:shouldFullScreen];
|
|
|
[self _adjustTransform:[self _orientationForFullScreen:shouldFullScreen]];
|
|
|
[self _switchToLayoutStyle:style];
|
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
- (void)seekToTime:(NSInteger)dragedSeconds {
|
|
|
if (!self.isLoaded || self.state == StateStopped) {
|
|
|
return;
|
|
|
}
|
|
|
if (self.isLive) {
|
|
|
[DataReport report:@"timeshift" param:nil];
|
|
|
int ret = [self.livePlayer seek:dragedSeconds];
|
|
|
if (ret != 0) {
|
|
|
[self showMiddleBtnMsg:kStrTimeShiftFailed withAction:ActionNone];
|
|
|
[self.middleBlackBtn fadeOut:2];
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:YES];
|
|
|
} else {
|
|
|
if (!self.isShiftPlayback)
|
|
|
self.isLoaded = NO;
|
|
|
self.isShiftPlayback = YES;
|
|
|
self.state = StateBuffering;
|
|
|
[self resetControlViewWithLive:YES
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:YES]; //时移播放不能切码率
|
|
|
}
|
|
|
} else {
|
|
|
[self.vodPlayer resume];
|
|
|
[self.vodPlayer seek:dragedSeconds];
|
|
|
[self.controlView setPlayState:YES];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)setPlayRate:(CGFloat)playRate {
|
|
|
if (self.isLive) {
|
|
|
return;
|
|
|
}
|
|
|
self.playerConfig.playRate = playRate;
|
|
|
[self.vodPlayer setRate:playRate];
|
|
|
}
|
|
|
|
|
|
#pragma mark - UIPanGestureRecognizer手势方法
|
|
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
|
{
|
|
|
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
|
|
|
if (((UITapGestureRecognizer*)gestureRecognizer).numberOfTapsRequired == 2) {
|
|
|
if (self.isLockScreen == YES)
|
|
|
return NO;
|
|
|
}
|
|
|
return YES;
|
|
|
}
|
|
|
|
|
|
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
|
|
|
if (!self.isLoaded) { return NO; }
|
|
|
if (self.isLockScreen) { return NO; }
|
|
|
if (SuperPlayerWindowShared.isShowing) { return NO; }
|
|
|
|
|
|
if (self.disableGesture) {
|
|
|
if (!self.isFullScreen) {
|
|
|
return NO;
|
|
|
}
|
|
|
}
|
|
|
return YES;
|
|
|
}
|
|
|
|
|
|
return NO;
|
|
|
}
|
|
|
/**
|
|
|
* pan手势事件
|
|
|
*
|
|
|
* @param pan UIPanGestureRecognizer
|
|
|
*/
|
|
|
- (void)panDirection:(UIPanGestureRecognizer *)pan {
|
|
|
|
|
|
//根据在view上Pan的位置,确定是调音量还是亮度
|
|
|
CGPoint locationPoint = [pan locationInView:self];
|
|
|
|
|
|
// 我们要响应水平移动和垂直移动
|
|
|
// 根据上次和本次移动的位置,算出一个速率的point
|
|
|
CGPoint veloctyPoint = [pan velocityInView:self];
|
|
|
|
|
|
if (self.state == StateStopped)
|
|
|
return;
|
|
|
|
|
|
// 判断是垂直移动还是水平移动
|
|
|
switch (pan.state) {
|
|
|
case UIGestureRecognizerStateBegan:{ // 开始移动
|
|
|
// 使用绝对值来判断移动的方向
|
|
|
CGFloat x = fabs(veloctyPoint.x);
|
|
|
CGFloat y = fabs(veloctyPoint.y);
|
|
|
if (x > y) { // 水平移动
|
|
|
// 取消隐藏
|
|
|
self.panDirection = PanDirectionHorizontalMoved;
|
|
|
self.sumTime = [self playCurrentTime];
|
|
|
}
|
|
|
else if (x < y){ // 垂直移动
|
|
|
self.panDirection = PanDirectionVerticalMoved;
|
|
|
// 开始滑动的时候,状态改为正在控制音量
|
|
|
if (locationPoint.x > self.bounds.size.width / 2) {
|
|
|
self.isVolume = YES;
|
|
|
}else { // 状态改为显示亮度调节
|
|
|
self.isVolume = NO;
|
|
|
}
|
|
|
}
|
|
|
self.isDragging = YES;
|
|
|
[self.controlView fadeOut:0.2];
|
|
|
break;
|
|
|
}
|
|
|
case UIGestureRecognizerStateChanged:{ // 正在移动
|
|
|
switch (self.panDirection) {
|
|
|
case PanDirectionHorizontalMoved:{
|
|
|
[self horizontalMoved:veloctyPoint.x]; // 水平移动的方法只要x方向的值
|
|
|
break;
|
|
|
}
|
|
|
case PanDirectionVerticalMoved:{
|
|
|
[self verticalMoved:veloctyPoint.y]; // 垂直移动方法只要y方向的值
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
self.isDragging = YES;
|
|
|
break;
|
|
|
}
|
|
|
case UIGestureRecognizerStateEnded:{ // 移动停止
|
|
|
// 移动结束也需要判断垂直或者平移
|
|
|
// 比如水平移动结束时,要快进到指定位置,如果这里没有判断,当我们调节音量完之后,会出现屏幕跳动的bug
|
|
|
switch (self.panDirection) {
|
|
|
case PanDirectionHorizontalMoved:{
|
|
|
self.isPauseByUser = NO;
|
|
|
[self seekToTime:self.sumTime];
|
|
|
// 把sumTime滞空,不然会越加越多
|
|
|
self.sumTime = 0;
|
|
|
break;
|
|
|
}
|
|
|
case PanDirectionVerticalMoved:{
|
|
|
// 垂直移动结束后,把状态改为不再控制音量
|
|
|
self.isVolume = NO;
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
[self fastViewUnavaliable];
|
|
|
self.isDragging = NO;
|
|
|
break;
|
|
|
}
|
|
|
case UIGestureRecognizerStateCancelled: {
|
|
|
self.sumTime = 0;
|
|
|
self.isVolume = NO;
|
|
|
[self fastViewUnavaliable];
|
|
|
self.isDragging = NO;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* pan垂直移动的方法
|
|
|
*
|
|
|
* @param value void
|
|
|
*/
|
|
|
- (void)verticalMoved:(CGFloat)value {
|
|
|
|
|
|
self.isVolume ? ([[self class] volumeViewSlider].value -= value / 10000) : ([UIScreen mainScreen].brightness -= value / 10000);
|
|
|
|
|
|
if (self.isVolume) {
|
|
|
[self fastViewImageAvaliable:SuperPlayerImage(@"sound_max") progress:[[self class] volumeViewSlider].value];
|
|
|
} else {
|
|
|
[self fastViewImageAvaliable:SuperPlayerImage(@"light_max") progress:[UIScreen mainScreen].brightness];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* pan水平移动的方法
|
|
|
*
|
|
|
* @param value void
|
|
|
*/
|
|
|
- (void)horizontalMoved:(CGFloat)value {
|
|
|
// 每次滑动需要叠加时间
|
|
|
CGFloat totalMovieDuration = [self playDuration];
|
|
|
self.sumTime += value / 10000 * totalMovieDuration;
|
|
|
|
|
|
if (self.sumTime > totalMovieDuration) { self.sumTime = totalMovieDuration;}
|
|
|
if (self.sumTime < 0) { self.sumTime = 0; }
|
|
|
|
|
|
[self fastViewProgressAvaliable:self.sumTime];
|
|
|
}
|
|
|
|
|
|
- (void)volumeChanged:(NSNotification *)notification
|
|
|
{
|
|
|
if (self.isDragging)
|
|
|
return; // 正在拖动,不响应音量事件
|
|
|
|
|
|
if (![[[notification userInfo] objectForKey:@"AVSystemController_AudioVolumeChangeReasonNotificationParameter"] isEqualToString:@"ExplicitVolumeChange"]) {
|
|
|
return;
|
|
|
}
|
|
|
float volume = [[[notification userInfo] objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"] floatValue];
|
|
|
[self fastViewImageAvaliable:SuperPlayerImage(@"sound_max") progress:volume];
|
|
|
[self.fastView fadeOut:1];
|
|
|
}
|
|
|
|
|
|
- (SuperPlayerFastView *)fastView
|
|
|
{
|
|
|
if (_fastView == nil) {
|
|
|
_fastView = [[SuperPlayerFastView alloc] init];
|
|
|
[self addSubview:_fastView];
|
|
|
[_fastView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.edges.mas_equalTo(UIEdgeInsetsZero);
|
|
|
}];
|
|
|
}
|
|
|
return _fastView;
|
|
|
}
|
|
|
|
|
|
- (void)fastViewImageAvaliable:(UIImage *)image progress:(CGFloat)draggedValue {
|
|
|
if (self.controlView.isShowSecondView)
|
|
|
return;
|
|
|
[self.fastView showImg:image withProgress:draggedValue];
|
|
|
[self.fastView fadeShow];
|
|
|
}
|
|
|
|
|
|
- (void)fastViewProgressAvaliable:(NSInteger)draggedTime
|
|
|
{
|
|
|
NSInteger totalTime = 0;
|
|
|
if (self.originalDuration > 0) {
|
|
|
totalTime = self.originalDuration;
|
|
|
} else {
|
|
|
totalTime = [self playDuration];
|
|
|
}
|
|
|
NSString *currentTimeStr = [StrUtils timeFormat:draggedTime];
|
|
|
NSString *totalTimeStr = [StrUtils timeFormat:totalTime];
|
|
|
NSString *timeStr = [NSString stringWithFormat:@"%@ / %@", currentTimeStr, totalTimeStr];
|
|
|
if (self.isLive) {
|
|
|
timeStr = [NSString stringWithFormat:@"%@", currentTimeStr];
|
|
|
}
|
|
|
|
|
|
UIImage *thumbnail;
|
|
|
if (self.isFullScreen) {
|
|
|
thumbnail = [self.imageSprite getThumbnail:draggedTime];
|
|
|
}
|
|
|
if (thumbnail) {
|
|
|
self.fastView.videoRatio = self.videoRatio;
|
|
|
[self.fastView showThumbnail:thumbnail withText:timeStr];
|
|
|
} else {
|
|
|
CGFloat sliderValue = 1;
|
|
|
if (totalTime > 0) {
|
|
|
sliderValue = (CGFloat)draggedTime/totalTime;
|
|
|
}
|
|
|
if (self.isLive && totalTime > MAX_SHIFT_TIME) {
|
|
|
CGFloat base = totalTime - MAX_SHIFT_TIME;
|
|
|
if (self.sumTime < base)
|
|
|
self.sumTime = base;
|
|
|
sliderValue = (self.sumTime - base) / MAX_SHIFT_TIME;
|
|
|
NSLog(@"%f",sliderValue);
|
|
|
}
|
|
|
[self.fastView showText:timeStr withText:sliderValue];
|
|
|
}
|
|
|
[self.fastView fadeShow];
|
|
|
}
|
|
|
|
|
|
- (void)fastViewUnavaliable
|
|
|
{
|
|
|
[self.fastView fadeOut:0.1];
|
|
|
}
|
|
|
|
|
|
#pragma mark - UIGestureRecognizerDelegate
|
|
|
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
|
|
|
|
|
|
|
|
|
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
|
|
|
if (self.playDidEnd){
|
|
|
return NO;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ([touch.view isKindOfClass:[UISlider class]] || [touch.view.superview isKindOfClass:[UISlider class]]) {
|
|
|
return NO;
|
|
|
}
|
|
|
|
|
|
if (SuperPlayerWindowShared.isShowing)
|
|
|
return NO;
|
|
|
|
|
|
return YES;
|
|
|
}
|
|
|
|
|
|
#pragma mark - Setter
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 设置播放的状态
|
|
|
*
|
|
|
* @param state SuperPlayerState
|
|
|
*/
|
|
|
- (void)setState:(SuperPlayerState)state {
|
|
|
if (_state != state) {
|
|
|
[self.delegate onPlayStateChange:state];
|
|
|
}
|
|
|
|
|
|
_state = state;
|
|
|
// 控制菊花显示、隐藏
|
|
|
if (state == StateBuffering) {
|
|
|
[self.spinner startAnimating];
|
|
|
} else {
|
|
|
[self.spinner stopAnimating];
|
|
|
}
|
|
|
if (state == StatePlaying) {
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
|
name:@"AVSystemController_SystemVolumeDidChangeNotification"
|
|
|
object:nil];
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
selector:@selector(volumeChanged:)
|
|
|
name:@"AVSystemController_SystemVolumeDidChangeNotification"
|
|
|
object:nil];
|
|
|
|
|
|
if (self.coverImageView.alpha == 1) {
|
|
|
[UIView animateWithDuration:0.2 animations:^{
|
|
|
self.coverImageView.alpha = 0;
|
|
|
}];
|
|
|
}
|
|
|
} else if (state == StateFailed) {
|
|
|
|
|
|
} else if (state == StateStopped) {
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
|
name:@"AVSystemController_SystemVolumeDidChangeNotification"
|
|
|
object:nil];
|
|
|
|
|
|
self.coverImageView.alpha = 1;
|
|
|
|
|
|
} else if (state == StatePause) {
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)setControlView:(SuperPlayerControlView *)controlView {
|
|
|
if (_controlView == controlView) {
|
|
|
return;
|
|
|
}
|
|
|
[_controlView removeFromSuperview];
|
|
|
|
|
|
_controlView = controlView;
|
|
|
controlView.delegate = self;
|
|
|
[self addSubview:controlView];
|
|
|
[controlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.edges.mas_equalTo(UIEdgeInsetsZero);
|
|
|
}];
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:self.autoPlay];
|
|
|
[controlView setTitle:_controlView.title];
|
|
|
[controlView setPointArray:_controlView.pointArray];
|
|
|
}
|
|
|
|
|
|
- (SuperPlayerControlView *)controlView
|
|
|
{
|
|
|
if (_controlView == nil) {
|
|
|
self.controlView = [[SPDefaultControlView alloc] initWithFrame:CGRectZero];
|
|
|
}
|
|
|
return _controlView;
|
|
|
}
|
|
|
|
|
|
- (void)setDragging:(BOOL)dragging
|
|
|
{
|
|
|
_isDragging = dragging;
|
|
|
if (dragging) {
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
|
removeObserver:self name:@"AVSystemController_SystemVolumeDidChangeNotification"
|
|
|
object:nil];
|
|
|
} else {
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
|
addObserver:self
|
|
|
selector:@selector(volumeChanged:)
|
|
|
name:@"AVSystemController_SystemVolumeDidChangeNotification"
|
|
|
object:nil];
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)setLoop:(BOOL)loop
|
|
|
{
|
|
|
_loop = loop;
|
|
|
if (self.vodPlayer) {
|
|
|
self.vodPlayer.loop = loop;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#pragma mark - Getter
|
|
|
|
|
|
- (CGFloat)playDuration {
|
|
|
if (self.isLive) {
|
|
|
return self.maxLiveProgressTime;
|
|
|
}
|
|
|
return self.vodPlayer.duration;
|
|
|
}
|
|
|
|
|
|
- (CGFloat)playCurrentTime {
|
|
|
if (self.isLive) {
|
|
|
if (self.isShiftPlayback) {
|
|
|
return self.liveProgressTime;
|
|
|
}
|
|
|
return self.maxLiveProgressTime;
|
|
|
}
|
|
|
|
|
|
return _playCurrentTime;
|
|
|
}
|
|
|
|
|
|
+ (UISlider *)volumeViewSlider {
|
|
|
return _volumeSlider;
|
|
|
}
|
|
|
#pragma mark - SuperPlayerControlViewDelegate
|
|
|
|
|
|
- (void)controlViewPlay:(SuperPlayerControlView *)controlView
|
|
|
{
|
|
|
[self resume];
|
|
|
if (self.state == StatePause) { self.state = StatePlaying; }
|
|
|
}
|
|
|
|
|
|
- (void)controlViewPause:(SuperPlayerControlView *)controlView
|
|
|
{
|
|
|
[self pause];
|
|
|
if (self.state == StatePlaying) { self.state = StatePause;}
|
|
|
}
|
|
|
|
|
|
- (void)controlViewBack:(SuperPlayerControlView *)controlView {
|
|
|
[self controlViewBackAction:controlView];
|
|
|
}
|
|
|
|
|
|
- (void)controlViewBackAction:(id)sender {
|
|
|
if (self.isFullScreen) {
|
|
|
self.isFullScreen = NO;
|
|
|
return;
|
|
|
}
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerBackAction:)]) {
|
|
|
[self.delegate superPlayerBackAction:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)controlViewChangeScreen:(SuperPlayerControlView *)controlView withFullScreen:(BOOL)isFullScreen {
|
|
|
self.isFullScreen = isFullScreen;
|
|
|
}
|
|
|
|
|
|
- (void)controlViewDidChangeScreen:(UIView *)controlView
|
|
|
{
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerFullScreenChanged:)]) {
|
|
|
[self.delegate superPlayerFullScreenChanged:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)controlViewLockScreen:(SuperPlayerControlView *)controlView withLock:(BOOL)isLock {
|
|
|
self.isLockScreen = isLock;
|
|
|
}
|
|
|
|
|
|
- (void)controlViewSwitch:(SuperPlayerControlView *)controlView withDefinition:(NSString *)definition {
|
|
|
if ([self.playerModel.playingDefinition isEqualToString:definition])
|
|
|
return;
|
|
|
|
|
|
self.playerModel.playingDefinition = definition;
|
|
|
NSString *url = self.playerModel.playingDefinitionUrl;
|
|
|
if (self.isLive) {
|
|
|
[self.livePlayer switchStream:url];
|
|
|
[self showMiddleBtnMsg:[NSString stringWithFormat:@"正在切换到%@...", definition] withAction:ActionNone];
|
|
|
} else {
|
|
|
if ([self.vodPlayer supportedBitrates].count > 1) {
|
|
|
[self.vodPlayer setBitrateIndex:self.playerModel.playingDefinitionIndex];
|
|
|
} else {
|
|
|
CGFloat startTime = [self.vodPlayer currentPlaybackTime];
|
|
|
[self.vodPlayer setStartTime:startTime];
|
|
|
[self.vodPlayer startPlay:url];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)controlViewConfigUpdate:(SuperPlayerView *)controlView withReload:(BOOL)reload {
|
|
|
if (self.isLive) {
|
|
|
[self.livePlayer setMute:self.playerConfig.mute];
|
|
|
[self.livePlayer setRenderMode:self.playerConfig.renderMode];
|
|
|
} else {
|
|
|
[self.vodPlayer setRate:self.playerConfig.playRate];
|
|
|
[self.vodPlayer setMirror:self.playerConfig.mirror];
|
|
|
[self.vodPlayer setMute:self.playerConfig.mute];
|
|
|
[self.vodPlayer setRenderMode:self.playerConfig.renderMode];
|
|
|
}
|
|
|
if (reload) {
|
|
|
if (!self.isLive)
|
|
|
self.startTime = [self.vodPlayer currentPlaybackTime];
|
|
|
self.isShiftPlayback = NO;
|
|
|
[self configTXPlayer]; // 软硬解需要重启
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
- (void)controlViewReload:(UIView *)controlView {
|
|
|
if (self.isLive) {
|
|
|
self.isShiftPlayback = NO;
|
|
|
self.isLoaded = NO;
|
|
|
[self.livePlayer resumeLive];
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:YES];
|
|
|
} else {
|
|
|
self.startTime = [self.vodPlayer currentPlaybackTime];
|
|
|
[self configTXPlayer];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- (void)controlViewSnapshot:(SuperPlayerControlView *)controlView {
|
|
|
|
|
|
void (^block)(UIImage *img) = ^(UIImage *img) {
|
|
|
[self.fastView showSnapshot:img];
|
|
|
|
|
|
if ([self.fastView.snapshotView gestureRecognizers].count == 0) {
|
|
|
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openPhotos)];
|
|
|
singleTap.numberOfTapsRequired = 1;
|
|
|
[self.fastView.snapshotView setUserInteractionEnabled:YES];
|
|
|
[self.fastView.snapshotView addGestureRecognizer:singleTap];
|
|
|
}
|
|
|
[self.fastView fadeShow];
|
|
|
[self.fastView fadeOut:2];
|
|
|
UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
|
|
|
};
|
|
|
|
|
|
if (_isLive) {
|
|
|
[_livePlayer snapshot:block];
|
|
|
} else {
|
|
|
[_vodPlayer snapshot:block];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-(void)openPhotos {
|
|
|
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"photos-redirect://"]];
|
|
|
}
|
|
|
|
|
|
- (CGFloat)sliderPosToTime:(CGFloat)pos
|
|
|
{
|
|
|
// 视频总时间长度
|
|
|
CGFloat totalTime = 0;
|
|
|
if (self.originalDuration > 0) {
|
|
|
totalTime = self.originalDuration;
|
|
|
} else {
|
|
|
totalTime = [self playDuration];
|
|
|
}
|
|
|
|
|
|
//计算出拖动的当前秒数
|
|
|
CGFloat dragedSeconds = floorf(totalTime * pos);
|
|
|
if (self.isLive && totalTime > MAX_SHIFT_TIME) {
|
|
|
CGFloat base = totalTime - MAX_SHIFT_TIME;
|
|
|
dragedSeconds = floor(MAX_SHIFT_TIME * pos) + base;
|
|
|
}
|
|
|
return dragedSeconds;
|
|
|
}
|
|
|
|
|
|
- (void)controlViewSeek:(SuperPlayerControlView *)controlView where:(CGFloat)pos {
|
|
|
CGFloat dragedSeconds = [self sliderPosToTime:pos];
|
|
|
[self seekToTime:dragedSeconds];
|
|
|
[self fastViewUnavaliable];
|
|
|
}
|
|
|
|
|
|
- (void)controlViewPreview:(SuperPlayerControlView *)controlView where:(CGFloat)pos {
|
|
|
CGFloat dragedSeconds = [self sliderPosToTime:pos];
|
|
|
if ([self playDuration] > 0) { // 当总时长 > 0时候才能拖动slider
|
|
|
[self fastViewProgressAvaliable:dragedSeconds];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
#pragma mark - 点播回调
|
|
|
|
|
|
- (void)_removeOldPlayer
|
|
|
{
|
|
|
for (UIView *w in [self subviews]) {
|
|
|
if ([w isKindOfClass:NSClassFromString(@"TXCRenderView")])
|
|
|
[w removeFromSuperview];
|
|
|
if ([w isKindOfClass:NSClassFromString(@"TXIJKSDLGLView")])
|
|
|
[w removeFromSuperview];
|
|
|
if ([w isKindOfClass:NSClassFromString(@"TXCAVPlayerView")])
|
|
|
[w removeFromSuperview];
|
|
|
}
|
|
|
}
|
|
|
/*
|
|
|
- (NSString *)_getResolutionName:(long)minEdge {
|
|
|
for (NSInteger i = 0; i < self.resolutions.count; ++ i) {
|
|
|
SPResolutionDefination *resDef = self.resolutions[i];
|
|
|
if (minEdge <= resDef.minEdge) {
|
|
|
return resDef.name;
|
|
|
}
|
|
|
}
|
|
|
return self.resolutions.lastObject.name;
|
|
|
}
|
|
|
|
|
|
- (NSArray<SuperPlayerUrl *>*)_getHLSDefinations:(NSArray<TXBitrateItem *> *)supportedBitrates {
|
|
|
NSMutableArray *definations = [NSMutableArray arrayWithCapacity:3];
|
|
|
[supportedBitrates enumerateObjectsUsingBlock:^(TXBitrateItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
|
SuperPlayerUrl *url = [[SuperPlayerUrl alloc] init];
|
|
|
url.title = [self _getResolutionName:MIN(obj.width, obj.height)];
|
|
|
[definations addObject:url];
|
|
|
}];
|
|
|
return definations;
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
-(void) onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary*)param
|
|
|
{
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
if (EvtID != PLAY_EVT_PLAY_PROGRESS) {
|
|
|
NSString *desc = [param description];
|
|
|
NSLog(@"%@", [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]);
|
|
|
}
|
|
|
|
|
|
float duration = 0;
|
|
|
if (self.originalDuration > 0) {
|
|
|
duration = self.originalDuration;
|
|
|
} else {
|
|
|
duration = player.duration;
|
|
|
}
|
|
|
|
|
|
if (EvtID == PLAY_EVT_PLAY_BEGIN || EvtID == PLAY_EVT_RCV_FIRST_I_FRAME) {
|
|
|
[self setNeedsLayout];
|
|
|
[self layoutIfNeeded];
|
|
|
self.isLoaded = YES;
|
|
|
[self _removeOldPlayer];
|
|
|
[self.vodPlayer setupVideoWidget:self insertIndex:0];
|
|
|
[self layoutSubviews]; // 防止横屏状态下添加view显示不全
|
|
|
self.state = StatePlaying;
|
|
|
|
|
|
// if (self.playerModel.playDefinitions.count == 0) {
|
|
|
[self updateBitrates:player.supportedBitrates];
|
|
|
// }
|
|
|
for (SPVideoFrameDescription *p in self.keyFrameDescList) {
|
|
|
if (player.duration > 0)
|
|
|
p.where = p.time/duration;
|
|
|
}
|
|
|
self.controlView.pointArray = self.keyFrameDescList;
|
|
|
|
|
|
// 不使用vodPlayer.autoPlay的原因是暂停的时候会黑屏,影响体验
|
|
|
if (!self.autoPlay) {
|
|
|
self.autoPlay = YES; // 下次用户设置自动播放失效
|
|
|
[self pause];
|
|
|
}
|
|
|
|
|
|
if (self.originalDuration > 0) {
|
|
|
// 当前是试看
|
|
|
self.controlView.maxPlayableRatio = player.duration / self.originalDuration;
|
|
|
}
|
|
|
}
|
|
|
if (EvtID == PLAY_EVT_VOD_PLAY_PREPARED) {
|
|
|
// 防止暂停导致加载进度不消失
|
|
|
if (self.isPauseByUser)
|
|
|
[self.spinner stopAnimating];
|
|
|
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerDidStart:)]) {
|
|
|
[self.delegate superPlayerDidStart:self];
|
|
|
}
|
|
|
}
|
|
|
if (EvtID == PLAY_EVT_PLAY_PROGRESS) {
|
|
|
if (self.state == StateStopped)
|
|
|
return;
|
|
|
if ((int) self.playCurrentTime != (int) player.currentPlaybackTime) {
|
|
|
[self.delegate onPlayProgressChange:(int) player.currentPlaybackTime
|
|
|
duration:(int) duration];
|
|
|
}
|
|
|
|
|
|
self.playCurrentTime = player.currentPlaybackTime;
|
|
|
CGFloat totalTime = duration;
|
|
|
CGFloat value = player.currentPlaybackTime / duration;
|
|
|
|
|
|
[self.controlView setProgressTime:self.playCurrentTime
|
|
|
totalTime:totalTime
|
|
|
progressValue:value
|
|
|
playableValue:player.playableDuration / duration];
|
|
|
} else if (EvtID == PLAY_EVT_PLAY_END) {
|
|
|
[self.controlView setProgressTime:[self playDuration]
|
|
|
totalTime:[self playDuration]
|
|
|
progressValue:player.duration/duration
|
|
|
playableValue:player.duration/duration];
|
|
|
[self moviePlayDidEnd];
|
|
|
} else if (EvtID == PLAY_ERR_NET_DISCONNECT || EvtID == PLAY_ERR_FILE_NOT_FOUND || EvtID == PLAY_ERR_HLS_KEY /*|| EvtID == PLAY_ERR_VOD_LOAD_LICENSE_FAIL*/) {
|
|
|
// DRM视频播放失败自动降级
|
|
|
// if ([self.playerModel.drmType isEqualToString:kDrmType_FairPlay]) {
|
|
|
// if ([self.playerModel canSetDrmType:kDrmType_SimpleAES]) {
|
|
|
// self.playerModel.drmType = kDrmType_SimpleAES;
|
|
|
// NSLog(@"降级SimpleAES");
|
|
|
// } else {
|
|
|
// NSLog(@"降级无加密");
|
|
|
// self.playerModel.drmType = nil;
|
|
|
// }
|
|
|
// [self configTXPlayer];
|
|
|
// return;
|
|
|
// } else if ([self.playerModel.drmType isEqualToString:kDrmType_SimpleAES]) {
|
|
|
// NSLog(@"降级无加密");
|
|
|
// self.playerModel.drmType = nil;
|
|
|
// [self configTXPlayer];
|
|
|
// return;
|
|
|
// }
|
|
|
|
|
|
if (EvtID == PLAY_ERR_NET_DISCONNECT) {
|
|
|
[self showMiddleBtnMsg:kStrBadNetRetry withAction:ActionContinueReplay];
|
|
|
} else {
|
|
|
[self showMiddleBtnMsg:kStrLoadFaildRetry withAction:ActionRetry];
|
|
|
}
|
|
|
self.state = StateFailed;
|
|
|
[player stopPlay];
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) {
|
|
|
[self.delegate superPlayerError:self errCode:EvtID errMessage:param[EVT_MSG]];
|
|
|
}
|
|
|
} else if (EvtID == PLAY_EVT_PLAY_LOADING){
|
|
|
// 当缓冲是空的时候
|
|
|
self.state = StateBuffering;
|
|
|
} else if (EvtID == PLAY_EVT_VOD_LOADING_END) {
|
|
|
[self.spinner stopAnimating];
|
|
|
} else if (EvtID == PLAY_EVT_CHANGE_RESOLUTION) {
|
|
|
if (player.height != 0) {
|
|
|
self.videoRatio = (GLfloat)player.width / player.height;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 更新当前播放的视频信息,包括清晰度、码率等
|
|
|
- (void)updateBitrates:(NSArray<TXBitrateItem *> *)bitrates;
|
|
|
{
|
|
|
if (bitrates.count > 0) {
|
|
|
if (self.resolutions) {
|
|
|
if (_playerModel.multiVideoURLs == nil) {
|
|
|
NSMutableArray *urlDefs = [[NSMutableArray alloc] initWithCapacity:self.resolutions.count];
|
|
|
for (SPSubStreamInfo *info in self.resolutions) {
|
|
|
SuperPlayerUrl *url = [[SuperPlayerUrl alloc] init];
|
|
|
url.title = info.resolutionName;
|
|
|
[urlDefs addObject:url];
|
|
|
}
|
|
|
_playerModel.playingDefinition = _playerModel.multiVideoURLs.firstObject.title;
|
|
|
}
|
|
|
} else {
|
|
|
NSArray *titles = [TXBitrateItemHelper sortWithBitrate:bitrates];
|
|
|
_playerModel.multiVideoURLs = titles;
|
|
|
self.netWatcher.playerModel = _playerModel;
|
|
|
if (_playerModel.playingDefinition == nil)
|
|
|
_playerModel.playingDefinition = self.netWatcher.adviseDefinition;
|
|
|
}
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:self.autoPlay];
|
|
|
[self.vodPlayer setBitrateIndex:_playerModel.playingDefinitionIndex];
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#pragma mark - 直播回调
|
|
|
|
|
|
- (void)onPlayEvent:(int)EvtID withParam:(NSDictionary *)param {
|
|
|
NSDictionary* dict = param;
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
if (EvtID != PLAY_EVT_PLAY_PROGRESS) {
|
|
|
NSString *desc = [param description];
|
|
|
NSLog(@"%@", [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]);
|
|
|
}
|
|
|
|
|
|
if (EvtID == PLAY_EVT_PLAY_BEGIN || EvtID == PLAY_EVT_RCV_FIRST_I_FRAME) {
|
|
|
if (!self.isLoaded) {
|
|
|
[self setNeedsLayout];
|
|
|
[self layoutIfNeeded];
|
|
|
self.isLoaded = YES;
|
|
|
[self _removeOldPlayer];
|
|
|
[self.livePlayer setupVideoWidget:CGRectZero containView:self insertIndex:0];
|
|
|
[self layoutSubviews]; // 防止横屏状态下添加view显示不全
|
|
|
self.state = StatePlaying;
|
|
|
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerDidStart:)]) {
|
|
|
[self.delegate superPlayerDidStart:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (self.state == StateBuffering)
|
|
|
self.state = StatePlaying;
|
|
|
[self.netWatcher loadingEndEvent];
|
|
|
} else if (EvtID == PLAY_EVT_PLAY_END) {
|
|
|
[self moviePlayDidEnd];
|
|
|
} else if (EvtID == PLAY_ERR_NET_DISCONNECT) {
|
|
|
if (self.isShiftPlayback) {
|
|
|
[self controlViewReload:self.controlView];
|
|
|
[self showMiddleBtnMsg:kStrTimeShiftFailed withAction:ActionRetry];
|
|
|
[self.middleBlackBtn fadeOut:2];
|
|
|
} else {
|
|
|
[self showMiddleBtnMsg:kStrBadNetRetry withAction:ActionRetry];
|
|
|
self.state = StateFailed;
|
|
|
}
|
|
|
if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) {
|
|
|
[self.delegate superPlayerError:self errCode:EvtID errMessage:param[EVT_MSG]];
|
|
|
}
|
|
|
} else if (EvtID == PLAY_EVT_PLAY_LOADING){
|
|
|
// 当缓冲是空的时候
|
|
|
self.state = StateBuffering;
|
|
|
if (!self.isShiftPlayback) {
|
|
|
[self.netWatcher loadingEvent];
|
|
|
}
|
|
|
} else if (EvtID == PLAY_EVT_STREAM_SWITCH_SUCC) {
|
|
|
[self showMiddleBtnMsg:[@"已切换为" stringByAppendingString:self.playerModel.playingDefinition] withAction:ActionNone];
|
|
|
[self.middleBlackBtn fadeOut:1];
|
|
|
} else if (EvtID == PLAY_ERR_STREAM_SWITCH_FAIL) {
|
|
|
[self showMiddleBtnMsg:kStrHDSwitchFailed withAction:ActionRetry];
|
|
|
self.state = StateFailed;
|
|
|
} else if (EvtID == PLAY_EVT_PLAY_PROGRESS) {
|
|
|
if (self.state == StateStopped)
|
|
|
return;
|
|
|
NSInteger progress = [dict[EVT_PLAY_PROGRESS] intValue];
|
|
|
self.liveProgressTime = progress;
|
|
|
self.maxLiveProgressTime = MAX(self.maxLiveProgressTime, self.liveProgressTime);
|
|
|
|
|
|
if (self.isShiftPlayback) {
|
|
|
CGFloat sv = 0;
|
|
|
if (self.maxLiveProgressTime > MAX_SHIFT_TIME) {
|
|
|
CGFloat base = self.maxLiveProgressTime - MAX_SHIFT_TIME;
|
|
|
sv = (self.liveProgressTime - base) / MAX_SHIFT_TIME;
|
|
|
} else {
|
|
|
sv = self.liveProgressTime / (self.maxLiveProgressTime + 1);
|
|
|
}
|
|
|
[self.controlView setProgressTime:self.liveProgressTime totalTime:-1 progressValue:sv playableValue:0];
|
|
|
} else {
|
|
|
[self.controlView setProgressTime:self.maxLiveProgressTime totalTime:-1 progressValue:1 playableValue:0];
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 日志回调
|
|
|
-(void) onLog:(NSString*)log LogLevel:(int)level WhichModule:(NSString*)module
|
|
|
{
|
|
|
NSLog(@"%@:%@", module, log);
|
|
|
}
|
|
|
|
|
|
- (int)livePlayerType {
|
|
|
int playType = -1;
|
|
|
NSString *videoURL = self.playerModel.playingDefinitionUrl;
|
|
|
NSURLComponents *components = [NSURLComponents componentsWithString:videoURL];
|
|
|
NSString *scheme = [[components scheme] lowercaseString];
|
|
|
if ([scheme isEqualToString:@"rtmp"]) {
|
|
|
playType = PLAY_TYPE_LIVE_RTMP;
|
|
|
} else if ([scheme hasPrefix:@"http"]
|
|
|
&& [[components path].lowercaseString hasSuffix:@".flv"]) {
|
|
|
playType = PLAY_TYPE_LIVE_FLV;
|
|
|
}
|
|
|
return playType;
|
|
|
}
|
|
|
|
|
|
- (void)reportPlay {
|
|
|
if (self.reportTime == nil)
|
|
|
return;
|
|
|
int usedtime = -[self.reportTime timeIntervalSinceNow];
|
|
|
if (self.isLive) {
|
|
|
[DataReport report:@"superlive" param:@{@"usedtime":@(usedtime)}];
|
|
|
} else {
|
|
|
[DataReport report:@"supervod" param:@{@"usedtime":@(usedtime), @"fileid":@(self.playerModel.videoId.fileId?1:0)}];
|
|
|
}
|
|
|
if (self.imageSprite) {
|
|
|
[DataReport report:@"image_sprite" param:nil];
|
|
|
}
|
|
|
self.reportTime = nil;
|
|
|
}
|
|
|
|
|
|
#pragma mark - middle btn
|
|
|
|
|
|
- (UIButton *)middleBlackBtn
|
|
|
{
|
|
|
if (_middleBlackBtn == nil) {
|
|
|
_middleBlackBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
|
[_middleBlackBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
|
|
_middleBlackBtn.titleLabel.font = [UIFont systemFontOfSize:14.0];
|
|
|
_middleBlackBtn.backgroundColor = RGBA(0, 0, 0, 0.7);
|
|
|
[_middleBlackBtn addTarget:self action:@selector(middleBlackBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
[self addSubview:_middleBlackBtn];
|
|
|
[_middleBlackBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.center.equalTo(self);
|
|
|
make.height.mas_equalTo(33);
|
|
|
}];
|
|
|
}
|
|
|
return _middleBlackBtn;
|
|
|
}
|
|
|
|
|
|
- (void)showMiddleBtnMsg:(NSString *)msg withAction:(ButtonAction)action {
|
|
|
[self.middleBlackBtn setTitle:msg forState:UIControlStateNormal];
|
|
|
self.middleBlackBtn.titleLabel.text = msg;
|
|
|
self.middleBlackBtnAction = action;
|
|
|
CGFloat width = self.middleBlackBtn.titleLabel.attributedText.size.width;
|
|
|
|
|
|
[self.middleBlackBtn mas_updateConstraints:^(MASConstraintMaker *make) {
|
|
|
make.width.equalTo(@(width+10));
|
|
|
}];
|
|
|
[self.middleBlackBtn fadeShow];
|
|
|
}
|
|
|
|
|
|
- (void)middleBlackBtnClick:(UIButton *)btn
|
|
|
{
|
|
|
switch (self.middleBlackBtnAction) {
|
|
|
case ActionNone:
|
|
|
break;
|
|
|
case ActionContinueReplay: {
|
|
|
if (!self.isLive) {
|
|
|
self.startTime = self.playCurrentTime;
|
|
|
}
|
|
|
[self configTXPlayer];
|
|
|
}
|
|
|
break;
|
|
|
case ActionRetry:
|
|
|
[self reloadModel];
|
|
|
break;
|
|
|
case ActionSwitch:
|
|
|
[self controlViewSwitch:self.controlView withDefinition:self.netWatcher.adviseDefinition];
|
|
|
[self resetControlViewWithLive:self.isLive
|
|
|
shiftPlayback:self.isShiftPlayback
|
|
|
isPlaying:YES];
|
|
|
break;
|
|
|
case ActionIgnore:
|
|
|
return;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
[btn fadeOut:0.2];
|
|
|
}
|
|
|
|
|
|
- (UIView *)maskView {
|
|
|
if (!_maskView) {
|
|
|
_maskView = [[UIView alloc] initWithFrame:CGRectZero];
|
|
|
[_maskView setBackgroundColor:[[UIColor blackColor]colorWithAlphaComponent:0.3]];
|
|
|
[self addSubview:_maskView];
|
|
|
[_maskView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.edges.mas_equalTo(UIEdgeInsetsZero);
|
|
|
}];
|
|
|
}
|
|
|
return _maskView;
|
|
|
}
|
|
|
|
|
|
- (UIButton *)repeatBtn {
|
|
|
if (!_repeatBtn) {
|
|
|
_repeatBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
|
[_repeatBtn setImage:SuperPlayerImage(@"repeat_video") forState:UIControlStateNormal];
|
|
|
[_repeatBtn addTarget:self action:@selector(repeatBtnClick:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
[self addSubview:_repeatBtn];
|
|
|
[_repeatBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.center.equalTo(self);
|
|
|
}];
|
|
|
}
|
|
|
return _repeatBtn;
|
|
|
}
|
|
|
|
|
|
- (UIButton *)repeatBackBtn {
|
|
|
if (!_repeatBackBtn) {
|
|
|
_repeatBackBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
|
[_repeatBackBtn setImage:SuperPlayerImage(@"back_full") forState:UIControlStateNormal];
|
|
|
[_repeatBackBtn addTarget:self action:@selector(controlViewBackAction:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
[self addSubview:_repeatBackBtn];
|
|
|
[_repeatBackBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.left.equalTo(self).offset(15);
|
|
|
make.top.equalTo(self).offset(15);
|
|
|
make.width.mas_equalTo(@30);
|
|
|
}];
|
|
|
}
|
|
|
return _repeatBackBtn;
|
|
|
}
|
|
|
|
|
|
- (void)repeatBtnClick:(UIButton *)sender {
|
|
|
[self configTXPlayer];
|
|
|
}
|
|
|
|
|
|
- (MMMaterialDesignSpinner *)spinner {
|
|
|
if (!_spinner) {
|
|
|
_spinner = [[MMMaterialDesignSpinner alloc] init];
|
|
|
_spinner.lineWidth = 1;
|
|
|
_spinner.duration = 1;
|
|
|
_spinner.hidden = YES;
|
|
|
_spinner.hidesWhenStopped = YES;
|
|
|
_spinner.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
|
|
|
[self addSubview:_spinner];
|
|
|
[_spinner mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.center.equalTo(self);
|
|
|
make.width.with.height.mas_equalTo(45);
|
|
|
}];
|
|
|
}
|
|
|
return _spinner;
|
|
|
}
|
|
|
|
|
|
- (UIImageView *)coverImageView {
|
|
|
if (!_coverImageView) {
|
|
|
_coverImageView = [[UIImageView alloc] init];
|
|
|
_coverImageView.userInteractionEnabled = YES;
|
|
|
_coverImageView.contentMode = UIViewContentModeScaleAspectFit;
|
|
|
_coverImageView.alpha = 0;
|
|
|
[self insertSubview:_coverImageView belowSubview:self.controlView];
|
|
|
[_coverImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
make.edges.mas_equalTo(UIEdgeInsetsZero);
|
|
|
}];
|
|
|
}
|
|
|
return _coverImageView;
|
|
|
}
|
|
|
|
|
|
- (void)uiHideDanmu {
|
|
|
[_controlView hideDanmu];
|
|
|
}
|
|
|
|
|
|
- (void)uiHideReplay {
|
|
|
[_controlView hideReplay];
|
|
|
}
|
|
|
|
|
|
@end
|