").text(
typeof v == "string" ?
JSON.stringify(v) :
(v[2] > 0 ? JSON.stringify(v[3]) : "" )
)
)
).append(
$("").text(typeof v == "string" ? "" : v[0])
).append(
$(" ").html(typeof v == "string" ? "Never" : (v[1] == 0 ? "Once" : UI.format.duration(v[1])))
).append(
$(" ").attr("title",
v[2] > 0 ?
(typeof v == "string" ? "" : "At "+UI.format.dateTime(new Date(v[2]),"long")) :
"Not yet"
).html(
typeof v == "string" ?
"" :
(v[2] > 0 ? UI.format.duration(new Date().getTime()*1e-3 - v[2])+" ago" : "Not yet")
)
).append(
$(" ").html(
$("").text("Edit").click(function(){
var i = $(this).closest("tr").attr("data-name");
UI.navto("Edit variable",i);
})
).append(
$("").text("Remove").click(function(){
var i = $(this).closest("tr").attr("data-name");
if (confirm("Are you sure you want to remove the custom variable $"+i+"?")) {
mist.send(function(){
UI.showTab("General");
},{variable_remove:i});
}
})
)
)
);
}
},{variable_list:true});
$main.append(UI.buildUI([
$('').text("Custom variables"),
{
type: "help",
help: "In certain places, like target URL's and pushes, variable substitution is applied in order to replace a $variable with their corresponding value. Here you can define your own constants and variables which will be used when variable substitution is applied. Variables can be used within variables but will not be reflected in their latest value on this page."
},
$("").addClass("button_container").css("text-align","right").html(
$("
").text("New variable").click(function(){
UI.navto("Edit variable","");
})
),
$variables
]));
$main.append(UI.buildUI([
$('').text("Load balancer"),
{
type: "help",
help: "If you're using MistServer's load balancer, the information below is passed to it so that it can make informed decisions."
},
{
type: "selectinput",
label: "Server's bandwidth limit",
selectinput: [
["","Default (1 gbps)"],
[{
label: "Custom",
type: "int",
min: 0,
unit: $bitunit
},"Custom"]
],
pointer: {
main: b,
index: "limit"
},
help: "This is the amount of traffic this server is willing to handle."
},{
type: "inputlist",
label: "Bandwidth exceptions",
pointer: {
main: b,
index: "exceptions"
},
help: "Data sent to the hosts and subnets listed here will not count towards reported bandwidth usage. Examples:192.168.0.0/16 localhost 10.0.0.0/8 fe80::/16 "
},{
type: "int",
step: 0.00000001,
label: "Server latitude",
pointer: {
main: s_balancer.location,
index: "lat"
},
help: "This setting is only useful when MistServer is combined with a load balancer. When this is set, the balancer can send users to a server close to them."
},{
type: "int",
step: 0.00000001,
label: "Server longitude",
pointer: {
main: s_balancer.location,
index: "lon"
},
help: "This setting is only useful when MistServer is combined with a load balancer. When this is set, the balancer can send users to a server close to them."
},{
type: "str",
label: "Server location name",
pointer: {
main: s_balancer.location,
index: "name"
},
help: "This setting is only useful when MistServer is combined with a load balancer. This will be displayed as the server's location."
},{
type: 'buttons',
buttons: [{
type: 'save',
label: 'Save',
'function': function(ele){
$(ele).text("Saving..");
var save = {config: s_balancer};
var bandwidth = {};
bandwidth.limit = (b.limit ? $bitunit.val() * b.limit : 0);
bandwidth.exceptions = b.exceptions;
if (bandwidth.exceptions === null) {
bandwidth.exceptions = [];
}
save.bandwidth = bandwidth;
mist.send(function(){
UI.navto('Overview');
},save)
}
}]
}
]));
var $uploaders = $("").html("Loading..");
$main.append(UI.buildUI([
$('
').text("External writers"),
{
type: "help",
help: "When pushing a stream to a target unsupported by MistServer like S3 storage, an external writer can be provided which handles writing the media data to the target location. The writer will receive data over stdin and MistServer will print any info written to stdout and stderr as log messages."
},
$("").addClass("button_container").css("text-align","right").html(
$("
").text("New external writer").click(function(){
UI.navto("Edit external writer","");
})
),
$uploaders
]));
mist.send(function(d){
if (!d.external_writer_list) {
$uploaders.html("None configured.");
}
else {
var $tbody = $("");
$uploaders.html(
$("").html(
$("").html(
$("").append(
$("").text("Name")
).append(
$(" ").text("Command line")
).append(
$(" ").text("URI protocols handled")
).append(
$(" ")
)
)
).append($tbody)
);
for (var i in d.external_writer_list) {
var uploader = d.external_writer_list[i];
$tbody.append(
$(" ").addClass("uploader").attr("data-name",i).html(
$("").text(uploader[0])
).append(
$(" ").html($("").html(uploader[1]))
).append(
$("").text(uploader[2] ? uploader[2].join(", ") : "none").addClass("desc")
).append(
$(" ").html(
$("").text("Edit").click(function(){
var i = $(this).closest("tr").attr("data-name");
UI.navto("Edit external writer",i);
})
).append(
$("").text("Remove").click(function(){
var i = $(this).closest("tr").attr("data-name");
var name = d.external_writer_list[i][0];
if (confirm("Are you sure you want to remove the Uploader '"+name+"'?")) {
mist.send(function(){
UI.showTab("General");
},{external_writer_remove:name});
}
})
)
)
);
}
}
},{external_writer_list:true});
break;
}
case "Edit external writer": {
var editing = false;
if (other != '') { editing = true; }
function buildPage() {
if (!editing) {
$main.html($('').text('New external writer'));
}
else {
$main.html($('').text('Edit external writer \''+other+'\''));
}
var saveas = {};
if (mist.data.external_writer_list && (other in mist.data.external_writer_list)) {
var uploader = mist.data.external_writer_list[other];
saveas.name = uploader[0];
saveas.cmdline = uploader[1];
saveas.protocols = uploader[2];
}
$main.append(UI.buildUI([
{
type: "str",
label: "Human readable name",
help: "A human readable name for the external writer.",
validate: ['required'],
pointer: {
main: saveas,
index: "name"
}
},{
type: "str",
label: "Command line",
help: "Command line for a local command (with optional arguments) which will write media data to the target.",
validate: ['required'],
pointer: {
main: saveas,
index: "cmdline"
}
},{
type: "inputlist",
label: "URI protocols handled",
help: "URI protocols which the external writer will be handling.",
validate: ['required',function(val){
for (var i in val) {
var v = val[i];
if (v.match(/^([a-z\d\+\-\.])+?$/) === null) {
return {
classes: ["red"],
msg: "There was a problem with the protocol URI '"+function(s){ return $("").text(s).html() }(v)+"':
A protocol URI may only contain lower case letters, digits, and the following special characters . + and -"
}
break;
}
}
}],
input: {
type: "str",
unit: "://"
},
pointer: {
main: saveas,
index: "protocols"
}
},{
type: "buttons",
buttons: [
{
type: 'cancel',
label: 'Cancel',
'function': function(){
UI.navto('General');
}
},{
type: 'save',
label: 'Save',
'function': function(){
var o = {external_writer_add:saveas};
var prev_name = null;
if ((other != "") && (other in mist.data.external_writer_list)) {
prev_name = mist.data.external_writer_list[other][0];
}
if ((prev_name !== null) && (saveas.name != prev_name)) {
o.external_writer_remove = prev_name;
}
mist.send(function(){
UI.navto('General');
},o);
}
}
]
}
]));
}
if ("external_writer_list" in mist.data) {
buildPage();
}
else {
mist.send(function(){
buildPage();
},{external_writer_list:true});
}
break;
}
case 'Edit variable': {
var editing = false;
if (other != '') { editing = true; }
function build(saveas,saveas_dyn) {
if (!editing) {
$main.html($('
').text('New Variable'));
}
else {
$main.html($('').text('Edit Variable "$'+other+'"'));
}
var $dynamicinputs = $("");
$main.append(UI.buildUI([
{
type: "str",
maxlength: 31,
label: "Variable name",
prefix: "$",
help: "What should the variable be called? A dollar sign will automatically be prepended.",
pointer: {
main: saveas,
index: "name"
},
validate: ["required",function(val){
if (val.length && (val[0] == "$")) {
return {
msg: 'The dollar sign will automatically be prepended. You don\'t need to type it here.',
classes: ['red']
};
}
if ((val.indexOf("{") !== -1) || (val.indexOf("}") !== -1) || (val.indexOf("$") !== -1)) {
return {
msg: 'The following symbols are not permitted: "$ { }".',
classes: ['red']
};
}
}]
},{
type: "select",
label: "Type",
help: "What kind of variable is this? It can either be a static value that you can enter below, or a dynamic one that is returned by a command.",
select: [
["value","Static value"],
["command","Dynamic through command"]
],
value: "value",
pointer: {
main: saveas_dyn,
index: "type"
},
'function': function(){
var b = [$("Invalid variable type")];
switch ($(this).val()) {
case "value": {
b = [{
type: "str",
label: "Value",
pointer: {
main: saveas_dyn,
index: "value"
},
help: "The static value that this variable should be replaced with. There is a character limit of 63 characters.",
validate: ["required"]
}];
break;
}
case "command": {
b = [{
type: "str",
label: "Command",
help: "The command that should be executed to retrieve the value for this variable.
For example:
/usr/bin/date +%A
There is a character limit of 511 characters.",
validate: ["required"],
pointer: {
main: saveas_dyn,
index: "target"
}
},{
type: "int",
min: 0,
max: 4294967295,
'default': 0,
label: "Checking interval",
unit: "s",
help: "At what interval, in seconds, MistServer should execute the command and update the value.
To execute the command once when MistServer starts up (and then never update), set the interval to 0.",
pointer: {
main: saveas_dyn,
index: "interval"
}
},{
type: "int",
min: 0,
max: 4294967295,
'default': 1,
label: "Wait time",
unit: "s",
help: "Specifies the maximum time, in seconds, MistServer should wait for data when executing the variable target. If set to 0 this variable takes on the same value as the interval.
MistServer only updates one variable at a time, so setting this value too high can block other variables from updating.",
pointer: {
main: saveas_dyn,
index: "waitTime"
}
}];
break;
}
}
$dynamicinputs.html(UI.buildUI(b));
}
},
$dynamicinputs,
{
type: "buttons",
buttons: [
{
type: 'cancel',
label: 'Cancel',
'function': function(){
UI.navto('General');
}
},{
type: 'save',
label: 'Save',
'function': function(){
var o = {variable_add:saveas};
switch (saveas_dyn.type) {
case "value": {
saveas.value = saveas_dyn.value;
break;
}
case "command": {
saveas.target = saveas_dyn.target;
saveas.interval = saveas_dyn.interval;
saveas.waitTime = saveas_dyn.waitTime;
break;
}
}
if (saveas.name != other) {
o.variable_remove = other;
}
mist.send(function(){
UI.navto('General');
},o);
}
}
]
}
]));
}
$main.html("Loading..");
if (!editing) {
build({},{});
}
else {
mist.send(function(d){
if (other in d.variable_list) {
var v = d.variable_list[other];
build({
name: other
},(typeof v == "string" ? {
value: v,
type: "value"
} : {
target: v[0],
interval: v[1],
waitTime: v[4],
type: "command"
}));
}
else {
$main.append('Variable "$'+other+'" does not exist.');
}
},{variable_list:true});
}
break;
}
case 'Protocols':
if (typeof mist.data.capabilities == 'undefined') {
mist.send(function(d){
UI.navto(tab);
},{capabilities: true});
$main.append('Loading..');
return;
}
var $tbody = $('
');
$main.append(
UI.buildUI([{
type: 'help',
help: 'You can find an overview of all the protocols and their relevant information here. You can add, edit or delete protocols.'
}])
).append(
$('').text('Delete all protocols').click(function(){
if (confirm('Are you sure you want to delete all currently configured protocols?')) {
mist.data.config.protocols = [];
mist.send(function(d){
UI.navto('Protocols');
},{config: mist.data.config});
}
})
).append(
$('').text('Enable default protocols').click(function(){
var toenable = Object.keys(mist.data.capabilities.connectors);
for (var i in mist.data.config.protocols) {
var p = mist.data.config.protocols[i];
var index = toenable.indexOf(p.connector)
if (index > -1) {
toenable.splice(index,1);
}
}
var dontskip = [];
for (var i in toenable) {
if ((!('required' in mist.data.capabilities.connectors[toenable[i]])) || (Object.keys(mist.data.capabilities.connectors[toenable[i]].required).length == 0)) {
dontskip.push(toenable[i]);
}
}
var msg = 'Click OK to enable disabled protocols with their default settings:'+"\n ";
if (dontskip.length) {
msg += dontskip.join(', ');
}
else {
msg += 'None.';
}
if (dontskip.length != toenable.length) {
var skip = toenable.filter(function(ele){
return dontskip.indexOf(ele) < 0;
});
msg += "\n\n"+'The following protocols can only be set manually:'+"\n "+skip.join(', ');
}
if (confirm(msg) && dontskip.length) {
if (mist.data.config.protocols === null) { mist.data.config.protocols = []; }
for (var i in dontskip) {
mist.data.config.protocols.push({connector: dontskip[i]});
}
mist.send(function(d){
UI.navto('Protocols');
},{config: mist.data.config});
}
})
).append(' ').append(
$('').text('New protocol').click(function(){
UI.navto('Edit Protocol');
}).css('clear','both')
).append(
$('').html(
$('').html(
$('').html(
$('').text('Protocol')
).append(
$(' ').text('Status')
).append(
$(' ').text('Settings')
).append(
$(' ')
)
)
).append(
$tbody
)
);
function updateProtocols() {
function displaySettings(protocol){
var capabilities = mist.data.capabilities.connectors[protocol.connector];
if (!capabilities) {
return '';
}
var str = [];
var types = ['required','optional']
for (var j in types) {
for (var i in capabilities[types[j]]) {
if ((protocol[i]) && (protocol[i] != '')) {
str.push(i+': '+protocol[i]);
}
else if (capabilities[types[j]][i]['default']) {
str.push(i+': '+capabilities[types[j]][i]['default']);
}
}
}
return $('').addClass('description').text(str.join(', '));
}
$tbody.html('');
for (var i in mist.data.config.protocols) {
var protocol = mist.data.config.protocols[i];
var capa = mist.data.capabilities.connectors[protocol.connector];
$tbody.append(
$('').data('index',i).append(
$('').text(capa && capa.friendly ? capa.friendly : protocol.connector)
).append(
$(' ').html(UI.format.status(protocol))
).append(
$(' ').html(displaySettings(protocol))
).append(
$(' ').css('text-align','right').html(
$('').text('Edit').click(function(){
UI.navto('Edit Protocol',$(this).closest('tr').data('index'));
})
).append(
$('').text('Delete').click(function(){
var index = $(this).closest('tr').data('index');
if (confirm('Are you sure you want to delete the protocol "'+mist.data.config.protocols[index].connector+'"?')) {
mist.send(function(d){
UI.navto('Protocols');
},{deleteprotocol: mist.data.config.protocols[index]});
mist.data.config.protocols.splice(index,1);
}
})
)
)
);
}
}
updateProtocols();
UI.interval.set(function(){
mist.send(function(){
updateProtocols();
});
},10e3);
break;
case 'Edit Protocol':
if (typeof mist.data.capabilities == 'undefined') {
mist.send(function(d){
UI.navto(tab,other);
},{capabilities: true});
$main.append('Loading..');
return;
}
var editing = false;
if ((other != '') && (other >= 0)) { editing = true; }
var current = {};
for (var i in mist.data.config.protocols) {
current[mist.data.config.protocols[i].connector] = 1;
}
function buildProtocolSettings(kind) {
var input = mist.data.capabilities.connectors[kind];
var build = mist.convertBuildOptions(input,saveas);
if (editing) {
var orig = $.extend({},saveas);
}
build.push({
type: 'hidden',
pointer: {
main: saveas,
index: 'connector'
},
value: kind
});
build.push({
type: 'buttons',
buttons: [
{
type: 'save',
label: 'Save',
'function': function(){
var send = {};
if (editing) {
send.updateprotocol = [orig,saveas];
}
else {
send.addprotocol = saveas;
}
mist.send(function(d){
UI.navto('Protocols');
},send);
}
},{
type: 'cancel',
label: 'Cancel',
'function': function(){
UI.navto('Protocols');
}
}
]
});
if (('deps' in input) && (input.deps != '')) {
$t = $('').text('Dependencies:');
$ul = $('');
$t.append($ul);
if (typeof input.deps == 'string') { input.deps = input.deps.split(', '); }
for (var i in input.deps) {
var $li = $('').text(input.deps[i]+' ');
$ul.append($li);
if ((typeof current[input.deps[i]] != 'undefined') || (typeof current[input.deps[i]+'.exe'] != 'undefined')) {
//also check for the windows executable
$li.append(
$('').addClass('green').text('(Configured)')
);
}
else {
$li.append(
$('').addClass('red').text('(Not yet configured)')
);
}
}
build.unshift({
type: 'text',
text: $t[0].innerHTML
});
}
return UI.buildUI(build);
}
var current = {};
for (var i in mist.data.config.protocols) {
current[mist.data.config.protocols[i].connector] = 1;
}
if (!editing) {
//new
$main.html(
$('').text('New Protocol')
);
var saveas = {};
var select = [['','']];
for (var i in mist.data.capabilities.connectors) {
select.push([i,(mist.data.capabilities.connectors[i].friendly ? mist.data.capabilities.connectors[i].friendly : i)]);
}
var $cont = $('');
$main.append(UI.buildUI([{
label: 'Protocol',
type: 'select',
select: select,
'function': function(){
if ($(this).getval() == '') { return; }
$cont.html(buildProtocolSettings($(this).getval()));
}
}])).append(
$cont
);
}
else {
//editing
var protocol = mist.data.config.protocols[other];
var saveas = protocol;
$main.find('h2').append(' "'+protocol.connector+'"');
$main.append(buildProtocolSettings(protocol.connector));
}
break;
case 'Streams':
if (!('capabilities' in mist.data)) {
$main.html('Loading..');
mist.send(function(){
UI.navto(tab);
},{capabilities: true});
return;
}
var $switchmode = $('');
var $loading = $('').text('Loading..');
$main.append(
UI.buildUI([{
type: 'help',
help: 'Here you can create, edit or delete new and existing streams. Go to stream preview or embed a video player on your website.'
},
$('').css({
width: '45.25em',
display: 'flex',
'justify-content':'flex-end'
}).append(
$switchmode
).append(
$('
').text('Create a new stream').click(function(){
UI.navto('Edit');
})
)
])
).append($loading);
if (other == '') {
var s = mist.stored.get();
if ('viewmode' in s) {
other = s.viewmode;
}
}
$switchmode.text('Switch to '+(other == 'thumbnails' ? 'list' : 'thumbnail')+' view').click(function(){
mist.stored.set('viewmode',(other == 'thumbnails' ? 'list' : 'thumbnails'));
UI.navto('Streams',(other == 'thumbnails' ? 'list' : 'thumbnails'));
});
var allstreams = $.extend(true,{},mist.data.streams);
function createWcStreamObject(streamname,parent) {
var wcstream = $.extend({},parent);
delete wcstream.meta;
delete wcstream.error;
wcstream.online = 2; //should either be available (2) or active (1)
wcstream.name = streamname;
wcstream.ischild = true;
return wcstream;
}
function createPage(type,streams,folders) {
$loading.remove();
switch (type) {
case 'thumbnails': {
var $shortcuts = $('').addClass('preview_icons');
function selectastream(select,folders) {
folders = folders || [];
var saveas = {};
select.sort();
select.unshift('');
$loading.remove();
$main.append(
$('
').text(tab)
).append(UI.buildUI([
{
label: 'Filter the streams',
type: 'datalist',
datalist: select,
pointer: {
main: saveas,
index: 'stream'
},
help: 'If you type something here, the box below will only show streams with names that contain your text.',
'function': function(){
var val = $(this).val();
$shortcuts.children().each(function(){
$(this).hide();
if ($(this).attr('data-stream').indexOf(val) > -1) {
$(this).show();
}
});
}
}
]));
select.shift();
$main.append(
$('').addClass('description').text('Choose a stream below.')
).append($shortcuts);
//if there is a JPG output, add actual thumnails \o/
var thumbnails = false;
///\todo activate this code when the backend is ready
if (UI.findOutput('JPG')) {
var jpgport = false;
//find the http port and make sure JPG is enabled
for (var i in mist.data.config.protocols) {
var protocol = mist.data.config.protocols[i];
if ((protocol.connector == 'HTTP') || (protocol.connector == 'HTTP.exe')) {
jpgport = (protocol.port ? ':'+protocol.port : ':8080');
}
if ((protocol.connector == 'JPG') || (protocol.connector == 'JPG.exe')) {
thumbnails = true;
}
}
if ((thumbnails) && (jpgport)) {
//now we get to use it as a magical function wheee!
jpgport = parseURL(mist.user.host).host+jpgport;
thumbnails = function(streamname) {
return 'http://'+jpgport+'/'+encodeURIComponent(streamname)+'.jpg';
}
}
}
for (var i in select) {
var streamname = select[i];
var source = '';
var $delete = $('').text('Delete').click(function(){
var streamname = $(this).closest('div').attr('data-stream');
if (confirm('Are you sure you want to delete the stream "'+streamname+'"?')) {
delete mist.data.streams[streamname];
var send = {};
send.deletestream = [streamname];
mist.send(function(d){
UI.navto('Streams');
},send);
}
});
var $edit = $('').text('Settings').click(function(){
UI.navto('Edit',$(this).closest('div').attr('data-stream'));
});
var $preview = $('').text('Preview').click(function(){
UI.navto('Preview',$(this).closest('div').attr('data-stream'));
});
var $embed = $('').text('Embed').click(function(){
UI.navto('Embed',$(this).closest('div').attr('data-stream'));
});
var $image = $('').addClass('image');
if ((thumbnails) && (folders.indexOf(streamname) == -1)) {
//there is a JPG output and this isn't a folder
$image.append(
$(' ').attr('src',thumbnails(streamname)).error(function(){
$(this).hide();
})
);
}
//its a wildcard stream
if (streamname.indexOf('+') > -1) {
var streambits = streamname.split('+');
source = mist.data.streams[streambits[0]].source+streambits[1];
$delete = '';
$edit = '';
$image.addClass('wildcard');
}
else {
source = mist.data.streams[streamname].source;
//its a folder stream
if (folders.indexOf(streamname) > -1) {
$preview = '';
$embed = '';
$image.addClass('folder');
}
}
$shortcuts.append(
$('').append(
$('
').addClass('streamname').text(streamname)
).append(
$image
).append(
$('').addClass('description').text(source)
).append(
$('').addClass('button_container').append(
$edit
).append(
$delete
).append(
$preview
).append(
$embed
)
).attr('title',streamname).attr('data-stream',streamname)
);
}
}
selectastream(streams,folders);
break;
}
case 'list':
default: {
var $tbody = $('').append($('').append('').attr('colspan',6).text('Loading..'));
var $table = $('').html(
$('').html(
$('').html(
$('').text('Stream name').attr('data-sort-type','string').addClass('sorting-asc')
).append(
$(' ')
).append(
$(' ').text('Source').attr('data-sort-type','string')
).append(
$(' ').text('Status').attr('data-sort-type','int')
).append(
$(' ').css('text-align','right').text('Connections').attr('data-sort-type','int')
)
)
).append($tbody);
$main.append($table);
$table.stupidtable();
function buildStreamTable() {
var i = 0;
$tbody.html('');
streams.sort();
for (var s in streams) {
var streamname = streams[s];
var stream;
if (streamname in mist.data.streams) { stream = mist.data.streams[streamname]; }
else { stream = allstreams[streamname]; }
var $viewers = $(' ').css('text-align','right').html($('').addClass('description').text('Loading..'));
var v = 0;
if ((typeof mist.data.totals != 'undefined') && (typeof mist.data.totals[streamname] != 'undefined')) {
var data = mist.data.totals[streamname].all_protocols.clients;
var v = 0;
//get the average value
if (data.length) {
for (var i in data) {
v += data[i][1];
}
v = Math.round(v / data.length);
}
}
$viewers.html(UI.format.number(v));
if ((v == 0) && (stream.online == 1)) {
stream.online = 2;
}
var $buttons = $(' ').css('white-space','nowrap');
if ((!('ischild' in stream)) || (!stream.ischild)) {
$buttons.html(
$('').text('Settings').click(function(){
UI.navto('Edit',$(this).closest('tr').data('index'));
})
).append(
$('').text('Delete').click(function(){
var index = $(this).closest('tr').data('index');
if (confirm('Are you sure you want to delete the stream "'+index+'"?')) {
delete mist.data.streams[index];
var send = {};
if (mist.data.LTS) {
send.deletestream = [index];
}
else {
send.streams = mist.data.streams;
}
mist.send(function(d){
UI.navto('Streams');
},send);
}
})
);
}
var $streamnamelabel = $('').text(stream.name);
if (stream.ischild) {
$streamnamelabel.css('padding-left','1em');
}
var $online = UI.format.status(stream);
var $status = $("").text("Status").click(function(){
UI.navto('Status',$(this).closest('tr').data('index'));
});
var $preview = $('').text('Preview').click(function(){
UI.navto('Preview',$(this).closest('tr').data('index'));
});
var $embed = $('').text('Embed').css("margin-right","1em").click(function(){
UI.navto('Embed',$(this).closest('tr').data('index'));
});
if (('filesfound' in allstreams[streamname]) || (stream.online < 0)) {
$online.html('');
$viewers.html('');
$preview.css({opacity: 0, pointerEvents: "none"});
$embed.css({opacity: 0, pointerEvents: "none"});
}
$buttons.prepend($embed).prepend($status).prepend($preview);
$tbody.append(
$('').data('index',streamname).html(
$('').html($streamnamelabel).attr('title',(stream.name == "..." ? "The results were truncated" : stream.name)).addClass('overflow_ellipsis')
).append(
$buttons
).append(
$(' ').text(stream.source).attr('title',stream.source).addClass('description').addClass('overflow_ellipsis').css('max-width','20em')
).append(
$(' ').data('sort-value',stream.online).html($online)
).append(
$viewers
)
);
i++;
}
}
function updateStreams() {
var totals = [];
for (var i in mist.data.active_streams) {
totals.push({
streams: [mist.data.active_streams[i]],
fields: ['clients'],
start: -2
});
}
mist.send(function(){
$.extend(true,allstreams,mist.data.streams);
buildStreamTable();
},{
totals: totals,
active_streams: true
});
}
//insert folder streams
var browserequests = 0;
var browsecomplete = 0;
for (var s in mist.data.streams) {
var inputs_f = mist.data.capabilities.inputs.Folder || mist.data.capabilities.inputs['Folder.exe'];
if (!inputs_f) { break; }
if (mist.inputMatch(inputs_f.source_match,mist.data.streams[s].source)) {
//this is a folder stream
allstreams[s].source += '*';
allstreams[s].filesfound = null;
mist.send(function(d,opts){
var s = opts.stream;
var matches = 0;
outer:
for (var i in d.browse.files) {
inner:
for (var j in mist.data.capabilities.inputs) {
if ((j.indexOf('Buffer') >= 0) || (j.indexOf('Buffer.exe') >= 0) || (j.indexOf('Folder') >= 0) || (j.indexOf('Folder.exe') >= 0)) { continue; }
if (mist.inputMatch(mist.data.capabilities.inputs[j].source_match,'/'+d.browse.files[i])) {
var streamname = s+'+'+d.browse.files[i];
allstreams[streamname] = createWcStreamObject(streamname,mist.data.streams[s]);
allstreams[streamname].source = mist.data.streams[s].source+d.browse.files[i];
matches++;
if (matches >= 500) {
//stop retrieving more file names TODO properly display when this happens
allstreams[s+"+zzzzzzzzz"] = {
ischild: true,
name: "...",
online: -1
};
break outer;
}
}
}
}
if (('files' in d.browse) && (d.browse.files.length)) {
allstreams[s].filesfound = true;
}
else {
mist.data.streams[s].filesfound = false;
}
browsecomplete++;
if (browserequests == browsecomplete) {
mist.send(function(){
updateStreams();
},{active_streams: true});
UI.interval.set(function(){
updateStreams();
},5e3);
}
},{browse:mist.data.streams[s].source},{stream: s});
browserequests++;
}
}
if (browserequests == 0) {
mist.send(function(){
updateStreams();
},{active_streams: true});
UI.interval.set(function(){
updateStreams();
},5e3);
}
break;
}
}
}
//browse into folder streams
var browserequests = 0;
var browsecomplete = 0;
var select = {};
var folders = [];
for (var s in mist.data.streams) {
var inputs_f = mist.data.capabilities.inputs.Folder || mist.data.capabilities.inputs['Folder.exe'];
if (mist.inputMatch(inputs_f.source_match,mist.data.streams[s].source)) {
//this is a folder stream
folders.push(s);
mist.send(function(d,opts){
var s = opts.stream;
var matches = 0;
outer:
for (var i in d.browse.files) {
inner:
for (var j in mist.data.capabilities.inputs) {
if ((j.indexOf('Buffer') >= 0) || (j.indexOf('Folder') >= 0)) { continue; }
if (mist.inputMatch(mist.data.capabilities.inputs[j].source_match,'/'+d.browse.files[i])) {
select[s+'+'+d.browse.files[i]] = true;
matches++;
if (matches >= 500) {
//stop retrieving more file names
select[s+"+zzzzzzzzz"] = true;
break outer;
}
}
}
}
browsecomplete++;
if (browserequests == browsecomplete) {
mist.send(function(){
for (var i in mist.data.active_streams) {
var split = mist.data.active_streams[i].split('+');
if ((split.length > 1) && (split[0] in mist.data.streams)) {
select[mist.data.active_streams[i]] = true;
allstreams[mist.data.active_streams[i]] = createWcStreamObject(mist.data.active_streams[i],mist.data.streams[split[0]]);
}
}
select = Object.keys(select);
select = select.concat(Object.keys(mist.data.streams));
select.sort();
createPage(other,select,folders);
},{active_streams: true});
}
},{browse:mist.data.streams[s].source},{stream: s});
browserequests++;
}
}
if (browserequests == 0) {
mist.send(function(){
//var select = [];
for (var i in mist.data.active_streams) {
var split = mist.data.active_streams[i].split('+');
if ((split.length > 1) && (split[0] in mist.data.streams)) {
select[mist.data.active_streams[i]] = true;
allstreams[mist.data.active_streams[i]] = createWcStreamObject(mist.data.active_streams[i],mist.data.streams[split[0]]);
}
}
select = Object.keys(select);
if (mist.data.streams) { select = select.concat(Object.keys(mist.data.streams)); }
select.sort();
createPage(other,select);
},{active_streams: true});
}
break;
case 'Edit':
if (typeof mist.data.capabilities == 'undefined') {
mist.send(function(d){
UI.navto(tab,other);
},{capabilities: true});
$main.append('Loading..');
return;
}
var editing = false;
if (other != '') { editing = true; }
if (!editing) {
//new
$main.html(
$('').text('New Stream')
);
var saveas = {};
}
else {
//editing
var streamname = other.split("+")[0];
var saveas = mist.data.streams[streamname];
$main.find('h2').append(' "'+streamname+'"');
}
var filetypes = [];
var $source_datalist = $("").attr("id","source_datalist");
var $source_info = $("").addClass("source_info");
var dynamic_capa_rate_limit = false;
var dynamic_capa_source = false;
for (var i in mist.data.capabilities.inputs) {
for (var j in mist.data.capabilities.inputs[i].source_match) {
filetypes.push(mist.data.capabilities.inputs[i].source_match[j]);
$source_datalist.append(
$("
").val(mist.data.capabilities.inputs[i].source_match[j])
);
}
}
var $inputoptions = $('');
function save(tab) {
var send = {};
if (!mist.data.streams) {
mist.data.streams = {};
}
mist.data.streams[saveas.name] = saveas;
if (other != saveas.name) {
delete mist.data.streams[other];
}
send.addstream = {};
send.addstream[saveas.name] = saveas;
if (other != saveas.name) {
send.deletestream = [other];
}
if ((saveas.stop_sessions) && (other != '')) {
send.stop_sessions = other;
delete saveas.stop_sessions;
}
var type = null;
for (var i in mist.data.capabilities.inputs) {
if (typeof mist.data.capabilities.inputs[i].source_match == 'undefined') { continue; }
if (mist.inputMatch(mist.data.capabilities.inputs[i].source_match,saveas.source)) {
type = i;
break;
}
}
if (type) {
//sanatize saveas, remove options not in capabilities
var input = mist.data.capabilities.inputs[type];
for (var i in saveas) {
if ((i == "name") || (i == "source") || (i == "stop_sessions") || (i == "processes")) { continue; }
if (("optional" in input) && (i in input.optional)) { continue; }
if (("required" in input) && (i in input.required)) { continue; }
if ((i == "always_on") && ("always_match" in input) && (mist.inputMatch(input.always_match,saveas.source))) { continue; }
delete saveas[i];
}
}
mist.send(function(){
delete mist.data.streams[saveas.name].online;
delete mist.data.streams[saveas.name].error;
UI.navto(tab,(tab == 'Preview' ? saveas.name : ''));
},send);
}
var $style = $('"
);
tab.document.write($logs[0].innerHTML);
tab.document.scrollingElement.scrollTop = tab.document.scrollingElement.scrollHeight;
})
).append(
$logs
);
},
accesslogs: function(streamname){
var $accesslogs = $("
").attr("onempty","None.").addClass("accesslogs");
var tab = false;
UI.sockets.ws.active_streams.subscribe(function(type,data){
if (type == "access") {
var scroll = ($accesslogs[0].scrollTop >= $accesslogs[0].scrollHeight - $accesslogs[0].clientHeight); //scroll to bottom unless scrolled elsewhere
if (data[2] != "" && data[2] != streamname.split("+")[0]) { //filter out messages about other streams
return;
}
// [UNIX_TIMESTAMP, "session identifier", "stream name", "connector name", "hostname", SECONDS_ACTIVE, BYTES_UP_TOTAL, BYTES_DOWN_TOTAL, "tags"]
var $msg = $("
").html(
$("
").addClass("description").text(UI.format.dateTime(data[0]))
).append(
$("").attr("beforeifnotempty","Token: ").attr("title",data[1]).css("max-width","10em").text(data[1]) //session identifier
).append(
$("").attr("beforeifnotempty","Connector: ").text(data[3]) //connector name
).append(
$("").attr("beforeifnotempty","Hostname: ").text(data[4]) //host name
).append(
$("").attr("beforeifnotempty","Connected for: ").html(UI.format.duration(data[5])) //seconds active
).append(
$("").html("↑"+UI.format.bytes(data[6])) //bytes up
).append(
$("").html("↓"+UI.format.bytes(data[7])) //bytes down
).append(
$("").attr("beforeifnotempty","Tags: ").text(data[8]) //tags
);
$accesslogs.append($msg);
if (scroll) $accesslogs[0].scrollTop = $accesslogs[0].scrollHeight;
if (tab) {
try {
var scroll = (tab.document.scrollingElement.scrollTop >= tab.document.scrollingElement.scrollHeight - tab.document.scrollingElement.clientHeight);
tab.document.write($msg[0].outerHTML);
if (scroll) tab.document.scrollingElement.scrollTop = tab.document.scrollingElement.scrollHeight;
}
catch (e) {}
}
}
});
return $("").addClass("accesslogs").append(
$("").text("Access logs")
).append(
$("").text("Open raw").click(function(){
tab = window.open("", "MistServer access logs for "+streamname);
tab.document.write(
"MistServer access logs for '"+streamname+"' "
);
tab.document.write($accesslogs[0].innerHTML);
tab.document.scrollingElement.scrollTop = tab.document.scrollingElement.scrollHeight;
})
).append(
$accesslogs
);
},
preview: function(streamname,MistVideoObject){
var $preview_cont = $('').addClass("preview");
if (!MistVideoObject) {
window.mv = {};
MistVideoObject = mv;
}
UI.sockets.http.player(function(){
mistPlay(streamname,{
target: $preview_cont[0],
host: UI.sockets.http_host,
skin: "dev",
loop: true,
MistVideoObject: MistVideoObject
});
},function(e){
$preciew_cont.html(e);
});
return $preview_cont;
},
playercontrols: function(MistVideoObject,$video){
var $controls = $("").addClass("controls").addClass("mistvideo").html(
$("").text("MistPlayer")
).append(
$(" ").text("Waiting for player..")
);
if (!$video) { $video = $(".dashboard"); }
if (!$("link#devcss").length) {
document.head.appendChild(
$(" ").attr("rel","stylesheet").attr("type","text/css").attr("href",UI.sockets.http_host+"skins/dev.css").attr("id","devcss")[0]
);
}
function init() {
var MistVideo = MistVideoObject.reference;
function buildBlueprint(obj) {
return MistVideo.UI.buildStructure.call(MistVideo.UI,obj);
}
var name = buildBlueprint({
"if": function(){
return (this.playerName && this.source)
},
then: {
type: "container",
classes: ["mistvideo-description"],
style: { display: "block" },
children: [
{type: "playername", style: { display: "inline" }},
{type: "text", text: "is playing", style: {margin: "0 0.2em"}},
{type: "mimetype"}
]
}
});
var controls = buildBlueprint({
type: "container",
classes: ["mistvideo-column","mistvideo-devcontrols"],
children: [
{
type: "text",
text: "Player control"
},{
type: "container",
classes: ["mistvideo-devbuttons"],
style: {"flex-wrap": "wrap"},
children: [
{
"if": function(){ return !!(this.player && this.player.api); },
then: {
type: "button",
title: "Reload the video source",
label: "Reload video",
onclick: function(){
this.player.api.load();
}
}
},{
type: "button",
title: "Build MistVideo again",
label: "Reload player",
onclick: function(){
this.reload();
}
},{
type: "button",
title: "Switch to the next available player and source combination",
label: "Try next combination",
onclick: function(){
this.nextCombo();
}
}
]
},
{type:"forcePlayer"},
{type:"forceType"},
{type:"forceSource"}
]
});
var statistics = buildBlueprint({type:"decodingIssues", style: {"max-width":"30em","flex-flow":"column nowrap"}});
var logs = buildBlueprint({type:"log"});
var rawlogs = $("").text("Open raw").css({display:"block",marginTop:"0.333em"}).click(function(){
var streamname = MistVideo.stream;
tab = window.open("", "Player logs for "+streamname);
tab.document.write(
"Player logs for '"+streamname+"' "
);
tab.document.write(logs.lastChild.outerHTML);
tab.document.scrollingElement.scrollTop = tab.document.scrollingElement.scrollHeight;
});
logs.insertBefore(rawlogs[0],logs.children[0]);
$controls.html(
$("").append(
$("
").append(
$("
").addClass("title").text("MistPlayer")
).append(name)
).append(controls).append(statistics)
).append(logs);
}
if (MistVideoObject && MistVideoObject.reference && MistVideoObject.reference.skin) {
init();
}
$video[0].addEventListener("initialized",function(){ init(); });
$video[0].addEventListener("initializedFailed",function(){ init(); });
return $controls;
},
embedurls: function(streamname,misthost,urls){
var cont = $("").addClass("embedurls");
var $datalist = $("").attr("id","urlhints");
var allurls = urls.HTTPS.concat(urls.HTTP);
for (var i in allurls) {
$datalist.append(
$("").val(allurls[i])
);
}
var otherbase = otherhost.host ? otherhost.host : (allurls.length ? allurls[0] : misthost);
cont.append(
$('').addClass('input_container').append(
$('').addClass('UIelement').append(
$('').addClass('label').text('Use base URL:')
).append(
$('').addClass('field_container').append(
$(' ').attr('type','text').addClass('field').val(otherbase).attr("list","urlhints")
).append(
$datalist
).append(
$('').addClass('unit').append(
$('').text('Apply').click(function(){
otherhost.host = $(this).closest('label').find('input').val();
if (otherhost.host.slice(-1) != "/") { otherhost.host += "/"; }
UI.navto('Embed',streamname);
})
)
)
)
)
);
var escapedstream = encodeURIComponent(streamname);
var done = false;
var defaultembedoptions = {
forcePlayer: '',
forceType: '',
controls: true,
autoplay: true,
loop: false,
muted: false,
fillSpace: false,
poster: '',
urlappend: '',
setTracks: {}
};
var embedoptions = $.extend({},defaultembedoptions);
var stored = UI.stored.getOpts();
if ('embedoptions' in stored) {
embedoptions = $.extend(embedoptions,stored.embedoptions,true);
if (typeof embedoptions.setTracks != 'object') {
embedoptions.setTracks = {};
}
}
var custom = {};
switch (embedoptions.controls) {
case 'stock':
custom.controls = 'stock';
break;
case true:
custom.controls = 1;
break;
case false:
custom.controls = 0;
break;
}
function embedhtml() {
function randomstring(length){
var s = '';
function randomchar() {
var n= Math.floor(Math.random()*62);
if (n < 10) { return n; } //1-10
if (n < 36) { return String.fromCharCode(n+55); } //A-Z
return String.fromCharCode(n+61); //a-z
}
while (length--) { s += randomchar(); }
return s;
}
function maybequotes(val) {
switch (typeof val) {
case 'string':
if ($.isNumeric(val)) {
return val;
}
return '"'+val+'"';
case 'object':
return JSON.stringify(val);
default:
return val;
}
if (typeof val == 'string') {
return
}
}
if (done) {
UI.stored.saveOpt('embedoptions',embedoptions);
}
var target = streamname+'_'+randomstring(12);
var options = ['target: document.getElementById("'+target+'")'];
for (var i in embedoptions) {
if (i == "prioritize_type") {
if ((embedoptions[i]) && (embedoptions[i] != "")) {
options.push("forcePriority: "+JSON.stringify({source:[["type",[embedoptions[i]]]]}));
}
continue;
}
if (i == "monitor_action") {
if ((embedoptions[i]) && (embedoptions[i] != "")) {
if (embedoptions[i] == "nextCombo") {
options.push("monitor: {\n"+
" action: function(){\n"+
' this.MistVideo.log("Switching to nextCombo because of poor playback in "+this.MistVideo.source.type+" ("+Math.round(this.vars.score*1000)/10+"%)");'+"\n"+
" this.MistVideo.nextCombo();\n"+
" }\n"+
" }");
}
}
continue;
}
if ((embedoptions[i] != defaultembedoptions[i]) && (embedoptions[i] != null) && ((typeof embedoptions[i] != 'object') || (JSON.stringify(embedoptions[i]) != JSON.stringify(defaultembedoptions[i])))) {
options.push(i+': '+maybequotes(embedoptions[i]));
}
}
var output = [];
output.push('');
output.push(' ');
output.push('