jafea7-ctbrec-v5.3.0-based/server/src/main/resources/html/static/index.html

623 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-129283922-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'UA-129283922-1');
</script>
<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="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom fonts for this template -->
<link href="/static/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="/static/vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
<link href="/static/vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
<!-- Custom styles for this template -->
<link href="/static/freelancer.css" rel="stylesheet">
<!-- Flowplayer -->
<link rel="stylesheet" href="/static/vendor/flowplayer/skin/skin.css">
<!-- custom css -->
<link rel="stylesheet" href="/static/custom.css">
<link rel="shortcut icon" href="/static/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="/static/index.html"><img src="/static/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>
<!--
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#code">Source Code</a>
</li>
<li class="nav-item mx-0 mx-lg-1">
<a href="#donate" class="js-scroll-trigger" style="float:right; margin-left: 10px">
<img src="/static/button-red.png" alt="Buy a coffee" style="width:150px; padding-top: 7px"/>
</a>
</li>
-->
</ul>
</div>
</div>
</nav>
<!-- Header -
<header class="masthead bg-primary text-white text-center">
<div class="container">
<h1 class="text-uppercase mb-0">CTB Recorder</h1>
<hr class="star-light">
<h2 class="font-weight-light mb-0">A free recording software for different camsites.<br/>Currently supported: BongaCams, Cam4, CamSoda, Chaturbate, FC2Live, LiveJasmin, MyFreeCams, Streamate</h2>
</div>
</header>
-->
<div id="alert-container"></div>
<div class="tab-content" id="myTabContent">
<section id="models" class="tab-pane fade show active" role="tabpanel" aria-labelledby="models-tab">
<div class="container">
<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-10 mx-auto">
<p class="lead"></p>
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Model</th>
<th>Date</th>
<th>Status</th>
<th>Progress</th>
<th>Size</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: date"></td>
<td data-bind="text: status"></td>
<td data-bind="text: progressString"></td>
<td data-bind="text: size"></td>
<td>
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="click: play"></button>
</td>
<td>
<button class="btn btn-secondary fa fa-download" title="Download recording" data-bind="click: function() { $.notify('Not implemented, yet', 'info'); }"></button>
</td>
<td>
<button class="btn btn-secondary fa fa-trash" title="Delete recording" data-bind="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">
<h1>Coming soon</h1>
</section>
</div>
<!-- Footer -->
<!--
<footer class="footer text-center">
<div class="container">
<div class="row">
<div class="col-md-4 mb-5 mb-lg-0">
<h4 class="text-uppercase mb-4">Location</h4>
<p class="lead mb-0">2215 John Daniel Drive
<br>Clark, MO 65243</p>
</div>
<div class="col-md-4 mb-5 mb-lg-0">
<h4 class="text-uppercase mb-4">Around the Web</h4>
<ul class="list-inline mb-0">
<li class="list-inline-item">
<a class="btn btn-outline-light btn-social text-center rounded-circle" href="#">
<i class="fa fa-fw fa-facebook"></i>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-outline-light btn-social text-center rounded-circle" href="#">
<i class="fa fa-fw fa-google-plus"></i>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-outline-light btn-social text-center rounded-circle" href="#">
<i class="fa fa-fw fa-twitter"></i>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-outline-light btn-social text-center rounded-circle" href="#">
<i class="fa fa-fw fa-linkedin"></i>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-outline-light btn-social text-center rounded-circle" href="#">
<i class="fa fa-fw fa-dribbble"></i>
</a>
</li>
</ul>
</div>
<div class="col-md-4">
<h4 class="text-uppercase mb-4">About Freelancer</h4>
<p class="lead mb-0">Freelance is a free to use, open source Bootstrap theme created by
<a href="http://startbootstrap.com">Start Bootstrap</a>.</p>
</div>
</div>
</div>
</footer>
-->
<!-- Bootstrap core JavaScript -->
<script src="/static/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Plugin JavaScript -->
<script src="/static/vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="/static/vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
<script src="/static/vendor/notify.js/notify.min.js"></script>
<!-- knockout -->
<script src="/static/vendor/knockout/knockout-3.5.0.js"></script>
<script src="/static/vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
<!-- Custom scripts for this template -->
<script src="/static/freelancer.min.js"></script>
<div id="player-window" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<video id="player" preload="none" controls width="100%">
</video>
</div>
</div>
<!-- HLS MediaSource support -->
<script src="/static/vendor/hls.js/hls.js"></script>
<script type="text/javascript">
</script>
<script>
let onlineModels = [];
let observableModelsArray = ko.observableArray();
let observableRecordingsArray = ko.observableArray();
let ctbrec = {
resume: function(model) {
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "resume", "model": ' + JSON.stringify(model) + '}',
success : function(data) {
if (data.status === 'success') {
$.notify('Recording of ' + model.name + ' resumed', 'info');
} else {
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Resuming recording of model ' + model.name + ' failed', 'error');
}
});
} catch (e) {
console.log(e);
}
},
suspend: function(model) {
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "suspend", "model": ' + JSON.stringify(model) + '}',
success : function(data) {
if (data.status === 'success') {
$.notify('Recording of ' + model.name + ' suspended', 'info');
} else {
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Suspending recording of model ' + model.name + ' failed', 'error');
}
});
} catch (e) {
console.log(e);
}
},
stop: function(model) {
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "stop", "model": ' + JSON.stringify(model) + '}',
success : function(data) {
if (data.status === 'success') {
$.notify('Removed ' + model.name, 'info');
observableModelsArray.remove(model);
} else {
$.notify('Removing model ' + model.name + ' failed', 'error');
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Removing model ' + model.name + ' failed', 'error');
}
});
} catch (e) {
console.log(e);
}
},
deleteRecording: function(recording) {
let name = recording.model.name + ' ' + recording.date;
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}',
success : function(data) {
if (data.status === 'success') {
$.notify('Removed recording ' + name, 'info');
observableRecordingsArray.remove(recording);
} else {
$.notify('Removing recording ' + name + ' failed', 'error');
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Removing recording ' + name + ' failed', 'error');
}
});
} catch (e) {
console.log(e);
}
}
};
function play(recording) {
if (Hls.isSupported()) {
var video = document.getElementById('player');
var hls = new Hls();
hls.attachMedia(video);
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
hls.loadSource(recording.playlist);
hls.on(Hls.Events.MANIFEST_PARSED, function (event, 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');
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() {
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
});
function updateOnlineModels() {
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "listOnline"}',
success : function(data) {
if (data.status === 'success') {
onlineModels = data.models;
} else {
console.log('request failed', data);
}
updateModels();
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
} catch (e) {
console.log(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)) {
console.log('remove', 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 {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "list"}',
success : function(data) {
if (data.status === 'success') {
syncModels(data.models);
} else {
console.log('request failed', data);
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
} catch (e) {
console.log(e);
}
}
function updateRecordings() {
try {
$.ajax({
type : 'POST',
url : '/rec',
dataType : 'json',
async : true,
timeout : 60000,
data : '{"action": "recordings"}',
success : function(data) {
if (data.status === 'success') {
observableRecordingsArray.removeAll();
for ( let idx in data.recordings) {
let recording = data.recordings[idx];
recording.date = new Date(recording.startDate).toLocaleString('default', {
'day': '2-digit',
'month': '2-digit',
'year': 'numeric',
'hour': '2-digit',
'minute': '2-digit',
'second': '2-digit'
});
recording.progressString = recording.progress === -1 ? '' : recording.progress;
recording.size = calculateSize(recording.sizeInByte);
recording.playlist = '/hls' + recording.path + '/playlist.m3u8';
observableRecordingsArray.push(recording);
}
} else {
console.log('request failed', data);
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
}
});
} catch (e) {
console.log(e);
}
setTimeout(updateRecordings, 60000);
}
updateOnlineModels();
updateRecordings();
});
</script>
<script src="/static/modal.js"></script>
</body>
</html>