iOS學(xué)習(xí)筆記26-視頻播放

一、視頻

在iOS中播放視頻可以使用兩個(gè)框架來(lái)實(shí)現(xiàn):

  1. MediaPlayer框架的MPMoviePlayerControllerMPMoviePlayerViewController
  1. AVFoundation框架中的AVPlayer
  2. AVKit框架的AVPlayerViewController【iOS8之后才有】

但在近兩年的WWDC上字管,MediaPlayer框架被iOS9標(biāo)記為deprcated悟狱,意味著它已經(jīng)不再被蘋果繼續(xù)維護(hù),而且該框架集成度較高篙程,不如AVFoundation靈活性高蟀给,所以這里就講AVFoundationAVPlayer來(lái)實(shí)現(xiàn)播放視頻,AVPlayerViewController實(shí)際上就是對(duì)AVPlayer的封裝随夸。

下面是兩個(gè)框架的應(yīng)用所在層:

二、AVPlayer

AVPlayer存在于AVFoundation中震放,它更加接近于底層宾毒,所以靈活性極高。
AVPlayer本身并不能顯示視頻殿遂,如果AVPlayer要顯示必須創(chuàng)建一個(gè)播放器圖層AVPlayerLayer用于展示诈铛,該播放器圖層繼承于CALayer

AVPlayer視頻播放使用步驟:
  1. 創(chuàng)建視頻資源地址URL墨礁,可以是網(wǎng)絡(luò)URL
  1. 通過(guò)URL創(chuàng)建視頻內(nèi)容對(duì)象AVPlayerItem幢竹,一個(gè)視頻對(duì)應(yīng)一個(gè)AVPlayerItem
  2. 創(chuàng)建AVPlayer視頻播放器對(duì)象,需要一個(gè)AVPlayerItem進(jìn)行初始化
  3. 創(chuàng)建AVPlayerLayer播放圖層對(duì)象恩静,添加到顯示視圖上去
  4. 播放器播放play焕毫,播放器暫停pause
  5. 添加通知中心監(jiān)聽(tīng)視頻播放完成,使用KVO監(jiān)聽(tīng)播放內(nèi)容的屬性變化
  6. 進(jìn)度條監(jiān)聽(tīng)是調(diào)用AVPlayer的對(duì)象方法:
-(id)addPeriodicTimeObserverForInterval:(CMTime)interval/*監(jiān)聽(tīng)頻率*/ 
                                     queue:(dispatch_queue_t)queue /*監(jiān)聽(tīng)GCD線程*/
                                usingBlock:(void (^)(CMTime time))block;/*監(jiān)聽(tīng)回調(diào)*/
測(cè)試環(huán)境搭建:
  1. 利用終端開(kāi)啟Apache服務(wù)驶乾,使得手機(jī)可以通過(guò)網(wǎng)絡(luò)訪問(wèn)本機(jī)資源


  1. 下載視頻MP4到Apache的Web資源目錄
    默認(rèn)的Apache的Web資源目錄是/Library/WebServer/Documents
  2. 查看本地服務(wù)器的IP


  3. 別忘了進(jìn)入info.plist設(shè)置HTTP網(wǎng)絡(luò)解禁


下面是一個(gè)具體的項(xiàng)目:
ViewController屬性
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (strong, nonatomic) AVPlayer *player;//視頻播放器
@property (strong, nonatomic) AVPlayerLayer *playerLayer;//視頻播放圖層
@property (strong, nonatomic) IBOutlet UIView *movieView;//播放容器視圖
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;//進(jìn)度條
@property (strong, nonatomic) IBOutlet UISegmentedControl *segmentView;//選擇欄
@property (strong, nonatomic) NSArray *playerItemArray;//視頻播放URL列表
@end
1. 初始化AVPlayerItem視頻內(nèi)容對(duì)象
/* 獲取播放內(nèi)容對(duì)象邑飒,一個(gè)AVPlayerItem對(duì)應(yīng)一個(gè)視頻文件 */
- (AVPlayerItem *)getPlayItemByNum:(NSInteger)num {
    if (num >= self.playerItemArray.count) {
        return nil;
    }
    //創(chuàng)建URL
    NSString *urlStr = self.playerItemArray[num];
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlStr];
    //創(chuàng)建播放內(nèi)容對(duì)象
    AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
    return item;
}
2. 初始化AVPlayer視頻播放器對(duì)象
/* 初始化視頻播放器 */
- (void)initAVPlayer {
    //獲取播放內(nèi)容
    AVPlayerItem *item = [self getPlayItemByNum:0];
    //創(chuàng)建視頻播放器
    AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
    self.player = player;
    //添加播放進(jìn)度監(jiān)聽(tīng)
    [self addProgressObserver];
    //添加播放內(nèi)容KVO監(jiān)聽(tīng)
    [self addObserverToPlayerItem:item];
    //添加通知中心監(jiān)聽(tīng)播放完成
    [self addNotificationToPlayerItem];
}
3. 初始化AVPlayerLayer播放圖層對(duì)象
#pragma mark - 初始化
/* 初始化播放器圖層對(duì)象 */
- (void)initAVPlayerLayer {
    //創(chuàng)建視頻播放器圖層對(duì)象
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    layer.frame = self.movieView.bounds;//尺寸大小
    layer.videoGravity = AVLayerVideoGravityResizeAspect;//視頻填充模式
    //添加進(jìn)控件圖層
    [self.movieView.layer addSublayer:layer];
    self.playerLayer = layer;
    self.movieView.layer.masksToBounds = YES;
}
4. 通知中心監(jiān)聽(tīng)播放完成
#pragma mark - 通知中心
- (void)addNotificationToPlayerItem {
    //添加通知中心監(jiān)聽(tīng)視頻播放完成
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerDidFinished:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:self.player.currentItem];
}
- (void)removeNotificationFromPlayerItem {
    //移除通知中心的通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
/* 播放完成后會(huì)調(diào)用 */
- (void)playerDidFinished:(NSNotification *)notification {
    //自動(dòng)播放下一個(gè)視頻
    NSInteger currentIndex = self.segmentView.selectedSegmentIndex;
    self.segmentView.selectedSegmentIndex = (currentIndex + 1)%self.playerItemArray.count;
    [self segmentValueChange:self.segmentView];
}
5. KVO屬性監(jiān)聽(tīng)
#pragma mark - KVO監(jiān)聽(tīng)屬性
/* 添加KVO,監(jiān)聽(tīng)播放狀態(tài)和緩沖加載狀況 */
- (void)addObserverToPlayerItem:(AVPlayerItem *)item {
    //監(jiān)控狀態(tài)屬性
    [item addObserver:self
           forKeyPath:@"status"
              options:NSKeyValueObservingOptionNew
              context:nil];
    //監(jiān)控緩沖加載情況屬性
    [item addObserver:self
           forKeyPath:@"loadedTimeRanges"
              options:NSKeyValueObservingOptionNew
              context:nil];
}
/* 移除KVO */
- (void)removeObserverFromPlayerItem:(AVPlayerItem *)item {
    [item removeObserver:self forKeyPath:@"status"];
    [item removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
/* 屬性發(fā)生變化级乐,KVO響應(yīng)函數(shù) */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
{
    AVPlayerItem *playerItem = (AVPlayerItem *)object;
    if ([keyPath isEqualToString:@"status"]) {//狀態(tài)發(fā)生改變
        AVPlayerStatus status = [[change objectForKey:@"new"] integerValue];
        if (status == AVPlayerStatusReadyToPlay) {
            NSLog(@"正在播放..幸乒,視頻總長(zhǎng)度為:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    } else if ( [keyPath isEqualToString:@"loadedTimeRanges"] ) {//緩沖區(qū)域變化
        NSArray *array = playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//已緩沖范圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長(zhǎng)度
        NSLog(@"共緩沖:%.2f",totalBuffer);
    }
}
6. 進(jìn)度條監(jiān)聽(tīng)
#pragma mark - 進(jìn)度監(jiān)聽(tīng)
- (void)addProgressObserver {
    AVPlayerItem *item = self.player.currentItem;
    UIProgressView *progress = self.progressView;
    //進(jìn)度監(jiān)聽(tīng)
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0)
                                              queue:dispatch_get_main_queue()
                                         usingBlock:^(CMTime time)
     {
         //CMTime是表示視頻時(shí)間信息的結(jié)構(gòu)體,包含視頻時(shí)間點(diǎn)唇牧、每秒幀數(shù)等信息
         //獲取當(dāng)前播放到的秒數(shù)
         float current = CMTimeGetSeconds(time);
         //獲取視頻總播放秒數(shù)
         float total = CMTimeGetSeconds(item.duration);
         if (current) {
             [progress setProgress:(current/total) animated:YES];
         }
     }];
}
7. UI點(diǎn)擊事件以及視圖控制器加載
- (void)viewDidLoad {
    [super viewDidLoad];
    //屬性初始化
    self.segmentView.selectedSegmentIndex = 0;
    self.progressView.progress = 0;
    self.playerItemArray = @[@"http://192.168.6.147/1.mp4",
                             @"http://192.168.6.147/2.mp4",
                             @"http://192.168.6.147/3.mp4"];
    //視頻播放器初始化
    [self initAVPlayer];
    //視頻播放器顯示圖層初始化
    [self initAVPlayerLayer];
    //視頻開(kāi)始播放
    [self.player play];

}
- (void)dealloc {
    //移除監(jiān)聽(tīng)和通知
    [self removeObserverFromPlayerItem:self.player.currentItem];
    [self removeNotificationFromPlayerItem];
}
#pragma mark UI點(diǎn)擊
/* 點(diǎn)擊播放按鈕 */
- (IBAction)playMovie:(UIButton *)sender {
    sender.enabled = NO;
    if ( self.player.rate == 0 ) {//播放速度為0罕扎,表示播放暫停
        sender.titleLabel.text = @"暫停";
        [self.player play];//啟動(dòng)播放
    } else if ( self.player.rate == 1.0 ) {//播放速度為1.0,表示正在播放
        sender.titleLabel.text = @"播放";
        [self.player pause];//暫停播放
    }
    sender.enabled = YES;
}
/* 選擇視頻播放列表 */
- (IBAction)segmentValueChange:(UISegmentedControl *)sender {
    //先移除對(duì)AVPlayerItem的所有監(jiān)聽(tīng)
    [self removeNotificationFromPlayerItem];
    [self removeObserverFromPlayerItem:self.player.currentItem];
    //獲取新的播放內(nèi)容
    AVPlayerItem *playerItem = [self getPlayItemByNum:sender.selectedSegmentIndex];
    //添加屬性監(jiān)聽(tīng)
    [self addObserverToPlayerItem:playerItem];
    //替換視頻內(nèi)容
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
    //添加播放完成監(jiān)聽(tīng)
    [self addNotificationToPlayerItem];
}
效果圖

三丐重、AVPlayerViewController

一個(gè)簡(jiǎn)單的視頻播放器就這么搞定了腔召,感覺(jué)還是好麻煩,而且很多功能還沒(méi)有實(shí)現(xiàn)扮惦。
實(shí)際上在iOS8.0之后臀蛛,蘋果為我們封裝了AVPlayer等視頻播放相關(guān)的類 ,形成了一個(gè)直接可以簡(jiǎn)單使用的播放器控制器類崖蜜,那就是AVPlayerViewController浊仆,下面來(lái)講下你就覺(jué)得有多爽,上面那一大堆豫领,只需要下面的一小塊代碼就可以實(shí)現(xiàn)了抡柿。

使用步驟:
  1. 導(dǎo)入框架:


  1. 添加頭文件:
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
  1. 創(chuàng)建URL
  2. 創(chuàng)建AVPlayer
  3. 創(chuàng)建AVPlayerViewController

Over宴卖,一個(gè)功能十分齊全的播放器就好了

下面是全部代碼【/(ㄒoㄒ)/~~淚奔】:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>
@interface ViewController ()
@property (strong, nonatomic) AVPlayerViewController *playerVC;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建URL
    NSURL *url = [NSURL URLWithString:@"http://192.168.6.147/1.mp4"];
    //直接創(chuàng)建AVPlayer湾蔓,它內(nèi)部也是先創(chuàng)建AVPlayerItem,這個(gè)只是快捷方法
    AVPlayer *player = [AVPlayer playerWithURL:url];
    //創(chuàng)建AVPlayerViewController控制器
    AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
    playerVC.player = player;
    playerVC.view.frame = self.view.frame;
    [self.view addSubview:playerVC.view];
    self.playerVC = playerVC;
    //調(diào)用控制器的屬性player的開(kāi)始播放方法
    [self.playerVC.player play];
}
@end

這酸爽不敢相信空幻,不過(guò)這個(gè)是iOS9才有的课蔬,就是為了替代
MediaPlayer框架的MPMoviePlayerViewController而定制的非常方便的視頻播放器
我用AVPlayer寫的視頻播放器被甩了好幾十條街囱稽,/(ㄒoㄒ)/~~。

四二跋、擴(kuò)展--生成視頻縮略圖

AVFoundation框架還提供了一個(gè)類AVAssetImageGenerator战惊,用于獲取視頻截圖。

應(yīng)用場(chǎng)景:
  • 播放視頻時(shí)扎即,拖動(dòng)進(jìn)度條時(shí)吞获,可以顯示視頻縮略圖,查看視頻播放到哪個(gè)畫(huà)面了
  • 選擇某個(gè)視頻播放的時(shí)候铺遂,可以使用視頻縮略圖衫哥,點(diǎn)擊視頻縮放圖,進(jìn)入真正的播放視頻界面
  • 一些有意思的視頻場(chǎng)景需要截屏留念的時(shí)候襟锐,可以使用視頻縮略圖
具體使用步驟:
  1. 創(chuàng)建AVURLAsset對(duì)象撤逢,該對(duì)象主要用于獲取媒體信息,包括視頻粮坞、聲音蚊荣。
  1. 根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator對(duì)象
  2. 使用對(duì)象方法copyCGImageAtTime:獲得指定時(shí)間點(diǎn)的截圖
-(CGImageRef)copyCGImageAtTime:(CMTime)requestedTime /* 要在視頻的哪個(gè)時(shí)間點(diǎn)生成縮略圖 */
                       actualTime:(CMTime *)actualTime /* 實(shí)際生成縮略圖的媒體時(shí)間 */
                            error:(NSError **)outError;/* 錯(cuò)誤信息 */
下面是實(shí)際代碼:
/* 獲取視頻縮略圖 */
- (UIImage *)getThumbailImageRequestAtTimeSecond:(CGFloat)timeBySecond {
    //視頻文件URL地址
    NSURL *url = [NSURL URLWithString:@"http://192.168.6.147/2.mp4"];
    //創(chuàng)建媒體信息對(duì)象AVURLAsset
    AVURLAsset *urlAsset = [AVURLAsset assetWithURL:url];
    //創(chuàng)建視頻縮略圖生成器對(duì)象AVAssetImageGenerator
    AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
    //創(chuàng)建視頻縮略圖的時(shí)間,第一個(gè)參數(shù)是視頻第幾秒莫杈,第二個(gè)參數(shù)是每秒幀數(shù)
    CMTime time = CMTimeMake(timeBySecond, 10);
    CMTime actualTime;//實(shí)際生成視頻縮略圖的時(shí)間
    NSError *error = nil;//錯(cuò)誤信息
    //使用對(duì)象方法互例,生成視頻縮略圖,注意生成的是CGImageRef類型筝闹,如果要在UIImageView上顯示媳叨,需要轉(zhuǎn)為UIImage
    CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time
                                                actualTime:&actualTime
                                                     error:&error];
    if (error) {
        NSLog(@"截取視頻縮略圖發(fā)生錯(cuò)誤腥光,錯(cuò)誤信息:%@",error.localizedDescription);
        return nil;
    }
    //CGImageRef轉(zhuǎn)UIImage對(duì)象
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    //記得釋放CGImageRef
    CGImageRelease(cgImage);
    return image;
}

代碼Demo點(diǎn)這里:LearnDemo里面的MovieDemo

如果有什么問(wèn)題可以在下方評(píng)論區(qū)中提出!O(∩_∩)O哈糊秆!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末武福,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痘番,更是在濱河造成了極大的恐慌捉片,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汞舱,死亡現(xiàn)場(chǎng)離奇詭異伍纫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)昂芜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門莹规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人说铃,你說(shuō)我怎么就攤上這事访惜。” “怎么了腻扇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵债热,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我幼苛,道長(zhǎng)窒篱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任舶沿,我火速辦了婚禮墙杯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘括荡。我一直安慰自己高镐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布畸冲。 她就那樣靜靜地躺著嫉髓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邑闲。 梳的紋絲不亂的頭發(fā)上算行,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音苫耸,去河邊找鬼州邢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛褪子,可吹牛的內(nèi)容都是我干的量淌。 我是一名探鬼主播骗村,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼类少!你這毒婦竟也來(lái)了叙身?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤硫狞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后晃痴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體残吩,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年倘核,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泣侮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡紧唱,死狀恐怖活尊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漏益,我是刑警寧澤蛹锰,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站绰疤,受9級(jí)特大地震影響铜犬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轻庆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一癣猾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧余爆,春花似錦纷宇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至转捕,卻和暖如春作岖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背五芝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工痘儡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢步。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓沉删,卻偏偏與公主長(zhǎng)得像渐尿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矾瑰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容