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