xpra icon
Bug tracker and wiki

Ticket #1341: html5-mediasource.patch

File html5-mediasource.patch, 16.2 KB (added by Antoine Martin, 3 years ago)

video and audio through mediasource api

  • html5/js/Client.js

     
    2424        // some client stuff
    2525        this.capabilities = {};
    2626        this.RGB_FORMATS = ["RGBX", "RGBA"];
    27         this.supported_encodings = ["jpeg", "png", "rgb", "rgb32"];     //, "h264"];
    28         this.enabled_encodings = [];
     27        //this.supported_encodings = ["jpeg", "png", "rgb", "rgb32", "h264+mp4"];
     28        this.supported_encodings = ["h264+mp4"];
     29        this.enabled_encodings = ["h264+mp4"];
    2930        this.normal_fullscreen_mode = false;
    3031        this.start_new_session = null;
    3132        this.username = "";
    3233        this.disconnect_reason = null;
     34        // audio
     35        this.audio_codecs = ["aac+mpeg4", "mp3", "vorbis+ogg", "opus+ogg", "wav", "flac", "opus+mka", "vorbis+mka", "vorbis+ogg", "speex+ogg", "flac+ogg"];
     36        //this.audio_codec = "aac+mpeg4";
     37        this.audio_codec = "mp3";
    3338        // encryption
    3439        this.encryption = false;
    3540        this.encryption_key = null;
     
    617622                "encoding.transparency"         : true,
    618623                "encoding.client_options"       : true,
    619624                "encoding.csc_atoms"            : true,
    620                 "encoding.scrolling"            : true,
     625                //"encoding.scrolling"          : true,
    621626                //video stuff we may handle later:
    622627                "encoding.video_reinit"         : false,
    623628                "encoding.video_scaling"        : false,
    624                 "encoding.full_csc_modes"       : {"h264" : ["YUV420P"]},
     629                "encoding.full_csc_modes"       : {
     630                        "h264"          : ["YUV420P"],
     631                        "h264+mp4"      : ["YUV420P"],
     632                },
    625633                "encoding.x264.YUV420P.profile" : "baseline",
    626634                //sound (not yet):
    627635                "sound.receive"                         : true,
    628636                "sound.send"                            : false,
    629                 "sound.decoders"                        : ["wav"],
     637                "sound.decoders"                        : this.audio_codecs,
    630638                // encoding stuff
    631639                "encoding.rgb24zlib"            : true,
    632640                "encoding.rgb_zlib"                     : true,
     
    777785}
    778786
    779787XpraClient.prototype._sound_start_receiving = function() {
    780         try {
    781                 this.audio_ctx = AV.Player.fromXpraSource();
    782         } catch(e) {
    783                 console.error('Could not start audio player:', e);
    784                 return;
     788        var me = this;
     789        var ms = window.MediaSource || window.WebKitMediaSource;
     790        this.media_source = new ms();
     791        this.media_source.addEventListener('sourceopen',        function(e) { console.log('audio source open: ' + me.media_source.readyState); });
     792        this.media_source.addEventListener('sourceended',       function(e) { console.log('audio source ended: ' + me.media_source.readyState); });
     793        this.media_source.addEventListener('sourceclose',       function(e) { console.log('audio source close: ' + me.media_source.readyState); });
     794        this.media_source.addEventListener('error',             function(e) { console.log('audio source error: ' + me.media_source.readyState); });
     795
     796        this.audio = document.createElement("audio");
     797        this.audio.setAttribute('autoplay', true);
     798    this.audio.addEventListener('waiting', function(){
     799        console.log("audio waiting");
     800    },false);
     801    this.audio.addEventListener('stalled', function(){
     802        console.log("audio stalled");
     803    },false);
     804    this.audio.addEventListener('playing', function(){
     805        console.log("audio playing");
     806    },false);
     807    this.audio.addEventListener('loadstart', function(){
     808        console.log("audio loadstart");
     809    },false);
     810    this.audio.addEventListener('loadedmetadata', function(){
     811        console.log("audio loadedmetadata");
     812    },false);
     813    this.audio.addEventListener('loadeddata', function(){
     814        console.log("audio loadeddata");
     815    },false);
     816    this.audio.addEventListener('error', function(){
     817        console.log("audio error");
     818    },false);
     819    this.audio.addEventListener('canplay', function(){
     820        console.log("audio canplay");
     821    },false);
     822        document.body.appendChild(this.audio);
     823    this.audio.addEventListener('play', function(){
     824        console.log("audio play!");
     825    },false);
     826    this.audio.src = window.URL.createObjectURL(this.media_source);
     827        this.audio_buffers = []
     828        this.audio_source_ready = false;
     829        this.media_source.addEventListener('sourceopen', function() {
     830                console.log("audio media source open");
     831                if (me.audio_source_ready) {
     832                        console.warn("ignoring: source already open");
     833                        return;
     834                }
     835                //var codec_string = "audio/aac";
     836                var codec_string = {
     837                                "aac+mpeg4"             : 'audio/mp4; codecs="mp4a.40.2"',
     838                                //"aac+mpeg4"           : 'audio/mp4; codecs="aac51"',
     839                                //"aac+mpeg4"           : 'audio/aac',
     840                                "mp3"                   : "audio/mpeg",
     841                                //"mp3"                 : "audio/mp3",
     842                                "ogg"                   : "audio/ogg",
     843                                //"wav"                 : 'audio/wave',
     844                                //"wav"                 : 'audio/wav; codec="1"',
     845                                "wav"                   : 'audio/wav',
     846                                "flac"                  : 'audio/flac',
     847                                "opus+mka"              : 'audio/webm; codecs="opus"',
     848                                "vorbis+mka"    : 'audio/webm; codecs="vorbis"',
     849                                "vorbis+ogg"    : 'audio/ogg; codecs="vorbis"',
     850                                "speex+ogg"             : 'audio/ogg; codecs="speex"',
     851                                "flac+ogg"              : 'audio/ogg; codecs="flac"',
     852                                "opus+ogg"              : 'audio/ogg; codecs="opus"',
     853                }[me.audio_codec];
     854                if(codec_string==null) {
     855                        console.warn("invalid codec '"+me.audio_codec+"'");
     856                        return;
     857                }
     858                console.log("using audio codec string for "+me.audio_codec+": "+codec_string);
     859                var asb = null;
     860                try {
     861                        asb = me.media_source.addSourceBuffer(codec_string);
     862                } catch (e) {
     863                        console.error("audio setup error for '"+codec_string+"': "+e);
     864                        me._close_audio(me);
     865                        return;
     866                }
     867            me.audio_source_buffer = asb;
     868            asb.mode = "sequence";
     869            asb.addEventListener('updatestart', function(e) { console.log('audio buffer updatestart: ' + me.media_source.readyState); });
     870            asb.addEventListener('updateend',   function(e) { console.log('audio buffer updateend: ' + me.media_source.readyState);
     871                        while(me.audio_buffers.length>0 && !asb.updating) {
     872                                console.log("audio pushing one initial buffer from queue of size "+me.audio_buffers.length);
     873                                me.audio_source_buffer.appendBuffer(me.audio_buffers.shift());
     874                        }
     875            });
     876            asb.addEventListener('error',               function(e) { console.log('audio buffer error: ' + me.media_source.readyState); });
     877            asb.addEventListener('abort',               function(e) { console.log('audio buffer abort: ' + me.media_source.readyState); });
     878            //add any buffers that may have accumulated since we initialized the video element:
     879                while(me.audio_buffers.length>0 && !asb.updating) {
     880                        console.log("audio pushing one initial buffer from queue of size "+me.audio_buffers.length);
     881                        me.audio_source_buffer.appendBuffer(me.audio_buffers.shift());
     882                }
     883            me.audio_source_ready = true;
     884        });
     885        this.protocol.send(["sound-control", "start", this.audio_codec]);
     886}
     887
     888XpraClient.prototype._close_audio = function(ctx) {
     889        console.log("close_audio: audio_source_buffer="+ctx.audio_source_buffer+", media_source="+ctx.media_source+", video="+ctx.audio);
     890        this.audio_source_ready = false;
     891        if(ctx.audio) {
     892                ctx.protocol.send(["sound-control", "stop"]);
     893                if(ctx.media_source) {
     894                        try {
     895                                if(ctx.audio_source_buffer) {
     896                                        ctx.media_source.removeSourceBuffer(ctx.audio_source_buffer);
     897                                        ctx.audio_source_buffer = null;
     898                                }
     899                                ctx.media_source.endOfStream();
     900                        } catch(e) {
     901                                console.warn("audio media source EOS error: "+e);
     902                        }
     903                        ctx.media_source = null;
     904                }               
     905                document.body.removeChild(ctx.audio);
     906                ctx.audio = null;
    785907        }
    786         this.audio_ctx.play();
    787         this.protocol.send(["sound-control", "start", "wav"]);
    788908}
    789909
     910
    790911/*
    791912 * packet processing functions start here
    792913 */
     
    807928}
    808929
    809930XpraClient.prototype._process_close = function(packet, ctx) {
     931        ctx._close_audio(ctx);
    810932        // terminate the worker
    811933        ctx.protocol.terminate();
    812934        // call the client's close callback
     
    11701292                options = {};
    11711293        if (packet.length>10)
    11721294                options = packet[10];
     1295        console.log("draw "+coding);
    11731296        var win = ctx.id_to_window[wid];
    11741297        var decode_time = -1;
    11751298        if (win) {
     
    11961319}
    11971320
    11981321XpraClient.prototype._process_sound_data = function(packet, ctx) {
     1322        if(!ctx.audio) {
     1323                return;
     1324        }
    11991325        if(packet[3]["start-of-stream"] == 1) {
    1200                 console.log("start of stream");
     1326                console.log("audio start of stream");
     1327                ctx.audio.play();
    12011328        } else {
    1202                 ctx.audio_ctx.asset.source._on_data(packet[2]);
    1203                 //console.log(ctx.audio_ctx.format);
     1329                if(packet[1]!=ctx.audio_codec) {
     1330                        console.log("invalid audio codec '"+packet[1]+"' (expected "+ctx.audio_codec+"), stopping audio stream");
     1331                        ctx._close_audio(ctx);
     1332                        return;
     1333                }
     1334                try {
     1335                        console.log("sound-data options: "+packet[3]);
     1336                        var ready_state = {
     1337                                0       : "NOTHING",
     1338                                1       : "METADATA",
     1339                                2       : "CURRENT DATA",
     1340                                3       : "FUTURE DATA",
     1341                                4       : "ENOUGH DATA",
     1342                        }
     1343                        var network_state = {
     1344                                0       : "EMPTY",
     1345                                1       : "IDLE",
     1346                                2       : "LOADING",
     1347                                3       : "NO_SOURCE",
     1348                        };
     1349                        console.log("audio state="+ready_state[ctx.audio.readyState]+", network state="+network_state[ctx.audio.networkState]);
     1350                        console.log("audio paused="+ctx.audio.paused+", queue size="+ctx.audio_buffers.length);
     1351                        console.log("audio source ready="+ctx.audio_source_ready+", source buffer updating="+ctx.audio_source_buffer.updating);
     1352                        if(ctx.audio_buffers.length>20) {
     1353                                console.error("audio queue overflowing: "+ctx.audio_buffers.length+", stopping");
     1354                                ctx._close_audio(ctx);
     1355                                return;
     1356                        }
     1357                        var buf = new Uint8Array(packet[2]).buffer;
     1358                        if(ctx.audio_source_ready && !ctx.audio_source_buffer.updating) {
     1359                                ctx.audio_source_buffer.appendBuffer(buf);
     1360                        }
     1361                        else {
     1362                                ctx.audio_buffers.push(buf);
     1363                        }
     1364                        if(ctx.audio.paused) {
     1365                                ctx.audio.play();
     1366                        }
     1367                } catch(e) {
     1368                        console.warn("audio failed: "+e);
     1369                        ctx.protocol.send(["sound-control", "stop"]);
     1370                }
    12041371        }
    12051372}
    12061373
  • html5/js/Window.js

     
    364364                //copy previous pixels to new image, ignoring bit gravity
    365365        //      this.offscreen_canvas.getContext('2d').putImageData(previous_image, 0, 0);
    366366        //}
    367 
    368367        // this should copy canvas pixel data since a canvas is the backing!
    369368};
    370369
     
    744743        };
    745744};
    746745
     746XpraWindow.prototype._close_video = function() {
     747        console.log("close_video: video_source_buffer="+this.video_source_buffer+", media_source="+this.media_source+", video="+this.video);
     748        this.video_source_ready = false;
     749        if(this.video) {
     750                if(this.media_source) {
     751                        try {
     752                                if(this.video_source_buffer) {
     753                                        this.media_source.removeSourceBuffer(this.video_source_buffer);
     754                                        this.video_source_buffer = null;
     755                                }
     756                                this.media_source.endOfStream();
     757                        } catch(e) {
     758                                console.warn("video media source EOS error: "+e);
     759                        }
     760                        this.media_source = null;
     761                }               
     762                document.body.removeChild(this.video);
     763                this.video = null;
     764        }
     765}
    747766
     767XpraWindow.prototype._init_video = function(width, height, profile, level) {
     768        var me = this;
     769        var ms = window.MediaSource || window.WebKitMediaSource;
     770        this.media_source = new ms();
     771        this.media_source.addEventListener('sourceopen',        function(e) { console.log('video source open: ' + me.media_source.readyState); });
     772        this.media_source.addEventListener('sourceended',       function(e) { console.log('video source ended: ' + me.media_source.readyState); });
     773        this.media_source.addEventListener('sourceclose',       function(e) { console.log('video source close: ' + me.media_source.readyState); });
     774        this.media_source.addEventListener('error',             function(e) { console.log('video source error: ' + me.media_source.readyState); });
     775       
     776        this.video = document.createElement("video");
     777        this.video.setAttribute('autoplay', true);
     778        this.video.setAttribute('muted', "muted");
     779        this.video.setAttribute('width', width);
     780        this.video.setAttribute('height', height);
     781        this.video.setAttribute('display', "none");
     782        //for debugging:
     783        //this.video.setAttribute('controls', "controls");
     784        //this.video.setAttribute('position', "absolute");
     785        //this.video.setAttribute('z-index', 100000);
     786        //this.video.setAttribute('margin', "auto");
     787        //this.video.setAttribute('top', "50%");
     788    this.video.addEventListener('waiting', function(){
     789        console.log("video waiting");
     790    },false);
     791    this.video.addEventListener('stalled', function(){
     792        console.log("video stalled");
     793    },false);
     794    this.video.addEventListener('playing', function(){
     795        console.log("video playing");
     796        me.offscreen_canvas_ctx.drawImage(me.video, 0, 0, width, height);
     797    },false);
     798    this.video.addEventListener('loadstart', function(){
     799        console.log("video loadstart");
     800    },false);
     801    this.video.addEventListener('loadedmetadata', function(){
     802        console.log("video loadedmetadata");
     803    },false);
     804    this.video.addEventListener('loadeddata', function(){
     805        console.log("video loadeddata");
     806    },false);
     807    this.video.addEventListener('error', function(){
     808        console.log("video error");
     809    },false);
     810    this.video.addEventListener('canplay', function(){
     811        console.log("video canplay");
     812    },false);
     813    this.video.addEventListener('play', function(){
     814        console.log("video play");
     815    },false);
     816    this.video.src = window.URL.createObjectURL(this.media_source);
     817        //this.video.src = "https://html5-demos.appspot.com/static/test.webm"
     818        this.video_buffers = []
     819        this.video_source_ready = false;
     820        this.media_source.addEventListener('sourceopen', function() {
     821                console.log("video media source open");
     822                //var vsb = me.media_source.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
     823                var profile_value  = {
     824                                "baseline"      : "42E0",
     825                                "main"          : "4D40",
     826                                "high"          : "6400",
     827                                "extended"      : "58A0",
     828                }
     829                var level_value = {
     830                                "3.0"           : "1E",
     831                                "3.1"           : "1F",
     832                                "4.1"           : "29",
     833                                "5.1"           : "33",
     834                }
     835                var codec_string = 'video/mp4; codecs="avc1.' + profile_value[profile] + level_value[level]+'"';
     836                console.log("video codec string: "+codec_string);
     837                var vsb = me.media_source.addSourceBuffer(codec_string);
     838            me.video_source_buffer = vsb;
     839            vsb.addEventListener('updatestart', function(e) { console.log('video buffer updatestart: ' + me.media_source.readyState); });
     840            vsb.addEventListener('updateend',   function(e) { console.log('video buffer updateend: ' + me.media_source.readyState); });
     841            vsb.addEventListener('error',               function(e) { console.log('video buffer error: ' + me.media_source.readyState); });
     842            vsb.addEventListener('abort',               function(e) { console.log('video buffer abort: ' + me.media_source.readyState); });
     843            vsb.addEventListener('updateend', function() {
     844                        console.log("video buffer updateend: queue length="+me.video_buffers.length+", updating="+vsb.updating);
     845                if(me.video_buffers.length > 0 && !vsb.updating) {
     846                        vsb.appendBuffer(me.video_buffers.shift());
     847                }
     848            });
     849            //add any buffers that may have accumulated since we initialized the video element:
     850                while(me.video_buffers.length>0 && !vsb.updating) {
     851                        console.log("video initial: pushing one buffer from queue of size "+me.video_buffers.length);
     852                        me.video_source_buffer.appendBuffer(me.video_buffers.shift());
     853                }
     854            me.video_source_ready = true;
     855        });
     856        //var div = document.getElementById("screen");
     857        //div.appendChild(this.video);
     858        document.body.appendChild(this.video);
     859};
     860
     861
    748862/**
    749863 * Updates the window image with new pixel data
    750864 * we have received from the server.
     
    833947                decode_callback(this.client);
    834948                //this._h264_process_raw(img_data);
    835949        }
     950        else if (coding=="h264+mp4") {
     951                var frame = options["frame"] || -1;
     952                if(frame==0) {
     953                        this._close_video();
     954                }
     955                if(!this.video || frame==0) {
     956                        var profile = options["profile"] || "baseline";
     957                        var level  = options["level"] || "3.0";
     958                        this._init_video(width, height, profile, level);
     959                }
     960                if(img_data.length>0) {
     961                        var ready_state = {
     962                                0       : "NOTHING",
     963                                1       : "METADATA",
     964                                2       : "CURRENT DATA",
     965                                3       : "FUTURE DATA",
     966                                4       : "ENOUGH DATA",
     967                        }
     968                        var network_state = {
     969                                0       : "EMPTY",
     970                                1       : "IDLE",
     971                                2       : "LOADING",
     972                                3       : "NO_SOURCE",
     973                        };
     974                        console.log("video state="+ready_state[this.video.readyState]+", network state="+network_state[this.video.networkState]);
     975                        console.log("video paused="+this.video.paused);
     976                        var buf = new Uint8Array(img_data).buffer;
     977                        if(this.video_source_ready && !this.video_source_buffer.updating) {
     978                                this.video_source_buffer.appendBuffer(buf);
     979                        }
     980                        else {
     981                                this.video_buffers.push(buf);
     982                        }
     983                        if(this.video.paused) {
     984                                this.video.play();
     985                        }
     986                }
     987                decode_callback(this.client);
     988        }
    836989        else if (coding=="scroll") {
    837990                if(this.offscreen_canvas_mode!='2d') {
    838991                        this._init_2d_canvas();
     
    8591012XpraWindow.prototype.destroy = function destroy() {
    8601013        "use strict";
    8611014        // remove div
     1015        this._close_video()
    8621016        this.div.remove()
    8631017};