multipart / x-mixed-replace ActionScript3 и Google Chrome (и другие также) - PullRequest
2 голосов
/ 26 февраля 2011

У меня странная проблема, я работаю над камерой Bluetooth, мы хотим предоставить миру интерфейс mjpeg.

Mjpeg - это просто http-сервер, отвечающий один jpeg за другим с помощью keept соединенияоткрыть.Мой сервер сейчас дает мне:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Cache-Directive: no-cache
Expires: 0
Pragma-Directive: no-cache
Server: TwistedWeb/10.0.0
Connection: Keep-Alive
Pragma: no-cache
Cache-Control: no-cache, no-store, must-revalidate;
Date: Sat, 26 Feb 2011 20:29:56 GMT
Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Cache-Directive: no-cache
Expires: 0
Pragma-Directive: no-cache
Server: TwistedWeb/10.0.0
Connection: Keep-Alive
Pragma: no-cache
Cache-Control: no-cache, no-store, must-revalidate;
Cate: Sat, 26 Feb 2011 20:29:56 GMT
Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY

И затем для каждого кадра:

--myBOUNDARY
Content-Type: image/jpeg
Content-Size: 25992

BINARY JPEG CONTENT.....
(new line)

Я сделал для него Flash-клиент, чтобы мы могли использовать один и тот же код на любом устройстве., сервер реализован на Python с использованием витой и нацелена на Android, среди прочего проблема в Android заключается в том, что Google забыл включить поддержку mjpeg .... Этот клиент использует URLStream.

Кодвот что:

package net.aircable {
  import flash.errors.*;
  import flash.events.*;
  import flash.net.URLRequest;
  import flash.net.URLRequestMethod;
  import flash.net.URLRequestHeader;
  import flash.net.URLStream;
  import flash.utils.ByteArray;
  import flash.utils.Dictionary;
  import flash.system.Security;
  import mx.utils.Base64Encoder;
  import flash.external.ExternalInterface;
  import net.aircable.XHRMultipartEvent;

  public class XHRMultipart extends EventDispatcher{

    private function trc(what: String): void{
        //ExternalInterface.call("console.log", what); //for android
        trace(what);
    }

    private var uri: String;
    private var username: String;
    private var password: String;
    private var stream: URLStream;
    private var buffer: ByteArray;
    private var pending: int;
    private var flag: Boolean;
    private var type: String;
    private var browser: String;

    private function connect(): void {
      stream = new URLStream();
      trc("connect")
      var request:URLRequest = new URLRequest(uri);
      request.method = URLRequestMethod.POST;
      request.contentType = "multipart/x-mixed-replace";
      trc(request.contentType)
/*      request.requestHeaders = new Array(
        new URLRequestHeader("Content-type", "multipart/x-mixed-replace"),
        new URLRequestHeader("connection", "keep-alive"),
        new URLRequestHeader("keep-alive", "115"));
*/
      trace(request.requestHeaders);
      trc("request.requestHeaders")
      configureListeners();
      try {
        trc("connecting");
        stream.load(request);
        trc("connected")
      } catch (error:Error){
          trc("Unable to load requested resource");
      }
      this.pending = 0;
      this.flag = false;
      this.buffer = new ByteArray();
    }

    public function XHRMultipart(uri: String = null, 
                                        username: String = null, 
                                        password: String = null){
      trc("XHRMultipart()");
      var v : String = ExternalInterface.call("function(){return navigator.appVersion+'-'+navigator.appName;}");
      trc(v);
      v=v.toLowerCase();
      if (v.indexOf("chrome") > -1){
        browser="chrome";
      } else if (v.indexOf("safari") > -1){
        browser="safari";
      }
      else {
        browser=null;
      }
      trc(browser);
      if (uri == null)
        uri = "../stream?ohhworldIhatethecrap.mjpeg";
      this.uri = uri;
      connect();
    }


    private function configureListeners(): void{
      stream.addEventListener(Event.COMPLETE, completeHandler, false, 0, true);
      stream.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler, false, 0, true);
      stream.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, false, 0, true);
      stream.addEventListener(Event.OPEN, openHandler, false, 0, true);
      stream.addEventListener(ProgressEvent.PROGRESS, progressHandler, false, 0, true);
      stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler, false, 0, true);
    }

    private function propagatePart(out: ByteArray, type: String): void{
        trc("found " + out.length + " mime: " + type);
        dispatchEvent(new XHRMultipartEvent(XHRMultipartEvent.GOT_DATA, true, false, out));
    }

    private function readLine(): String {
        var out: String = "";
        var temp: String;

        while (true){
            if (stream.bytesAvailable == 0)
                break;
            temp = stream.readUTFBytes(1);
            if (temp == "\n")
                break;
            out+=temp;
        }
        return out;
    }

    private function extractHeader(): void {
        var line: String;
        var headers: Object = {};
        var head: Array;

        while ( (line=readLine()) != "" ){
            if ( stream.bytesAvailable == 0)
                return;
            if (line.indexOf('--') > -1){
                continue;
            }
            head = line.split(":");
            if (head.length==2){
                headers[head[0].toLowerCase()]=head[1];
            }
        }

        pending=int(headers["content-size"]);
        type = headers["content-type"];
        if ( pending > 0 && type != null)
            flag = true;
        trc("pending: " + pending + " type: " + type);
    }

    private function firefoxExtract(): void {
        trc("firefoxPrepareToExtract");
        if (stream.bytesAvailable == 0){
            trc("No more bytes, aborting")
            return;
        }

        while ( flag == false ) {
            if (stream.bytesAvailable == 0){
                trc("No more bytes, aborting - can't extract headers");
                return;
            }
            extractHeader()
        }

        trc("so far have: " + stream.bytesAvailable);
        trc("we need: " + pending);
        if (stream.bytesAvailable =0; x-=1){
            buffer.position=x;
            buffer.readBytes(temp, 0, 2);
            // check if we found end marker
            if (temp[0]==0xff && temp[1]==0xd9){
                end=x;
                break;
            }
        }

        trc("findImageInBuffer, start: " + start + " end: " + end);
        if (start >-1 && end > -1){
            var output: ByteArray = new ByteArray();
            buffer.position=start;
            buffer.readBytes(output, 0 , end-start);
            propagatePart(output, type);
            buffer.position=0; // drop everything
            buffer.length=0;
        }
    }

    private function safariExtract(): void {
        trc("safariExtract()");
        stream.readBytes(buffer, buffer.length);
        findImageInBuffer();
    }

    private function chromeExtract(): void {
        trc("chromeExtract()");
        stream.readBytes(buffer, buffer.length);
        findImageInBuffer();
    }

    private function extractImage(): void {
        trc("extractImage");

        if (browser == null){
            firefoxExtract();
        }
        else if (browser == "safari"){
            safariExtract();
        }
        else if (browser == "chrome"){
            chromeExtract();
        }
    }

    private function isCompressed():Boolean {
        return (stream.readUTFBytes(3) == ZLIB_CODE);
    }

    private function completeHandler(event:Event):void {
        trc("completeHandler: " + event);
        //extractImage();
        //connect();
    }

    private function openHandler(event:Event):void {
        trc("openHandler: " + event);
    }

    private function progressHandler(event:ProgressEvent):void {
        trc("progressHandler: " + event)
        trc("available: " + stream.bytesAvailable);
        extractImage();
        if (event.type == ProgressEvent.PROGRESS)
            if (event.bytesLoaded > 1048576) { //1
XHRMultipart()
5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.114 Safari/534.16-Netscape
chrome
connect
multipart/x-mixed-replace

request.requestHeaders
connecting
connected
openHandler: [Event type="open" bubbles=false cancelable=false eventPhase=2]
openHandler: [Event type="open" bubbles=false cancelable=false eventPhase=2]
progressHandler: [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=3680 bytesTotal=0]
available: 3680
extractImage
chromeExtract()
findImageInBuffer, start: 0 end: -1
httpStatusHandler: [HTTPStatusEvent type="httpStatus" bubbles=false cancelable=false eventPhase=2 status=200 responseURL=null]
available: 0
extractImage
chromeExtract()
findImageInBuffer, start: 0 end: -1
1024 bytes = 1MB trc("transfered " + event.bytesLoaded +" closing") stream.close(); connect(); } } private function securityErrorHandler(event:SecurityErrorEvent):void { trc("securityErrorHandler: " + event); } private function httpStatusHandler(event:HTTPStatusEvent):void { trc("httpStatusHandler: " + event); trc("available: " + stream.bytesAvailable); extractImage(); //connect(); } private function ioErrorHandler(event:IOErrorEvent):void { trc("ioErrorHandler: " + event); } } };

Клиент работает достаточно хорошо в Firefox, где я получаю весь заголовок http:

--myBOUNDARY
Content-Type: image/jpeg
Content-Size: 25992

Поэтому я использую размер содержимого, чтобы узнать, сколько байтов осталосьвперед.То же самое происходит в IE8 (даже глючный IE совместим!)

В Safari это работает немного по-другому (возможно, это делает webkit). Я не получаю кусок http, только бинарный контент, который вынуждает меня искатьиз-за буфера для начала и конца кадра.

Проблема в Chrome, верите или нет, он не работает.Что-то странное происходит, по-видимому, я получаю первый пакет tcp / ip, а затем по какой-то причине Chrome решает закрыть соединение, вывод журнала таков:

*1024*

Я не должен получатьhttpStatus до тех пор, пока сервер не закроет соединение, что здесь не так.

Пожалуйста, не говорите мне использовать HTML5 Canvas или Video. Я все так готов, проблема в том, что мы хотим, чтобы это приложение работало во многих ОСи компиляция видеокодера для всех них (например, ffmpeg) не облегчит работу.Также мы хотим предоставить аудио SCO, который является просто потоком PCM, поэтому я не могу использовать обычный mjpeg.Холст слишком медленный, я проверял это, особенно на Android.

Ответы [ 2 ]

3 голосов
/ 14 апреля 2011

Наконец я нашел проблему!

Неправильный тип содержимого в соответствии с плагином Chrome для флэш-памяти. Правильный: Content-Type: multipart/x-mixed-replace

а не Content-Type: multipart/x-mixed-replace; boundary=myBOUNDARY

Итак, мой сервер теперь отправляет или нет границу в зависимости от аргумента запроса.

1 голос
/ 29 сентября 2012

Это также не сработало для меня - в конце концов я получил его в Chrome, используя:

Тип контента: text / html; border = - myboundary

Идет 6 часовмоей жизни: (

...