您的位置:首页 > 其它

利用NSURLProtocol和本地代理实现在线视频边播放边缓存

2015-08-28 17:34 573 查看
Wrote By Fanxiushu 2015-08-28,引用或转载请注明原始作者

接上文,

因为真机上,好像是iOS7以后的系统,AVPlayer和MPMovie等iOS自带的视频播放控件,

虽然表面上还是使用 NSURL的概念,但是在内部并不是通过 URL Loading System,

应该是直接通过更底层的socket来通讯获取服务端视频数据,并不清楚苹果这么做的理由,估计是为了效率吧。

我们要实现一边播放在线视频,一边还得缓存视频数据;就得想方设法在播放视频过程中获取视频数据,然后缓存起来。

因为AVPlayer或MPMovie等iOS自带的多媒体播放控件并没有提供获取正在播放视频数据的接口,

所以得从网络通信这个地方,想法获取视频文件数据。

既然不能直接从URL Loading System截获到数据,那么就绕一个圈。

实现一个本地代理服务,让AVPlayer对视频的数据的请求都发送到代理,

代理再把请求发到真正的服务端,

然后一边从真正服务端拉取视频数据,一边缓存视频数据,一边再发给AVPlayer等控件。

如果我们在代理服务中, 发送请求到真正视频服务端的时候,使用 NSURLConnection接口,

那么很显然,网络通信数据流再次进入到 URL Loading System,

从而通过 子类化的 NSURLProtocol可以拦截和缓存到这个视频数据流。

这样前一篇中实现的 NSURLProtocol 就再次发挥了作用。

当然 AVPlayer等播放控件是使用断点续传(也就是206请求)方式获取视频数据的。

我们也可以直接在代理服务中获取视频数据,然后自己缓存和处理,反正效果都差不多。

但是我更喜欢使用NSURLProtocol,因为它掌控着全局,能缓存程序的所有NSURL网络请求,只要你愿意缓存的话。

这里重点说说这个代理服务的开发。其实不要把他想象的多复杂,

只要具备基本的 socket编程基础和基本的HTTP协议概念,就能自己实现这个代理服务。

当然你也可以使用网络上现有的代理开源代码,比如 HTTPServer等。但这不是我的习惯,

因为发现自己实现一个简单代理,远比使用别人一堆的代码,还得研究如何使用,还得担心BUG来的更加简洁和方便。

使用c开发,objc的好处就是能在其中嵌入大家都熟悉的c代码,而不会出现编译错误。

由于是个简单的代理服务端,所以采用多线程模式就可以了,一个连接一个线程。

int fd = socket(AF_INET,SOCK_STREAM,0);

bind(fd,....); ///// 绑定到 127.0.0.1的随意一个端口,比如 端口 12345 。

listen(fd,5);

while(1){

int s = accept(fd,0,0);

/////开启新线程处理新到来的连接。

dispatch_async(dispatch_get_global_queue(0,0), ^{

////

do_client(s); /////处理每个到来的连接,在这里负责把请求转发到真正的视频服务端,

同时负责把从服务端获得的数据转发给 s。

/////

});

///////////

}

以上就是简单的代理服务框架。 重点是在 do_client函数的处理。

为了简单,把真正要请求的URL直接作为本地代理请求的参数。比如如下:

一个真实的URL: http://v.sohu.com/video/a.mp4,
给 AVPlayer或MPMovie等控件的参数就是 http://127.0.0.1:12345/http://v.sohu.com/video/a.mp4
void do_client(int s)

{

///////这里首先接收AVPlayer或MPMovie等控件发来的视频请求头,其实就是HTTP请求头,

这里只考虑 GET请求,毫无疑问,几乎都是GET请求。

char buf[4096];

int pos = 0;

while(pos<4096){

int r = (int)recv( s, (buf + pos), (size_t)(4096-pos), 0);

if(r<=0){

NSLog(@"**** proxyServer: Recv Header Error.");

close(s);

return ;

}

pos += r;

buf[pos] = 0;

char* ptr = strstr(buf,"\r\n\r\n");

if( ptr ){

*(ptr+2)=0;

break;

}

}

////////////////////////解析HTTP协议头,从中分析出真正请求的URL,同时如果请求是 断点续传请求的话,还得分析出断点的信息。

char* hdr = buf;

char* range = NULL;

const char* t="Range: "; int ll = (int)strlen(t);

while(hdr){

char* e = strstr( hdr, "\r\n"); if(!e)break;

*e = 0;

///

// NSLog(@"[%s]", hdr);

if(strncasecmp(hdr, t, ll ) ==0 ){

range = hdr + ll;

}

///

hdr = e+2; ////

}

////////

char* uri = strchr( buf,' '); if(!uri) {close(s);return;}

uri++;

char* end = strchr( uri, ' '); if(!end){close(s);return;}

*end = 0;

//////////////////////////////////然后就是发起真正的网络请求,这里使用 NSURLConnection来发起网络请求,

这样所有网络数据都会经过 NSURLProtocol 协议。

NSString* strurl = [NSString stringWithUTF8String:(uri+1)]; ////

NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:strurl]

cachePolicy:NSURLRequestReloadIgnoringLocalCacheData

timeoutInterval:10.0];

if(range){///如果是断点续传请求,还必须标志这个断点

[request setValue:[NSString stringWithUTF8String:range] forHTTPHeaderField:@"Range"];

//////

}

ProxyPerClient* proxy = [[ProxyPerClient alloc]init]; ////这个是代理类,在这个代理类无非就是把接收到数据发送给AVPlayer等控件。

proxy.sock = s;

//////

NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:proxy startImmediately:NO];

proxy.conn = conn;

[conn setDelegateQueue:[[NSOperationQueue alloc]init]]; /// enter Queue for execute

////////

。。。。。。。。。

}

至于 ProxyPerClient如何实现,我就不贴代码了,稍后可以从CSDN上下载这个代理服务的代码,

代码如果要实现边播放边缓存功能,需要配合 NSURLProtocol协议的代码。

至此 ,基本就完成了一个简单的代理服务的开发工作。

对应资源

http://download.csdn.net/detail/fanxiushu/9059053
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: