diff --git a/assets/html/h264.html b/assets/html/h264.html
index 191f47a5..fee7ac28 100644
--- a/assets/html/h264.html
+++ b/assets/html/h264.html
@@ -7,2654 +7,36 @@
play
+
+
-
-
diff --git a/assets/html/jmuxer.min.js b/assets/html/jmuxer.min.js
new file mode 100644
index 00000000..6e31d497
--- /dev/null
+++ b/assets/html/jmuxer.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("stream")):"function"==typeof define&&define.amd?define(["stream"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).JMuxer=t(e.stream)}(this,(function(e){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,s=!0,o=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return s=e.done,e},e:function(e){o=!0,a=e},f:function(){try{s||null==n.return||n.return()}finally{if(o)throw a}}}}var v,m;function k(e){if(v){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r>5,this.ntype=31&this.payload[0],this.isvcl=1==this.ntype||5==this.ntype,this.stype="",this.isfmb=!1}return i(e,[{key:"toString",value:function(){return"".concat(e.type(this),": NRI: ").concat(this.getNri())}},{key:"getNri",value:function(){return this.nri}},{key:"type",value:function(){return this.ntype}},{key:"isKeyframe",value:function(){return this.ntype===e.IDR}},{key:"getPayload",value:function(){return this.payload}},{key:"getPayloadSize",value:function(){return this.payload.byteLength}},{key:"getSize",value:function(){return 4+this.getPayloadSize()}},{key:"getData",value:function(){var e=new Uint8Array(this.getSize());return new DataView(e.buffer).setUint32(0,this.getSize()-4),e.set(this.getPayload(),4),e}}],[{key:"NDR",get:function(){return 1}},{key:"IDR",get:function(){return 5}},{key:"SEI",get:function(){return 6}},{key:"SPS",get:function(){return 7}},{key:"PPS",get:function(){return 8}},{key:"AUD",get:function(){return 9}},{key:"TYPES",get:function(){var t;return a(t={},e.IDR,"IDR"),a(t,e.SEI,"SEI"),a(t,e.SPS,"SPS"),a(t,e.PPS,"PPS"),a(t,e.NDR,"NDR"),a(t,e.AUD,"AUD"),t}},{key:"type",value:function(t){return t.ntype in e.TYPES?e.TYPES[t.ntype]:"UNKNOWN"}}]),e}();function S(e,t){var n=new Uint8Array((0|e.byteLength)+(0|t.byteLength));return n.set(e,0),n.set(t,0|e.byteLength),n}var w=function(){function e(t){n(this,e),this.data=t,this.index=0,this.bitLength=8*t.byteLength}return i(e,[{key:"setData",value:function(e){this.data=e,this.index=0,this.bitLength=8*e.byteLength}},{key:"bitsAvailable",get:function(){return this.bitLength-this.index}},{key:"skipBits",value:function(e){if(this.bitsAvailable1&&void 0!==arguments[1])||arguments[1],n=this.getBits(e,this.index,t);return n}},{key:"getBits",value:function(e,t){var n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(this.bitsAvailable>>r,a=8-r;if(a>=e)return n&&(this.index+=e),i>>a-e;n&&(this.index+=a);var s=e-a;return i<>>1:-1*(e>>>1)}},{key:"readBoolean",value:function(){return 1===this.readBits(1)}},{key:"readUByte",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return this.readBits(8*e)}},{key:"readUShort",value:function(){return this.readBits(16)}},{key:"readUInt",value:function(){return this.readBits(32)}}]),e}(),x=function(){function e(t){n(this,e),this.remuxer=t,this.track=t.mp4track}return i(e,[{key:"parseSPS",value:function(t){var n=e.readSPS(new Uint8Array(t));this.track.fps=n.fps,this.track.width=n.width,this.track.height=n.height,this.track.sps=[new Uint8Array(t)],this.track.codec="avc1.";for(var r=new DataView(t.buffer,t.byteOffset+1,4),i=0;i<3;++i){var a=r.getUint8(i).toString(16);a.length<2&&(a="0"+a),this.track.codec+=a}}},{key:"parsePPS",value:function(e){this.track.pps=[new Uint8Array(e)]}},{key:"parseNAL",value:function(e){if(!e)return!1;var t=!1;switch(e.type()){case b.IDR:case b.NDR:t=!0;break;case b.PPS:this.track.pps||(this.parsePPS(e.getPayload()),!this.remuxer.readyToDecode&&this.track.pps&&this.track.sps&&(this.remuxer.readyToDecode=!0)),t=!0;break;case b.SPS:this.track.sps||(this.parseSPS(e.getPayload()),!this.remuxer.readyToDecode&&this.track.pps&&this.track.sps&&(this.remuxer.readyToDecode=!0)),t=!0;break;case b.AUD:k("AUD - ignoing");break;case b.SEI:k("SEI - ignoing")}return t}}],[{key:"extractNALu",value:function(e){for(var t,n,r=0,i=e.byteLength,a=0,s=[],o=0;r0&&x[1]>0&&(h=x[0]/x[1])}if(u.readBoolean()&&u.skipBits(1),u.readBoolean()&&(u.skipBits(4),u.readBoolean()&&u.skipBits(24)),u.readBoolean()&&(u.skipUEG(),u.skipUEG()),u.readBoolean()){var A=u.readUInt(),U=u.readUInt();u.readBoolean()&&(p=U/(2*A))}}return{fps:p>0?p:void 0,width:Math.ceil((16*(i+1)-2*c-2*f)*h),height:(2-s)*(a+1)*16-(s?2:4)*(l+d)}}},{key:"parseHeader",value:function(e){var t=new w(e.getPayload());t.readUByte(),e.isfmb=0===t.readUEG(),e.stype=t.readUEG()}}]),e}(),A=function(){function e(t){n(this,e),this.remuxer=t,this.track=t.mp4track}return i(e,[{key:"setAACConfig",value:function(){var t,n,r,i=new Uint8Array(2),a=e.aacHeader;a&&(t=1+((192&a[2])>>>6),n=(60&a[2])>>>2,r=(1&a[2])<<2,r|=(192&a[3])>>>6,i[0]=t<<3,i[0]|=(14&n)>>1,i[1]|=(1&n)<<7,i[1]|=r<<3,this.track.codec="mp4a.40."+t,this.track.channelCount=r,this.track.config=i,this.remuxer.readyToDecode=!0)}}],[{key:"samplingRateMap",get:function(){return[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350]}},{key:"getHeaderLength",value:function(e){return 1&e[1]?7:9}},{key:"getFrameLength",value:function(e){return(3&e[3])<<11|e[4]<<3|(224&e[5])>>>5}},{key:"isAACPattern",value:function(e){return 255===e[0]&&240==(240&e[1])&&0==(6&e[1])}},{key:"extractAAC",value:function(t){var n,r,i=0,a=t.byteLength,s=[];if(!e.isAACPattern(t))return g("Invalid ADTS audio format"),s;for(n=e.getHeaderLength(t),e.aacHeader||(e.aacHeader=t.subarray(0,n));i-1&&this.listener[e].splice(n,1),!0}return!1}},{key:"offAll",value:function(){this.listener={}}},{key:"dispatch",value:function(e,t){return!!this.listener[e]&&(this.listener[e].map((function(e){e.apply(null,[t])})),!0)}}]),e}(),B=function(){function e(){n(this,e)}return i(e,null,[{key:"init",value:function(){var t;for(t in e.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],mvex:[],mvhd:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]},e.types)e.types.hasOwnProperty(t)&&(e.types[t]=[t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2),t.charCodeAt(3)]);var n=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),r=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]);e.HDLR_TYPES={video:n,audio:r};var i=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),a=new Uint8Array([0,0,0,0,0,0,0,0]);e.STTS=e.STSC=e.STCO=a,e.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),e.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0]),e.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),e.STSD=new Uint8Array([0,0,0,0,0,0,0,1]);var s=new Uint8Array([105,115,111,109]),o=new Uint8Array([97,118,99,49]),u=new Uint8Array([0,0,0,1]);e.FTYP=e.box(e.types.ftyp,s,u,s,o),e.DINF=e.box(e.types.dinf,e.box(e.types.dref,i))}},{key:"box",value:function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r>24&255,i[1]=a>>16&255,i[2]=a>>8&255,i[3]=255&a,i.set(e,4),s=0,a=8;s>24&255,t>>16&255,t>>8&255,255&t,n>>24,n>>16&255,n>>8&255,255&n,85,196,0,0]))}},{key:"mdia",value:function(t){return e.box(e.types.mdia,e.mdhd(t.timescale,t.duration),e.hdlr(t.type),e.minf(t))}},{key:"mfhd",value:function(t){return e.box(e.types.mfhd,new Uint8Array([0,0,0,0,t>>24,t>>16&255,t>>8&255,255&t]))}},{key:"minf",value:function(t){return"audio"===t.type?e.box(e.types.minf,e.box(e.types.smhd,e.SMHD),e.DINF,e.stbl(t)):e.box(e.types.minf,e.box(e.types.vmhd,e.VMHD),e.DINF,e.stbl(t))}},{key:"moof",value:function(t,n,r){return e.box(e.types.moof,e.mfhd(t),e.traf(r,n))}},{key:"moov",value:function(t,n,r){for(var i=t.length,a=[];i--;)a[i]=e.trak(t[i]);return e.box.apply(null,[e.types.moov,e.mvhd(r,n)].concat(a).concat(e.mvex(t)))}},{key:"mvex",value:function(t){for(var n=t.length,r=[];n--;)r[n]=e.trex(t[n]);return e.box.apply(null,[e.types.mvex].concat(r))}},{key:"mvhd",value:function(t,n){var r=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,2,t>>24&255,t>>16&255,t>>8&255,255&t,n>>24&255,n>>16&255,n>>8&255,255&n,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return e.box(e.types.mvhd,r)}},{key:"sdtp",value:function(t){var n,r,i=t.samples||[],a=new Uint8Array(4+i.length);for(r=0;r>>8&255),a.push(255&i),a=a.concat(Array.prototype.slice.call(r));for(n=0;n>>8&255),s.push(255&i),s=s.concat(Array.prototype.slice.call(r));var o=e.box(e.types.avcC,new Uint8Array([1,a[3],a[4],a[5],255,224|t.sps.length].concat(a).concat([t.pps.length]).concat(s))),u=t.width,c=t.height;return e.box(e.types.avc1,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,u>>8&255,255&u,c>>8&255,255&c,0,72,0,0,0,72,0,0,0,0,0,0,0,1,18,98,105,110,101,108,112,114,111,46,114,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),o,e.box(e.types.btrt,new Uint8Array([0,28,156,128,0,45,198,192,0,45,198,192])))}},{key:"esds",value:function(e){var t=e.config.byteLength,n=new Uint8Array(26+t+3);return n.set([0,0,0,0,3,23+t,0,1,0,4,15+t,64,21,0,0,0,0,0,0,0,0,0,0,0,5,t]),n.set(e.config,26),n.set([6,1,2],26+t),n}},{key:"mp4a",value:function(t){var n=t.audiosamplerate;return e.box(e.types.mp4a,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t.channelCount,0,16,0,0,0,0,n>>8&255,255&n,0,0]),e.box(e.types.esds,e.esds(t)))}},{key:"stsd",value:function(t){return"audio"===t.type?e.box(e.types.stsd,e.STSD,e.mp4a(t)):e.box(e.types.stsd,e.STSD,e.avc1(t))}},{key:"tkhd",value:function(t){var n=t.id,r=t.duration,i=t.width,a=t.height,s=t.volume;return e.box(e.types.tkhd,new Uint8Array([0,0,0,7,0,0,0,0,0,0,0,0,n>>24&255,n>>16&255,n>>8&255,255&n,0,0,0,0,r>>24,r>>16&255,r>>8&255,255&r,0,0,0,0,0,0,0,0,0,0,0,0,s>>0&255,s%1*10>>0&255,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,i>>8&255,255&i,0,0,a>>8&255,255&a,0,0]))}},{key:"traf",value:function(t,n){var r=e.sdtp(t),i=t.id;return e.box(e.types.traf,e.box(e.types.tfhd,new Uint8Array([0,0,0,0,i>>24,i>>16&255,i>>8&255,255&i])),e.box(e.types.tfdt,new Uint8Array([0,0,0,0,n>>24,n>>16&255,n>>8&255,255&n])),e.trun(t,r.length+16+16+8+16+8+8),r)}},{key:"trak",value:function(t){return t.duration=t.duration||4294967295,e.box(e.types.trak,e.tkhd(t),e.mdia(t))}},{key:"trex",value:function(t){var n=t.id;return e.box(e.types.trex,new Uint8Array([0,0,0,0,n>>24,n>>16&255,n>>8&255,255&n,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]))}},{key:"trun",value:function(t,n){var r,i,a,s,o,u,c=t.samples||[],f=c.length,l=12+16*f,d=new Uint8Array(l);for(n+=8+l,d.set([0,0,15,1,f>>>24&255,f>>>16&255,f>>>8&255,255&f,n>>>24&255,n>>>16&255,n>>>8&255,255&n],0),r=0;r>>24&255,a>>>16&255,a>>>8&255,255&a,s>>>24&255,s>>>16&255,s>>>8&255,255&s,o.isLeading<<2|o.dependsOn,o.isDependedOn<<6|o.hasRedundancy<<4|o.paddingValue<<1|o.isNonSync,61440&o.degradPrio,15&o.degradPrio,u>>>24&255,u>>>16&255,u>>>8&255,255&u],12+16*r);return e.box(e.types.trun,d)}},{key:"initSegment",value:function(t,n,r){e.types||e.init();var i,a=e.moov(t,n,r);return(i=new Uint8Array(e.FTYP.byteLength+a.byteLength)).set(e.FTYP),i.set(a,e.FTYP.byteLength),i}}]),e}(),D=1,C=function(){function e(){n(this,e)}return i(e,[{key:"flush",value:function(){this.mp4track.len=0,this.mp4track.samples=[]}},{key:"isReady",value:function(){return!(!this.readyToDecode||!this.samples.length)||null}}],[{key:"getTrackID",value:function(){return D++}}]),e}(),E=function(e){s(r,e);var t=l(r);function r(e){var i;return n(this,r),(i=t.call(this)).readyToDecode=!1,i.nextDts=0,i.dts=0,i.mp4track={id:C.getTrackID(),type:"audio",channelCount:0,len:0,fragmented:!0,timescale:e,duration:e,samples:[],config:"",codec:""},i.samples=[],i.aac=new A(c(i)),i}return i(r,[{key:"resetTrack",value:function(){this.readyToDecode=!1,this.mp4track.codec="",this.mp4track.channelCount="",this.mp4track.config="",this.mp4track.timescale=this.timescale,this.nextDts=0,this.dts=0}},{key:"remux",value:function(e){if(e.length>0)for(var t=0;t0&&this.readyToDecode&&(this.mp4track.len+=s,this.samples.push({units:a,size:s,keyFrame:i.keyFrame,duration:i.duration,compositionTimeOffset:i.compositionTimeOffset}))}}catch(e){n.e(e)}finally{n.f()}}},{key:"getPayload",value:function(){if(!this.isReady())return null;var e,t,n=new Uint8Array(this.mp4track.len),r=0,i=this.mp4track.samples;for(this.dts=this.nextDts;this.samples.length;){var a=this.samples.shift(),s=a.units;if((t=a.duration)<=0)k("remuxer: invalid sample duration at DTS: ".concat(this.nextDts," :").concat(t)),this.mp4track.len-=a.size;else{this.nextDts+=t,e={size:a.size,duration:t,cts:a.compositionTimeOffset||0,flags:{isLeading:0,isDependedOn:0,hasRedundancy:0,degradPrio:0,isNonSync:a.keyFrame?0:1,dependsOn:a.keyFrame?2:1}};var o,u=y(s);try{for(u.s();!(o=u.n()).done;){var c=o.value;n.set(c.getData(),r),r+=c.getSize()}}catch(e){u.e(e)}finally{u.f()}i.push(e)}}return i.length?new Uint8Array(n.buffer,0,this.mp4track.len):null}}]),r}(C),P=function(e){s(r,e);var t=l(r);function r(e){var i;return n(this,r),(i=t.call(this,"remuxer")).initialized=!1,i.trackTypes=[],i.tracks={},i.seq=1,i.env=e,i.timescale=1e3,i.mediaDuration=0,i.aacParser=null,i}return i(r,[{key:"addTrack",value:function(e){if("video"!==e&&"both"!==e||(this.tracks.video=new T(this.timescale),this.trackTypes.push("video")),"audio"===e||"both"===e){var t=new E(this.timescale);this.aacParser=t.getAacParser(),this.tracks.audio=t,this.trackTypes.push("audio")}}},{key:"reset",value:function(){var e,t=y(this.trackTypes);try{for(t.s();!(e=t.n()).done;){var n=e.value;this.tracks[n].resetTrack()}}catch(e){t.e(e)}finally{t.f()}this.initialized=!1}},{key:"destroy",value:function(){this.tracks={},this.offAll()}},{key:"flush",value:function(){if(this.initialized){var e,t=y(this.trackTypes);try{for(t.s();!(e=t.n()).done;){var n=e.value,r=this.tracks[n],i=r.getPayload();if(i&&i.byteLength){var a={type:n,payload:S(B.moof(this.seq,r.dts,r.mp4track),B.mdat(i)),dts:r.dts};"video"===n&&(a.fps=r.mp4track.fps),this.dispatch("buffer",a);var s=(o=r.dts/this.timescale,u=void 0,c=void 0,f=void 0,l=void 0,l="",u=Math.floor(o),(c=parseInt(u/3600,10)%24)>0&&(l+=(c<10?"0"+c:c)+":"),l+=((f=parseInt(u/60,10)%60)<10?"0"+f:f)+":"+((u=u<0?0:u%60)<10?"0"+u:u));k("put segment (".concat(n,"): dts: ").concat(r.dts," frames: ").concat(r.mp4track.samples.length," second: ").concat(s)),r.flush(),this.seq++}}}catch(e){t.e(e)}finally{t.f()}}else this.isReady()&&(this.dispatch("ready"),this.initSegment(),this.initialized=!0,this.flush());var o,u,c,f,l}},{key:"initSegment",value:function(){var e,t=[],n=y(this.trackTypes);try{for(n.s();!(e=n.n()).done;){var r=e.value,i=this.tracks[r];if("browser"==this.env){var a={type:r,payload:B.initSegment([i.mp4track],this.mediaDuration,this.timescale)};this.dispatch("buffer",a)}else t.push(i.mp4track)}}catch(e){n.e(e)}finally{n.f()}if("node"==this.env){var s={type:"all",payload:B.initSegment(t,this.mediaDuration,this.timescale)};this.dispatch("buffer",s)}k("Initial segment generated.")}},{key:"isReady",value:function(){var e,t=y(this.trackTypes);try{for(t.s();!(e=t.n()).done;){var n=e.value;if(!this.tracks[n].readyToDecode||!this.tracks[n].samples.length)return!1}}catch(e){t.e(e)}finally{t.f()}return!0}},{key:"remux",value:function(e){var t,n=y(this.trackTypes);try{for(n.s();!(t=n.n()).done;){var r=t.value,i=e[r];"audio"===r&&this.tracks.video&&!this.tracks.video.readyToDecode||i.length>0&&this.tracks[r].remux(i)}}catch(e){n.e(e)}finally{n.f()}this.flush()}}]),r}(U),L=function(e){s(r,e);var t=l(r);function r(e,i){var a;return n(this,r),(a=t.call(this,"buffer")).type=i,a.queue=new Uint8Array,a.cleaning=!1,a.pendingCleaning=0,a.cleanOffset=30,a.cleanRanges=[],a.sourceBuffer=e,a.sourceBuffer.addEventListener("updateend",(function(){a.pendingCleaning>0&&(a.initCleanup(a.pendingCleaning),a.pendingCleaning=0),a.cleaning=!1,a.cleanRanges.length&&a.doCleanup()})),a.sourceBuffer.addEventListener("error",(function(){a.dispatch("error",{type:a.type,name:"buffer",error:"buffer error"})})),a}return i(r,[{key:"destroy",value:function(){this.queue=null,this.sourceBuffer=null,this.offAll()}},{key:"doCleanup",value:function(){if(this.cleanRanges.length){var e=this.cleanRanges.shift();k("".concat(this.type," remove range [").concat(e[0]," - ").concat(e[1],")")),this.cleaning=!0,this.sourceBuffer.remove(e[0],e[1])}else this.cleaning=!1}},{key:"initCleanup",value:function(e){try{if(this.sourceBuffer.updating)return void(this.pendingCleaning=e);if(this.sourceBuffer.buffered&&this.sourceBuffer.buffered.length&&!this.cleaning){for(var t=0;tthis.cleanOffset&&n<(r=e-this.cleanOffset)&&this.cleanRanges.push([n,r])}this.doCleanup()}}catch(e){g("Error occured while cleaning ".concat(this.type," buffer - ").concat(e.name,": ").concat(e.message))}}},{key:"doAppend",value:function(){if(this.queue.length&&this.sourceBuffer&&!this.sourceBuffer.updating)try{this.sourceBuffer.appendBuffer(this.queue),this.queue=new Uint8Array}catch(t){var e="unexpectedError";"QuotaExceededError"===t.name?(k("".concat(this.type," buffer quota full")),e="QuotaExceeded"):(g("Error occured while appending ".concat(this.type," buffer - ").concat(t.name,": ").concat(t.message)),e="InvalidStateError"),this.dispatch("error",{type:this.type,name:e,error:"buffer error"})}}},{key:"feed",value:function(e){this.queue=S(this.queue,e)}}]),r}(U);return function(r){s(o,r);var a=l(o);function o(e){var r;n(this,o),(r=a.call(this,"jmuxer")).isReset=!1;return r.options=Object.assign({},{node:"",mode:"both",flushingTime:500,maxDelay:500,clearBuffer:!0,fps:30,readFpsFromTrack:!1,debug:!1,onReady:function(){},onData:function(){},onError:function(){},onMissingVideoFrames:function(){},onMissingAudioFrames:function(){}},e),r.env="object"===("undefined"==typeof process?"undefined":t(process))&&"undefined"==typeof window?"node":"browser",r.options.debug&&(v=console.log,m=console.error),r.options.fps||(r.options.fps=30),r.frameDuration=1e3/r.options.fps|0,r.remuxController=new P(r.env),r.remuxController.addTrack(r.options.mode),r.initData(),r.remuxController.on("buffer",r.onBuffer.bind(c(r))),"browser"==r.env&&(r.remuxController.on("ready",r.createBuffer.bind(c(r))),r.initBrowser()),r}return i(o,[{key:"initData",value:function(){this.lastCleaningTime=Date.now(),this.kfPosition=[],this.kfCounter=0,this.pendingUnits={},this.remainingData=new Uint8Array,this.startInterval()}},{key:"initBrowser",value:function(){"string"==typeof this.options.node&&""==this.options.node&&g("no video element were found to render, provide a valid video element"),this.node="string"==typeof this.options.node?document.getElementById(this.options.node):this.options.node,this.mseReady=!1,this.setupMSE()}},{key:"createStream",value:function(){var t=this.feed.bind(this),n=this.destroy.bind(this);return this.stream=new e.Duplex({writableObjectMode:!0,read:function(e){},write:function(e,n,r){t(e),r()},final:function(e){n(),e()}}),this.stream}},{key:"setupMSE",value:function(){if(window.MediaSource=window.MediaSource||window.WebKitMediaSource||window.ManagedMediaSource,!window.MediaSource)throw"Oops! Browser does not support Media Source Extension or Managed Media Source (IOS 17+).";if(this.isMSESupported=!!window.MediaSource,this.mediaSource=new window.MediaSource,this.url=URL.createObjectURL(this.mediaSource),window.MediaSource===window.ManagedMediaSource)try{this.node.removeAttribute("src"),this.node.disableRemotePlayback=!0;var e=document.createElement("source");e.type="video/mp4",e.src=this.url,this.node.appendChild(e),this.node.load()}catch(e){this.node.src=this.url}else this.node.src=this.url;this.mseEnded=!1,this.mediaSource.addEventListener("sourceopen",this.onMSEOpen.bind(this)),this.mediaSource.addEventListener("sourceclose",this.onMSEClose.bind(this)),this.mediaSource.addEventListener("webkitsourceopen",this.onMSEOpen.bind(this)),this.mediaSource.addEventListener("webkitsourceclose",this.onMSEClose.bind(this))}},{key:"endMSE",value:function(){if(!this.mseEnded)try{this.mseEnded=!0,this.mediaSource.endOfStream()}catch(e){g("mediasource is not available to end")}}},{key:"feed",value:function(e){var t,n,r,i=!1,a={video:[],audio:[]};if(e&&this.remuxController){if(r=e.duration?parseInt(e.duration):0,e.video){e.video=S(this.remainingData,e.video);var s=d(x.extractNALu(e.video),2);if(t=s[0],n=s[1],this.remainingData=n||new Uint8Array,!(t.length>0))return g("Failed to extract any NAL units from video data:",n),void("function"==typeof this.options.onMissingVideoFrames&&this.options.onMissingVideoFrames.call(null,e));a.video=this.getVideoFrames(t,r,e.compositionTimeOffset),i=!0}if(e.audio){if(!((t=A.extractAAC(e.audio)).length>0))return g("Failed to extract audio data from:",e.audio),void("function"==typeof this.options.onMissingAudioFrames&&this.options.onMissingAudioFrames.call(null,e));a.audio=this.getAudioFrames(t,r),i=!0}i?this.remuxController.remux(a):g("Input object must have video and/or audio property. Make sure it is a valid typed array")}}},{key:"getVideoFrames",value:function(e,t,n){var r,i=this,a=[],s=[],o=0,u=!1,c=!1;this.pendingUnits.units&&(a=this.pendingUnits.units,c=this.pendingUnits.vcl,u=this.pendingUnits.keyFrame,this.pendingUnits={});var f,l=y(e);try{for(l.s();!(f=l.n()).done;){var d=f.value,h=new b(d);h.type()!==b.IDR&&h.type()!==b.NDR||x.parseHeader(h),a.length&&c&&(h.isfmb||!h.isvcl)&&(s.push({units:a,keyFrame:u}),a=[],u=!1,c=!1),a.push(h),u=u||h.isKeyframe(),c=c||h.isvcl}}catch(e){l.e(e)}finally{l.f()}if(a.length)if(t)if(c)s.push({units:a,keyFrame:u});else{var p=s.length-1;p>=0&&(s[p].units=s[p].units.concat(a))}else this.pendingUnits={units:a,keyFrame:u,vcl:c};return r=t?t/s.length|0:this.frameDuration,o=t?t-r*s.length:0,s.map((function(e){e.duration=r,e.compositionTimeOffset=n,o>0&&(e.duration++,o--),i.kfCounter++,e.keyFrame&&i.options.clearBuffer&&i.kfPosition.push(i.kfCounter*r/1e3)})),k("jmuxer: No. of frames of the last chunk: ".concat(s.length)),s}},{key:"getAudioFrames",value:function(e,t){var n,r,i=[],a=0,s=y(e);try{for(s.s();!(r=s.n()).done;){var o=r.value;i.push({units:o})}}catch(e){s.e(e)}finally{s.f()}return n=t?t/i.length|0:this.frameDuration,a=t?t-n*i.length:0,i.map((function(e){e.duration=n,a>0&&(e.duration++,a--)})),i}},{key:"destroy",value:function(){if(this.stopInterval(),this.stream&&(this.remuxController.flush(),this.stream.push(null),this.stream=null),this.remuxController&&(this.remuxController.destroy(),this.remuxController=null),this.bufferControllers){for(var e in this.bufferControllers)this.bufferControllers[e].destroy();this.bufferControllers=null,this.endMSE()}this.node=!1,this.mseReady=!1,this.videoStarted=!1,this.mediaSource=null}},{key:"reset",value:function(){if(this.stopInterval(),this.isReset=!0,this.node.pause(),this.remuxController&&this.remuxController.reset(),this.bufferControllers){for(var e in this.bufferControllers)this.bufferControllers[e].destroy();this.bufferControllers=null,this.endMSE()}this.initData(),"browser"==this.env&&this.initBrowser(),k("JMuxer was reset")}},{key:"createBuffer",value:function(){if(this.mseReady&&this.remuxController&&this.remuxController.isReady()&&!this.bufferControllers)for(var e in this.bufferControllers={},this.remuxController.tracks){var t=this.remuxController.tracks[e];if(!o.isSupported("".concat(e,'/mp4; codecs="').concat(t.mp4track.codec,'"')))return g("Browser does not support codec"),!1;var n=this.mediaSource.addSourceBuffer("".concat(e,'/mp4; codecs="').concat(t.mp4track.codec,'"'));this.bufferControllers[e]=new L(n,e),this.bufferControllers[e].on("error",this.onBufferError.bind(this))}}},{key:"startInterval",value:function(){var e=this;this.interval=setInterval((function(){e.options.flushingTime?e.applyAndClearBuffer():e.bufferControllers&&e.cancelDelay()}),this.options.flushingTime||1e3)}},{key:"stopInterval",value:function(){this.interval&&clearInterval(this.interval)}},{key:"cancelDelay",value:function(){if(this.node.buffered&&this.node.buffered.length>0&&!this.node.seeking){var e=this.node.buffered.end(0);e-this.node.currentTime>this.options.maxDelay/1e3&&(console.log("delay"),this.node.currentTime=e-.001)}}},{key:"releaseBuffer",value:function(){for(var e in this.bufferControllers)this.bufferControllers[e].doAppend()}},{key:"applyAndClearBuffer",value:function(){this.bufferControllers&&(this.releaseBuffer(),this.clearBuffer())}},{key:"getSafeClearOffsetOfBuffer",value:function(e){for(var t,n="audio"===this.options.mode&&e||0,r=0;r=e);r++)t=this.kfPosition[r];return t&&(this.kfPosition=this.kfPosition.filter((function(e){return e=t}))),n}},{key:"clearBuffer",value:function(){if(this.options.clearBuffer&&Date.now()-this.lastCleaningTime>1e4){for(var e in this.bufferControllers){var t=this.getSafeClearOffsetOfBuffer(this.node.currentTime);this.bufferControllers[e].initCleanup(t)}this.lastCleaningTime=Date.now()}}},{key:"onBuffer",value:function(e){this.options.readFpsFromTrack&&void 0!==e.fps&&this.options.fps!=e.fps&&(this.options.fps=e.fps,this.frameDuration=Math.ceil(1e3/e.fps),k("JMuxer changed FPS to ".concat(e.fps," from track data"))),"browser"==this.env?this.bufferControllers&&this.bufferControllers[e.type]&&this.bufferControllers[e.type].feed(e.payload):this.stream&&this.stream.push(e.payload),this.options.onData&&this.options.onData(e.payload),0===this.options.flushingTime&&this.applyAndClearBuffer()}},{key:"onMSEOpen",value:function(){this.mseReady=!0,URL.revokeObjectURL(this.url),"function"==typeof this.options.onReady&&this.options.onReady.call(null,this.isReset)}},{key:"onMSEClose",value:function(){this.mseReady=!1,this.videoStarted=!1}},{key:"onBufferError",value:function(e){if("QuotaExceeded"==e.name)return k("JMuxer cleaning ".concat(e.type," buffer due to QuotaExceeded error")),void this.bufferControllers[e.type].initCleanup(this.node.currentTime);"InvalidStateError"==e.name?(k("JMuxer is reseting due to InvalidStateError"),this.reset()):this.endMSE(),"function"==typeof this.options.onError&&this.options.onError.call(null,e)}}],[{key:"isSupported",value:function(e){return window.MediaSource&&window.MediaSource.isTypeSupported(e)}}]),o}(U)}));
diff --git a/lib/appRouters.dart b/lib/appRouters.dart
index c7d403a1..e1fa7672 100755
--- a/lib/appRouters.dart
+++ b/lib/appRouters.dart
@@ -61,6 +61,7 @@ import 'package:star_lock/mine/valueAddedServices/advancedFeaturesWeb/advancedFe
import 'package:star_lock/mine/valueAddedServices/advancedFunctionRecord/advancedFunctionRecord_page.dart';
import 'package:star_lock/mine/valueAddedServices/valueAddedServicesRecord/value_added_services_record_page.dart';
import 'package:star_lock/talk/starChart/views/talkView/talk_view_page.dart';
+import 'package:star_lock/talk/starChart/webView/h264_web_view.dart';
import 'common/safetyVerification/safetyVerification_page.dart';
import 'login/forgetPassword/starLock_forgetPassword_page.dart';
@@ -515,6 +516,7 @@ abstract class Routers {
static const String doubleLockLinkPage = '/doubleLockLinkPage'; //双锁联动
static const String starChartPage = '/starChartPage'; //星图
static const String starChartTalkView = '/starChartTalkView'; //星图对讲页面
+ static const String h264WebView = '/h264WebView'; //星图对讲页面
}
abstract class AppRouters {
@@ -1195,5 +1197,6 @@ abstract class AppRouters {
page: () => const DoubleLockLinkPage()),
GetPage(
name: Routers.starChartTalkView, page: () => const TalkViewPage()),
+ GetPage(name: Routers.h264WebView, page: () => H264WebView()),
];
}
diff --git a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart
index c5b6b7cd..a95b1c78 100644
--- a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart
+++ b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart
@@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
+import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
@@ -158,16 +159,13 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
void _handleVideoH264(TalkData talkData) {
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
talkDataH264Frame.mergeFromBuffer(talkData.content);
- // AppLog.log('H264 TalkData :$talkDataH264Frame');
- // talkDataRepository.addTalkData(talkData);
+ frameHandler.handleFrame(talkDataH264Frame);
}
/// 处理图片数据
void _handleVideoImage(TalkData talkData) async {
final List processCompletePayload =
await _processCompletePayload(Uint8List.fromList(talkData.content));
- // AppLog.log('得到完整的帧:${processCompletePayload.length}'); // 循环发送每一帧的数据
-
processCompletePayload.forEach((element) {
talkData.content = element;
talkDataRepository.addTalkData(talkData);
@@ -181,7 +179,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
// // 转pcm数据
// List pcmBytes = G711().convertList(g711Data);
// talkData.content = pcmBytes;
- talkDataRepository.addTalkData(talkData);
+ // talkDataRepository.addTalkData(talkData);
} catch (e) {
print('Error decoding G.711 to PCM: $e');
}
diff --git a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart
index d9a8c2db..10f7c784 100644
--- a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart
+++ b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart
@@ -73,9 +73,19 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
// 启动对讲请求超时定时器
talkeRequestOverTimeTimerManager.start();
// 收到呼叫请求,跳转到接听页面
- Get.toNamed(
- Routers.starChartTalkView,
- );
+ if (startChartManage
+ .getDefaultTalkExpect()
+ .videoType
+ .indexOf(VideoTypeE.H264) ==
+ -1) {
+ Get.toNamed(
+ Routers.starChartTalkView,
+ );
+ } else {
+ Get.toNamed(
+ Routers.h264WebView,
+ );
+ }
}
// 收到来电请求时进行本地通知
diff --git a/lib/talk/starChart/handle/other/h264_frame_buffer.dart b/lib/talk/starChart/handle/other/h264_frame_buffer.dart
new file mode 100644
index 00000000..5ace9001
--- /dev/null
+++ b/lib/talk/starChart/handle/other/h264_frame_buffer.dart
@@ -0,0 +1,22 @@
+import 'dart:typed_data';
+
+import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
+
+class H264FrameBuffer {
+ List frames = [];
+
+ void addFrame(TalkDataH264Frame frame) {
+ frames.add(frame);
+ }
+
+ Uint8List getCompleteStream() {
+ final List completeStream = [];
+ for (final frame in frames) {
+ // 添加起始码(假设为 0x00 0x00 0x01)
+ completeStream.addAll([0x00, 0x00, 0x01]);
+ // 添加帧数据
+ completeStream.addAll(frame.frameData);
+ }
+ return Uint8List.fromList(completeStream);
+ }
+}
diff --git a/lib/talk/starChart/handle/other/h264_frame_handler.dart b/lib/talk/starChart/handle/other/h264_frame_handler.dart
new file mode 100644
index 00000000..5eaa27df
--- /dev/null
+++ b/lib/talk/starChart/handle/other/h264_frame_handler.dart
@@ -0,0 +1,84 @@
+import 'package:star_lock/app_settings/app_settings.dart';
+import '../../proto/talk_data_h264_frame.pb.dart';
+
+class H264FrameHandler {
+ final Map _frameBuffer = {};
+ final void Function(List frameData) onCompleteFrame;
+ int _lastProcessedSeq = -1;
+
+ H264FrameHandler({required this.onCompleteFrame});
+
+ void handleFrame(TalkDataH264Frame frame) {
+ // 存储帧
+ _frameBuffer[frame.frameSeq] = frame;
+
+ // 检查是否可以组装完整的 GOP (Group of Pictures)
+ _tryAssembleFrames(frame.frameSeq);
+ }
+
+ void _tryAssembleFrames(int currentSeq) {
+ // 找到连续的帧序列
+ final List sortedSeqs = _frameBuffer.keys.toList()..sort();
+ final List framesToProcess = [];
+
+ // 从当前帧开始向前找到最近的 I 帧或 P 帧
+ int? startFrameSeq;
+ for (var seq in sortedSeqs.reversed) {
+ final frame = _frameBuffer[seq];
+ if (frame?.frameType == TalkDataH264Frame_FrameTypeE.I) {
+ startFrameSeq = seq;
+ break;
+ } else if (frame?.frameType == TalkDataH264Frame_FrameTypeE.P) {
+ // 检查 P 帧是否有对应的 I 帧
+ if (_frameBuffer.containsKey(frame?.frameSeqI)) {
+ startFrameSeq = seq;
+ break;
+ } else {
+ // 丢弃没有对应 I 帧的 P 帧
+ _frameBuffer.remove(seq);
+ }
+ }
+ }
+
+ if (startFrameSeq != null) {
+ // 收集从 I 帧或 P 帧开始的连续帧
+ int expectedSeq = startFrameSeq;
+ for (var seq in sortedSeqs.where((s) => s >= startFrameSeq!)) {
+ if (seq != expectedSeq) break;
+ framesToProcess.add(seq);
+ expectedSeq++;
+ }
+
+ if (framesToProcess.isNotEmpty) {
+ _processFrames(framesToProcess);
+ }
+ } else {
+ _clearOldFrames(currentSeq);
+ }
+ }
+
+ void _clearOldFrames(int currentSeq) {
+ // 清理比当前帧序列旧的帧
+ _frameBuffer.removeWhere((seq, frame) => seq < currentSeq - 200); // 调整阈值
+ }
+
+ void _processFrames(List frameSeqs) {
+ // 按顺序组装帧数据
+ final List assembledData = [];
+
+ for (var seq in frameSeqs) {
+ final frame = _frameBuffer[seq]!;
+ assembledData.addAll(frame.frameData);
+
+ // 处理完后从缓冲区移除
+ _frameBuffer.remove(seq);
+ }
+
+ // 回调完整的帧数据
+ onCompleteFrame(assembledData);
+ }
+
+ void clear() {
+ _frameBuffer.clear();
+ }
+}
diff --git a/lib/talk/starChart/handle/other/talk_data_repository.dart b/lib/talk/starChart/handle/other/talk_data_repository.dart
index 90fc77df..864aa4aa 100644
--- a/lib/talk/starChart/handle/other/talk_data_repository.dart
+++ b/lib/talk/starChart/handle/other/talk_data_repository.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
class TalkDataRepository {
@@ -27,6 +26,9 @@ class TalkDataRepository {
bool _isListening = false;
+ // 用于存储数据的缓冲区
+ final List _buffer = [];
+
// 提供一个方法来获取 Stream
Stream get talkDataStream =>
_talkDataStreamController.stream.transform(
@@ -41,14 +43,11 @@ class TalkDataRepository {
},
),
);
- final List _buffer = []; // 用于存储数据的缓冲区
// 提供一个方法来添加 TalkData 到 Stream
- void addTalkData(TalkData talkData) async {
+ void addTalkData(TalkData talkData) {
if (_isListening) {
- Future.microtask(() {
- _talkDataStreamController.add(talkData);
- });
+ _talkDataStreamController.add(talkData);
}
}
diff --git a/lib/talk/starChart/handle/scp_message_base_handle.dart b/lib/talk/starChart/handle/scp_message_base_handle.dart
index 4cfc04c1..c1225f0e 100644
--- a/lib/talk/starChart/handle/scp_message_base_handle.dart
+++ b/lib/talk/starChart/handle/scp_message_base_handle.dart
@@ -14,6 +14,7 @@ import 'package:star_lock/talk/starChart/constant/payload_type_constant.dart';
import 'package:star_lock/talk/starChart/constant/udp_constant.dart';
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
+import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.dart';
@@ -52,6 +53,15 @@ class ScpMessageBaseHandle {
final audioManager = AudioPlayerManager();
+ // 处理出完整帧数据后的回调
+ final H264FrameHandler frameHandler =
+ H264FrameHandler(onCompleteFrame: (frameData) {
+ // 处理完整的帧数据
+ TalkDataRepository.instance.addTalkData(
+ TalkData(contentType: TalkData_ContentTypeE.H264, content: frameData),
+ );
+ });
+
// 回复成功消息
void replySuccessMessage(ScpMessage scpMessage) {
startChartManage.sendGenericRespSuccessMessage(
diff --git a/lib/talk/starChart/proto/talk_data_h264_frame.pb.dart b/lib/talk/starChart/proto/talk_data_h264_frame.pb.dart
index 4a77a31a..e0239875 100644
--- a/lib/talk/starChart/proto/talk_data_h264_frame.pb.dart
+++ b/lib/talk/starChart/proto/talk_data_h264_frame.pb.dart
@@ -22,6 +22,7 @@ class TalkDataH264Frame extends $pb.GeneratedMessage {
$core.int? frameSeq,
TalkDataH264Frame_FrameTypeE? frameType,
$core.List<$core.int>? frameData,
+ $core.int? frameSeqI,
}) {
final $result = create();
if (frameSeq != null) {
@@ -33,6 +34,9 @@ class TalkDataH264Frame extends $pb.GeneratedMessage {
if (frameData != null) {
$result.frameData = frameData;
}
+ if (frameSeqI != null) {
+ $result.frameSeqI = frameSeqI;
+ }
return $result;
}
TalkDataH264Frame._() : super();
@@ -43,6 +47,7 @@ class TalkDataH264Frame extends $pb.GeneratedMessage {
..a<$core.int>(1, _omitFieldNames ? '' : 'FrameSeq', $pb.PbFieldType.OU3, protoName: 'FrameSeq')
..e(2, _omitFieldNames ? '' : 'FrameType', $pb.PbFieldType.OE, protoName: 'FrameType', defaultOrMaker: TalkDataH264Frame_FrameTypeE.NONE, valueOf: TalkDataH264Frame_FrameTypeE.valueOf, enumValues: TalkDataH264Frame_FrameTypeE.values)
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'FrameData', $pb.PbFieldType.OY, protoName: 'FrameData')
+ ..a<$core.int>(4, _omitFieldNames ? '' : 'FrameSeqI', $pb.PbFieldType.OU3, protoName: 'FrameSeqI')
..hasRequiredFields = false
;
@@ -95,6 +100,16 @@ class TalkDataH264Frame extends $pb.GeneratedMessage {
$core.bool hasFrameData() => $_has(2);
@$pb.TagNumber(3)
void clearFrameData() => clearField(3);
+
+ /// 帧序号I
+ @$pb.TagNumber(4)
+ $core.int get frameSeqI => $_getIZ(3);
+ @$pb.TagNumber(4)
+ set frameSeqI($core.int v) { $_setUnsignedInt32(3, v); }
+ @$pb.TagNumber(4)
+ $core.bool hasFrameSeqI() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearFrameSeqI() => clearField(4);
}
diff --git a/lib/talk/starChart/proto/talk_data_h264_frame.pbjson.dart b/lib/talk/starChart/proto/talk_data_h264_frame.pbjson.dart
index bc7b066c..6bf9aa9b 100644
--- a/lib/talk/starChart/proto/talk_data_h264_frame.pbjson.dart
+++ b/lib/talk/starChart/proto/talk_data_h264_frame.pbjson.dart
@@ -20,6 +20,7 @@ const TalkDataH264Frame$json = {
{'1': 'FrameSeq', '3': 1, '4': 1, '5': 13, '10': 'FrameSeq'},
{'1': 'FrameType', '3': 2, '4': 1, '5': 14, '6': '.main.TalkDataH264Frame.FrameTypeE', '10': 'FrameType'},
{'1': 'FrameData', '3': 3, '4': 1, '5': 12, '10': 'FrameData'},
+ {'1': 'FrameSeqI', '3': 4, '4': 1, '5': 13, '10': 'FrameSeqI'},
],
'4': [TalkDataH264Frame_FrameTypeE$json],
};
@@ -38,6 +39,6 @@ const TalkDataH264Frame_FrameTypeE$json = {
final $typed_data.Uint8List talkDataH264FrameDescriptor = $convert.base64Decode(
'ChFUYWxrRGF0YUgyNjRGcmFtZRIaCghGcmFtZVNlcRgBIAEoDVIIRnJhbWVTZXESQAoJRnJhbW'
'VUeXBlGAIgASgOMiIubWFpbi5UYWxrRGF0YUgyNjRGcmFtZS5GcmFtZVR5cGVFUglGcmFtZVR5'
- 'cGUSHAoJRnJhbWVEYXRhGAMgASgMUglGcmFtZURhdGEiJAoKRnJhbWVUeXBlRRIICgROT05FEA'
- 'ASBQoBSRABEgUKAVAQAg==');
+ 'cGUSHAoJRnJhbWVEYXRhGAMgASgMUglGcmFtZURhdGESHAoJRnJhbWVTZXFJGAQgASgNUglGcm'
+ 'FtZVNlcUkiJAoKRnJhbWVUeXBlRRIICgROT05FEAASBQoBSRABEgUKAVAQAg==');
diff --git a/lib/talk/starChart/proto/talk_data_h264_frame.proto b/lib/talk/starChart/proto/talk_data_h264_frame.proto
index 09815d16..1d08adef 100644
--- a/lib/talk/starChart/proto/talk_data_h264_frame.proto
+++ b/lib/talk/starChart/proto/talk_data_h264_frame.proto
@@ -15,4 +15,6 @@ message TalkDataH264Frame {
FrameTypeE FrameType = 2;
// 帧数据
bytes FrameData = 3;
+ // 帧序号I
+ uint32 FrameSeqI = 4;
}
diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart
index 1a3d2a85..1e53b7f0 100644
--- a/lib/talk/starChart/star_chart_manage.dart
+++ b/lib/talk/starChart/star_chart_manage.dart
@@ -419,9 +419,15 @@ class StartChartManage {
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
// 停止播放铃声
// AudioPlayerManager().playRingtone();
- Get.toNamed(
- Routers.starChartTalkView,
- );
+ if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) {
+ Get.toNamed(
+ Routers.h264WebView,
+ );
+ } else {
+ Get.toNamed(
+ Routers.starChartTalkView,
+ );
+ }
}
talkRequestTimer ??= Timer.periodic(
Duration(
@@ -1118,6 +1124,10 @@ class StartChartManage {
);
}
+ TalkExpectReq getDefaultTalkExpect() {
+ return _defaultTalkExpect;
+ }
+
/// 修改预期接收到的数据
void sendOnlyImageVideoTalkExpectData() {
final talkExpectReq = TalkExpectReq(
diff --git a/lib/talk/starChart/webView/h264_web_logic.dart b/lib/talk/starChart/webView/h264_web_logic.dart
new file mode 100644
index 00000000..02b1c2ed
--- /dev/null
+++ b/lib/talk/starChart/webView/h264_web_logic.dart
@@ -0,0 +1,75 @@
+import 'dart:math';
+
+import 'package:flutter/services.dart';
+import 'package:star_lock/app_settings/app_settings.dart';
+import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
+import 'package:star_lock/talk/starChart/star_chart_manage.dart';
+import 'package:star_lock/talk/starChart/webView/h264_web_view_state.dart';
+import 'package:star_lock/tools/baseGetXController.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+
+class H264WebViewLogic extends BaseGetXController {
+ final H264WebViewState state = H264WebViewState();
+
+ @override
+ void onInit() {
+ super.onInit();
+ // 初始化 WebView 控制器
+ state.webViewController = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..enableZoom(false)
+ ..addJavaScriptChannel(
+ 'Flutter',
+ onMessageReceived: (message) {
+ print("来自 HTML 的消息: ${message.message}");
+ },
+ );
+
+ // 加载本地 HTML
+ _loadLocalHtml();
+ // 创建流数据监听
+ _createFramesStreamListen();
+ }
+
+ void _createFramesStreamListen() async {
+ state.talkDataRepository.talkDataStream.listen((TalkData event) async {
+ // 发送数据给js处理
+ _sendBufferedData(event.content);
+ });
+ }
+
+ /// 加载html文件
+ Future _loadLocalHtml() async {
+ // 加载 HTML 文件内容
+ final String fileHtmlContent =
+ await rootBundle.loadString('assets/html/h264.html');
+
+ // 加载 JS 文件内容
+ final String jsContent =
+ await rootBundle.loadString('assets/html/jmuxer.min.js');
+
+ // 将 JS 文件内容嵌入到 HTML 中
+ final String htmlWithJs = fileHtmlContent.replaceAll(
+ '', // 替换掉引用外部 JS 的标签
+ '' // 使用内联方式嵌入 JS 内容
+ );
+
+ // 加载最终的 HTML 字符串到 WebView 中
+ if (state.webViewController != null) {
+ state.webViewController.loadHtmlString(htmlWithJs); // 设置 baseUrl 避免资源加载问题
+ }
+ }
+
+ // 修改后的发送方法
+ _sendBufferedData(List buffer) async {
+ // 原始发送逻辑
+ String jsCode = "feedDataFromFlutter($buffer);";
+ await state.webViewController.runJavaScript(jsCode);
+ }
+
+ @override
+ void onClose() {
+ super.onClose();
+ StartChartManage().startTalkHangupMessageTimer();
+ }
+}
diff --git a/lib/talk/starChart/webView/h264_web_view.dart b/lib/talk/starChart/webView/h264_web_view.dart
index 2392c5fa..a83bd21d 100644
--- a/lib/talk/starChart/webView/h264_web_view.dart
+++ b/lib/talk/starChart/webView/h264_web_view.dart
@@ -3,9 +3,14 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show ByteData, Uint8List, rootBundle;
import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
import 'package:star_lock/app_settings/app_colors.dart';
+import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart';
+import 'package:star_lock/talk/starChart/star_chart_manage.dart';
+import 'package:star_lock/talk/starChart/webView/h264_web_logic.dart';
+import 'package:star_lock/talk/starChart/webView/h264_web_view_state.dart';
import 'package:star_lock/tools/titleAppBar.dart';
import 'package:webview_flutter/webview_flutter.dart';
@@ -15,168 +20,15 @@ class H264WebView extends StatefulWidget {
}
class _H264WebViewState extends State {
- late final WebViewController _controller;
- Timer? timer;
- Timer? _sendTimer;
-
- // 私有缓冲区,外部无法直接访问
- final List _buffer = [];
-
- // 发送数据至html文件间隔时间
- final int sendDataToHtmlIntervalTime = 820;
-
- // 通话数据流的单例流数据处理类
- final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
-
- @override
- void initState() {
- super.initState();
-
- _controller = WebViewController()
- ..setJavaScriptMode(JavaScriptMode.unrestricted)
- ..enableZoom(false)
- ..addJavaScriptChannel(
- 'Flutter',
- onMessageReceived: (message) {
- print("来自 HTML 的消息: ${message.message}");
- },
- );
-
- // 加载本地 HTML
- _loadLocalHtml();
- simulateStreamFromAsset();
- _sendFramesToHtml();
- }
-
- void simulateStreamFromAsset() async {
- // 读取 assets 文件
- final ByteData data = await rootBundle.load('assets/talk.h264');
- final List byteData = data.buffer.asUint8List();
- int current = 0;
- int start = 0;
- int end = 0;
- final List chunks = extractChunks(byteData);
- // 定时器控制发送数据块的节奏
- timer ??= Timer.periodic(Duration(milliseconds: 10), (timer) {
- if (current >= chunks.length) {
- print('数据已经发完,重新进行发送');
- start = 0;
- end = 0;
- current = 0;
- timer.cancel();
- return;
- }
- // 提取 NALU 边界并生成 chunks
- end = chunks[current];
- current++;
- List frameData = byteData.sublist(start, end);
- if (frameData.length == 0) timer.cancel();
-
- talkDataRepository.addTalkData(TalkData(contentType: TalkData_ContentTypeE.H264,content: frameData));
- start = end;
- });
- }
-
- void _sendFramesToHtml() async {
- // 接收到流数据,保存到缓冲区
- // talkDataRepository.talkDataStream.listen((TalkData event) async {
- // _buffer.addAll(event.content);
- // });
- // 缓冲800ms的数据,定时发送
- _sendTimer ??= Timer.periodic(
- Duration(milliseconds: sendDataToHtmlIntervalTime), (timer) async {
- // 发送累积的数据
- if (_buffer.isNotEmpty) {
- await _sendBufferedData(_buffer);
- _buffer.clear(); // 清空缓冲区
- }
- });
- }
-
- // 提取 NALU 边界并生成 chunks
- List extractChunks(List byteData) {
- int i = 0;
- int length = byteData.length;
- int naluCount = 0;
- int value;
- int state = 0;
- int lastIndex = 0;
- List result = [];
- const minNaluPerChunk = 22; // 每个数据块包含的最小NALU数量
-
- while (i < length) {
- value = byteData[i++];
- // finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
- switch (state) {
- case 0:
- if (value == 0) {
- state = 1;
- }
- break;
- case 1:
- if (value == 0) {
- state = 2;
- } else {
- state = 0;
- }
- break;
- case 2:
- case 3:
- if (value == 0) {
- state = 3;
- } else if (value == 1 && i < length) {
- if (lastIndex > 0) {
- naluCount++;
- }
- if (naluCount >= minNaluPerChunk) {
- result.add(lastIndex - state - 1);
- naluCount = 0;
- }
- state = 0;
- lastIndex = i;
- } else {
- state = 0;
- }
- break;
- default:
- break;
- }
- }
-
- if (naluCount > 0) {
- result.add(lastIndex);
- }
-
-
-
- return result;
- }
-
- /// 加载html文件
- Future _loadLocalHtml() async {
- final String fileHtmlContent =
- await rootBundle.loadString('assets/html/h264.html');
- _controller.loadHtmlString(fileHtmlContent);
- }
-
- // 发送数据给js处理
- _sendBufferedData(List buffer) async {
- String jsCode = "feedDataFromFlutter(${buffer});";
- await _controller.runJavaScript(jsCode);
- }
+ final H264WebViewLogic logic = Get.put(H264WebViewLogic());
+ final H264WebViewState state = Get.find().state;
@override
Widget build(BuildContext context) {
- return WebViewWidget(controller: _controller);
- }
-
- @override
- void dispose() {
- timer?.cancel();
- timer = null;
- _sendTimer?.cancel();
- timer = null;
- // talkDataRepository.dispose();
- super.dispose();
+ return Stack(
+ children: [
+ WebViewWidget(controller: state.webViewController),
+ ],
+ );
}
}
diff --git a/lib/talk/starChart/webView/h264_web_view_state.dart b/lib/talk/starChart/webView/h264_web_view_state.dart
new file mode 100644
index 00000000..7a79d89a
--- /dev/null
+++ b/lib/talk/starChart/webView/h264_web_view_state.dart
@@ -0,0 +1,12 @@
+import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+
+class H264WebViewState {
+
+ // webview 控制器
+ late final WebViewController webViewController;
+
+ // 通话数据流的单例流数据处理类
+ final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
+
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 5473124e..42517986 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -313,6 +313,7 @@ flutter:
- images/lockType/
- assets/
- assets/html/h264.html
+ - assets/html/jmuxer.min.js
- lan/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware