MPMoviePlayerController 和 Auth-Based HLS 后端服务器
MPMoviePlayerController and Auth-Based HLS Backend Server
我目前正在使用 MPMoviePlayerController
在我的 iOS 应用程序中提供视频服务。这些文件是从我们需要身份验证的后端服务器流式传输的。它是 key-based 在授权 HTTP Header 中设置的身份验证。
它曾经完美地处理单个视频文件。现在我们正在尝试实施 HLS 自适应流,但我们遇到了障碍。我目前正在使用自定义 NSURLProtocol
子类来捕获对我们的后端服务器发出的请求并注入正确的授权 header。对于 HLS,它根本不起作用。
当我们查看服务器日志时,我们清楚地看到对 m3u8 文件的第一次请求工作正常。然后所有后续调用(其他 m3u8 文件和 ts 也)被 403 禁止。 MPMoviePlayerController
似乎没有对其他文件使用 NSURLProtocol
。 (旁注:它确实适用于模拟器思想,但不适用于物理设备,这让我认为两者的实现方式不同)。
MPMoviePlayerController 实例化
self.videoController = [[MPMoviePlayerController alloc] initWithContentURL:video.videoURL];
URL协议拦截
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *newRequest = request.mutableCopy;
[newRequest setValue:@"HIDDEN" forHTTPHeaderField:@"Authorization"];
return newRequest;
}
有任何想法、建议和解决方法吗?
通过使用NSURLProtocol,您可以拦截MPMoviePlayerController 与流请求之间的通信。沿途注入 cookie,或者可能保存离线视频流。为此,您应该创建一个新的 class extending NSURLProtocol:
希望对您有所帮助:
GAURLProtocol.h
#import <Foundation/Foundation.h>
@interface GAURLProtocol : NSURLProtocol
+ (void) register;
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie;
@end
GAURLProtocol.m
#import "GAURLProtocol.h"
@interface GAURLProtocol() <NSURLConnectionDelegate> {
NSMutableURLRequest* myRequest;
NSURLConnection * connection;
}
@end
static NSString* injectedURL = nil;
static NSString* myCookie = nil;
@implementation GAURLProtocol
+ (void) register
{
[NSURLProtocol registerClass:[self class]];
}
// public static function to call when injecting a cookie
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie
{
injectedURL = urlString;
myCookie = cookie;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if([[[request allHTTPHeaderFields] objectForKey:@"Heeehey"] isEqualToString:@"Huuu"])
{
return NO;
}
return [[[request URL] absoluteString] isEqualToString:injectedURL];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
// intercept the request and handle it yourself
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) {
myRequest = request.mutableCopy;
[myRequest setValue:@"Huuu" forHTTPHeaderField:@"Heeehey"]; // add your own signature to the request
}
return self;
}
// load the request
- (void)startLoading {
// inject your cookie
[myRequest setValue:myCookie forHTTPHeaderField:@"Cookie"];
connection = [[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
}
// overload didReceive data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}
// overload didReceiveResponse
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse *)response {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[myRequest cachePolicy]];
}
// overload didFinishLoading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[[self client] URLProtocolDidFinishLoading:self];
}
// overload didFail
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[[self client] URLProtocol:self didFailWithError:error];
}
// handle load cancelation
- (void)stopLoading {
[connection cancel];
}
@end
注册
// register protocol
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[GAURLProtocol class]];
return YES;
}
用法
[GAURLProtocol injectURL:@"http://example.com/video.mp4" cookie:@"cookie=f23r3121"];
MPMoviePlayerController * moviePlayer = [[MPMoviePlayerController alloc]initWithContentURL:@"http://example.com/video.mp4"];
[moviePlayer play];
在与 Apple Developer Technical Support 核实后,我认为我想要实现的目标是不可能的(并且不受支持)。
引用自回复:
The problem you're seeing with NSURLProtocol and so on is that the movie playback subsystem does not run its HTTP requests within your process. Rather, these requests are run from within a separate system process, mediaserverd. Thus, all your efforts to affect the behaviour of that playback are futile.
@Marc-Alexandre Bérubé 我可以想到以下解决方法:
运行 您应用中的代理服务器来代理所有视频 URL。通过向请求中注入必要的 auth headers 来下载所有视频内容,并通过代理服务器将内容中继回媒体播放器以进行渲染。这种方法可能不适用于大型视频,因为视频渲染只会在整个视频下载完毕后才开始。
我目前正在使用 MPMoviePlayerController
在我的 iOS 应用程序中提供视频服务。这些文件是从我们需要身份验证的后端服务器流式传输的。它是 key-based 在授权 HTTP Header 中设置的身份验证。
它曾经完美地处理单个视频文件。现在我们正在尝试实施 HLS 自适应流,但我们遇到了障碍。我目前正在使用自定义 NSURLProtocol
子类来捕获对我们的后端服务器发出的请求并注入正确的授权 header。对于 HLS,它根本不起作用。
当我们查看服务器日志时,我们清楚地看到对 m3u8 文件的第一次请求工作正常。然后所有后续调用(其他 m3u8 文件和 ts 也)被 403 禁止。 MPMoviePlayerController
似乎没有对其他文件使用 NSURLProtocol
。 (旁注:它确实适用于模拟器思想,但不适用于物理设备,这让我认为两者的实现方式不同)。
MPMoviePlayerController 实例化
self.videoController = [[MPMoviePlayerController alloc] initWithContentURL:video.videoURL];
URL协议拦截
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *newRequest = request.mutableCopy;
[newRequest setValue:@"HIDDEN" forHTTPHeaderField:@"Authorization"];
return newRequest;
}
有任何想法、建议和解决方法吗?
通过使用NSURLProtocol,您可以拦截MPMoviePlayerController 与流请求之间的通信。沿途注入 cookie,或者可能保存离线视频流。为此,您应该创建一个新的 class extending NSURLProtocol:
希望对您有所帮助:
GAURLProtocol.h
#import <Foundation/Foundation.h>
@interface GAURLProtocol : NSURLProtocol
+ (void) register;
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie;
@end
GAURLProtocol.m
#import "GAURLProtocol.h"
@interface GAURLProtocol() <NSURLConnectionDelegate> {
NSMutableURLRequest* myRequest;
NSURLConnection * connection;
}
@end
static NSString* injectedURL = nil;
static NSString* myCookie = nil;
@implementation GAURLProtocol
+ (void) register
{
[NSURLProtocol registerClass:[self class]];
}
// public static function to call when injecting a cookie
+ (void) injectURL:(NSString*) urlString cookie:(NSString*)cookie
{
injectedURL = urlString;
myCookie = cookie;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if([[[request allHTTPHeaderFields] objectForKey:@"Heeehey"] isEqualToString:@"Huuu"])
{
return NO;
}
return [[[request URL] absoluteString] isEqualToString:injectedURL];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
// intercept the request and handle it yourself
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) {
myRequest = request.mutableCopy;
[myRequest setValue:@"Huuu" forHTTPHeaderField:@"Heeehey"]; // add your own signature to the request
}
return self;
}
// load the request
- (void)startLoading {
// inject your cookie
[myRequest setValue:myCookie forHTTPHeaderField:@"Cookie"];
connection = [[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
}
// overload didReceive data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}
// overload didReceiveResponse
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse *)response {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[myRequest cachePolicy]];
}
// overload didFinishLoading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[[self client] URLProtocolDidFinishLoading:self];
}
// overload didFail
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[[self client] URLProtocol:self didFailWithError:error];
}
// handle load cancelation
- (void)stopLoading {
[connection cancel];
}
@end
注册
// register protocol
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[GAURLProtocol class]];
return YES;
}
用法
[GAURLProtocol injectURL:@"http://example.com/video.mp4" cookie:@"cookie=f23r3121"];
MPMoviePlayerController * moviePlayer = [[MPMoviePlayerController alloc]initWithContentURL:@"http://example.com/video.mp4"];
[moviePlayer play];
在与 Apple Developer Technical Support 核实后,我认为我想要实现的目标是不可能的(并且不受支持)。
引用自回复:
The problem you're seeing with NSURLProtocol and so on is that the movie playback subsystem does not run its HTTP requests within your process. Rather, these requests are run from within a separate system process, mediaserverd. Thus, all your efforts to affect the behaviour of that playback are futile.
@Marc-Alexandre Bérubé 我可以想到以下解决方法: 运行 您应用中的代理服务器来代理所有视频 URL。通过向请求中注入必要的 auth headers 来下载所有视频内容,并通过代理服务器将内容中继回媒体播放器以进行渲染。这种方法可能不适用于大型视频,因为视频渲染只会在整个视频下载完毕后才开始。