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