0
배경 : MPMoviePlayer에서 지원하지 않는 서버 측의 사용자 지정 인증 메커니즘이 있습니다. 그래서, 플레이어의 초기 요청을 받아 HLS 매니페스트 파일을 제공하는 로컬 루프백 HTTP 서버를 갖기로 결정했습니다.iOS의 로컬 HTTP 서버를 통한 HLS 재생
플레이어가 로컬 HTTP 서버에 요청을 시작한 후 로컬 HTTP 서버가 서버에서 매니페스트 파일을 가져 와서 플레이어에게 HTTP 응답으로 다시 씁니다. 그러나 MPMoviePlayer는 그 후에 비디오를 재생하지 않습니다.
나를 도와 줄 사람이 있습니까?
#import "QumuMediaPlayerProxy.h"
#import "GZIP.h"
#define WELCOME_MSG 0
#define ECHO_MSG 1
#define WARNING_MSG 2
#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0
@interface QumuMediaPlayerProxy()
@property NSURL *contentURL;
@end
@implementation QumuMediaPlayerProxy
+(NSURL*)getProxyURL{
return [NSURL URLWithString:[NSString stringWithFormat:@"http://192.168.2.11:%d%@", SERVER_PORT, @"/nkm.do"]];
}
- (id)initWithURL:(NSURL*)contentURL
{
if((self = [super init]))
{
socketQueue = dispatch_queue_create("socketQueue", NULL);
listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];
connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
isRunning = NO;
self.contentURL = contentURL;
}
return self;
}
- (void)startOnPort:(int)port
{
if(!isRunning)
{
if (port < 0 || port > 65535)
{
port = 0;
}
NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
NSLog(@"Error starting QumuMediaPlayerProxy: %@", error.debugDescription);
return;
}
NSLog(@"QumuMediaPlayerProxy started on port %hu", [listenSocket localPort]);
isRunning = YES;
}
}
-(void)stop
{
if(isRunning)
{
// Stop accepting connections
[listenSocket disconnect];
// Stop any client connections
@synchronized(connectedSockets)
{
NSUInteger i;
for (i = 0; i < [connectedSockets count]; i++)
{
// Call disconnect on the socket,
// which will invoke the socketDidDisconnect: method,
// which will remove the socket from the list.
[[connectedSockets objectAtIndex:i] disconnect];
}
}
NSLog(@"Stopped QumuMediaPlayerProxy");
isRunning = false;
}
}
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
// This method is executed on the socketQueue (not the main thread)
@synchronized(connectedSockets)
{
[connectedSockets addObject:newSocket];
NSLog(@"==Accepted client==");
}
[newSocket readDataWithTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
// This method is executed on the socketQueue (not the main thread)
if (tag == ECHO_MSG)
{
[sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
// This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
if (msg)
{
NSLog(@"msg===>%@",msg);
NSLog(@"contentURL===>%@",self.contentURL.absoluteString);
NSString *getStr = [msg componentsSeparatedByString:@"\n"][0];
NSString *requestedURL = [getStr substringWithRange:NSMakeRange(4, getStr.length-9)];
//NSString *host = @"http://127.0.0.1:6910/";
NSString *host = @"http://192.168.2.11:6910/";
NSURL *requestURL = self.contentURL;
if(![requestedURL containsString:@"nkm.do"]){
NSString *actualHost = [self.contentURL.absoluteString stringByReplacingOccurrencesOfString:self.contentURL.lastPathComponent withString:@""];
requestURL = [NSURL URLWithString:[actualHost stringByAppendingString:requestedURL]];
}
NSData *manifestData = [[QumuJSONHelper getInstance] fetchM3U8Playlist:requestURL];
NSString *manifestStr = [[NSString alloc] initWithData:manifestData encoding:NSUTF8StringEncoding];
NSLog(@"manifestStr===>%@",manifestStr);
/* NSArray *manifestArray = [manifestStr componentsSeparatedByString:@"\n"];
NSString *modifiedManifest = @"";
for(int i=0;i<manifestArray.count;i++){
NSString *token = manifestArray[i];
if([token containsString:@"#EXT-X-STREAM-INF"]){
NSLog(@"== Found tag EXT-X-STREAM-INF ==");
modifiedManifest = [modifiedManifest stringByAppendingString:token];
modifiedManifest = [modifiedManifest stringByAppendingString:@"\n"];
token = manifestArray[++i];
// token = [@"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/" stringByAppendingString:token];
token = [host stringByAppendingString:token];
NSLog(@"Modified URL===>%@",token);
}
modifiedManifest = [modifiedManifest stringByAppendingString:token];
modifiedManifest = [modifiedManifest stringByAppendingString:@"\n"];
}
modifiedManifest = [modifiedManifest stringByReplacingOccurrencesOfString:@"URI=\"" withString:[NSString stringWithFormat:@"URI=\"%@",host]];
NSLog(@"modifiedManifest===>%@",modifiedManifest);*/
NSString *response = @"HTTP/1.1 200 OK";
response = [response stringByAppendingString:@"\r\nContent-Type: application/vnd.apple.mpegurl;charset=UTF-8"];
response = [response stringByAppendingFormat:@"\r\nContent-Length: %ld", (unsigned long)manifestData.length];
response = [response stringByAppendingString:@"\r\nConnection: keep-alive"];
response = [response stringByAppendingString:@"\r\n\r\n"];
NSLog(@"response header ===>%@",response);
NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:responseData withTimeout:-1 tag:0];
[sock writeData:manifestData withTimeout:-1 tag:0];
}
else
{
NSLog(@"Error converting received data into UTF-8 String");
}
}
});
// Echo message back to client
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}
/**
* This method is called if a read has timed out.
* It allows us to optionally extend the timeout.
* We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length
{
if (elapsed <= READ_TIMEOUT)
{
NSString *warningMsg = @"Are you still there?\r\n";
NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];
return READ_TIMEOUT_EXTENSION;
}
return 0.0;
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (sock != listenSocket)
{
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
NSLog(@"Client Disconnected");
}
});
@synchronized(connectedSockets)
{
[connectedSockets removeObject:sock];
}
}
}
-(void)dealloc{
// [self stop];
}
@end
미리 감사드립니다.
나는 그것이 도움이되지 않는다는 것을 알고 있지만, HLS 프로토콜이 로컬 호스트에서 재생을 허용하는지 잘 모르겠습니다. 나는 몇 달 전에 비슷한 기술을 시도하고 있었고, 애플 미디어 개발자 포럼에서 어딘가에 가능하지 않다는 것을 발견했다. 오프라인 재생을 원했고 .ts 파일을 다운로드하여 스트리밍하도록 구성된 내 플레이어를 사용할 수 있기를 바랬습니다. 그것은 불가능했기 때문에, 나는 mp4 파일을 다운로드하고 그것을 지원하기 위해 플레이어를 변경했습니다. – timgcarlson
Apple dev 포럼에 액세스 할 수 있다고 가정하면이 게시물을 참조하십시오 ... https://devforums.apple.com/message/1031399#1031399 (baleighsdad는 Apple HLS 팀의 엔지니어입니다). – timgcarlson
timgcarlson 링크를 제공해 주셔서 감사합니다. 현재 MPMoviePlayerController를 사용하고 있습니다. 나는 MPMoviePlayerController를 사용하여 MP4 파일을 오프라인에서 성공적으로 스트리밍하고 있습니다. 나는 HLS 비디오와 동일하게하고 싶다. 필자의 시나리오에서는 전체 비디오를 로컬로 스트리밍 할 필요가 없습니다. 클라이언트 인증서 인증 메커니즘에 의해 보호되고 MPMoviePlayerController가 인증을 처리 할 수 없기 때문에 HLS 매니페스트를 제공 할 수 있기를 바랍니다. AVPlayer로 변경해야합니까? – Madala