Initial commit
This commit is contained in:
79
static/index.html
Normal file
79
static/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/dt-1.10.18/datatables.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="main.css"/>
|
||||
<title>µgrep</title>
|
||||
</head>
|
||||
<body>
|
||||
<audio id="player" style="display:none;"></audio>
|
||||
<div class="overflow-auto" style="display: block; padding: 4px; position: absolute; bottom: 50%; height: 50%; top: 0; width: 100%;">
|
||||
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||
<div class="btn-group mr-2" role="group" aria-label="First group">
|
||||
<input class="form-control" type="text" placeholder="Search" aria-label="Search" id="search">
|
||||
<button id="queue-selection" type="button" class="btn btn-primary" title="Queue selected items"><i class="fa fa-plus"></i></button>
|
||||
<button id="queue-all" type="button" class="btn btn-primary" title="Queue all search results"><i class="fa fa-cart-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="top:3em; bottom:0; left:0; right:0; position: absolute; padding:4px;">
|
||||
<table id="results" class="table table-striped table-bordered pageResize display" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th>Artist</th>
|
||||
<th>Album</th>
|
||||
<th>#</th>
|
||||
<th>Title</th>
|
||||
<th>Duration</th>
|
||||
<th>MIME</th>
|
||||
<th>Bitrate</th>
|
||||
<th>Quality</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto" style="display: block; padding: 4px; position: absolute; top: 50%; bottom: 0; width: 100%;">
|
||||
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||
<div class="btn-group mr-2" role="group" aria-label="First group">
|
||||
<button id="playback-prev" type="button" class="btn btn-primary"><i class="fa fa-step-backward"></i></button>
|
||||
<button type="button" class="btn btn-primary"><i class="fa fa-stop"></i></button>
|
||||
<button type="button" class="btn btn-primary"><i class="fa fa-pause"></i></button>
|
||||
<button type="button" class="btn btn-primary"><i class="fa fa-play"></i></button>
|
||||
<button id="playback-next" type="button" class="btn btn-primary"><i class="fa fa-step-forward"></i></button>
|
||||
</div>
|
||||
<div class="btn-group mr-2" role="group" aria-label="Second group">
|
||||
<button type="button" class="btn btn-secondary" data-toggle="button" title="Repeat"><i class="fa fa-redo"></i></button>
|
||||
<button type="button" class="btn btn-secondary" data-toggle="button" title="Play in random order"><i class="fa fa-random"></i></button>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Third group">
|
||||
<button type="button" class="btn btn-secondary" title="Clear queue" id="queue-clear"><i class="fa fa-trash"></i></button>
|
||||
<button type="button" class="btn btn-secondary" data-toggle="button" title="Always transcode to save bandwidth"><i class="fa fa-piggy-bank"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" min="1" max="100" value="50" class="slider" id="seek" style="width:100%;">
|
||||
<div style="top:6em; bottom:0; left:0; right:0; position: absolute; padding:0;">
|
||||
<table id="queue" class="table table-striped table-bordered pageResize" style="width:100%;">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/v/dt/dt-1.10.18/datatables.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/1.5.6/js/dataTables.buttons.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.datatables.net/select/1.3.0/js/dataTables.select.min.js"></script>
|
||||
<script src="//cdn.datatables.net/plug-ins/1.10.19/features/pageResize/dataTables.pageResize.min.js" type="text/javascript"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
30
static/main.css
Normal file
30
static/main.css
Normal file
@@ -0,0 +1,30 @@
|
||||
.maximize {
|
||||
position:absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
bottom:0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
font-family: sans;
|
||||
}
|
||||
|
||||
|
||||
|
||||
table td, table th {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11pt;
|
||||
padding: 3pt 7pt !important;
|
||||
}
|
||||
|
||||
div.dataTables_length {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table {
|
||||
cursor: pointer;
|
||||
}
|
||||
229
static/main.js
Normal file
229
static/main.js
Normal file
@@ -0,0 +1,229 @@
|
||||
|
||||
// Probe for supported codecs
|
||||
AUDIO_CODECS = [
|
||||
'audio/ogg',
|
||||
'audio/mpeg',
|
||||
'audio/flac',
|
||||
];
|
||||
|
||||
timeout = null;
|
||||
es = null;
|
||||
prev = null;
|
||||
|
||||
function humanize_duration(d) {
|
||||
var seconds = Math.floor(d % 60);
|
||||
var minutes = Math.floor(d / 60) % 60;
|
||||
var hours = Math.floor(d / 3600);
|
||||
if (seconds < 10) seconds = '0' + seconds;
|
||||
if (minutes < 10) minutes = '0' + minutes;
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
}
|
||||
|
||||
function onDelayedSearch() {
|
||||
var expr = $("#search").val();
|
||||
if (expr == window.expr) return;
|
||||
console.info("Searching", expr);
|
||||
results.rows().clear().draw(false);
|
||||
if (es) {
|
||||
es.close();
|
||||
$("#results tbody").empty();
|
||||
}
|
||||
|
||||
es = new EventSource("/api/search/?expr=" + expr);
|
||||
|
||||
// Close socket event listener
|
||||
es.addEventListener("close", function(e) {
|
||||
console.info("Closing search socket");
|
||||
});
|
||||
|
||||
// Open socket event listener
|
||||
es.addEventListener("open", function(e) {
|
||||
console.info("Opened event stream for search expression", expr);
|
||||
})
|
||||
|
||||
// Search end result marker
|
||||
es.addEventListener("done", function(e) {
|
||||
var totals = JSON.parse(e.data)
|
||||
console.info("Finished searching, total", totals.relevant_files, "relevant files and", totals.hits, "hits");
|
||||
es.close();
|
||||
})
|
||||
|
||||
// Keep track of relative path of the XSPF blobs
|
||||
es.addEventListener("chdir", function(e) {
|
||||
console.info("Moving to directory:", e.data);
|
||||
window.relpath = e.data;
|
||||
|
||||
})
|
||||
|
||||
// If XSPF blob is received append it to search results
|
||||
es.addEventListener("xspf", function(e) {
|
||||
var $trackMetadatas = $("playlist trackList track", $.parseXML(e.data));
|
||||
$trackMetadatas.each(function(index, element) {
|
||||
|
||||
var location = element.getElementsByTagName("location")[0];
|
||||
var filename = location.innerHTML;
|
||||
|
||||
// Decode escaped XML
|
||||
var filename = decodeURIComponent(location.innerHTML);
|
||||
if (filename.indexOf("/") >= 0) {
|
||||
filename = filename.substring(filename.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
// Escape quotes for jQuery
|
||||
filename = filename.replace("\"", "\\\"");
|
||||
results.row.add([
|
||||
window.relpath + "/" + filename,
|
||||
$("creator", element).html() || "-",
|
||||
$("album", element).html() || "-",
|
||||
$("trackNum", element).html() || "-",
|
||||
$("title", element).html() || "-",
|
||||
humanize_duration(parseInt($("duration", element).html())/1000),
|
||||
$("meta[rel='mimetype']", element).html(),
|
||||
Math.floor(parseInt($("meta[rel='bitrate']", element).html())/1000)+"kbps",
|
||||
// $("meta[rel='bit_depth']", element).html() + "bit @ " + $("meta[rel='sample_rate']", element).html(),
|
||||
"bla"
|
||||
]).on('click', 'tr', function () {
|
||||
$(this).toggleClass('selected');
|
||||
});
|
||||
clearTimeout(window.timeoutDraw);
|
||||
window.timeoutDraw = setTimeout(results.draw, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function playNextAudioTrack() {
|
||||
queue.row(queue.row({selected:true}).index()+1).select();
|
||||
}
|
||||
|
||||
|
||||
function playPrevAudioTrack() {
|
||||
queue.row(queue.row({selected:true}).index()-1).select();
|
||||
}
|
||||
|
||||
|
||||
function playTrack(url, mimetype) {
|
||||
var url = "/api/stream/" + url;
|
||||
console.info("Playing:", url, mimetype);
|
||||
|
||||
$("#seek").val(0);
|
||||
$("#offset").html("00:00:00");
|
||||
$("#player").unbind("ended").bind("ended", function () {
|
||||
playNextAudioTrack();
|
||||
});
|
||||
$("#player").empty();
|
||||
|
||||
if (window.AUDIO_CODECS_SUPPORTED.indexOf(mimetype) >= 0) {
|
||||
$("#player").append("<source src=\"" + url + "\" type=\"" + mimetype + "\"/>");
|
||||
} else {
|
||||
console.info("Codec", mimetype, "not supported by browser");
|
||||
}
|
||||
|
||||
for (var j = 0; j < window.AUDIO_CODECS_SUPPORTED.length; j++) {
|
||||
var alternative = window.AUDIO_CODECS_SUPPORTED[j];
|
||||
if (mimetype != alternative) {
|
||||
$("#player").append("<source src=\"/transcode/?mimetype=" + alternative + "&url=" + url.replace("&", "%26") + "\" type=\"" + alternative + "\"/>");
|
||||
}
|
||||
}
|
||||
$("#player").trigger("load");
|
||||
$("#player").trigger("play");
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#results tbody').on('dblclick', 'tr', function () {
|
||||
console.info("Got double click");
|
||||
queue.rows.add(results.rows({ selected: true }).data());
|
||||
queue.rows.add([results.row(this).data()]);
|
||||
results.rows().deselect();
|
||||
results.draw(false);
|
||||
queue.draw(false);
|
||||
});
|
||||
|
||||
$("#playback-next").on("click", function() {
|
||||
playNextAudioTrack();
|
||||
});
|
||||
$("#playback-prev").on("click", function() {
|
||||
playPrevAudioTrack();
|
||||
});
|
||||
$("#queue-selection").on("click", function() {
|
||||
queue.rows.add(results.rows({ selected: true }).data()).draw(false);
|
||||
});
|
||||
$("#queue-all").on("click", function() {
|
||||
queue.rows.add(results.data()).draw(false);
|
||||
});
|
||||
$("#queue thead").html($("#results thead").html());
|
||||
$("#queue-clear").on("click", function() {
|
||||
queue.rows().clear().draw(false);
|
||||
});
|
||||
|
||||
window.results = $('#results').DataTable({
|
||||
autoWidth: false,
|
||||
columns: [
|
||||
null,
|
||||
{width:"20%"},
|
||||
{width:"20%"},
|
||||
{width:"1em"},
|
||||
{width:"20%"},
|
||||
],
|
||||
|
||||
paging: true,
|
||||
ordering: false,
|
||||
searching: false,
|
||||
select: {
|
||||
style: 'multi'
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
"targets": [ 0, 6, 7, 8 ],
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
]
|
||||
});
|
||||
window.queue = $('#queue').DataTable({
|
||||
autoWidth: false,
|
||||
columns: [
|
||||
null,
|
||||
{width:"20%"},
|
||||
{width:"20%"},
|
||||
{width:"1em"},
|
||||
{width:"20%"},
|
||||
],
|
||||
paging: true,
|
||||
ordering: false,
|
||||
searching: false,
|
||||
select: {
|
||||
style: 'single'
|
||||
},
|
||||
columnDefs: [
|
||||
{
|
||||
"targets": [ 0, 6, 7, 8 ],
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
],
|
||||
}).on("select", function(e, dt, type, indexes) {
|
||||
var item = queue.row(indexes).data();
|
||||
playTrack(item[0], item[6]);
|
||||
});
|
||||
|
||||
window.AUDIO_CODECS_SUPPORTED = [];
|
||||
|
||||
var audioPlayer = document.getElementById("player");
|
||||
for (var j = 0; j < window.AUDIO_CODECS.length; j++) {
|
||||
if (audioPlayer.canPlayType(window.AUDIO_CODECS[j])) {
|
||||
window.AUDIO_CODECS_SUPPORTED.push(AUDIO_CODECS[j]);
|
||||
}
|
||||
}
|
||||
|
||||
console.info("This browser supports following audio codecs:", window.AUDIO_CODECS_SUPPORTED);
|
||||
$("#search").keyup(function() {
|
||||
if (prev == $("#search").val())
|
||||
return;
|
||||
prev = $("#search").val();
|
||||
clearTimeout(window.timeout);
|
||||
window.timeout = setTimeout(function() {
|
||||
onDelayedSearch();
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user