Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> AVPlayer自定制視頻播放器(1)——視頻播放器基本實現

AVPlayer自定制視頻播放器(1)——視頻播放器基本實現

編輯:關於Android編程

首先,要使用AVPlayer進行自定制視頻播放,要引入頭文件:  
#import 
因為AVPlayer屬於AVFoundation框架,所以要引入這個頭文件。其次,當然是要創建我們的視頻播放器AVPlayer了,這裡在.h文件中聲明了一個全局的Player對象,便於在不同的函數中進行相關操作。  
@property (nonatomic,strong) AVPlayer * player;
然後,在初始化方法中對其進行初始化。在初始化過程中,需要傳入視頻的URL,這個URL是NSURL類型的,這裡簡單說明一下,AVPlayer支持本地視頻播放和媒體視頻播放,因此,這個URL既可以是本地視頻的URL,也可以是網絡視頻的URL,本篇博客選取了一段網絡視頻:  
    //網絡視頻
    NSString * urlStr = [NSString stringWithFormat:@"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
    urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    NSURL * url = [NSURL URLWithString:urlStr];
首先用字符串傳進來一個地址,然後,為了對字符串進行UTF-8編碼,之前是用的其他的方法,但是,在iOS9之後,已經被啟用了,所以這裡用上面的方法,進行編碼,前後,用編碼之後的字符串初始化URL。其實,如果不考慮比較多的內容的話,直接用下面的方法就可以創建一個AVPlayer了:  
self.player = [[AVPlayer alloc] initWithURL:url];
  這其實是最簡單的方法,但是一般不推薦使用,操作起來會很不方便。其實,自定制音頻播放,也是用AVPlayer自定制,到這裡的話,基本上就能夠實現音頻的播放了。但是,視頻播放器的話,還要有畫面,因此,還需要用這個Player去初始化一個圖層,然後將圖層加到當前的view的Layer上,這樣,就有畫面了,這裡也是創建了一個全局的AVPlayerLayer對象:  
@property (nonatomic,strong) AVPlayerLayer * playerLayer;
然後在.m中,接著上面的方法寫入下面的代碼:  
    self.player = [[AVPlayer alloc] initWithURL:url];
    self.playerLayer.frame = self.layer.bounds;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.layer addSublayer:self.playerLayer];
我在創建的時候,是在一個View中創建的,所以直接是self.layer,如果是在Controller中,則是self.view.layer,然後,設置一下playerLayer的大小和方向。這樣,就創建了一個Player。當然,上面說過,這只是簡單的創建方式,通常情況下使用下面將要介紹的方式進行創建: 首先在.h中定義了一個AVPlayerItem對象:  
@property (nonatomic,strong) AVPlayerItem * playerItem;

然後,在.m文件中進行下面的操作:  
    AVURLAsset * movieAsset = [[AVURLAsset alloc] initWithURL:URL options:nil];
    self.playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer.frame = self.layer.bounds;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.layer addSublayer:self.playerLayer];

    使用這種方式,就創建了一個player,並加到的當前視圖的layer上。這裡涉及到了其他的兩個類AVURLAsset和AVPlayerItem。這裡的AVURLAsset是AVAsset的子類,AVAsset不能直接用AVAsset進行初始化,需要用子類初始化,AVAsset和AVURLAsset其實是一個資源類,代表了視頻資源,AVAsset也是AVFoundation中最終要的一個類,是對資源的抽象,想詳細了解的,可以了解一下AVFoundation框架。而AVPlayerItem對應的其實就是要播放的視頻了,先通過URL來創建一個視頻播放器資源,然後,再用這個資源來初始化一個要播放的視頻,之後,用這個視頻初始化播放器,將播放器指定要播放的圖層,基本上就創建完成了一個視頻播放器,這裡的初始化,是一層層進行的,希望大家不要被繞暈了,可以反復揣摩一下。player有一個rate屬性,用來表示視頻播放的速度,取值范圍是0-1,1為正常速度,0的話,表示視頻暫停了。上面說到,AVPlayerItem其實就相當於要播放的視頻,因此,可以通過這個item,可以獲得視頻的總時長以及當前緩存到了哪裡,所以,為了獲取這些信息,要對item的相關屬性進行監聽:  
    /**
     *  監聽AVPlayerItem的屬性
     */
    [self.playerItem addObserver:self forKeyPath:@"status" options:0 context:NULL];
    [self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:0 context:NULL];
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];

  然後,添加監聽的方法:  
/**
 *  KVO監聽playItem的屬性變化
 *
 *  @param keyPath keyPath description
 *  @param object  object description
 *  @param change  change description
 *  @param context context description
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem * item = self.player.currentItem;
    if ([keyPath isEqualToString:@"status"]) {
        //正在播放
        if (AVPlayerItemStatusReadyToPlay == item.status) {
            NSLog(@"正在播放...,視頻總長度:%.2f",CMTimeGetSeconds(item.duration));

        }
        else if (AVPlayerItemStatusUnknown == item.status){
            NSLog(@"視頻加載中");
        }
        else if (AVPlayerStatusFailed == item.status){
            NSLog(@"視頻獲取失敗");
            NSLog(@"%@",item.error);
        }
        
    } else if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=item.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時間范圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度
        NSLog(@"共緩沖:%.2f",totalBuffer);
    }
}

  通過監聽status屬性,來獲得當前食品播放的狀態,當狀態為AVPlayerItemStatusReadyToPlay的時候,便是視頻已經准備好了,此時,就可以播放當前的視頻,可以在這裡調用方法:  
 [self.player play];
來播放當前的視頻,player有一個屬性叫做currentItem,這個屬性,就是當前player的item,也就是前面初始化過程中的那個item。監聽item的loadedTimeRanges屬性,可以獲得當前緩沖了多少視頻以及視頻的總長度。item的loadedTimeRanges其實是一個數組,裡面存放了CMTimeRange類型的結構體,通過獲得該array的firstObject可以獲得本次緩沖的時間信息timeRange,timeRange.start表示本次緩沖的開始位置,timeRange.duration表示本次緩沖的視頻長度,兩者相加,就獲得了緩沖的總時長,這就是好多播放器中,底部進度條中緩沖的視頻長度的獲取方式。由於loadedTimeRanges經常要變化,所以,會反復出發這個KVO的監聽,因此,可以做到隨時刷新緩沖進度。此外,當退出播放器頁面的時候,要移除相關的觀察者。  
//移除觀察者
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
    [playerItem removeObserver:self forKeyPath:@"status"];
    [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
然後在dealloc調用這個方法該方法即可。 此外,當視頻播放完成之後,還會有相關的通知,在這裡,可以對其進行監聽,當播放完成之後,進行相關的UI刷新:  
/**
 *  添加播放器通知
 */
-(void)addNotification{
    //給AVPlayerItem添加播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}
例如,可以更改播放按鈕的圖片:  
- (void)playbackFinished:(NSNotification *)notification{
    [self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
    
}
既然監聽通知了,就要在dealloc中移除監聽:  
    [self removeNotification];
上面只是創建了視頻播放器,下面將講解視頻播放器的控制。我們在進行視頻自定制的時候,還要實現視頻播放器的播放、暫停、繼續播放、停止功能,因此,還要進行一些其他的操作,我這裡將播放和繼續播放進行了區分,這裡說的播放,是從頭開始播放,繼續播放,則是從上次暫停的位置進行播放,因此要設置一個屬性,來保存當前播放的位置:  
//當前播放進度
@property (nonatomic,assign) double currentTime;
  下面是從頭播放的方法:  
/**
 *  開始播放
 */
- (void)play{
    AVPlayerItem * item = self.player.currentItem;
//    [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
    [item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    self.progressBar.value = 0;
    [self.player play];
    //設置播放速度
}
在該方法中,用到了seekToTime方法,該方法用來從指定位置開始播放,傳入一個CMTime類型的時間值,來指定比方的位置。我的項目中,添加了一個進度條(UISlider),來表示當前播放的進度,因此,當從0來說播放的時候,在這裡將slider的value設置成0。這裡還注釋掉了一個seek方法,下面簡單說一下,第一個seek方法,seek的時間沒有第二個精確,但第二個更好性能,但還是推薦使用第二個。 下面是暫停的方法:  
/**
 *  暫停播放
 */
- (void)pause{
    self.currentTime = [self playableCurrentTime];
    [self.player pause];
    //設置播放按鈕
    [self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
}
Avplayer自帶一個pause方法,因此,這裡可以直接調用,還有上面的play方法也是自帶的。下面是resume方法:  
/**
 *  繼續播放
 */
- (void)resume{
    AVPlayerItem * item = self.player.currentItem;
//    [item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0)];
    [item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    [self.player play];
    //設置播放速度
    self.player.rate = self.rate;
}
上面說到了player的rate屬性,其實這個屬性的取值並不一定非要在0-1之間,當大於1的時候,會加速播放,小於1會減緩,這裡可以通過這個屬性來控制播放的速度。下面是停止方法:  
/**
 *  停止
 */
- (void)stop{
    AVPlayerItem * item = self.player.currentItem;
//    [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
    [item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    [self.player pause];
    self.currentTime = 0;
    
}
這裡使用seek方法,seek到0,然後調用player的pause方法,將視頻pause,就實現了視頻的stop功能。 此外,有些情況下,還會遇到這樣的需求,就是將視頻靜音,在AVFoundation中,已經為我們提供了這樣一個方法,就是將play的volume值設置為0。但是,這裡要仔細考慮一下了,當設置為0之後,要想設置回來,怎麼辦呢?因此,要將當前的volume保存起來。定義一個變量保存當前的音量。  
//音量
@property (nonatomic,assign) float volumn;
當點擊靜音按鈕的時候,調用下面的方法即可:  
/**
 *  設置靜音
 *
 *  @param mute 靜音傳入的一個BOOL值,YES為靜音,NO不靜音
 */
- (void)playerMute:(BOOL)mute{
    if (mute) {
        [self.player setVolume:0];
    } else {
        [self.player setVolume:self.volumn];
    }
}
這裡有一個問題需要注意,這裡設置的音量,只是應用中的音量,並不是系統音量,也就是說,當系統音量為0的時候,及時這個volume再大,也是沒有聲音的,因此,當想要恢復音量的時候,這裡的volume一般都設置1,即正常的音量。 想獲取系統音量,請跳轉到這個博客,了解一下就好,也可以查詢一下其他的博客。 下面,再講一些其他的控制。 自定制播放器的過程中,當視頻播放進度發生改變的時候,我們也希望對應的進度條也跟著變化,因此,要監聽視頻播放的進度,可以使用下面的方法進行實現:  
/**
 *  進度更新設置,監聽視頻播放進度,同時更新進度條的value
 */
- (void)addProgressBarObserver{
    AVPlayerItem *playerItem=self.player.currentItem;
    __weak typeof(self) weakSelf = self;
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        float current = CMTimeGetSeconds(time);
        float total = CMTimeGetSeconds([playerItem duration]);
        if (current) {
            [weakSelf.progressBar setValue:(current/total) animated:YES];
        }
    }];
}
  這裡將進度條更新的操作封裝成了一個方法,其實還是調用了AVPlayer自帶的方法

 

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

 

這個方法的前一個參數還是CMTime類型的,後面是一個block,表示每個interval的時間,就回調一下這個block,這樣的話,我們就可以在這裡通過播放進度和總時長,來設置進度條的value值了。

 

通過以上的方法,基本上就能實現一個簡單的視頻播放器了,可能有些地方說的不好或者說法有誤,歡迎大家在下面進行評論,指出我的錯誤,大家共同進步。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved