<!DOCTYPE html>
<!-----------------------------------------------------------------------------
Copyright 2014-2025 Jesper Larsson
This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
Klipspringer is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
Klipspringer is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with Klipspringer. If not, see <https://www.gnu.org/licenses/>.
--------------------------------------------------------------------------->
<html>
<head>
<meta charset="UTF-8">
<style>$(klipstyle.css:file)</style>
<title>$(path)</title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
$(klipicons.html:file)
</head>
<body>
<div class="recording-menus">
Download format:
<select id="format-select" class="recording-menu">
<option value="raw">Raw PCM</option>
<option value="wav">WAV PCM</option>
<option value="flac" id="flac-option">FLAC</option>
<option value="ogg" id="vorbis-option">Ogg Vorbis (.9 quality)</option>
<option value="mp3" id="mp3-option">MP3 (320 kbit/s)</option>
</select>
<button class="recording-menu" id="clear-cache">Clear cache</button>
</div>
<table id="recs"></table>
<dialog id="trash-alert">
<div id="trash-alert-explain" class="dialog-message"></div>
<div>
<input type="button" class="click-choose dialog-button" id="trash-alert-hide" value="OK" />
</div>
</dialog>
<dialog id="dlod-alert">
<div class="dialog-message">Download begins when <span id="dlod-alert-format"></span> file is ready. The file is cached for faster access.</div>
<div>
<input type="button" class="click-choose dialog-button" id="dlod-alert-hide" value="OK" />
</div>
</dialog>
<script>
'use strict';
$(sendcmd.js:file)
if (!$(flac_supported)) { document.getElementById("flac-option").remove() }
if (!$(vorbis_supported)) { document.getElementById("vorbis-option").remove() }
if (!$(lame_supported)) { document.getElementById("mp3-option").remove() }
const touchdevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
const pressevent = touchdevice ? "click" : "mousedown";
const normalpress = touchdevice ? (ev) => true : (ev) => ev.buttons === 1;
// Button animation.
const blinkButton = (elt) => {
elt.classList.add("blinking");
clearTimeout(elt.tout);
elt.tout = setTimeout(() => {
elt.classList.remove("blinking");
}, 150);
}
// Modify href in download link before action, and show message.
const downloadNote = (ev) => {
const a = ev.currentTarget;
blinkButton(a.firstElementChild.firstElementChild);
const format = formatSelectElt.value;
if (format.toLowerCase() !== 'raw') {
dlodAlertFormatElt.innerHTML = format;
dlodAlertDialogElt.showModal();
}
const h = a.href;
const m = /&format=([a-z0-9]*)/d.exec(h);
if (m) {
const [i, j] = m.indices[1];
a.href = h.slice(0, i)+format+h.slice(j);
} else {
a.href = h+"&format="+format;
}
}
// Blink play button before action.
const blinkPlay = (ev) => {
const a = ev.currentTarget;
blinkButton(a.firstElementChild.firstElementChild);
}
let dontask = false;
const recs = $(recs);
const formatSelectElt = document.getElementById("format-select");
formatSelectElt.value = $(default_download_format);
const recsElt = document.getElementById("recs");
const playElt = document.createElement("a");
playElt.innerHTML = $(play_svg);
const dlodElt = document.createElement("a");
dlodElt.innerHTML = $(download_svg);
const remvElt = document.createElement("a");
remvElt.innerHTML = $(remove_svg);
document.getElementById("clear-cache").addEventListener(pressevent, (ev) => {
if (normalpress(ev)) {
sendUrl("/recording?cmd=clearcache");
}
});
const confirmDialogElt = document.createElement("dialog");
confirmDialogElt.innerHTML = '<div class="dialog-message"></div>'+
'<div class="dialog-content">'+
' <label>'+
' Don’t ask again'+
' <input type="checkbox" />'+
' </label> '+
'</div> '+
'<div>'+
' <input type="button" class="click-choose dialog-button" value="OK" />'+
' <input type="button" class="click-choose dialog-button" value="Cancel" />'+
'</div>';
const trashAlertDialogElt = document.getElementById("trash-alert");
const trashAlertExplainElt = document.getElementById("trash-alert-explain");
document.getElementById("trash-alert-hide").addEventListener(pressevent, (ev) => {
if (normalpress(ev)) { trashAlertDialogElt.close() }
});
const dlodAlertDialogElt = document.getElementById("dlod-alert");
const dlodAlertFormatElt = document.getElementById("dlod-alert-format");
document.getElementById("dlod-alert-hide").addEventListener(pressevent, (ev) => {
if (normalpress(ev)) { dlodAlertDialogElt.close() }
});
const trash = (tr, file) => sendUrl("/recording?cmd=unlink&file="+file).then(ans => {
if (ans.ok) { tr.remove() }
else {
trashAlertExplainElt.innerHTML = "Error deleting recording"+(ans.err ? ": "+ans.err : "");
trashAlertDialogElt.showModal();
}
});
for (const r of recs) {
const tr = document.createElement("tr");
recsElt.appendChild(tr);
let td = document.createElement("td");
tr.appendChild(td);
const play = playElt.cloneNode(true);
td.appendChild(play);
play.setAttribute("href", "/choice?source=$(source)&path="+encodeURIComponent(r.file));
play.addEventListener("click", blinkPlay);
const dlod = dlodElt.cloneNode(true);
td.appendChild(dlod);
dlod.setAttribute("href", "/recording?cmd=download&file="+encodeURIComponent(r.file));
dlod.setAttribute("download", "");
dlod.addEventListener("click", downloadNote);
const remv = remvElt.cloneNode(true);
td.appendChild(remv);
const dia = confirmDialogElt.cloneNode(true);
remv.appendChild(dia);
dia.children[0].innerHTML = "Deleting recording from "+r.date;
dia.children[2].children[0].addEventListener(pressevent, (ev) => {
if (normalpress(ev)) {
dontask = dontask || dia.children[1].children[0].children[0].checked;
dia.close();
trash(tr, r.file);
}
});
dia.children[2].children[1].addEventListener(pressevent, (ev) => {
if (normalpress(ev)) { dia.close(); }
});
remv.addEventListener("click", (ev) => {
blinkButton(ev.currentTarget.firstElementChild.firstElementChild);
if (dontask) { trash(tr, r.file) }
else { dia.showModal() }
});
td = document.createElement("td");
tr.appendChild(td);
td.innerHTML = r.date;
td = document.createElement("td");
tr.appendChild(td);
let t = Math.round(r.size / (r.rate*Math.ceil((r.bits+r.pad)/8) * r.channels) * 1000);
if (t >= 3600000) { // has hours
const h = Math.trunc(t/3600000);
t %= 3600000;
const m = Math.trunc(t/60000);
t %= 60000;
const s = t/1000;
td.innerHTML = "("+h+":"+(m < 10 ? "0" : "")+m+":"+(s < 10 ? "0" : "")+s+")";
} else if (t > 60000) {
const m = Math.trunc(t/60000);
t %= 60000;
const s = t/1000;
td.innerHTML = "("+m+":"+(s < 10 ? "0" : "")+s+")";
} else {
const s = t/1000;
td.innerHTML = "("+s+")";
}
td = document.createElement("td");
tr.appendChild(td);
td.innerHTML = r.bits+"-bit "+(r.rate >= 1000 ? r.rate/1000+" kHz" : r.rate+" Hz")+(r.channels === 2 ? " stereo" : (r.channels === 1 ? " mono" : r.channels+" channels"));
}
</script>
</body>
</html>
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home