iOS画中画

原理

  1. 在播放视频的时候,先使用IJKPlayer播放流视频, 同时启动AVQueuePlayer播放一个loading的视频(AVQueuePlayer 播放的视频需要循环播放, 播放loading, 是为了在程序进入后台时候, 有一个loading的假象)
  2. 在程序进入后台时候, 画中画会自动启动, 同时关掉IJKPlayer播放, 让AVQueuePlayer独占音频播放, 然后预加载m3u8的视频流, 当m3u8可以播放时候, 直接让 AVQueuePlayer 播放 m3u8的视频流.
  3. 程序进入前台时候, 继续让 AVQueuePlayer 播放loading的视频;

趟坑

  1. 如果loading的视频时长很短(4s), 在播放loading视频时候程序退到后台, 无法启动画中画.
  2. 启动画中画, 必须将 AVAudioSession 的category 必须是 AVAudioSessionCategoryPlayback.
  3. 如果程序退出到后台在使用AVQueuePlayer 播放视频, 是无法启动画中画的, 所以为了避免这个问题, 需要在一个不显示的view上 静默启动 AVQueuePlayer 播放loading的视频.
  4. _playerContent是防止动画导致卡顿一下的情况
  5. 程序进入前台, 直接播放静默的loading的视频.
  6. 如果希望自动小窗播放, 需要延迟0s在主线程启动画中画, 如代码中所示.
  7. 需要在 [player play] 之前 初始化 AVPictureInPictureController, 并设置 AVPictureInPictureController的playerLayer, 否则户出现第一次启动不成功的问题.
//
//  QIEPIPManager.m
//  QEZB
//
//  Created by 李超群 on 2021/7/6.
//  Copyright © 2021 zhou. All rights reserved.
//

#import "QIEPIPManager.h"
#import 
#import 
#import "DYTabBarManager.h"
#import "DYTabBarViewController.h"
#import 
#import 

///kvo 监听状态
static NSString *const kForPlayerItemStatus = @"status";

@interface QIEPIPManager ()
///#画中画
@property (nonatomic, strong) AVPictureInPictureController *pipViewController;// 画中画

/** 播放 */
@property (nonatomic, weak) UIView *tempView;
@property (nonatomic, strong) UIView *playerContent;
@property (nonatomic, strong) AVPlayerItem *loadingItem;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@property (nonatomic, strong) AVQueuePlayer *queuePlayer;

@end

@implementation QIEPIPManager

+ (instancetype)pictureInpicture {
    static QIEPIPManager *_p;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _p = [QIEPIPManager new];
    });
    return _p;
}

-(void)prepareToPlay{
    [self.queuePlayer play];
    [self pipViewController];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)openPictureInPicture:(NSString *)url{
    if (!url || url.length == 0 ) return;
    if (![url containsString:@"m3u8"]) return;
    // - 播放视频
    NSArray *keys = @[@"tracks"];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:url] options:nil];
    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^(){
        for (NSString *thisKey in keys) {
            NSError *error = nil;
            AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
            if (keyStatus != AVKeyValueStatusLoaded) {
                return ;
            }
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
            [item addObserver:self forKeyPath:kForPlayerItemStatus options:NSKeyValueObservingOptionNew context:nil];
            [self.queuePlayer replaceCurrentItemWithPlayerItem:item];
        });
    }];
}

-(void)_changeToLoadingVideo{
    [self.queuePlayer pause];
    [self.queuePlayer replaceCurrentItemWithPlayerItem:self.loadingItem];
    [self.queuePlayer play];
}

-(void)configPlayCenter{
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    //歌曲名称
    dict[MPMediaItemPropertyTitle] = @"标题";
    dict[MPMediaItemPropertyArtist] = @"用户信息";
    dict[MPMediaItemPropertyAlbumTitle] = @"专辑名称";
    CGSize size = CGSizeMake(250, 100);
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:size requestHandler:^UIImage * _Nonnull(CGSize size) {
        UIImage *image = [UIImage imageNamed:@"medal_share_header"];
        return image;
    }];
    [dict setObject:artwork forKey:MPMediaItemPropertyArtwork];
    //音乐当前播放时间 在计时器中修改
    dict[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @0;
    //设置锁屏状态下屏幕显示播放音乐信息
    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}


- (void)_removeObserve {
    if (_loadingItem) {
        [_loadingItem removeObserver:self forKeyPath:@"status"];
        _loadingItem = nil;
    }
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)_removePlayerContentView {
    if (_playerContent && _playerContent.superview) {
        [_playerContent removeFromSuperview];
    }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (![keyPath isEqualToString:kForPlayerItemStatus]) return;
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        if (_queuePlayer.status == AVPlayerStatusReadyToPlay) {
//            [_queuePlayer play];
//            if (!_pipViewController.isPictureInPictureActive) {
//                [self doPicInPic];
//            }
//        } else {
//            [self closePicInPic];
//        }
//    });
    if (object == self.loadingItem) {
        NSLog(@"// - 李超群 console [error] ... beginItem %ld", (long)self.queuePlayer.status);
    }else {
        // - 如果当前开始使用 _queuePlayer 播放m3u8, 停止播放视频.
        [[QIEPlayer shareInstance] stopPlay];
        NSLog(@"// - 李超群 console [error] ... xxxxx");
    }
}

-(void)_mediaPlayDidEnd:(NSNotification *)noti{
    [self performSelectorOnMainThread:@selector(_mediaPlayDidEndInMainThread:) withObject:noti waitUntilDone:NO];
}
-(void)_mediaPlayDidEndInMainThread:(NSNotification *)noti{
    AVPlayerItem *item = [noti object];
    if (item != self.loadingItem) return;
    [self.loadingItem seekToTime:kCMTimeZero completionHandler:nil];
}
-(void)applicationDidBecomeActive{
    [self.pipViewController stopPictureInPicture];
}

// - MARK: <-- 代理方法 -->
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureControllerWillStartPictureInPicture");
}
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureControllerDidStartPictureInPicture");
    
    // - 临时黑色的cover
    [self.tempView removeFromSuperview];
    UIView *v = [[UIView alloc]initWithFrame:self.playerContent.bounds];
    [self.playerContent.superview addSubview:v];
    v.backgroundColor = [UIColor blackColor];
    self.tempView = v;
}
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureController:failedToStartPictureInPictureWithError");
}
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureController");
    [self _changeToLoadingVideo];
    completionHandler(YES);
}
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureControllerWillStopPictureInPicture");
}
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"// - 李超群 console [log] ...pictureInPictureControllerDidStopPictureInPicture");
    [UIView animateWithDuration:0.3 animations:^{
        self.tempView.backgroundColor = [UIColor clearColor];
    } completion:^(BOOL finished) {
        [self.tempView removeFromSuperview];
    }];
}

// - MARK: <-- 懒加载 -->
/** 加载的video */
-(AVPlayerItem *)loadingItem{
    if(!_loadingItem){
        _loadingItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"loading11"ofType:@"mov"]]];
        [_loadingItem addObserver:self forKeyPath:kForPlayerItemStatus options:NSKeyValueObservingOptionNew context:nil];
    }
    return _loadingItem;
}

- (AVQueuePlayer *)queuePlayer{
    if (!_queuePlayer) {
        _queuePlayer = [AVQueuePlayer queuePlayerWithItems:@[self.loadingItem]];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mediaPlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_queuePlayer.currentItem];
        _queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
        _playerLayer.frame = self.playerContent.bounds;
    }
    return _queuePlayer;
}

- (AVPlayerLayer *)playerLayer{
    if (!_playerLayer) {
        _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_queuePlayer];
        _playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;  // 适配视频尺寸
        _playerLayer.backgroundColor = (__bridge CGColorRef _Nullable)([UIColor blackColor]);
    }
    return _playerLayer;
}

- (UIView *)playerContent{
    if (!_playerContent) {
        _playerContent = [UIView new];
        _playerContent.frame = [UIScreen mainScreen].bounds;
        _playerContent.backgroundColor = [UIColor blackColor];
        _playerContent.userInteractionEnabled = NO;
        [_playerContent.layer addSublayer:self.playerLayer];
        [[DYTabBarManager shareInstance].tabBarController.view insertSubview:_playerContent atIndex:0];
    }
    return _playerContent;
}

- (AVPictureInPictureController *)pipViewController{
    if (!_pipViewController && [AVPictureInPictureController isPictureInPictureSupported]) {
        _pipViewController =  [[AVPictureInPictureController alloc] initWithPlayerLayer:_playerLayer];
        _pipViewController.delegate = self;
    }
    return _pipViewController;
}

@end

你可能感兴趣的:(项目中使用的技巧)