ijkplayer源碼閱讀03-系統(tǒng)播放器

概述

ijkplayer 是Bilibili開(kāi)發(fā)并開(kāi)源的輕量級(jí)視頻播放器,支持本地網(wǎng)絡(luò)視頻播放以及流媒體播放,支持iOS和Android平臺(tái)。ijkplayer基于 FFmpeg 是一套可以用來(lái)記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開(kāi)源計(jì)算機(jī)程序。 FFmpeg 采用LGPL或GPL許可證,提供了錄制、轉(zhuǎn)換以及流化音視頻的完整解決方案,包括了領(lǐng)先的音、視頻編碼庫(kù)libavcodec等。這篇文章的主要目的是介紹ijkplayer中IJKMPMoviePlayerControllerIJKAVMoviePlayerController。之所以放在一起是因?yàn)樗鼈兊牡讓佣际钦{(diào)用系統(tǒng)的播放器接口,因此源碼相對(duì)IJKFFMoviePlayerController來(lái)說(shuō)比較簡(jiǎn)單。

特性

platform | version | CPU| video-output|audio-output|hw-decoder
:---:|:---:|:---:|:---:|:---:
iOS | iOS 7.0+ | armv7, arm64, i386, x86_64|OpenGL ES 2.0|AudioQueue, AudioUnit|VideoToolbox (iOS 8+)
Android | API 9+ | ARMv7a, ARM64v8a, x86 |NativeWindow, OpenGL ES 2.0|AudioTrack, OpenSL ES|MediaCodec (API 16+, Android 4.1+)

播放效果

播放畫(huà)面.png

IJKMPMoviePlayerController

IJKMPMoviePlayerController 繼承自MPMoviePlayerController實(shí)現(xiàn)了IJKMediaPlayback協(xié)議。通過(guò)實(shí)現(xiàn) IJKMediaPlayback 協(xié)議,雖然每個(gè)播放器的底層實(shí)現(xiàn)不同,但是可以提供一套統(tǒng)一的播放接口。MPMoviePlayerController支持本地視頻和網(wǎng)絡(luò)視頻的播放,它實(shí)現(xiàn)了MPMediaPlayback協(xié)議,因此具備一般的播放器控制功能,例如播放、暫停、停止等。但是 MPMediaPlayerController自身并不是一個(gè)完整的視圖控制器,如果要在UI中展示視頻需要將view屬性添加到界面中。

  • 初始化。URL可以是本地視頻的URL,也可以是網(wǎng)絡(luò)視頻的URL。
- (id)initWithContentURL:(NSURL *)aUrl;
- (id)initWithContentURLString:(NSString *)aUrl;

// URL初始化
- (id)initWithContentURL:(NSURL *)aUrl
{
    self = [super initWithContentURL:aUrl];
    if (self) {
        self.scalingMode = MPMovieScalingModeAspectFit;
        self.shouldAutoplay = YES;

        _notificationManager = [[IJKNotificationManager alloc] init];
        [self IJK_installMovieNotificationObservers];

        [[IJKAudioKit sharedInstance] setupAudioSession];
        
        _bufferingProgress = -1;
    }
    return self;
}

// 路徑初始化
- (id)initWithContentURLString:(NSString *)aUrl
{
    NSURL *url;
    // 判斷是否為文件
    if ([aUrl rangeOfString:@"/"].location == 0) {
        //構(gòu)建本地URL
        url = [NSURL fileURLWithPath:aUrl];
    }
    else {
        url = [NSURL URLWithString:aUrl];
    }
    
    self = [self initWithContentURL:url];
    if (self) {
        
    }
    return self;
}
  • 相關(guān)方法。方法更多的是對(duì)MPMoviePlayerController的封裝。
- (BOOL)isPlaying
{
    switch (self.playbackState) {
        case MPMoviePlaybackStatePlaying:
            return YES;
        default:
            return NO;
    }
}

- (void)shutdown
{
    // do nothing
}

-(int64_t)numberOfBytesTransferred
{
    NSArray *events = self.accessLog.events;
    if (events.count>0) {
        MPMovieAccessLogEvent *currentEvent = [events objectAtIndex:events.count -1];
        return currentEvent.numberOfBytesTransferred;
    }
    return 0;
}

- (UIImage *)thumbnailImageAtCurrentTime
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    return [super thumbnailImageAtTime:self.currentPlaybackTime timeOption:MPMovieTimeOptionExact];
#pragma clang diagnostic pop
}
  • 相關(guān)通知
// 做好播放準(zhǔn)備后
IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification 
// 媒體播放完成或用戶手動(dòng)退出,具體完成原因可以通過(guò)通知userInfo中的key為IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對(duì)象獲取
IJKMPMoviePlayerPlaybackDidFinishNotification
// 播放狀態(tài)改變
IJKMPMoviePlayerPlaybackStateDidChangeNotification 
// 媒體網(wǎng)絡(luò)加載狀態(tài)改變
IJKMPMoviePlayerLoadStateDidChangeNotification
// 當(dāng)媒體開(kāi)始通過(guò)AirPlay播放或者結(jié)束AirPlay播放
IJKMPMoviePlayerIsAirPlayVideoActiveDidChangeNotification 
// 獲取了媒體的實(shí)際尺寸
IJKMPMovieNaturalSizeAvailableNotification 

IJKAVMoviePlayerController

IJKAVMoviePlayerController 是對(duì) AVPlayer 的封裝。IJKAVMoviePlayerController 相比 IJKMPMoviePlayerController 要復(fù)雜些,MPMoviePlayerController 提供的播放器具有高度的封裝性,使得自定義播放器變的很難。如果需要自定義播放器樣式的時(shí)候,一般使用 AVPlayer。AVPlayer 存在于 AVFoundtion 中,更接近于底層,也更加靈活。

  • 初始化。這里需要注意的是在初始化的時(shí)候并沒(méi)有初始化 AVPlayer,只是初始化相關(guān)的實(shí)例變量。
- (id)initWithContentURL:(NSURL *)aUrl;
- (id)initWithContentURLString:(NSString *)aUrl;

// 根據(jù)URL初始化
- (id)initWithContentURL:(NSURL *)aUrl
{
    self = [super init];
    if (self != nil) {
        self.scalingMode = IJKMPMovieScalingModeAspectFit;
        self.shouldAutoplay = NO;

        _playUrl = aUrl;

         // 初始化播放視圖
        _avView = [[IJKAVPlayerLayerView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        self.view = _avView;

        // TODO:
        [[IJKAudioKit sharedInstance] setupAudioSession];

        _isPrerolling           = NO;

        _isSeeking              = NO;
        _isError                = NO;
        _isCompleted            = NO;
        self.bufferingProgress  = 0;

        _playbackLikelyToKeeyUp = NO;
        _playbackBufferEmpty    = YES;
        _playbackBufferFull     = NO;

        _playbackRate           = 1.0f;
        _playbackVolume         = 1.0f;
        // init extra
        [self setScreenOn:YES];

        _notificationManager = [[IJKNotificationManager alloc] init];
    }
    return self;
}

// 根據(jù)路徑初始化
- (id)initWithContentURLString:(NSString *)aUrl
{
    NSURL *url;
    if (aUrl == nil) {
        aUrl = @"";
    }
    if ([aUrl rangeOfString:@"/"].location == 0) {
        //本地
        url = [NSURL fileURLWithPath:aUrl];
    }
    else {
        url = [NSURL URLWithString:aUrl];
    }
    self = [self initWithContentURL:url];
    if (self != nil) {
        
    }
    return self;
}
  • 異步加載。由于多媒體文件一般比較大,獲取或計(jì)算出Asset中的屬性非常耗時(shí),Apple對(duì)Asset的屬性采用了懶惰加載模式。在創(chuàng)建AVAsset的時(shí)候,只生成一個(gè)實(shí)例,并不初始化屬性。只有當(dāng)?shù)谝淮卧L問(wèn)屬性時(shí),系統(tǒng)才會(huì)根據(jù)多媒體中的數(shù)據(jù)初始化這個(gè)屬性。由于不用同時(shí)加載所有屬性,耗時(shí)問(wèn)題得到了一定緩解。但是屬性加載在計(jì)算量比較大的時(shí)候仍舊可能會(huì)阻塞線程。為了解決這個(gè)問(wèn)題,AVFoundation提供了AVAsynchronousKeyValueLoading協(xié)議,可以異步加載屬性:
@interface AVMetadataItem (AVAsynchronousKeyValueLoading)

// 異步加載屬性,通過(guò)keys傳入要加載的key數(shù)組,在handler中做加載完成的操作。
- (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError NS_AVAILABLE(10_7, 4_2);

// 獲得屬性的加載狀態(tài),如果是AVKeyValueStatusLoaded狀態(tài),表示已經(jīng)加載完成。
- (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler NS_AVAILABLE(10_7, 4_2);

@end
  • 相關(guān)方法。IJKAVMoviePlayerController的方法比較多,在這里主要關(guān)注IJKAVMoviePlayerController播放器從初始化到播放的整體流程:
    1、根據(jù)初始化的URL構(gòu)建AVURLAsset對(duì)象;
    2、異步加載獲取視頻相關(guān)屬性;
    3、加載完成后初始化AVPlayerItem,并監(jiān)聽(tīng)它的相關(guān)屬性;
    4、初始化AVPlayer,并監(jiān)聽(tīng)它的相關(guān)屬性;
    5、當(dāng)狀態(tài)為AVPlayerItemStatusReadyToPlay的時(shí)候發(fā)送相關(guān)通知,如果開(kāi)啟了自動(dòng)播放則自動(dòng)播放。如果沒(méi)有開(kāi)啟自動(dòng)播放,我們可以監(jiān)聽(tīng)I(yíng)JKMPMediaPlaybackIsPreparedToPlayDidChangeNotification這個(gè)通知,收到通知后再去播放。
// 預(yù)加載,異步加載相關(guān)屬性
- (void)prepareToPlay
{
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_playUrl options:nil];
    NSLog(@"%@", asset);
    
    NSArray *requestedKeys = @[@"playable"];
    
    _playAsset = asset;
    // 異步加載屬性
    [asset loadValuesAsynchronouslyForKeys:requestedKeys
                         completionHandler:^{
                             dispatch_async( dispatch_get_main_queue(), ^{
                                 [self didPrepareToPlayAsset:asset withKeys:requestedKeys];
                                 [[NSNotificationCenter defaultCenter]
                                  postNotificationName:IJKMPMovieNaturalSizeAvailableNotification
                                  object:self];
                             });
                         }];
}

// 異步加載完后,監(jiān)聽(tīng)相關(guān)通知、屬性以及初始化AVPlayer
- (void)didPrepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys
{
    if (_isShutdown)
        return;
    
    /* Make sure that the value of each key has loaded successfully. */
    for (NSString *thisKey in requestedKeys)
    {
        NSError *error = nil;
        AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
        if (keyStatus == AVKeyValueStatusFailed)
        {
            [self assetFailedToPrepareForPlayback:error];
            return;
        } else if (keyStatus == AVKeyValueStatusCancelled) {
            // TODO [AVAsset cancelLoading]
            error = [self createErrorWithCode:kEC_PlayerItemCancelled
                                  description:@"player item cancelled"
                                       reason:nil];
            [self assetFailedToPrepareForPlayback:error];
            return;
        }
    }
    
    /* Use the AVAsset playable property to detect whether the asset can be played. */
    if (!asset.playable)
    {
        NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"AVMoviePlayer"
                                                                code:0
                                                            userInfo:nil];
        
        [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
        return;
    }
    
    /* At this point we're ready to set up for playback of the asset. */
    
    /* Stop observing our prior AVPlayerItem, if we have one. */
    [_playerItemKVO safelyRemoveAllObservers];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:nil
                                                  object:_playerItem];
    
    /* Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. */
    _playerItem = [AVPlayerItem playerItemWithAsset:asset];
    _playerItemKVO = [[IJKKVOController alloc] initWithTarget:_playerItem];
    [self registerApplicationObservers];
    /* Observe the player item "status" key to determine when it is ready to play. */
    // 監(jiān)聽(tīng)AVPlayer的狀態(tài),比較重要
    [_playerItemKVO safelyAddObserver:self
                           forKeyPath:@"status"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayerItem_state];
    
    [_playerItemKVO safelyAddObserver:self
                           forKeyPath:@"loadedTimeRanges"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayerItem_loadedTimeRanges];
    
    [_playerItemKVO safelyAddObserver:self
                           forKeyPath:@"playbackLikelyToKeepUp"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayerItem_playbackLikelyToKeepUp];
    
    [_playerItemKVO safelyAddObserver:self
                           forKeyPath:@"playbackBufferEmpty"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayerItem_playbackBufferEmpty];
    
    [_playerItemKVO safelyAddObserver:self
                           forKeyPath:@"playbackBufferFull"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayerItem_playbackBufferFull];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerItemDidReachEnd:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:_playerItem];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerItemFailedToPlayToEndTime:)
                                                 name:AVPlayerItemFailedToPlayToEndTimeNotification
                                               object:_playerItem];
    
    _isCompleted = NO;
    
    /* Create new player, if we don't already have one. */
    if (!_player)
    {
        /* Get a new AVPlayer initialized to play the specified player item. */
        _player = [AVPlayer playerWithPlayerItem:_playerItem];
        _playerKVO = [[IJKKVOController alloc] initWithTarget:_player];
        
        NSLog(@"%@", _player);
        
        /* Observe the AVPlayer "currentItem" property to find out when any
         AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
         occur.*/
        [_playerKVO safelyAddObserver:self
                           forKeyPath:@"currentItem"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayer_currentItem];
        
        /* Observe the AVPlayer "rate" property to update the scrubber control. */
        [_playerKVO safelyAddObserver:self
                           forKeyPath:@"rate"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayer_rate];
        
        [_playerKVO safelyAddObserver:self
                           forKeyPath:@"airPlayVideoActive"
                              options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                              context:KVO_AVPlayer_airplay];
    }
    
    /* Make our new AVPlayerItem the AVPlayer's current item. */
    if (_player.currentItem != _playerItem)
    {
        /* Replace the player item with a new player item. The item replacement occurs
         asynchronously; observe the currentItem property to find out when the
         replacement will/did occur
         
         If needed, configure player item here (example: adding outputs, setting text style rules,
         selecting media options) before associating it with a player
         */
        [_player replaceCurrentItemWithPlayerItem:_playerItem];
        
        // TODO: notify state change
    }
    
    // TODO: set time to 0;
}

// 監(jiān)聽(tīng)到相關(guān)狀態(tài)改變的時(shí)候做進(jìn)一步處理,并且發(fā)送相關(guān)通知
- (void)observeValueForKeyPath:(NSString*)path
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context
{
    if (_isShutdown)
        return;
    
    if (context == KVO_AVPlayerItem_state)
    {
        /* AVPlayerItem "status" property value observer. */
        AVPlayerItemStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        switch (status)
        {
            case AVPlayerItemStatusUnknown:
            {
                /* Indicates that the status of the player is not yet known because
                 it has not tried to load new media resources for playback */
            }
                break;

             // 準(zhǔn)備播放
            case AVPlayerItemStatusReadyToPlay:
            {
                /* Once the AVPlayerItem becomes ready to play, i.e.
                 [playerItem status] == AVPlayerItemStatusReadyToPlay,
                 its duration can be fetched from the item. */
                dispatch_once(&_readyToPlayToken, ^{
                    [_avView setPlayer:_player];
                    
                    self.isPreparedToPlay = YES;
                    AVPlayerItem *playerItem = (AVPlayerItem *)object;
                    NSTimeInterval duration = CMTimeGetSeconds(playerItem.duration);
                    if (duration <= 0)
                        self.duration = 0.0f;
                    else
                        self.duration = duration;
                    
                    [[NSNotificationCenter defaultCenter]
                     postNotificationName:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
                     object:self];
                    
                    // 如果自動(dòng)播放,且應(yīng)用程序?yàn)榧せ顮顟B(tài)則自動(dòng)播放
                    if (_shouldAutoplay && (!_pauseInBackground || [UIApplication sharedApplication].applicationState == UIApplicationStateActive))
                        [_player play];
                });
            }
                break;
            // 播放準(zhǔn)備失敗
            case AVPlayerItemStatusFailed:
            {
                AVPlayerItem *playerItem = (AVPlayerItem *)object;
                [self assetFailedToPrepareForPlayback:playerItem.error];
            }
                break;
        }
        
        [self didPlaybackStateChange];
        [self didLoadStateChange];
    }
    else if (context == KVO_AVPlayerItem_loadedTimeRanges)
    {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        if (_player != nil && playerItem.status == AVPlayerItemStatusReadyToPlay) {
            NSArray *timeRangeArray = playerItem.loadedTimeRanges;
            CMTime currentTime = [_player currentTime];
            
            BOOL foundRange = NO;
            CMTimeRange aTimeRange = {0};
            
            if (timeRangeArray.count) {
                aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];
                if(CMTimeRangeContainsTime(aTimeRange, currentTime)) {
                    foundRange = YES;
                }
            }
            
            if (foundRange) {
                CMTime maxTime = CMTimeRangeGetEnd(aTimeRange);
                NSTimeInterval playableDuration = CMTimeGetSeconds(maxTime);
                if (playableDuration > 0) {
                    self.playableDuration = playableDuration;
                    [self didPlayableDurationUpdate];
                }
            }
        }
        else
        {
            self.playableDuration = 0;
        }
    }
    else if (context == KVO_AVPlayerItem_playbackLikelyToKeepUp) {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        NSLog(@"KVO_AVPlayerItem_playbackLikelyToKeepUp: %@\n", playerItem.isPlaybackLikelyToKeepUp ? @"YES" : @"NO");
        [self fetchLoadStateFromItem:playerItem];
        [self didLoadStateChange];
    }
    else if (context == KVO_AVPlayerItem_playbackBufferEmpty) {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        BOOL isPlaybackBufferEmpty = playerItem.isPlaybackBufferEmpty;
        NSLog(@"KVO_AVPlayerItem_playbackBufferEmpty: %@\n", isPlaybackBufferEmpty ? @"YES" : @"NO");
        if (isPlaybackBufferEmpty)
            _isPrerolling = YES;
        [self fetchLoadStateFromItem:playerItem];
        [self didLoadStateChange];
    }
    else if (context == KVO_AVPlayerItem_playbackBufferFull) {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        NSLog(@"KVO_AVPlayerItem_playbackBufferFull: %@\n", playerItem.isPlaybackBufferFull ? @"YES" : @"NO");
        [self fetchLoadStateFromItem:playerItem];
        [self didLoadStateChange];
    }
    else if (context == KVO_AVPlayer_rate)
    {
        if (_player != nil && !isFloatZero(_player.rate))
            _isPrerolling = NO;
        /* AVPlayer "rate" property value observer. */
        [self didPlaybackStateChange];
        [self didLoadStateChange];
    }
    else if (context == KVO_AVPlayer_currentItem)
    {
        _isPrerolling = NO;
        /* AVPlayer "currentItem" property observer.
         Called when the AVPlayer replaceCurrentItemWithPlayerItem:
         replacement will/did occur. */
        AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
        
        /* Is the new player item null? */
        if (newPlayerItem == (id)[NSNull null])
        {
            NSError *error = [self createErrorWithCode:kEC_CurrentPlayerItemIsNil
                                           description:@"current player item is nil"
                                                reason:nil];
            [self assetFailedToPrepareForPlayback:error];
        }
        else /* Replacement of player currentItem has occurred */
        {
            [_avView setPlayer:_player];
            
            [self didPlaybackStateChange];
            [self didLoadStateChange];
        }
    }
    else if (context == KVO_AVPlayer_airplay)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:IJKMPMoviePlayerIsAirPlayVideoActiveDidChangeNotification object:nil userInfo:nil];
    }
    else
    {
        [super observeValueForKeyPath:path ofObject:object change:change context:context];
    }
}

// 播放音視頻
- (void)play
{
    if (_isCompleted)
    {
        _isCompleted = NO;
        [_player seekToTime:kCMTimeZero];
    }
    
    [_player play];
}

// 生成截圖
- (UIImage *)thumbnailImageAtCurrentTime
{
    AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_playAsset];
    NSError *error = nil;
    CMTime time = CMTimeMakeWithSeconds(self.currentPlaybackTime, 1);
    CMTime actualTime;
    CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];
    UIImage *image = [UIImage imageWithCGImage:cgImage];
    return image;
}

// 定位到新的播放時(shí)間
- (void)setCurrentPlaybackTime:(NSTimeInterval)aCurrentPlaybackTime
{
    if (!_player)
        return;

    _seekingTime = aCurrentPlaybackTime;
    _isSeeking = YES;
    _bufferingProgress = 0;
    [self didPlaybackStateChange];
    [self didLoadStateChange];
    if (_isPrerolling) {
        [_player pause];
    }

    [_player seekToTime:CMTimeMakeWithSeconds(aCurrentPlaybackTime, NSEC_PER_SEC)
      completionHandler:^(BOOL finished) {
          dispatch_async(dispatch_get_main_queue(), ^{
              _isSeeking = NO;
              if (_isPrerolling) {
                  [_player play];
              }
              [self didPlaybackStateChange];
              [self didLoadStateChange];
          });
      }];
}
  • 相關(guān)通知
// 做好播放準(zhǔn)備后
IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification 
// 媒體播放完成或用戶手動(dòng)退出,具體完成原因可以通過(guò)通知userInfo中的key為IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對(duì)象獲取
IJKMPMoviePlayerPlaybackDidFinishNotification
// 播放狀態(tài)改變
IJKMPMoviePlayerPlaybackStateDidChangeNotification 
// 媒體網(wǎng)絡(luò)加載狀態(tài)改變
IJKMPMoviePlayerLoadStateDidChangeNotification
// 當(dāng)媒體開(kāi)始通過(guò)AirPlay播放或者結(jié)束AirPlay播放
IJKMPMoviePlayerIsAirPlayVideoActiveDidChangeNotification 
// 獲取了媒體的實(shí)際尺寸
IJKMPMovieNaturalSizeAvailableNotification 
  • 播放器使用
- (void)setupMPPlayer
{
    _mpPlayer = [[IJKMPMoviePlayerController alloc] initWithContentURLString:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"mp4"]];
    _mpPlayer.scalingMode = IJKMPMovieScalingModeAspectFit;
    _mpPlayer.view.frame = self.view.bounds;
    [self.view addSubview:_mpPlayer.view];
    
    [_mpPlayer prepareToPlay];
}

- (void)setupAVPlayer
{
    _avPlayer = [[IJKAVMoviePlayerController alloc] initWithContentURLString:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"mp4"]];
    [self.view addSubview:_avPlayer.view];
    
    [_avPlayer setShouldAutoplay:YES];
    [_avPlayer prepareToPlay];
}

總結(jié)

ijkplayer 中的 IJKMPMoviePlayerController 底層由 MPMoviePlayerController 實(shí)現(xiàn),由于它具有高度的封裝性。因此,二次封裝的時(shí)候比較簡(jiǎn)單,可定制化程度低。IJKAVMoviePlayerController 底層通過(guò) AVPlayer 實(shí)現(xiàn),更加靈活,可定程度高,二次封裝相對(duì)比較困難。如果希望了解如何定制 AVPlayer,讀一讀IJKAVMoviePlayerController 的源碼是個(gè)不錯(cuò)的選擇。

Demo地址 : https://github.com/QinminiOS/ijkplayer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容