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

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


你可能感兴趣的:(ios,objC)