772 lines
25 KiB
HTML
772 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<meta name="description" content="CTB Recorder is a free recording software for Chaturbate">
|
|
<meta name="author" content="">
|
|
|
|
<title>CTB Recorder</title>
|
|
|
|
<!-- Bootstrap core CSS -->
|
|
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
|
|
|
<!-- Custom fonts for this template -->
|
|
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Plugin CSS -->
|
|
<link href="vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
|
|
<link href="vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Custom styles for this template -->
|
|
<link href="freelancer.css" rel="stylesheet">
|
|
|
|
<!-- Flowplayer -->
|
|
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.css">
|
|
|
|
<!-- custom css -->
|
|
<link rel="stylesheet" href="custom.css">
|
|
|
|
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
|
|
|
|
<style>
|
|
.ui-front {
|
|
z-index: 2000;
|
|
}
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body id="page-top">
|
|
<!-- Navigation -->
|
|
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
|
|
<div class="container">
|
|
<a class="navbar-brand js-scroll-trigger" href="index.html"><img src="icon64.png" alt="Logo" />CTBREC</a>
|
|
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
|
|
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
|
Menu <i class="fa fa-bars"></i>
|
|
</button>
|
|
|
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
|
<ul class="navbar-nav nav ml-auto" role="tablist">
|
|
<!--
|
|
<li class="nav-item mx-0 mx-lg-1">
|
|
<div class="ui-widget">
|
|
<label for="search">Search: </label>
|
|
<input id="search" style="margin-top: .7rem" placeholder="Search">
|
|
</div>
|
|
<div class="ui-widget"><input id="search" class="ui-autocomplete-input" autocomplete="off" placeholder="Search" style="margin-top: .7rem"></div>
|
|
</li>
|
|
-->
|
|
<li class="nav-item mx-0 mx-lg-1"><a id="models-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded active" href="#models" role="tab"
|
|
aria-controls="models" aria-selected="true">Models</a></li>
|
|
<li class="nav-item mx-0 mx-lg-1"><a id="recordings-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#recordings" role="tab"
|
|
aria-controls="profile" aria-selected="false">Recordings</a></li>
|
|
<li class="nav-item mx-0 mx-lg-1"><a id="configuration-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#configuration" role="tab"
|
|
aria-controls="profile" aria-selected="false">Settings</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div id="alert-container"></div>
|
|
|
|
<div class="tab-content" id="myTabContent">
|
|
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<div class="form-group">
|
|
<label for="addModelByUrl">Add Model</label>
|
|
<input type="text" class="form-control" id="addModelByUrl" placeholder="URL"
|
|
onKeyUp="addModelKeyPressed(event)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<p class="lead"></p>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_name'}">Model</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_suspended'}">Paused</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_online'}">Online</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'models', field: 'ko_recording'}">Recording</a></th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-bind="foreach: models">
|
|
<tr>
|
|
<td><a data-bind="attr: { href: ko_url, title: ko_name }, text: ko_name"></a></td>
|
|
<td><input type="checkbox" data-bind="checked: ko_suspended" /></td>
|
|
<td><input type="checkbox" disabled data-bind="checked: ko_online" /></td>
|
|
<td><input type="checkbox" disabled data-bind="checked: ko_recording" /></td>
|
|
<td><button class="btn btn-secondary fa fa-minus-circle" title="Stop recording" data-bind="click: ctbrec.stop"></button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="recordings" class="tab-pane fade" role="tabpanel" aria-labelledby="recordings-tab">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-4 mx-auto">
|
|
Space left: <span data-bind="text: space.text()"></span>
|
|
<div class="progress">
|
|
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuemin="0"
|
|
data-bind="attr: {'aria-valuemax': space.total, 'aria-valuenow': space.free},
|
|
style: { width: space.percent() + '%' },
|
|
text: space.percent() + '%'">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<p class="lead"></p>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'progress'}">Progress</a></th>
|
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a></th>
|
|
<th></th>
|
|
<th></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-bind="foreach: recordings">
|
|
<tr>
|
|
<td><a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a></td>
|
|
<td data-bind="text: ko_date"></td>
|
|
<td data-bind="text: ko_status"></td>
|
|
<td data-bind="text: ko_progressString"></td>
|
|
<td data-bind="text: ko_size"></td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="enable: ko_status() == 'FINISHED', click: play"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-download" title="Download recording" data-bind="enable: ko_status() == 'FINISHED' && singleFile, click: function() { $.notify('Not implemented, yet', 'info'); }"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-trash" title="Delete recording" data-bind="enable: ko_status() == 'FINISHED', click: ctbrec.deleteRecording"></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="configuration" class="tab-pane fade" role="tabpanel" aria-labelledby="configuration-tab">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<h3>Not implemented, yet!</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Bootstrap core JavaScript -->
|
|
<script src="vendor/jquery/jquery.min.js"></script>
|
|
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- Plugin JavaScript -->
|
|
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
|
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
|
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
|
<script src="vendor/notify.js/notify.min.js"></script>
|
|
|
|
<!-- knockout -->
|
|
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
|
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
|
|
|
|
|
<!-- Custom scripts for this template -->
|
|
<script src="freelancer.min.js"></script>
|
|
|
|
<div id="player-window" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close">×</span>
|
|
<video id="player" preload="none" controls width="100%">
|
|
</video>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HLS MediaSource support -->
|
|
<script src="vendor/hls.js/hls.js"></script>
|
|
|
|
<!-- CryptoJS for HMAc authentication -->
|
|
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
|
|
|
<script>
|
|
let onlineModels = [];
|
|
let observableModelsArray = ko.observableArray();
|
|
let observableRecordingsArray = ko.observableArray();
|
|
let space = {
|
|
free: ko.observable(0),
|
|
total: ko.observable(0),
|
|
percent: ko.observable(0),
|
|
text: ko.observable('')
|
|
};
|
|
let hmac;
|
|
|
|
function addModelKeyPressed(e) {
|
|
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
|
|
if(charCode === 13) {
|
|
let url = $('#addModelByUrl').val();
|
|
ctbrec.add(url, function() {
|
|
$('#addModelByUrl').val('');
|
|
});
|
|
}
|
|
}
|
|
|
|
let ctbrec = {
|
|
add: function(modelUrl, onsuccess) {
|
|
try {
|
|
let model = {
|
|
type: null,
|
|
name: '',
|
|
url: modelUrl
|
|
};
|
|
if(console) console.log(model);
|
|
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
onsuccess.call(data);
|
|
$.notify('Model added', 'info');
|
|
} else {
|
|
$.notify('Adding model failed', 'error');
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Adding model failed', 'error');
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
},
|
|
|
|
resume: function(model) {
|
|
try {
|
|
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
$.notify('Recording of ' + model.name + ' resumed', 'info');
|
|
} else {
|
|
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
},
|
|
|
|
suspend: function(model) {
|
|
try {
|
|
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
$.notify('Recording of ' + model.name + ' suspended', 'info');
|
|
} else {
|
|
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
},
|
|
|
|
stop: function(model) {
|
|
try {
|
|
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
$.notify('Removed ' + model.name, 'info');
|
|
observableModelsArray.remove(model);
|
|
} else {
|
|
$.notify('Removing model ' + model.name + ' failed', 'error');
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Removing model ' + model.name + ' failed', 'error');
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
},
|
|
|
|
deleteRecording: function(recording) {
|
|
let name = recording.model.name + ' ' + recording.ko_date();
|
|
try {
|
|
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
$.notify('Removed recording ' + name, 'info');
|
|
observableRecordingsArray.remove(recording);
|
|
} else {
|
|
$.notify('Removing recording ' + name + ' failed', 'error');
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Removing recording ' + name + ' failed', 'error');
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
}
|
|
};
|
|
|
|
function play(recording) {
|
|
let src = recording.singleFile ? '/hls' + recording.path : recording.playlist;
|
|
console.log("####################", src);
|
|
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
|
|
src = '..' + src;
|
|
if(console) console.log("Path", src, "HMAC", hmacOfPath);
|
|
if (hmac.length > 0) {
|
|
src += "?hmac=" + hmacOfPath;
|
|
}
|
|
|
|
if(console) console.log('Playing video from', src);
|
|
|
|
if(recording.singleFile) {
|
|
var video = document.getElementById('player');
|
|
video.src = src;
|
|
video.load();
|
|
console.log(video);
|
|
video.oncanplay = function() {
|
|
video.height = window.innerHeight * 0.85;
|
|
$('#player-window').css('display', 'block');
|
|
video.play();
|
|
};
|
|
video.onerror = function() {
|
|
$.notify(video.error.message, 'error');
|
|
};
|
|
} else {
|
|
if (Hls.isSupported()) {
|
|
var video = document.getElementById('player');
|
|
var hls = new Hls();
|
|
hls.attachMedia(video);
|
|
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
|
if(console) console.log(src);
|
|
hls.loadSource(src);
|
|
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
|
if(console) {
|
|
console.log(data);
|
|
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
|
}
|
|
$('#player-window').css('display', 'block');
|
|
video.play();
|
|
});
|
|
|
|
});
|
|
} else {
|
|
$.notify('Loading HLS video streaming support failed', 'error');
|
|
if(console) console.log('HLS is not supported');
|
|
}
|
|
}
|
|
}
|
|
|
|
function calculateSize(sizeInByte) {
|
|
let size = sizeInByte;
|
|
let unit = "Bytes";
|
|
if(size > 1024.0 * 1024 * 1024) {
|
|
size = size / 1024.0 / 1024 / 1024;
|
|
unit = "GiB";
|
|
} else if(size > 1024.0 * 1024) {
|
|
size = size / 1024.0 / 1024;
|
|
unit = "MiB";
|
|
} else if(size > 1024.0) {
|
|
size = size / 1024.0;
|
|
unit = "KiB";
|
|
}
|
|
return size.toFixed(2) + ' ' + unit;
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
|
if(console) console.log('using hmac from local storage');
|
|
hmac = localStorage.hmac;
|
|
} else {
|
|
if(console) console.log('hmac not found in local storage. requesting hmac from server');
|
|
$.ajax({
|
|
type : 'GET',
|
|
url : '../secured/hmac',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000
|
|
})
|
|
.done(function(data) {
|
|
hmac = data.hmac;
|
|
if (localStorage !== undefined) {
|
|
if(console) console.log('saving hmac to local storage');
|
|
localStorage.setItem("hmac", hmac);
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
$.notify('Couldn\'t get HMAC', 'error');
|
|
hmac = '';
|
|
});
|
|
}
|
|
|
|
function tab(id) {
|
|
$(id).css('display: block');
|
|
}
|
|
|
|
var navMain = $("#mainNav");
|
|
navMain.on("click", "a", null, function () {
|
|
navMain.collapse('hide');
|
|
$('#navbarResponsive').collapse('hide');
|
|
});
|
|
|
|
ko.applyBindings({
|
|
models : observableModelsArray,
|
|
recordings: observableRecordingsArray,
|
|
space
|
|
});
|
|
|
|
function updateOnlineModels() {
|
|
try {
|
|
let action = '{"action": "listOnline"}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data, textStatus, jqXHR) {
|
|
if (data.status === 'success') {
|
|
onlineModels = data.models;
|
|
} else {
|
|
if(console) console.log('request failed', data);
|
|
}
|
|
updateModels();
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(jqXHR, textStatus, errorThrown);
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
setTimeout(updateOnlineModels, 3000);
|
|
}
|
|
|
|
function isModelInArray(array, model) {
|
|
for (let idx in array) {
|
|
let m = array[idx];
|
|
if(m.url === model.url) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Synchronizes models from the server with the displayed
|
|
* knockout model table
|
|
*/
|
|
function syncModels(models) {
|
|
// remove models from the observable array, which are not in the
|
|
// updated list
|
|
for (let idx in observableModelsArray()) {
|
|
let model = observableModelsArray()[idx];
|
|
if(!isModelInArray(models, model)) {
|
|
observableModelsArray.remove(model);
|
|
}
|
|
}
|
|
|
|
// add models to the observable array, which are new in the
|
|
// updated list
|
|
for (let idx in models) {
|
|
let model = models[idx];
|
|
if (!isModelInArray(observableModelsArray(), model)) {
|
|
model.ko_name = ko.observable(model.name);
|
|
model.ko_url = ko.observable(model.url);
|
|
model.ko_online = ko.observable(false);
|
|
for ( let i in onlineModels) {
|
|
let onlineModel = onlineModels[i];
|
|
if (onlineModel.url === model.url) {
|
|
model.ko_online(true);
|
|
break;
|
|
}
|
|
}
|
|
model.ko_recording = ko.observable(model.online && !model.suspended);
|
|
model.ko_suspended = ko.observable(model.suspended);
|
|
model.swallowEvents = false;
|
|
model.ko_suspended.subscribe(function(checked) {
|
|
if (model.swallowEvents) {
|
|
return;
|
|
}
|
|
if (!checked) {
|
|
ctbrec.resume(model);
|
|
} else {
|
|
ctbrec.suspend(model);
|
|
}
|
|
});
|
|
observableModelsArray.push(model);
|
|
}
|
|
}
|
|
|
|
// update existing models
|
|
for (let i in models) {
|
|
let model = models[i];
|
|
for (let j in observableModelsArray()) {
|
|
let m = observableModelsArray()[j];
|
|
if(model.url === m.ko_url()) {
|
|
m.ko_name(model.name);
|
|
m.ko_url(model.url);
|
|
let onlineState = false;
|
|
for ( let i in onlineModels) {
|
|
let onlineModel = onlineModels[i];
|
|
if (onlineModel.url === model.url) {
|
|
onlineState = true;
|
|
break;
|
|
}
|
|
}
|
|
m.ko_online(onlineState);
|
|
m.swallowEvents = true;
|
|
m.ko_suspended(model.suspended);
|
|
m.swallowEvents = false;
|
|
m.ko_recording(m.ko_online() && !m.ko_suspended());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateModels() {
|
|
try {
|
|
let action = '{"action": "list"}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
syncModels(data.models);
|
|
} else {
|
|
if(console) console.log('request failed', data);
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
}
|
|
|
|
function isRecordingInArray(array, recording) {
|
|
for (let idx in array) {
|
|
let r = array[idx];
|
|
if(r.path === recording.path) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Synchronizes recordings from the server with the displayed
|
|
* knockout recordings table
|
|
*/
|
|
function syncRecordings(recordings) {
|
|
// remove recordings from the observable array, which are not in the
|
|
// updated list
|
|
for (let idx in observableRecordingsArray()) {
|
|
let recording = observableRecordingsArray()[idx];
|
|
if(!isRecordingInArray(recordings, recording)) {
|
|
observableRecordingsArray.remove(recording);
|
|
}
|
|
}
|
|
|
|
// add recordings to the observable array, which are new in the
|
|
// updated list
|
|
for (let idx in recordings) {
|
|
let recording = recordings[idx];
|
|
if (!isRecordingInArray(observableRecordingsArray(), recording)) {
|
|
recording.ko_date = ko.computed(function() {
|
|
return new Date(recording.startDate).toLocaleString('default', {
|
|
'day': '2-digit',
|
|
'month': '2-digit',
|
|
'year': 'numeric',
|
|
'hour': '2-digit',
|
|
'minute': '2-digit',
|
|
'second': '2-digit'
|
|
})
|
|
});
|
|
recording.ko_progressString = ko.observable(recording.progress === -1 ? '' : recording.progress);
|
|
recording.ko_size = ko.observable(calculateSize(recording.sizeInByte));
|
|
recording.ko_status = ko.observable(recording.status);
|
|
if (recording.path.endsWith('.mp4')) {
|
|
recording.playlist = '/hls' + recording.path;
|
|
} else {
|
|
recording.playlist = '/hls' + recording.path + '/playlist.m3u8';
|
|
}
|
|
observableRecordingsArray.push(recording);
|
|
}
|
|
}
|
|
|
|
// update existing recordings
|
|
for (let i in recordings) {
|
|
let recording = recordings[i];
|
|
for (let j in observableRecordingsArray()) {
|
|
let r = observableRecordingsArray()[j];
|
|
if(recording.path === r.path) {
|
|
r.progress = recording.progress;
|
|
r.sizeInByte = recording.sizeInByte;
|
|
r.status = recording.status;
|
|
r.startDate = recording.startDate;
|
|
r.ko_progressString(recording.progress === -1 ? '' : (recording.progress + '%'));
|
|
r.ko_size(calculateSize(recording.sizeInByte));
|
|
r.ko_status(recording.status);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateRecordings() {
|
|
try {
|
|
let action = '{"action": "recordings"}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
syncRecordings(data.recordings);
|
|
updateDiskSpace();
|
|
} else {
|
|
if(console) console.log('request failed', data);
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
});
|
|
} catch (e) {
|
|
if(console) console.log('Unexpected error', e);
|
|
}
|
|
setTimeout(updateRecordings, 3000);
|
|
}
|
|
|
|
function updateDiskSpace() {
|
|
let action = '{"action": "space"}';
|
|
$.ajax({
|
|
type : 'POST',
|
|
url : '../rec',
|
|
dataType : 'json',
|
|
async : true,
|
|
timeout : 60000,
|
|
headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
|
data : action
|
|
})
|
|
.done(function(data) {
|
|
if (data.status === 'success') {
|
|
space.total(data.spaceTotal);
|
|
space.free(data.spaceFree);
|
|
space.percent( (data.spaceFree/data.spaceTotal*100).toFixed(2) );
|
|
space.text(calculateSize(data.spaceFree) + ' / ' + calculateSize(data.spaceTotal));
|
|
} else {
|
|
if(console) console.log('request failed', data);
|
|
}
|
|
})
|
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
|
if(console) console.log(textStatus, errorThrown);
|
|
});
|
|
}
|
|
|
|
updateOnlineModels();
|
|
updateRecordings();
|
|
});
|
|
</script>
|
|
<script src="modal.js"></script>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
|