视屏解密、AVPlayer/IJKPlayer/播放画中画

PictureInPictureDemo

画中画demo: https://github.com/eye1234456/PictureInPictureDemo.git

在线mp4转m3u8: https://mp4.to/m3u8/
测试视频下载:https://www.cnblogs.com/v5captain/p/12144699.html
https://www.jianshu.com/p/cab2cd7b3f1c
http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8
m3u8在线播放:

一、AVPlayer进行画中画

如果播放器是AVPlayer,直接使用系统提供的AVPictureInPictureController进行播放即可

- (void)pipWithAvplayer:(AVPlayer *)avPlayer {
  AVPlayerLayer *avPlayerLayer = manager.avPlayerLayer;
  AVPictureInPictureController *pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:avPlayerLayer];
    pipVC.delegate = self;
    ///要有延迟 否则可能开启不成功
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [self.pipVC startPictureInPicture];
    });
}

二、 ijkplayer进行画中画

如果播放器是IJKPlayer的,需要创建一个隐藏的AVPlayer,然后再通过avplayer进行画中画,这个过程,需要将ijkplayer的播放进度同步给avplayer,同时画中画结束时,也需要将avaplayer的进度同步到原始的ijkplayer

- (void)showPipWithPlayer:(ZFPlayerController *)player {
    
    if (self.isPipAvailable) {
        self.originPlayer = player;
        if ([player.currentPlayerManager isKindOfClass:ZFAVPlayerManager.class]) {

        }else {
            ZFIJKPlayerManager *manager = (ZFIJKPlayerManager *)player.currentPlayerManager;
            UIView *ijkContainerView = player.containerView;
            UIView *superView = nil;
            if ([UIApplication.sharedApplication.delegate respondsToSelector:@selector(window)]) {
                superView = UIApplication.sharedApplication.delegate.window;
            }else if (ijkContainerView.window != nil){
                superView = ijkContainerView.window;
            }
            
            // 将ijkplayer的frame转换为window的坐标体系
            CGRect ijkPlayerFrame = [superView convertRect:ijkContainerView.frame toView:superView];
            // 创建一个隐藏的AvPlayer
            NSError *error = nil;
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
            [[AVAudioSession sharedInstance] setActive:YES error:&error];
           
            if (error) {
                NSLog(@"请求权限失败的原因为%@",error);
                return;
            }
            self.avPlayer = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:manager.assetURL.absoluteString]];
            self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
            
            // 将创建的player添加到window上
            self.avPlayerLayerContainerView = [[UIView alloc] init];
            self.avPlayerLayerContainerView.frame = ijkPlayerFrame;
            [superView addSubview:self.avPlayerLayerContainerView];
            [self.avPlayerLayerContainerView.layer addSublayer:self.avPlayerLayer];
            self.avPlayerLayer.frame = self.avPlayerLayerContainerView.bounds;
            self.avPlayerLayerContainerView.hidden = YES;
            
            // 将之前正在播放的ijkplayer暂停
            if(manager.isPlaying){
                [manager pause];
            }
            
            // 只有ijkplayer进入才会有player
            [self.avPlayer  addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
            [self.avPlayer addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];
        }
        
  
    }else {
        // 不支持画中画
        NSLog(@"不支持画中画");
    }
}

- (void)setupPip {
    /// 配置画中画
    
    AVPictureInPictureController *pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.avPlayerLayer];
    pipVC.delegate = self;
    self.pipVC = pipVC;
    ///要有延迟 否则可能开启不成功
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [self.pipVC startPictureInPicture];
    });
}

将原始ijkplayer的时间进度同步到avplayer里


#pragma mark - kvo
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
        if ([keyPath isEqualToString:@"status"]) {
            [self fakeAvPlayerStatusChangeofObject:object];
        }else if ([keyPath isEqualToString:@"timeControlStatus"]){
            [self fakeAvPlayerTimeStatusChangeofObject:object];
        }
}
#pragma mark 创建的模拟播放器状态变化
- (void)fakeAvPlayerStatusChangeofObject:(id)object {
    
    switch (self.avPlayer.status) {
        case AVPlayerStatusUnknown:{
            NSLog(@"KVO:未知状态,此时不能播放");
            break;
        }
        case AVPlayerStatusReadyToPlay:{
             NSLog(@"KVO:准备完毕,可以播放");
            // 准备完毕,获取当前创建的avplyaer的时间
            int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
            // 获取原始的ijkplayer的播放时间
            NSTimeInterval currentPlayTime = self.originPlayer.currentTime;
            Float64 seekTo = currentPlayTime;
            // 将时间转换
            CMTime time = CMTimeMakeWithSeconds(seekTo, timeScale);
            BOOL fail = NO;
            @try {
                // 将播放器的播放时间与原始ijkplayer的播放地方同步
                [self.avPlayer seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
            } @catch (NSException *exception) {
                NSLog(@"%@",exception);
                fail = YES;
            }
            if (fail) {
            }else{
                [self.avPlayer play];
            }
            break;
        }
        case AVPlayerStatusFailed:{
            AVPlayerItem * item = (AVPlayerItem *)object;
            NSLog(@"加载异常 %@",item.error);
            
            break;
        }
        default:{

        }
      break;
    }
}
- (void)fakeAvPlayerTimeStatusChangeofObject:(id)object {
    if (@available(iOS 10.0, *)) {}
    else {
        return;
    }
    if (self.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
        //这个可能会多次回调,所以判断一下,防止多次调用[self startPip]
        if (!self.pipAlreadyStartedFlag) {
            //真正开始播放时候 再seek一下, 使播放点更准确
            int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
            NSTimeInterval currentPlayTime = self.originPlayer.currentTime;
            Float64 seekTo = currentPlayTime; //真正开始画中画 大概在2秒之后
            CMTime time = CMTimeMakeWithSeconds(seekTo, timeScale);
            BOOL fail = NO;
            @try {
                [self.avPlayer seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
            } @catch (NSException *exception) {
                NSLog(@"%@",exception);
                fail = YES;
            }
            if (fail) {
                
            }else{
                // 等player开始播放后再开启pip
                [self setupPip];
                self.pipAlreadyStartedFlag = YES;
            }


        }

    }
}

pip执行删除或恢复时,将avplayer的时间进度同步到ijk里

#pragma mark - AVPictureInPictureControllerDelegate
-(void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"即将开启画中画功能");
}

-(void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"已经开启画中画功能");

}

-(void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"即将停止画中画功能");
    if (self.avPlayer != nil) {
        // ijkplayer进入才需要恢复之前的播放时间
//        int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
        NSTimeInterval currentPlayTime = CMTimeGetSeconds(self.avPlayer.currentTime);
        Float64 seekTo = currentPlayTime; //真正开始画中画 大概在2秒之后
//        CMTime time = CMTimeMakeWithSeconds(seekTo, timeScale);
        __weak typeof(self) weakself = self;
        [self.originPlayer seekToTime:seekTo completionHandler:^(BOOL finished) {
            // 销毁内容
            if (weakself.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
                [weakself.originPlayer.currentPlayerManager play];
            }else if (weakself.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPaused) {
                [weakself.originPlayer.currentPlayerManager play];
                [weakself.originPlayer.currentPlayerManager pause];
            }
           
            }];
        
    }else {
        // 销毁内容
    }
    
    

}

-(void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    // 一只不走这个回调
    NSLog(@"已经停止画中画功能");

}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {
    NSLog(@"开启画中画功能失败,原因是%@",error);
}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler{
    // 点击右上角,将画中画恢复成原生播放
    NSLog(@"画中画功能恢复成原生播放,currentTime:%f",CMTimeGetSeconds(self.avPlayer.currentTime));
    // 结束回调
    completionHandler(YES);
}

三、解决加密视屏创建代理服务器进行解密

GCDWebServer

@interface WebServerManager()
@property(nonatomic, strong) GCDWebServer *encrptWebServer;
@property(nonatomic, assign) BOOL isStarting;
@end

@implementation WebServerManager

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static WebServerManager *instance = nil;
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (void)setupEncryptWebServer {
    self.encrptWebServer = [[GCDWebServer alloc] init];
//    __weak typeof(self) weakSelf = self;
    [self.encrptWebServer addHandlerWithMatchBlock:^GCDWebServerRequest * _Nullable(NSString * _Nonnull requestMethod, NSURL * _Nonnull requestURL, NSDictionary * _Nonnull requestHeaders, NSString * _Nonnull urlPath, NSDictionary * _Nonnull urlQuery) {
        // 从地址中取出真实的地址
        // 代理地址: http://localhost:12345/https://www.baidu.com/hello.index?a=1
        // 原始地址:https://www.baidu.com/hello.index?age=100&name=ming
        // 此处的urlPath:/https://www.baidu.com/hello.index?age=100&name=ming
        // 此处的urlQuery:{}
        // 获取到真实的地址
        NSString *path = [urlPath stringByReplacingOccurrencesOfString:@"/http" withString:@"http"];
        // 将真实地址构建成新的真实的请求
        GCDWebServerRequest *request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:[NSURL URLWithString:path] headers:requestHeaders path:urlPath query:urlQuery];
        return request;
    } asyncProcessBlock:^(__kindof GCDWebServerRequest * _Nonnull request, GCDWebServerCompletionBlock  _Nonnull completionBlock) {
        
        // 将GCD的request构造成真实请求的request
        NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:request.URL];
        NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:req.allHTTPHeaderFields];
        if (request.headers[@"Range"] != nil) {
            headers[@"Accept"] = request.headers[@"Accept"];
            headers[@"X-Playback-Session-Id"] = request.headers[@"X-Playback-Session-Id"];
            headers[@"Range"] = request.headers[@"Range"];
            headers[@"User-Agent"] = request.headers[@"User-Agent"];
            headers[@"Accept-Language"] = request.headers[@"Accept-Language"];
            headers[@"Accept-Encoding"] = request.headers[@"Accept-Encoding"];
        }
        
        req.HTTPMethod = request.method;
        req.allHTTPHeaderFields = headers;
//        req.timeoutInterval = 120;
        
        // 使用NSURLSession进行网络请求
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSData *deCodedData = nil;
            // 对原始数据进行解密
            if (!error && data) {
                deCodedData  = [data fe_aesDecryptWithKey:kAESKey];
            }
            // 使用解密的数据组装响应返回
            GCDWebServerDataResponse *res = [GCDWebServerDataResponse responseWithData:deCodedData contentType:@"audio/mpegurl"];
            // 回调完成
            completionBlock(res);
        }];
        [task resume];
    }];
    
    [self.encrptWebServer startWithPort:12345 bonjourName:nil];
}

- (void)start {
    if (!self.isStarting) {
        [self setupEncryptWebServer];
        self.isStarting = YES;
    }
    
}
+ (NSURL *)proxyUrl:(NSString *)url {
    NSURL *proxyURL = [[WebServerManager.sharedInstance.encrptWebServer serverURL] URLByAppendingPathComponent:url];
    return proxyURL;
}
@end

你可能感兴趣的:(视屏解密、AVPlayer/IJKPlayer/播放画中画)