Skip to content

Commit d85f829

Browse files
authored
Merge pull request #3 from restreamio/Restream/master
Restream/master
2 parents 5b7c7dd + 8e33539 commit d85f829

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2761
-831
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ endmacro()
551551

552552
makeOutput(RTMP rtmp)
553553
makeOutput(DTSC dtsc)
554+
makeOutput(JSONLine jsonline)
554555
makeOutput(OGG ogg http)
555556
makeOutput(FLV flv http)
556557
makeOutput(HTTPMinimalServer http_minimalserver http)

embed/min/player.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

embed/min/skins/default.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ svg.icon .spin,svg.icon.spin{animation:mistvideo-spin 1.5s infinite linear;trans
6767
.mistvideo{line-height:1.2;font-size:14.5px}
6868
.mistvideo svg{margin:2.5px}
6969
.mistvideo-video{display:flex;align-items:center;justify-content:center}
70+
.mistvideo-subtitles{position:absolute;width:100%;height:100%;pointer-events:none;display:flex;align-items:flex-end;justify-content:center}
71+
.mistvideo-subtitles>*{margin-bottom:.5em;padding:.1em .3em;text-align:center;background:rgba(0,0,0,.6);white-space:pre-wrap}
72+
.mistvideo-subtitles>:empty{display:none}
73+
.mistvideo[data-fullscreen] .mistvideo-subtitles{font-size:3vh}
7074
.mistvideo-background{background-color:$background}
7175
.mistvideo-totalTime:before{content:'/';margin:.2em}
7276
.mistvideo-progress{padding:10px 0;margin:-10px 0;z-index:2}

embed/min/skins/dev.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ svg.icon .spin,svg.icon.spin{animation:mistvideo-spin 1.5s infinite linear;trans
6767
.mistvideo{line-height:1.2;font-size:14.5px}
6868
.mistvideo svg{margin:2.5px}
6969
.mistvideo-video{display:flex;align-items:center;justify-content:center}
70+
.mistvideo-subtitles{position:absolute;width:100%;height:100%;pointer-events:none;display:flex;align-items:flex-end;justify-content:center}
71+
.mistvideo-subtitles>*{margin-bottom:.5em;padding:.1em .3em;text-align:center;background:rgba(0,0,0,.6);white-space:pre-wrap}
72+
.mistvideo-subtitles>:empty{display:none}
73+
.mistvideo[data-fullscreen] .mistvideo-subtitles{font-size:3vh}
7074
.mistvideo-background{background-color:$background}
7175
.mistvideo-totalTime:before{content:'/';margin:.2em}
7276
.mistvideo-progress{padding:10px 0;margin:-10px 0;z-index:2}

embed/player.js

Lines changed: 239 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ function MistVideo(streamName,options) {
3535
maxheight: false, //no max height (apart from targets dimensions)
3636
ABR_resize: true, //for supporting wrappers: when the player resizes, request a video track that matches the resolution best
3737
ABR_bitrate: true, //for supporting wrappers: when there are playback issues, request a lower bitrate video track
38-
useDateTime: true, //when the unix timestamp of the stream is known, display the date/time
38+
useDateTime: true, //when the unix timestamp of the stream is known, display the date/time,
39+
subscribeToMetaTrack: false, //pass [[track index,callback]]; the callback function will be called whenever the specified meta data track receives a message.
3940
MistVideoObject: false//no reference object is passed
4041
},options);
4142
if (options.host) { options.host = MistUtil.http.url.sanitizeHost(options.host); }
@@ -115,7 +116,6 @@ function MistVideo(streamName,options) {
115116
this.log("A reloadDelay of more than an hour was set: assuming milliseconds were intended. ReloadDelay is now "+options.reloadDelay+"s");
116117
}
117118

118-
119119
new MistSkin(this);
120120

121121
this.checkCombo = function(options,quiet) {
@@ -507,7 +507,7 @@ function MistVideo(streamName,options) {
507507
if (MistVideo.reporting) {
508508
MistVideo.reporting.init();
509509
}
510-
510+
511511
if ("api" in MistVideo.player) {
512512

513513
//add monitoring
@@ -663,7 +663,235 @@ function MistVideo(streamName,options) {
663663
MistUtil.event.addListener(MistVideo.video,events[i],function(){
664664
if (MistVideo.monitor) { MistVideo.monitor.reset(); }
665665
});
666-
}
666+
}
667+
668+
if ("currentTime" in MistVideo.player.api) {
669+
var json_source = MistUtil.sources.find(MistVideo.info.source,{
670+
type: "html5/text/javascript",
671+
protocol: "ws"+(location.protocol.charAt(location.protocol.length-2) == "s" ? "s" : "")+":"
672+
});
673+
if (json_source) {
674+
MistVideo.metaTrackSubscriptions = {
675+
subscriptions: {},
676+
socket: null,
677+
listeners: {},
678+
init: function(){
679+
var me = this;
680+
this.socket = new WebSocket(MistUtil.http.url.addParam(MistVideo.urlappend(json_source.url),{rate:1}));
681+
me.send_queue = [];
682+
me.checktimer = null;
683+
me.s = function(obj){
684+
if (me.socket.readyState == me.socket.OPEN) {
685+
me.socket.send(JSON.stringify(obj));
686+
return true;
687+
}
688+
if (me.socket.readyState >= me.socket.CLOSING) {
689+
//reopen websocket
690+
me.init();
691+
}
692+
//add message to queue
693+
this.send_queue.push(obj);
694+
};
695+
696+
var stayahead = 5; //ask MistServer to fastforward to stayahead seconds ahead, so we receive messages earlier
697+
698+
me.socket.setTracks = function(){
699+
me.s({type:"tracks",meta:MistUtil.object.keys(me.subscriptions).join(",")});
700+
};
701+
me.socket.onopen = function(){
702+
MistVideo.log("Metadata socket opened");
703+
704+
me.socket.setTracks();
705+
me.s({type:"set_speed",play_rate:MistVideo.player.api.playbackRate});
706+
me.s({type:"seek",seek_time:Math.round(MistVideo.player.api.currentTime*1e3),ff_to:Math.round((MistVideo.player.api.currentTime+stayahead)*1e3)});
707+
me.socket.addEventListener("message",function(e){
708+
if (!e.data) { MistVideo.log("Subtitle websocket received empty message."); return; }
709+
var message = JSON.parse(e.data);
710+
if (!message) { MistVideo.log("Subtitle websocket received invalid message."); return; }
711+
712+
if (("time" in message) && ("track" in message) && ("data" in message)) {
713+
if (message.track in me.subscriptions) {
714+
//console.warn("received:",message.track,message.data);
715+
me.subscriptions[message.track].buffer.push(message);
716+
//console.warn("received:",message.track,message.time,"currentTime:",MistVideo.player.api.currentTime,"bufferlength:",me.subscriptions[message.track].buffer.length,"timer:",!!me.checktimer);
717+
718+
if (!me.checktimer) {
719+
me.check();
720+
}
721+
else {
722+
var willCheckAt = MistVideo.timers.list[me.checktimer];
723+
if (willCheckAt) {
724+
var messageAt = (new Date()).getTime() + message.time - MistVideo.player.api.currentTime*1e3;
725+
if (willCheckAt > messageAt) {
726+
MistVideo.log("The metadata socket received a message that should be displayed sooner than the current check time; resetting");
727+
MistVideo.timers.stop(me.checktimer);
728+
me.checktimer = null;
729+
me.check();
730+
}
731+
}
732+
}
733+
}
734+
}
735+
//per track, the messages should arrive in the correct order and we shouldn't need to do sorting
736+
737+
738+
if ("type" in message) {
739+
switch (message.type) {
740+
case "seek": {
741+
for (var i in me.subscriptions) {
742+
me.subscriptions[i].buffer = [];
743+
}
744+
MistVideo.log("Cleared metadata buffer after completed seek");
745+
if (me.checktimer) {
746+
//there might be a timer going for some time in the future: stop it,
747+
MistVideo.timers.stop(me.checktimer);
748+
me.checktimer = null;
749+
}
750+
}
751+
}
752+
}
753+
754+
});
755+
me.socket.onclose = function(){
756+
//dont me.init();, send function will reopen if needed instead
757+
MistVideo.log("Metadata socket closed");
758+
}
759+
760+
while (me.send_queue.length && (me.socket.readyState == me.socket.OPEN)) {
761+
me.s(me.send_queue.shift());
762+
}
763+
};
764+
if (!("seeked" in this.listeners)) { //prevent duplication
765+
var lastff = 0;
766+
767+
me.check = function(){
768+
//console.warn(me.checktimer,"check");
769+
if (me.checktimer) {
770+
MistVideo.timers.stop(me.checktimer);
771+
me.checktimer = null;
772+
}
773+
774+
if (MistVideo.player.api.paused) { return; }
775+
776+
var nextAtGlobal = null;
777+
for (var i in me.subscriptions) {
778+
var buffer = me.subscriptions[i].buffer;
779+
while (buffer.length && (buffer[0].time <= MistVideo.player.api.currentTime*1e3)) {
780+
var message = buffer.shift();
781+
782+
if (message.time < (MistVideo.player.api.currentTime - 5) * 1e3) {
783+
//the message is at least 5 seconds older than the video time
784+
continue;
785+
}
786+
else {
787+
for (var j in me.subscriptions[i].callbacks) {
788+
me.subscriptions[i].callbacks[j].call(MistVideo,message);
789+
}
790+
}
791+
}
792+
if (buffer.length) {
793+
//save when the next message should be played
794+
nextAtGlobal = Math.min(nextAtGlobal === null ? 1e9 : nextAtGlobal,buffer[0].time);
795+
}
796+
}
797+
798+
//add rate limiting: do not ask for fast forward more than once a second
799+
var now = (new Date()).getTime()
800+
if (now > lastff+1e3) {
801+
me.s({type:"fast_forward",ff_to:Math.round((MistVideo.player.api.currentTime+stayahead)*1e3)});
802+
lastff = now;
803+
}
804+
805+
806+
if (nextAtGlobal) {
807+
var delay = nextAtGlobal-MistVideo.player.api.currentTime*1e3;
808+
me.checktimer = MistVideo.timers.start(function(){
809+
me.check();
810+
},delay);
811+
}
812+
813+
};
814+
815+
this.listeners.seeked = MistUtil.event.addListener(MistVideo.video,"seeked",function(){
816+
for (var i in me.subscriptions) {
817+
me.subscriptions[i].buffer = [];
818+
}
819+
me.s({type:"seek",seek_time:Math.round(MistVideo.player.api.currentTime*1e3),ff_to:Math.round((MistVideo.player.api.currentTime+stayahead)*1e3)});
820+
//console.warn("seek to",Math.round(MistVideo.player.api.currentTime*1e3));
821+
});
822+
this.listeners.pause = MistUtil.event.addListener(MistVideo.video,"pause",function(){
823+
me.s({type:"hold"});
824+
MistVideo.timers.stop(me.checktimer);
825+
me.checktimer = null;
826+
});
827+
this.listeners.playing = MistUtil.event.addListener(MistVideo.video,"playing",function(){
828+
me.s({type:"play"});
829+
if (!me.checktimer) me.check();
830+
});
831+
this.listeners.ratechange = MistUtil.event.addListener(MistVideo.video,"ratechange",function(){
832+
me.s({type:"set_speed",play_rate:MistVideo.player.api.playbackRate});
833+
});
834+
}
835+
},
836+
destroy: function(){
837+
MistVideo.log("Closing metadata socket..");
838+
this.socket.close();
839+
this.socket = null;
840+
this.subscriptions = {};
841+
for (var i in this.listeners) {
842+
MistUtil.event.removeListener(this.listeners[i]);
843+
}
844+
this.listeners = {};
845+
},
846+
add: function (trackid,callback) {
847+
if (typeof callback != "function") { return; }
848+
849+
if (!(trackid in this.subscriptions)) {
850+
this.subscriptions[trackid] = {
851+
buffer: [],
852+
callbacks: []
853+
};
854+
}
855+
this.subscriptions[trackid].callbacks.push(callback);
856+
857+
if (this.socket === null) {
858+
this.init();
859+
}
860+
else {
861+
this.socket.setTracks();
862+
}
863+
},
864+
remove: function(trackid,callback){
865+
if (trackid in this.subscriptions) {
866+
for (var i in this.subscriptions[trackid].callbacks) {
867+
if (callback == this.subscriptions[trackid].callbacks[i]) {
868+
this.subscriptions[trackid].callbacks.splice(i,1);
869+
break;
870+
}
871+
}
872+
if (this.subscriptions[trackid].callbacks.length == 0) {
873+
delete this.subscriptions[trackid];
874+
if (MistUtil.object.keys(this.subscriptions).length) {
875+
this.socket.setTracks();
876+
}
877+
else {
878+
this.destroy();
879+
}
880+
}
881+
}
882+
}
883+
};
884+
if (options.subscribeToMetaTrack.length) {
885+
if (typeof options.subscribeToMetaTrack[0] != "object") {
886+
options.subscribeToMetaTrack = [options.subscribeToMetaTrack];
887+
}
888+
for (var i in options.subscribeToMetaTrack) {
889+
MistVideo.metaTrackSubscriptions.add.apply(MistVideo.metaTrackSubscriptions,options.subscribeToMetaTrack[i]);
890+
}
891+
}
892+
}
893+
}
894+
667895
}
668896

669897
//remove placeholder and add UI structure
@@ -1051,7 +1279,7 @@ function MistVideo(streamName,options) {
10511279
//switch to polling-mode if websockets are not supported
10521280

10531281
function openWithGet() {
1054-
var url = MistUtil.http.url.addParam(MistVideo.urlappend(options.host+"/json_"+encodeURIComponent(MistVideo.stream)+".js"),{metaeverywhere:1});
1282+
var url = MistUtil.http.url.addParam(MistVideo.urlappend(options.host+"/json_"+encodeURIComponent(MistVideo.stream)+".js"),{metaeverywhere:1,inclzero:1});
10551283
MistVideo.log("Requesting stream info from "+url);
10561284
MistUtil.http.get(url,function(d){
10571285
if (MistVideo.destroyed) { return; }
@@ -1070,7 +1298,7 @@ function MistVideo(streamName,options) {
10701298
function openSocket() {
10711299
MistVideo.log("Opening stream status stream through websocket..");
10721300
var url = MistVideo.options.host.replace(/^http/i,"ws");
1073-
url = MistUtil.http.url.addParam(MistVideo.urlappend(url+"/json_"+encodeURIComponent(MistVideo.stream)+".js"),{metaeverywhere:1});
1301+
url = MistUtil.http.url.addParam(MistVideo.urlappend(url+"/json_"+encodeURIComponent(MistVideo.stream)+".js"),{metaeverywhere:1,inclzero:1});
10741302
var socket;
10751303
try {
10761304
socket = new WebSocket(url);
@@ -1415,10 +1643,8 @@ function MistVideo(streamName,options) {
14151643
if (diff) {
14161644
//console.log("Difference",diff,data,MistVideo.info);
14171645

1418-
if ("source" in diff) {
1419-
if ("error" in MistVideo.info) {
1420-
MistVideo.reload("Reloading, stream info has error");
1421-
}
1646+
if (("source" in diff) && ("error" in MistVideo.info)) {
1647+
MistVideo.reload("Reloading, stream info has error");
14221648
return;
14231649
}
14241650

@@ -1513,6 +1739,9 @@ function MistVideo(streamName,options) {
15131739
}
15141740
}
15151741
}
1742+
if (this.metaTrackSubscriptions && this.metaTrackSubscriptions.socket) {
1743+
this.metaTrackSubscriptions.destroy();
1744+
}
15161745
if ((this.UI) && (this.UI.elements)) {
15171746
for (var i in this.UI.elements) {
15181747
var e = this.UI.elements[i];

0 commit comments

Comments
 (0)