視屏解密、AVPlayer/IJKPlayer/播放畫(huà)中畫(huà)

PictureInPictureDemo

畫(huà)中畫(huà)demo: https://github.com/eye1234456/PictureInPictureDemo.git

在線(xiàn)mp4轉(zhuǎn)m3u8: https://mp4.to/m3u8/
測(cè)試視頻下載:https://www.cnblogs.com/v5captain/p/12144699.html
http://www.itdecent.cn/p/cab2cd7b3f1c
http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8
m3u8在線(xiàn)播放:

一、AVPlayer進(jìn)行畫(huà)中畫(huà)

如果播放器是AVPlayer,直接使用系統(tǒng)提供的AVPictureInPictureController進(jìn)行播放即可

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

二、 ijkplayer進(jìn)行畫(huà)中畫(huà)

如果播放器是IJKPlayer的,需要?jiǎng)?chuàng)建一個(gè)隱藏的AVPlayer,然后再通過(guò)avplayer進(jìn)行畫(huà)中畫(huà),這個(gè)過(guò)程,需要將ijkplayer的播放進(jìn)度同步給avplayer,同時(shí)畫(huà)中畫(huà)結(jié)束時(shí),也需要將avaplayer的進(jìn)度同步到原始的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轉(zhuǎn)換為window的坐標(biāo)體系
            CGRect ijkPlayerFrame = [superView convertRect:ijkContainerView.frame toView:superView];
            // 創(chuàng)建一個(gè)隱藏的AvPlayer
            NSError *error = nil;
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
            [[AVAudioSession sharedInstance] setActive:YES error:&error];
           
            if (error) {
                NSLog(@"請(qǐng)求權(quán)限失敗的原因?yàn)?@",error);
                return;
            }
            self.avPlayer = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:manager.assetURL.absoluteString]];
            self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
            
            // 將創(chuàng)建的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進(jìn)入才會(huì)有player
            [self.avPlayer  addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
            [self.avPlayer addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];
        }
        
  
    }else {
        // 不支持畫(huà)中畫(huà)
        NSLog(@"不支持畫(huà)中畫(huà)");
    }
}

- (void)setupPip {
    /// 配置畫(huà)中畫(huà)
    
    AVPictureInPictureController *pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.avPlayerLayer];
    pipVC.delegate = self;
    self.pipVC = pipVC;
    ///要有延遲 否則可能開(kāi)啟不成功
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [self.pipVC startPictureInPicture];
    });
}

將原始ijkplayer的時(shí)間進(jìn)度同步到avplayer里


#pragma mark - kvo
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:@"status"]) {
            [self fakeAvPlayerStatusChangeofObject:object];
        }else if ([keyPath isEqualToString:@"timeControlStatus"]){
            [self fakeAvPlayerTimeStatusChangeofObject:object];
        }
}
#pragma mark 創(chuàng)建的模擬播放器狀態(tài)變化
- (void)fakeAvPlayerStatusChangeofObject:(id)object {
    
    switch (self.avPlayer.status) {
        case AVPlayerStatusUnknown:{
            NSLog(@"KVO:未知狀態(tài),此時(shí)不能播放");
            break;
        }
        case AVPlayerStatusReadyToPlay:{
             NSLog(@"KVO:準(zhǔn)備完畢,可以播放");
            // 準(zhǔn)備完畢,獲取當(dāng)前創(chuàng)建的avplyaer的時(shí)間
            int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
            // 獲取原始的ijkplayer的播放時(shí)間
            NSTimeInterval currentPlayTime = self.originPlayer.currentTime;
            Float64 seekTo = currentPlayTime;
            // 將時(shí)間轉(zhuǎn)換
            CMTime time = CMTimeMakeWithSeconds(seekTo, timeScale);
            BOOL fail = NO;
            @try {
                // 將播放器的播放時(shí)間與原始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) {
        //這個(gè)可能會(huì)多次回調(diào),所以判斷一下,防止多次調(diào)用[self startPip]
        if (!self.pipAlreadyStartedFlag) {
            //真正開(kāi)始播放時(shí)候 再seek一下, 使播放點(diǎn)更準(zhǔn)確
            int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
            NSTimeInterval currentPlayTime = self.originPlayer.currentTime;
            Float64 seekTo = currentPlayTime; //真正開(kāi)始畫(huà)中畫(huà) 大概在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開(kāi)始播放后再開(kāi)啟pip
                [self setupPip];
                self.pipAlreadyStartedFlag = YES;
            }


        }

    }
}

pip執(zhí)行刪除或恢復(fù)時(shí),將avplayer的時(shí)間進(jìn)度同步到ijk里

#pragma mark - AVPictureInPictureControllerDelegate
-(void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"即將開(kāi)啟畫(huà)中畫(huà)功能");
}

-(void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"已經(jīng)開(kāi)啟畫(huà)中畫(huà)功能");

}

-(void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    NSLog(@"即將停止畫(huà)中畫(huà)功能");
    if (self.avPlayer != nil) {
        // ijkplayer進(jìn)入才需要恢復(fù)之前的播放時(shí)間
//        int32_t timeScale = self.avPlayer.currentItem.asset.duration.timescale;
        NSTimeInterval currentPlayTime = CMTimeGetSeconds(self.avPlayer.currentTime);
        Float64 seekTo = currentPlayTime; //真正開(kāi)始畫(huà)中畫(huà) 大概在2秒之后
//        CMTime time = CMTimeMakeWithSeconds(seekTo, timeScale);
        __weak typeof(self) weakself = self;
        [self.originPlayer seekToTime:seekTo completionHandler:^(BOOL finished) {
            // 銷(xiāo)毀內(nèi)容
            if (weakself.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
                [weakself.originPlayer.currentPlayerManager play];
            }else if (weakself.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPaused) {
                [weakself.originPlayer.currentPlayerManager play];
                [weakself.originPlayer.currentPlayerManager pause];
            }
           
            }];
        
    }else {
        // 銷(xiāo)毀內(nèi)容
    }
    
    

}

-(void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
    // 一只不走這個(gè)回調(diào)
    NSLog(@"已經(jīng)停止畫(huà)中畫(huà)功能");

}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {
    NSLog(@"開(kāi)啟畫(huà)中畫(huà)功能失敗,原因是%@",error);
}

- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler{
    // 點(diǎn)擊右上角,將畫(huà)中畫(huà)恢復(fù)成原生播放
    NSLog(@"畫(huà)中畫(huà)功能恢復(fù)成原生播放,currentTime:%f",CMTimeGetSeconds(self.avPlayer.currentTime));
    // 結(jié)束回調(diào)
    completionHandler(YES);
}

三、解決加密視屏創(chuàng)建代理服務(wù)器進(jìn)行解密

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<NSString *,NSString *> * _Nonnull requestHeaders, NSString * _Nonnull urlPath, NSDictionary<NSString *,NSString *> * _Nonnull urlQuery) {
        // 從地址中取出真實(shí)的地址
        // 代理地址: 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:{}
        // 獲取到真實(shí)的地址
        NSString *path = [urlPath stringByReplacingOccurrencesOfString:@"/http" withString:@"http"];
        // 將真實(shí)地址構(gòu)建成新的真實(shí)的請(qǐng)求
        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構(gòu)造成真實(shí)請(qǐng)求的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進(jìn)行網(wǎng)絡(luò)請(qǐng)求
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSData *deCodedData = nil;
            // 對(duì)原始數(shù)據(jù)進(jìn)行解密
            if (!error && data) {
                deCodedData  = [data fe_aesDecryptWithKey:kAESKey];
            }
            // 使用解密的數(shù)據(jù)組裝響應(yīng)返回
            GCDWebServerDataResponse *res = [GCDWebServerDataResponse responseWithData:deCodedData contentType:@"audio/mpegurl"];
            // 回調(diào)完成
            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
?著作權(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)容