VLC的集成和使用
VLC介绍
VLC Media Player (VideoLAN) 为 Windows、Linux、OS X、Android、iOS、Windows Phone等平台提供一个视频播放器、解码器。它可以播放来自网络、摄像头、磁盘、光驱的文件,支持包括MPEG 1/2/4, H264, VC-1, DivX, WMV, Vorbis, AC3, AAC等格式的解码。在 Windows 和 Linux 上的 VLC 是使用C++/Qt写成,提供了一致的用户体验。同时 VLC 还专门为 OS X 提供了原生版本,OS X 版的 VLC 的用户界面使用Cocoa框架编写,在 OS X 下拥有卓越的原生体验。
- VLC还有一个非常好的功能——播放那些没有下载完整的视频文件。
- VLC几乎覆盖所有媒体格式。
VLC集成
在iOS下,我们可以很方便的使用VLC,因为它经行了优秀的封装,源码中最核心的部分被封装成了独立的库(MoblieVLVKit.framework库),它是基于FFmpeg,Live555提供完整的媒体播放库,所以整一个库体积还是比较大(目前已经超过600M了),不过并不会太影响App的大小,经行App打包发布的是会自动超级压缩的。经过测试它比使用FFmpeg库仅仅是多7M左右的大小。
- VLC cocoapods方式集成
VLC可以有多种的方式安装,目前最简单的方式就是cocoapods安装,以前需要下载源码后经行编译,编译后的还分模拟器版和真机版,最后还得使用lipo 命令经行合并,比较麻烦,这里我就不进行介绍了,因为没必要(喜欢折腾的可以自行搜索相关资料),最终编译得到的结果一样是MoblieVLCKit.framework库,即使项目不允许使用cocoapods,也可以建立空项目后使用cocoapods得到MoblieVLCKit.framework在经行移植。
- 步骤
```objc
1.在podfile中写入:pod 'MobileVLCKit'
2.终端执行pod install即可(成功后会在项目里看到MoblieVLCKit.framework库)
3.添加依赖库:libz.tbd、libbz2.tbd、libiconv.tbd、libstdc++.6.0.9.tbd
```
VLC使用
MoblieVLCKit为我们提供了很多功能接口,不过我们不需要全部了解,只需要懂得几个常用的API接口就可以轻轻松松的写出一个播放器出来。因为它支持定制化UI,所以有心的开发者可以结合漂亮的UI设计一套专属的播放器
- MoblieVLCKit 常用API介绍
- VLCMediaPlayer
VCL对象,管理着播放的开始暂停等操作,有着几个代理方法可以经行状态和时间的监听回调
- VLCMediaPlayer属性
// 播放设置,比如设置播放路径是本地播放还是网络播放,以及播放的画面映射到哪个View
- VLCMediaPlayer方法 ```objc // 开始播放 -(BOOL)play; // 暂停播放 - (void)pause; // 停止播放 - (void)stop; /** *快进播放 *interval:需要快进的秒数 */ - (void)jumpForward:(int)interval; /** *快退播放 *interval:需要快退的秒数 */ - (void)jumpBackward:(int)interval; // 短时间的快退(10秒) - (void)shortJumpBackward; // 短时间的快进(10秒) - (void)shortJumpForward; /** * 以一定倍速播放 * rate:倍速 */ - (void)fastForwardAtRate:(float)rate;
- VLCMediaPlayer代理
// 播放状态改变的回调 - (void)mediaPlayerStateChanged:(NSNotification *)aNotification; // 播放时间改变的回调 - (void)mediaPlayerTimeChanged:(NSNotification *)aNotification;
- VLCMediaPlayer
- MoblieVLCKit使用demo
我就抛砖迎玉贴上自己写的一份播放器demo封装(有时间我在放进github)
效果预览
-
VLCPlayer.h和VLCPlayer.m(VLC创建)
// // VLCPlayer.h // VLCDemo // // Created by pocket on 16/6/27. // Copyright © 2016年 pocket. All rights reserved. // // VLC播放对象 #import <Foundation/Foundation.h> #import <MobileVLCKit/MobileVLCKit.h> #import <UIKit/UIKit.h> @interface VLCPlayer : NSObject /** * VCL对象 */ @property (nonatomic, strong) VLCMediaPlayer *player; /** * 根据路径初始化VCL对象 * * @param playView 播放承载View * @param path 本地路径 * * @return VLCPlayer 类 */ - (id)initWithView:(UIView *)playView andMediaPath:(NSString *)path; /** * 根据URL初始化VCL对象 * * @param playView 播放承载View * @param url url路径 * * @return VLCPlayer 类 */ - (id)initWithView:(UIView *)playView andMediaURL:(NSURL *)url; /** * 开始播放 */ - (void)playMedia; /** * 暂停播放 */ - (void)stopPlaying; @end
// // VLCPlayer.m // VLCDemo // // Created by pocket on 16/6/27. // Copyright © 2016年 pocket. All rights reserved. // #import "VLCPlayer.h" @implementation VLCPlayer - (id)initWithView:(UIView *)playView andMediaPath:(NSString *)path { self = [super init]; if (self) { // 创建VCL对象 _player = [[VLCMediaPlayer alloc] init]; // 设置VCL播放界面的View _player.drawable = playView; // 设置需要加载的路径 VLCMedia *media = [VLCMedia mediaWithPath:path]; [_player setMedia:media]; } return self; } - (id)initWithView:(UIView *)playView andMediaURL:(NSURL *)url { self = [super init]; if (self) { _player = [[VLCMediaPlayer alloc] init]; _player.drawable = playView; // 设置需要加载的url VLCMedia *media = [VLCMedia mediaWithURL:url]; [_player setMedia:media]; } return self; } // 播放 - (void)playMedia { [_player play]; } // 暂停 - (void)stopPlaying { [_player stop]; } @end
-
VLCViewController.h/VLCViewController.m(播放控制器)
// VLCViewController.h // VLCDemo // // Created by pocket on 16/6/27. // Copyright © 2016年 pocket. All rights reserved. // // VLC播放控制器 #import <UIKit/UIKit.h> #import "VLCPlayer.h" @interface VLCViewController : UIViewController /** * 文件名字 */ @property (nonatomic, strong) NSString *playName; /** * 文件路径(本地资源路径) */ @property (nonatomic, strong) NSString *playPath; /** * 网络URL路径 */ @property (nonatomic, strong) NSURL *playURL; @property (nonatomic, strong) VLCPlayer *player; @end
// VLCViewController.m // VLCDemo // // Created by pocket on 16/6/27. // Copyright © 2016年 pocket. All rights reserved. // #import "VLCViewController.h" #import <AVFoundation/AVFoundation.h> #import<MobileVLCKit/MobileVLCKit.h> #import "VLCPlayerView.h" @interface VLCViewController ()<VLCMediaPlayerDelegate> /** * VLC承载视图 */ @property (nonatomic,strong) VLCPlayerView *vlcPlayerView; /** * 是否本地路径 */ @property (nonatomic,assign) BOOL isLocal; /** * 视频总时间(秒) */ @property (nonatomic,assign) int videoAllTime; /** * 当前播放时间(秒) */ @property (nonatomic,assign) int videoCurrentTime; /** * 当前进度 */ @property (nonatomic,assign) float currentProgress; /** * 菊花加载 */ @property (nonatomic,strong) UIActivityIndicatorView *activityView; @end @implementation VLCViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = self.playName; self.videoAllTime = 0; [self tarnsformView]; // 添加播放的承载View [self addVLCPalyerView]; // 菊花 self.activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; self.activityView.center = self.vlcPlayerView.center; [self.activityView setHidesWhenStopped:YES]; // 旋转时隐藏 [self.activityView startAnimating]; // 开始旋转 [self.view addSubview:self.activityView]; //视频播放 if (_playPath) { // 本地 _player = [[VLCPlayer alloc] initWithView:self.vlcPlayerView.playView andMediaPath:_playPath]; _player.player.delegate = self; [_player playMedia]; // 播放 self.isLocal = YES; } if (_playURL) { // 网络 _player = [[VLCPlayer alloc] initWithView:self.vlcPlayerView.playView andMediaURL:_playURL]; _player.player.delegate = self; [_player playMedia]; self.isLocal = NO; }
}
- (void)dealloc
{
[_player stopPlaying];
_player = nil;
}
+ (void)initialize
{
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self addObserver];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self removeObserver];
}
#pragma mark - -- 屏幕旋转控制处理
// 这里采用假旋转,因为其他界面并不需要支持横屏(缺点是状态栏不支持转动,所以采取presentView方式最好)
- (void)tarnsformView
{
// 旋转view
self.view.transform = CGAffineTransformMakeRotation(M_PI/2); // 旋转90°
CGRect frame = [UIScreen mainScreen].applicationFrame; // 获取当前屏幕大小
// 重新设置所有view的frame
self.view.bounds = CGRectMake(0, 0,frame.size.height + 20,frame.size.width);
self.vlcPlayerView.frame = self.view.bounds;
}
// 隐藏状态栏显得和谐
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark - -- 播放的承载View的设置
- (void)addVLCPalyerView
{
//承载播放的视图初始化(自定义播放界面可在这里做UI定制)
self.vlcPlayerView = [[VLCPlayerView alloc] initWithFrame:self.view.bounds];
// 设置视频名称
self.vlcPlayerView.videoName = self.playName;
// 设置播放监听回调
__weak typeof(self) weakSelf = self;
self.vlcPlayerView.playBlock = ^(UIButton *playBtn){
[weakSelf playClick:playBtn];
};
// 设置进度条监听回调
self.vlcPlayerView.changeSliderBlock = ^(UISlider *sliderView){
[weakSelf changeProgress:sliderView];
};
// 设置屏幕锁监听回调
self.vlcPlayerView.lockBlock = ^(UIButton *lockBtn){
// 屏幕锁操作逻辑
// ...后续
};
// 设置返回按钮回调
self.vlcPlayerView.backBlock = ^{
// 关闭视图控制器
[weakSelf.player stopPlaying];
[weakSelf dismissViewControllerAnimated:YES completion:^{
}];
};
// 左右滑动手势结束回调
self.vlcPlayerView.endPanGesture = ^(float progress,int type){
if (type == 4) { // 快退
[weakSelf.player.player shortJumpBackward];
} else if (type == 3) { // 快进
[weakSelf.player.player shortJumpForward];
}
};
[self.view addSubview:self.vlcPlayerView];
}
#pragma mark - -- vlcPlayerView播放操作
// 播放(暂停)监听
- (void)playClick:(UIButton *)playBtn
{
if ([_player.player isPlaying]) { // 正在播放
[_player.player pause]; // 暂停
} else {
[_player.player play]; // 播放
}
}
// 倍率速度播放(一般很少使用)
- (void)fastForwardAtRate:(float)rate
{
[_player.player fastForwardAtRate:rate];
}
// 进度条拖拽
- (void)changeProgress:(UISlider *)sliderView
{
if (!_player.player.isPlaying) { // 防止暂停状态拖动(拖动触发播放)
[self.vlcPlayerView changePlayBtnState:YES];
[_player.player play];
}
// 根据拖动比例计算开始到播放节点的总秒数
int allSec = (int)(self.videoAllTime * sliderView.value);
// 根据当前播放秒数计算需要seek的秒数
int sec = abs(allSec - self.videoCurrentTime);
// 如果为获取到时间信息
if (sec == 0 && allSec == 0) {
[[AlertViewManager shareInstance] showPromptText:self.view context:CustomLocalizedString(@"未获取到视频总时间,请尝试手势快进", nil) minSize:CGSizeMake(60,44) afterDelay:2.0];
return;
}
NSLog(@"sec:%d",sec);
if (sec==0) { // 刚好等于视频总时间
[_player.player stop];
return;
}
if (self.currentProgress<=sliderView.value) { // 快进滑动
[_player.player jumpForward:sec]; // 快进播放
} else {
[_player.player jumpBackward:sec]; // 快退播放
}
}
#pragma mark - -- vlcPlayerView时间和进度刷新
- (void)updateTime
{
// 设置剩余时间
self.vlcPlayerView.remainingTime = [[_player.player remainingTime] stringValue];
// 设置当前时间
self.vlcPlayerView.currentTime = [[_player.player time] stringValue];
// 设置当前进度
self.vlcPlayerView.sliderValue = [_player.player position];
}
#pragma mark - -- KVO监听
// 添加监听
- (void)addObserver
{
// 监听VLC对象属性(时间和播放)
[_player.player addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
[_player.player addObserver:self forKeyPath:@"isPlaying" options:0 context:nil];
}
// 移除监听
- (void)removeObserver
{
[_player.player removeObserver:self forKeyPath:@"remainingTime"];
[_player.player removeObserver:self forKeyPath:@"isPlaying"];
}
// kvo监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
// 可以在这里设置显示的时间和进度
// position:VLCMediaPlayer对象中的进度比例,可用此值设置播放进度
// NSLog(@"[_player.player position]:%lf",[_player.player position]);
// remainingTime:VLCMediaPlayer对象中的剩余时间,stringValue可以转化为时间格式字符串
// NSLog(@"[[_player.player remainingTime] stringValue]:%@",[[_player.player remainingTime] stringValue]);
// time:VLCMediaPlayer对象中的当前时间
// NSLog(@"[[_player.player time] stringValue]:%@", [[_player.player time] stringValue]);
// NSLog(@"剩余的分钟:%@",[[_player.player remainingTime] minuteStringValue]);
// NSLog(@"播放的分钟:%@",[[_player.player time] minuteStringValue]);
// 记录当前进度
self.currentProgress = [_player.player position];
// 根据分钟计算播放的秒数(这里不够严格,还得加上秒数)
self.videoCurrentTime = [[[_player.player time] minuteStringValue] intValue] * 60;
// 根据剩余时间和已经播放的计算总秒数(这里不够严格,还得加上秒数)
self.videoAllTime = [[[_player.player remainingTime] minuteStringValue] intValue]*60 + self.videoCurrentTime;
// 有时候获取不到时间(个人想法是结合定时器和进度比例计算总时间等)
// ...
// 刷新最新时间和播放进度
[self updateTime];
// 停止菊花加载
if (self.activityView.isAnimating) {
[self.activityView stopAnimating];
}
}
#pragma mark - 监听程序进入前台和后台
#pragma mark - VLCMediaPlayerDelegate
// 播放状态改变的回调
- (void)mediaPlayerStateChanged:(NSNotification *)aNotification
{
/**
* VLCMediaPlayerStateStopped, //< Player has stopped
VLCMediaPlayerStateOpening, //< Stream is opening
VLCMediaPlayerStateBuffering, //< Stream is buffering
VLCMediaPlayerStateEnded, //< Stream has ended
VLCMediaPlayerStateError, //< Player has generated an error
VLCMediaPlayerStatePlaying, //< Stream is playing
VLCMediaPlayerStatePaused //< Stream is paused
*/
NSLog(@"mediaPlayerStateChanged");
NSLog(@"状态:%ld",(long)_player.player.state);
switch ((int)_player.player.state) {
case VLCMediaPlayerStateStopped: // 停止播放(播放完毕或手动stop)
{
[_player.player stop]; // 手动调用一次停止(一遍再次点击播放)
[self.vlcPlayerView changePlayBtnState:NO];
if (self.activityView.isAnimating) {
[self.activityView stopAnimating];
}
}
break;
case VLCMediaPlayerStateBuffering: // 播放中缓冲状态
{
// 显示菊花
if (!self.activityView.isAnimating) {
[self.activityView startAnimating];
}
}
break;
case VLCMediaPlayerStatePlaying: // 被暂停后开始播放
{
if (self.activityView.isAnimating) {
[self.activityView stopAnimating];
}
}
break;
case VLCMediaPlayerStatePaused: // 播放后被暂停
{
if (self.activityView.isAnimating) {
[self.activityView stopAnimating];
}
}
break;
}
}
// 播放时间改变的回调
- (void)mediaPlayerTimeChanged:(NSNotification *)aNotification
{
// NSLog(@"mediaPlayerTimeChanged");
}
@end
```
-
VLCPlayerView.h/VLCPlayerView.m(播放View设计)
// // VLCPlayerView.h // VLCDemo // // Created by pocket on 16/6/28. // Copyright © 2016年 pocket. All rights reserved. // // VLC播放界面 #import <UIKit/UIKit.h> @interface VLCPlayerView : UIView /** * 承载视频的View */ @property (nonatomic,strong) UIView *playView; /** * 视频名称 */ @property (nonatomic,copy) NSString *videoName; /** * 剩余时间 */ @property (nonatomic,copy) NSString *remainingTime; /** * 当前时间 */ @property (nonatomic,copy) NSString *currentTime; /** * 当前进度 */ @property (nonatomic,assign) float sliderValue; /** * 返回按钮监听回调 */ @property (nonatomic,copy) void (^backBlock)(void); /** * 屏幕锁监听回调 */ @property (nonatomic,copy) void (^lockBlock)(UIButton *lockBtn); /** * 播放(暂停)监听回调 */ @property (nonatomic,copy) void (^playBlock)(UIButton *playBtn); /** * 进度条监听回调 */ @property (nonatomic,copy) void (^changeSliderBlock)(UISlider *sliderView); /** * 拖动结束监听(progress:幅度,type: 3:右 ,4:左) */ @property (nonatomic,copy) void (^endPanGesture)(float progress,int type); /** * 变更播放(暂停)按钮状态 */ - (void)changePlayBtnState:(BOOL)select; @end
// // VLCPlayerView.m // VLCDemo // // Created by pocket on 16/6/28. // Copyright © 2016年 pocket. All rights reserved. // #import "VLCPlayerView.h" #import "CustomSlider.h" #define titleFontSize 15.0 #define titleColor [UIColor whiteColor] #define backgroundViewColor RGB(0, 0, 0, 0.6) #define space 15.0 #define alphaDef 0.5 #define viewHeight 60.0 #define gestureMinimumTranslation 20.0 // 定义滑动手势类型 typedef enum { PlayerMoveDirectionNone = 0, PlayerMoveDirectionUp, PlayerMoveDirectionDown, PlayerMoveDirectionRight, PlayerMoveDirectionLeft }PlayerMoveDirection; @interface VLCPlayerView() /*********************顶部栏*************************************/ @property (nonatomic,strong) UIView *topView; // 顶部view @property (nonatomic,strong) UIButton *backBtn; // 返回按钮 @property (nonatomic,strong) UILabel *nameLabel; // 名字 /*********************底部栏*************************************/ @property (nonatomic,strong) UIView *bottomView;// 底部View // 开始播放(暂停)按钮 @property (nonatomic,strong) UIButton *playBtn; @property (nonatomic,strong) CustomSlider *sliderView; // 滑动条 // 当前时间标签 @property (nonatomic,strong) UILabel *currentTimeLabel; // 结束时间标签 @property (nonatomic,strong) UILabel *endTimeLabel; /*********************快进/快退显示********************************/ @property (nonatomic,strong) UIView *baseView;// 快进/快退底View @property (nonatomic,strong) UIButton *changeBtn; @property (nonatomic,strong) UILabel *progressTitle; @property (nonatomic,strong) UIProgressView *progressView; // 屏幕锁 @property (nonatomic,strong) UIButton *lockBtn; // sliderView是否正在滑动 @property (nonatomic,assign) BOOL isSliding; // 滑动手势类型 @property (nonatomic,assign) PlayerMoveDirection moveDirection; // 开始拖动的位置 @property (nonatomic,assign) CGPoint beginPoint; // 当前拖动的位置 @property (nonatomic,assign) CGPoint currentPoint; @end @implementation VLCPlayerView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setUI]; // 添加手势 [self addGesture]; // 5秒后自动隐藏 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.topView.alpha>0.0) { [self oneGestureClick]; } }); } return self; } - (void)setUI { // 承载视频view self.playView = [[UIView alloc] init]; self.playView.backgroundColor = [UIColor blackColor]; [self addSubview:self.playView]; // 顶部View self.topView = [[UIView alloc] init]; self.topView.backgroundColor = backgroundViewColor; [self addSubview:self.topView]; // 名字 self.nameLabel = [[UILabel alloc] init]; self.nameLabel.textColor = titleColor; self.nameLabel.textAlignment = NSTextAlignmentCenter; self.nameLabel.font = [UIFont systemFontOfSize:titleFontSize]; [self.topView addSubview:self.nameLabel]; // 返回按钮 self.backBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [self.backBtn setImage:[UIImage imageNamed:@"ic_jt_bf"] forState:UIControlStateNormal]; [self.backBtn addTarget:self action:@selector(backClick) forControlEvents:UIControlEventTouchUpInside]; [self.topView addSubview:self.backBtn]; // 底部view self.bottomView = [[UIView alloc] init]; self.bottomView.backgroundColor = backgroundViewColor; [self addSubview:self.bottomView]; // 暂停 self.playBtn = [UIButton buttonWithType:UIButtonTypeCustom]; self.playBtn.selected = YES; [self.playBtn setImage:[UIImage imageNamed:@"ic_bf_zt"] forState:UIControlStateSelected]; [self.playBtn setImage:[UIImage imageNamed:@"ic_bf_zt"] forState:UIControlStateNormal]; [self.playBtn setAdjustsImageWhenHighlighted:NO]; // 设置无高亮状态 [self.playBtn addTarget:self action:@selector(playBtnClick) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:self.playBtn]; // 当前标签 self.currentTimeLabel = [[UILabel alloc] init]; self.currentTimeLabel.textColor = titleColor; self.currentTimeLabel.text = @"--:--"; self.currentTimeLabel.textAlignment = NSTextAlignmentLeft; self.currentTimeLabel.font = [UIFont systemFontOfSize:titleFontSize]; [self.bottomView addSubview:self.currentTimeLabel]; // 结束标签 self.endTimeLabel = [[UILabel alloc] init]; self.endTimeLabel.textColor = titleColor; self.endTimeLabel.text = @"--:--"; self.endTimeLabel.textAlignment = NSTextAlignmentRight; self.endTimeLabel.font = [UIFont systemFontOfSize:titleFontSize]; [self.bottomView addSubview:self.endTimeLabel]; // 滑动条 self.sliderView = [[CustomSlider alloc] init]; self.sliderView.minimumTrackTintColor = TedcallStorageProgressColor; // 设置滑动过的颜色 self.sliderView.maximumTrackTintColor = [UIColor grayColor]; // 设置总长度颜色 self.sliderView.thumbTintColor = RGB(255, 255, 255, 0.7); // 设置滑块颜色 [self.sliderView addTarget:self action:@selector(slideringListening) forControlEvents:UIControlEventValueChanged]; [self.sliderView addTarget:self action:@selector(sliderChange) forControlEvents:UIControlEventTouchUpInside]; [self.bottomView addSubview:self.sliderView]; // 快进/快退view self.baseView = [[UIView alloc] init]; self.baseView.backgroundColor = backgroundViewColor; self.baseView.layer.cornerRadius = 8.0; self.baseView.layer.masksToBounds = YES; self.baseView.alpha = 0.0; [self addSubview:self.baseView]; self.changeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [self.changeBtn setImage:[UIImage imageNamed:@"ic_kj_bf"] forState:UIControlStateNormal]; [self.changeBtn setImage:[UIImage imageNamed:@"ic_kt_bf"] forState:UIControlStateSelected]; [self.baseView addSubview:self.changeBtn]; self.progressTitle = [[UILabel alloc] init]; self.progressTitle.textColor = titleColor; self.progressTitle.textAlignment = NSTextAlignmentCenter; self.progressTitle.font = [UIFont systemFontOfSize:titleFontSize]; // self.progressTitle.text = @"30秒"; [self.baseView addSubview:self.progressTitle]; self.progressView = [[UIProgressView alloc] init]; self.progressView.trackTintColor = [UIColor clearColor]; self.progressView.progressTintColor = TedcallStorageProgressColor; [self.progressView setProgress:0.0]; [self.baseView addSubview:self.progressView]; // 屏幕锁 self.lockBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [self.lockBtn setImage:[UIImage imageNamed:@"ic_sp_bf"] forState:UIControlStateNormal]; [self.lockBtn setImage:[UIImage imageNamed:@"ic_ks_bf"] forState:UIControlStateSelected]; [self.lockBtn addTarget:self action:@selector(lockClick) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.lockBtn]; } - (void)setVideoName:(NSString *)videoName { _videoName = videoName; self.nameLabel.text = videoName; } - (void)setCurrentTime:(NSString *)currentTime { _currentTime = currentTime; self.currentTimeLabel.text = currentTime; } - (void)setRemainingTime:(NSString *)remainingTime { _remainingTime = remainingTime; self.endTimeLabel.text = remainingTime; } - (void)setSliderValue:(float)sliderValue { _sliderValue = sliderValue; if (!self.isSliding) { // 防止滑动过程中的手动设值 [self .sliderView setValue:sliderValue animated:YES]; } } // 返回按钮监听 - (void)backClick { if (self.backBlock) { self.backBlock(); // block返回回调 } } // 播放(暂停)监听 - (void)playBtnClick { NSLog(@"twoGesture"); self.playBtn.selected = !self.playBtn.selected; [self changePlayBtnState:self.playBtn.selected]; if (self.playBlock) { self.playBlock(self.playBtn); } } // 屏幕锁监听 - (void)lockClick { self.lockBtn.selected = !self.lockBtn.selected; if (self.lockBtn.selected) { self.playView.userInteractionEnabled = NO; [UIView transitionWithView:self.lockBtn duration:1.0 options:0 animations:^{ self.topView.alpha = 0.0; self.bottomView.alpha = 0.0; self.lockBtn.alpha = 0.0; } completion:^(BOOL finished) { }]; } else { self.topView.alpha = 1.0; self.bottomView.alpha = 1.0; self.playView.userInteractionEnabled = YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView transitionWithView:self.lockBtn duration:1.0 options:0 animations:^{ self.topView.alpha = 0.0; self.bottomView.alpha = 0.0; self.lockBtn.alpha = 0.0; } completion:^(BOOL finished) { }]; }); } } - (void)changePlayBtnState:(BOOL)select { self.playBtn.selected = select; if (select) { [self.playBtn setImage:[UIImage imageNamed:@"ic_bf_zt"] forState:UIControlStateNormal]; } else { [self.playBtn setImage:[UIImage imageNamed:@"ic_bf_ks"] forState:UIControlStateNormal]; } } // 滑动结束监听 - (void)sliderChange { // NSLog(@"滑动结束"); self.isSliding = NO; if (self.changeSliderBlock) { self.changeSliderBlock(self.sliderView); } } // 滑动监听 - (void)slideringListening { // NSLog(@"正在滑动"); if (!self.isSliding) { self.isSliding = YES; } } - (void)layoutSubviews { [super layoutSubviews]; CGFloat width = self.frame.size.width; CGFloat height = self.frame.size.height; // 承载视频View self.playView.frame = self.bounds; // 顶部view CGFloat topX = 0; CGFloat topY = 0; CGFloat topW = width; CGFloat topH = viewHeight - 10; self.topView.frame = CGRectMake(topX, topY, topW, topH); // 返回按钮 CGFloat backW = 30.0; CGFloat backH = backW; CGFloat backX = space; CGFloat backY = (topH - backH)/2; self.backBtn.frame = CGRectMake(backX, backY, backW, backH); // 视频名称 CGFloat nameX = space + CGRectGetMaxX(self.backBtn.frame); CGFloat nameH = 20.0; CGFloat nameW = width - 2*nameX; CGFloat nameY = (topH - nameH)/2; self.nameLabel.frame = CGRectMake(nameX, nameY, nameW, nameH); // 底部view CGFloat bottomX = 0; CGFloat bottomY = height - viewHeight; CGFloat bottomW = width; CGFloat bottomH = viewHeight; self.bottomView.frame = CGRectMake(bottomX, bottomY, bottomW, bottomH); // 播放(暂停)按钮 CGFloat playW = 40.0; CGFloat playH = playW; CGFloat playX = space; CGFloat playY = (viewHeight - playH)/2; self.playBtn.frame = CGRectMake(playX, playY, playW, playH); // 滑动条 CGFloat sliderX = space + CGRectGetMaxY(self.playBtn.frame); CGFloat sliderH = 6; CGFloat sliderW = width - sliderX - space; CGFloat sliderY = playY + playH/2 - 4; self.sliderView.frame = CGRectMake(sliderX, sliderY, sliderW, sliderH); // 当前标签 CGFloat currentX = sliderX; CGFloat currentY = CGRectGetMaxY(self.sliderView.frame); CGFloat currentH = 20.0; CGFloat currentW = sliderW/2; self.currentTimeLabel.frame = CGRectMake(currentX, currentY, currentW, currentH); // 结束标签 CGFloat endW = currentW; CGFloat endX = CGRectGetMaxX(self.sliderView.frame) - endW; CGFloat endY = currentY; CGFloat endH = currentH; self.endTimeLabel.frame = CGRectMake(endX, endY, endW, endH); // 快进/快退view CGFloat baseW = 140.0; CGFloat baseH = 74.0; CGFloat baseY = CGRectGetMaxY(self.topView.frame) + 20.0; CGFloat baseX = (width - baseW)/2; self.baseView.frame = CGRectMake(baseX, baseY, baseW, baseH); CGFloat changeW = 30.0; CGFloat changeH = 20.0; CGFloat changeX = (baseW - changeW)/2; CGFloat changeY = 8.0; self.changeBtn.frame = CGRectMake(changeX, changeY, changeW, changeH); CGFloat titleY = 8.0 + CGRectGetMaxY(self.changeBtn.frame); CGFloat titleX = 0; CGFloat titleW = baseW; CGFloat titleH = 20.0; self.progressTitle.frame = CGRectMake(titleX, titleY, titleW, titleH); CGFloat progressX = 3.0; CGFloat progressY = 8.0 + CGRectGetMaxY(self.progressTitle.frame); CGFloat progressW = baseW - 2*progressX; CGFloat progressH = 0.0; self.progressView.frame = CGRectMake(progressX, progressY, progressW, progressH); // 屏幕锁 CGFloat lockX = space; CGFloat lockW = 50.0; CGFloat lockH = 50.0; CGFloat lockY = (height - lockH)/2; self.lockBtn.frame = CGRectMake(lockX, lockY, lockW, lockH); } #pragma mark - -- 手势操作 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"touchesBegan"); if (self.lockBtn.selected) { // 锁屏状态 [UIView transitionWithView:self.lockBtn duration:1.0 options:0 animations:^{ self.lockBtn.alpha = 1.0; } completion:^(BOOL finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.lockBtn.selected) { [UIView transitionWithView:self.lockBtn duration:1.0 options:0 animations:^{ self.lockBtn.alpha = 0.0; } completion:^(BOOL finished) { }]; } }); }]; } } // 添加手势处理 - (void)addGesture { // 单点 UITapGestureRecognizer *oneGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(oneGestureClick)]; oneGesture.numberOfTapsRequired = 1; // 单击 oneGesture.numberOfTouchesRequired = 1; // 单指单击 [self.playView addGestureRecognizer:oneGesture]; // 双击 UITapGestureRecognizer *twoGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playBtnClick)]; twoGesture.numberOfTapsRequired = 2; // 双击 twoGesture.numberOfTouchesRequired = 1; // 单指双击 [self.playView addGestureRecognizer:twoGesture]; // 长按 UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGestureClick:)]; longGesture.minimumPressDuration = 3.0; // 长按3秒触发 [self.playView addGestureRecognizer:longGesture]; // 拖动 UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureClick:)]; [self.playView addGestureRecognizer:panGesture]; //解决拖动和长按手势之间的冲突 [longGesture requireGestureRecognizerToFail:panGesture]; // 解决单击和双击手势的冲突 [oneGesture requireGestureRecognizerToFail:twoGesture]; } // 单击手势监听 - (void)oneGestureClick { NSLog(@"oneGestureClick"); if (self.topView.alpha<=0.0) { // 显示 // 动画显示 [UIView transitionWithView:self.topView duration:1.0 options:0 animations:^{ self.topView.alpha = 1.0; self.bottomView.alpha = 1.0; self.lockBtn.alpha = 1.0; } completion:^(BOOL finished) { // 5秒后自动隐藏 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.topView.alpha>0.0) { [self oneGestureClick]; } }); }]; } else { // 动画隐藏 [UIView transitionWithView:self.topView duration:1.0 options:0 animations:^{ self.topView.alpha = 0.0; self.bottomView.alpha = 0.0; self.lockBtn.alpha = 0.0; } completion:^(BOOL finished) { }]; } } // 长按监听 - (void)longGestureClick:(UILongPressGestureRecognizer *)longGesture { // 长按手势会调用多次监听方法,先判断手势状态 if (longGesture.state == UIGestureRecognizerStateBegan) { NSLog(@"长按开始"); } } // 拖动监听 - (void)panGestureClick:(UIPanGestureRecognizer *)panGesture { CGPoint translation = [panGesture translationInView:self.playView]; // 取得相对位置的偏移点(相对位置为手指第一次在屏幕的点) NSLog(@"translation:%@",NSStringFromCGPoint(translation)); if (panGesture.state == UIGestureRecognizerStateBegan){ NSLog (@"滑动开始"); self.beginPoint = translation; self.currentPoint = translation; }else if (panGesture.state == UIGestureRecognizerStateChanged){ self.moveDirection = [self determineCameraDirectionIfNeeded:translation]; switch (self.moveDirection) { case PlayerMoveDirectionDown: NSLog(@"PlayerMoveDirectionDown"); break; case PlayerMoveDirectionUp: NSLog(@"PlayerMoveDirectionUp"); break; case PlayerMoveDirectionRight: { NSLog(@"PlayerMoveDirectionRight"); self.changeBtn.selected = NO; self.progressTitle.text = CustomLocalizedString(@"快进", nil); if (self.baseView.alpha == 0.0) { self.baseView.alpha = 1.0; } } break; case PlayerMoveDirectionLeft: { NSLog(@"PlayerMoveDirectionLeft"); self.changeBtn.selected = YES; self.progressTitle.text = CustomLocalizedString(@"快退", nil); if (self.baseView.alpha == 0.0) { self.baseView.alpha = 1.0; } } break; default : break; } self.currentPoint = translation; // 刷新当前位置 }else if (panGesture.state == UIGestureRecognizerStateEnded){ NSLog (@"滑动结束"); [UIView transitionWithView:self.baseView duration:1.0 options:0 animations:^{ self.baseView.alpha = 0.0; } completion:^(BOOL finished) { }]; if (self.endPanGesture) { self.endPanGesture(0.5,self.moveDirection); } } } - (PlayerMoveDirection) determineCameraDirectionIfNeeded:(CGPoint)translation { // 设定一个幅度使拖动在不够水平(略微有点偏差)的方向上的处理,上下一般不处理 if (translation.x > self.currentPoint.x && (fabs(translation.y - self.currentPoint.y) <= gestureMinimumTranslation)){ // 说明水平向右拖动了 return PlayerMoveDirectionRight; }else if(translation.x < self.currentPoint.x && (fabs(translation.y - self.currentPoint.y) <= gestureMinimumTranslation)){ // 说明水平向左 return PlayerMoveDirectionLeft; }else if (translation.x == self.currentPoint.x && translation.y > self.currentPoint.y) { // 向下 return PlayerMoveDirectionDown; } else if (translation.x == self.currentPoint.x && translation.y < self.currentPoint.y) { // 向上 return PlayerMoveDirectionUp; } else{ return PlayerMoveDirectionNone; } } - (void)updateFastImage:(int)type { if (type == 4) { // 左 } else if (type == 3) { // 右 } } @end
-
CustomSlider.h/CustomSlider.m(自定义滑块)
// // CustomSlider.h // TedcallStorage // // Created by tedcall on 16/6/28. // Copyright © 2016年 pocket. All rights reserved. // #import <UIKit/UIKit.h> @interface CustomSlider : UISlider @end
// // CustomSlider.m // TedcallStorage // // Created by tedcall on 16/6/28. // Copyright © 2016年 pocket. All rights reserved. // #import "CustomSlider.h" @implementation CustomSlider - (void)drawRect:(CGRect)rect { UIImageView *imageView = nil; // 取得滑块View for (UIView *view in self.subviews) { if ([view isKindOfClass:[UIImageView class]]) { if (view.frame.size.width == view.frame.size.height) { imageView = (UIImageView *)view; } } } if (imageView) { // 有值 CGFloat redViewW = 8.0; CGFloat redViewH =redViewW; CGFloat redViewX = (imageView.frame.size.width - redViewW)/2; CGFloat redViewY = (imageView.frame.size.height - redViewH)/2; UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(redViewX, redViewY, redViewW, redViewH)]; redView.backgroundColor = RGB(255, 255, 255, 0.7); redView.layer.cornerRadius = redViewW/2; redView.layer.masksToBounds = YES; [imageView addSubview:redView]; } } // 重写进度条frame - (CGRect)trackRectForBounds:(CGRect)bounds { return CGRectMake(0,0,bounds.size.width,6.0); } // 解决两边空隙问题 - (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value { rect.origin.x = rect.origin.x - 10 ; rect.size.width = rect.size.width +20; return CGRectInset ([super thumbRectForBounds:bounds trackRect:rect value:value], 10 , 10); } @end
VLC Demo
因为VLC库比较大所以github 上的demo里并不包含这个库以及测试时的视频。我另外提供了下载地址供需要的朋友自行下载。
demo地址:https://github.com/pocket-live/VLC-Demo
VLC库和测试资源下载地址:http://pan.baidu.com/s/1pLiBQKb