mirror of
https://codeberg.org/armin/noiz.git
synced 2026-09-01 05:50:52 +02:00
remove blank linez ;-)
This commit is contained in:
parent
98adc59450
commit
4cd4649c0a
1 changed files with 0 additions and 78 deletions
78
index.html
78
index.html
|
|
@ -5,16 +5,6 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>noiz://</title>
|
<title>noiz://</title>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #161821; color: #c6c8d1; height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #161821; color: #c6c8d1; height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
|
||||||
.btn { background: linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; box-shadow: 0 3px 0 #161821, 0 4px 6px rgba(0,0,0,0.4); }
|
.btn { background: linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; box-shadow: 0 3px 0 #161821, 0 4px 6px rgba(0,0,0,0.4); }
|
||||||
|
|
@ -71,7 +61,6 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
|
||||||
.playlist-toolbar .spacer { flex: 1; }
|
.playlist-toolbar .spacer { flex: 1; }
|
||||||
.playlist-toolbar select { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 4px 20px 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; outline: none; box-shadow: 0 2px 0 #161821, 0 3px 4px rgba(0,0,0,0.3); transition: transform .1s, box-shadow .1s; -webkit-appearance: none; appearance: none; }
|
.playlist-toolbar select { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #272c42, #1e2132); border: 1px solid #6b7089; color: #c6c8d1; padding: 4px 20px 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; outline: none; box-shadow: 0 2px 0 #161821, 0 3px 4px rgba(0,0,0,0.3); transition: transform .1s, box-shadow .1s; -webkit-appearance: none; appearance: none; }
|
||||||
.playlist-toolbar select:hover { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #2e3348, #272c42); }
|
.playlist-toolbar select:hover { background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3l3 3 3-3' stroke='%23c6c8d1' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 8px no-repeat, linear-gradient(to bottom, #2e3348, #272c42); }
|
||||||
|
|
||||||
.playlist-header { display: grid; grid-template-columns: 36px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) 60px 60px; gap: 8px; padding: 8px 24px; font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #6b7089; border-bottom: 1px solid #272c42; flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); }
|
.playlist-header { display: grid; grid-template-columns: 36px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) 60px 60px; gap: 8px; padding: 8px 24px; font-size: 10px; text-transform: uppercase; letter-spacing: .6px; color: #6b7089; border-bottom: 1px solid #272c42; flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); }
|
||||||
.playlist-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 2px 0; position: relative; background: #101219; }
|
.playlist-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 2px 0; position: relative; background: #101219; }
|
||||||
#scanning { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10; font-size: 14px; color: #c6c8d1; flex-direction: column; gap: 10px; }
|
#scanning { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10; font-size: 14px; color: #c6c8d1; flex-direction: column; gap: 10px; }
|
||||||
|
|
@ -122,14 +111,11 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="noiz-overlay">
|
<div id="noiz-overlay">
|
||||||
<img src="noizmainlogo.png" alt="noiz://">
|
<img src="noizmainlogo.png" alt="noiz://">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="error"></div>
|
<div id="error"></div>
|
||||||
<input type="file" id="folderPicker" webkitdirectory multiple style="display:none">
|
<input type="file" id="folderPicker" webkitdirectory multiple style="display:none">
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="pinned" id="pinned">
|
<div class="pinned" id="pinned">
|
||||||
<div class="player" id="player">
|
<div class="player" id="player">
|
||||||
|
|
@ -250,13 +236,11 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
|
||||||
<button class="btn" id="deleteBtn">Delete</button>
|
<button class="btn" id="deleteBtn">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer id="footer">
|
<footer id="footer">
|
||||||
<span class="footer-track" id="footerTrack">No track</span>
|
<span class="footer-track" id="footerTrack">No track</span>
|
||||||
<span id="footerTime">0:00 / 0:00</span>
|
<span id="footerTime">0:00 / 0:00</span>
|
||||||
<span class="brand">noiz<span>://</span></span>
|
<span class="brand">noiz<span>://</span></span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const audioElA = new Audio();
|
const audioElA = new Audio();
|
||||||
const audioElB = new Audio();
|
const audioElB = new Audio();
|
||||||
|
|
@ -274,7 +258,6 @@ let crossfadeTimer = null;
|
||||||
let isCrossfading = false;
|
let isCrossfading = false;
|
||||||
let repeatMode = 'off';
|
let repeatMode = 'off';
|
||||||
let shuffleOn = false;
|
let shuffleOn = false;
|
||||||
|
|
||||||
let audioCtx = null;
|
let audioCtx = null;
|
||||||
let analyser = null;
|
let analyser = null;
|
||||||
let analyserL = null;
|
let analyserL = null;
|
||||||
|
|
@ -285,8 +268,6 @@ let sortAsc = true;
|
||||||
let windowType = 'hanning';
|
let windowType = 'hanning';
|
||||||
let peakDecay = 0.985;
|
let peakDecay = 0.985;
|
||||||
let barDecay = 0.96;
|
let barDecay = 0.96;
|
||||||
|
|
||||||
|
|
||||||
function initAudioCtx() {
|
function initAudioCtx() {
|
||||||
if (audioCtx) return;
|
if (audioCtx) return;
|
||||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
|
@ -318,16 +299,13 @@ function initAudioCtx() {
|
||||||
analyser.connect(audioCtx.destination);
|
analyser.connect(audioCtx.destination);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvas = document.getElementById('spectrum');
|
const canvas = document.getElementById('spectrum');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const bigTime = document.getElementById('bigTime');
|
const bigTime = document.getElementById('bigTime');
|
||||||
|
|
||||||
let peakData = [];
|
let peakData = [];
|
||||||
let smoothedData = [];
|
let smoothedData = [];
|
||||||
let vuPeakL = 0, vuPeakR = 0;
|
let vuPeakL = 0, vuPeakR = 0;
|
||||||
let vuSmoothL = 0, vuSmoothR = 0;
|
let vuSmoothL = 0, vuSmoothR = 0;
|
||||||
|
|
||||||
function bessi0(x) {
|
function bessi0(x) {
|
||||||
let s = 1, t = 1;
|
let s = 1, t = 1;
|
||||||
for (let k = 1; k <= 20; k++) {
|
for (let k = 1; k <= 20; k++) {
|
||||||
|
|
@ -337,7 +315,6 @@ function bessi0(x) {
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyWindow(data, type) {
|
function applyWindow(data, type) {
|
||||||
const n = data.length;
|
const n = data.length;
|
||||||
let kaiserI0;
|
let kaiserI0;
|
||||||
|
|
@ -380,7 +357,6 @@ function applyWindow(data, type) {
|
||||||
data[i] *= w;
|
data[i] *= w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fft(re, im) {
|
function fft(re, im) {
|
||||||
const n = re.length;
|
const n = re.length;
|
||||||
for (let i = 1, j = 0; i < n; i++) {
|
for (let i = 1, j = 0; i < n; i++) {
|
||||||
|
|
@ -413,9 +389,7 @@ function fft(re, im) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCALE_EXP = { none: 1, hanning: 0.5, hamming: 0.48, blackman: 0.4, bartlett: 0.55, gaussian: 0.43, kaiser: 0.33, flatTop: 0.3 };
|
const SCALE_EXP = { none: 1, hanning: 0.5, hamming: 0.48, blackman: 0.4, bartlett: 0.55, gaussian: 0.43, kaiser: 0.33, flatTop: 0.3 };
|
||||||
|
|
||||||
function drawSpectrum() {
|
function drawSpectrum() {
|
||||||
if (!analyser) { animId = requestAnimationFrame(drawSpectrum); return; }
|
if (!analyser) { animId = requestAnimationFrame(drawSpectrum); return; }
|
||||||
const fftSize = analyser.fftSize;
|
const fftSize = analyser.fftSize;
|
||||||
|
|
@ -559,16 +533,13 @@ function drawSpectrum() {
|
||||||
}
|
}
|
||||||
animId = requestAnimationFrame(drawSpectrum);
|
animId = requestAnimationFrame(drawSpectrum);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSpectrum();
|
drawSpectrum();
|
||||||
|
|
||||||
let tracks = [];
|
let tracks = [];
|
||||||
let currentIndex = -1;
|
let currentIndex = -1;
|
||||||
let selectedIndices = new Set();
|
let selectedIndices = new Set();
|
||||||
let anchorTrack = null;
|
let anchorTrack = null;
|
||||||
let dragIndices = null;
|
let dragIndices = null;
|
||||||
let dirHandle = null;
|
let dirHandle = null;
|
||||||
|
|
||||||
const addFolderBtn = document.getElementById('addFolderBtn');
|
const addFolderBtn = document.getElementById('addFolderBtn');
|
||||||
const addFilesBtn = document.getElementById('addFilesBtn');
|
const addFilesBtn = document.getElementById('addFilesBtn');
|
||||||
const folderPicker = document.getElementById('folderPicker');
|
const folderPicker = document.getElementById('folderPicker');
|
||||||
|
|
@ -603,9 +574,7 @@ const windowSelect = document.getElementById('windowSelect');
|
||||||
const decaySelect = document.getElementById('decaySelect');
|
const decaySelect = document.getElementById('decaySelect');
|
||||||
const barDecaySelect = document.getElementById('barDecaySelect');
|
const barDecaySelect = document.getElementById('barDecaySelect');
|
||||||
const crossfadeSelect = document.getElementById('crossfadeSelect');
|
const crossfadeSelect = document.getElementById('crossfadeSelect');
|
||||||
|
|
||||||
const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']);
|
const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']);
|
||||||
|
|
||||||
const GENRES = [
|
const GENRES = [
|
||||||
'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal',
|
'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal',
|
||||||
'New Age','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial',
|
'New Age','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial',
|
||||||
|
|
@ -627,20 +596,17 @@ const GENRES = [
|
||||||
'Christian Gangsta Rap','Heavy Metal','Black Metal','Crossover','Contemporary Christian',
|
'Christian Gangsta Rap','Heavy Metal','Black Metal','Crossover','Contemporary Christian',
|
||||||
'Christian Rock','Merengue','Salsa','Thrash Metal','Anime','JPop','Synthpop'
|
'Christian Rock','Merengue','Salsa','Thrash Metal','Anime','JPop','Synthpop'
|
||||||
];
|
];
|
||||||
|
|
||||||
function fmtTime(s) {
|
function fmtTime(s) {
|
||||||
if (!isFinite(s) || s < 0) return '0:00';
|
if (!isFinite(s) || s < 0) return '0:00';
|
||||||
const m = Math.floor(s / 60);
|
const m = Math.floor(s / 60);
|
||||||
const sec = Math.floor(s % 60);
|
const sec = Math.floor(s % 60);
|
||||||
return `${m}:${sec.toString().padStart(2, '0')}`;
|
return `${m}:${sec.toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(msg) {
|
function showError(msg) {
|
||||||
errorEl.textContent = msg;
|
errorEl.textContent = msg;
|
||||||
errorEl.style.display = 'block';
|
errorEl.style.display = 'block';
|
||||||
setTimeout(() => { errorEl.style.display = 'none'; }, 5000);
|
setTimeout(() => { errorEl.style.display = 'none'; }, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function* walkDir(dirHandle, path = '') {
|
async function* walkDir(dirHandle, path = '') {
|
||||||
for await (const entry of dirHandle.values()) {
|
for await (const entry of dirHandle.values()) {
|
||||||
const fullPath = path ? `${path}/${entry.name}` : entry.name;
|
const fullPath = path ? `${path}/${entry.name}` : entry.name;
|
||||||
|
|
@ -651,13 +617,11 @@ async function* walkDir(dirHandle, path = '') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudioFile(name) {
|
function isAudioFile(name) {
|
||||||
const dot = name.lastIndexOf('.');
|
const dot = name.lastIndexOf('.');
|
||||||
if (dot === -1) return false;
|
if (dot === -1) return false;
|
||||||
return AUDIO_EXTS.has(name.slice(dot).toLowerCase());
|
return AUDIO_EXTS.has(name.slice(dot).toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function readBytes(file, start, length) {
|
function readBytes(file, start, length) {
|
||||||
const blob = file.slice(start, start + length);
|
const blob = file.slice(start, start + length);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|
@ -666,7 +630,6 @@ function readBytes(file, start, length) {
|
||||||
r.readAsArrayBuffer(blob);
|
r.readAsArrayBuffer(blob);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeText(arr, offset, len, enc) {
|
function decodeText(arr, offset, len, enc) {
|
||||||
const end = Math.min(offset + len, arr.length);
|
const end = Math.min(offset + len, arr.length);
|
||||||
if (end <= offset) return '';
|
if (end <= offset) return '';
|
||||||
|
|
@ -687,7 +650,6 @@ function decodeText(arr, offset, len, enc) {
|
||||||
return new TextDecoder('utf-8').decode(slice).replace(/\0/g, '').trim();
|
return new TextDecoder('utf-8').decode(slice).replace(/\0/g, '').trim();
|
||||||
} catch { return ''; }
|
} catch { return ''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSyncsafeInt(bytes, off, len) {
|
function parseSyncsafeInt(bytes, off, len) {
|
||||||
let val = 0;
|
let val = 0;
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
|
|
@ -695,7 +657,6 @@ function parseSyncsafeInt(bytes, off, len) {
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGenre(text) {
|
function parseGenre(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const m = text.match(/\((\d+)\)/);
|
const m = text.match(/\((\d+)\)/);
|
||||||
|
|
@ -703,7 +664,6 @@ function parseGenre(text) {
|
||||||
if (!isNaN(text) && text !== '') return GENRES[parseInt(text)] || text;
|
if (!isNaN(text) && text !== '') return GENRES[parseInt(text)] || text;
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readId3v2(file) {
|
async function readId3v2(file) {
|
||||||
try {
|
try {
|
||||||
const hdr = await readBytes(file, 0, 10);
|
const hdr = await readBytes(file, 0, 10);
|
||||||
|
|
@ -744,7 +704,6 @@ async function readId3v2(file) {
|
||||||
return tags;
|
return tags;
|
||||||
} catch { return null; }
|
} catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readId3v1(file) {
|
async function readId3v1(file) {
|
||||||
try {
|
try {
|
||||||
if (file.size < 128) return null;
|
if (file.size < 128) return null;
|
||||||
|
|
@ -765,7 +724,6 @@ async function readId3v1(file) {
|
||||||
return tags;
|
return tags;
|
||||||
} catch { return null; }
|
} catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readTags(file) {
|
async function readTags(file) {
|
||||||
let tags = await readId3v2(file);
|
let tags = await readId3v2(file);
|
||||||
if (tags && (tags.title || tags.artist)) return tags;
|
if (tags && (tags.title || tags.artist)) return tags;
|
||||||
|
|
@ -781,7 +739,6 @@ async function readTags(file) {
|
||||||
}
|
}
|
||||||
return tags || {};
|
return tags || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const MP3_BITRATES = [
|
const MP3_BITRATES = [
|
||||||
[0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0],
|
[0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0],
|
||||||
[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0],
|
[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0],
|
||||||
|
|
@ -789,22 +746,17 @@ const MP3_BITRATES = [
|
||||||
[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0],
|
[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0],
|
||||||
[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0]
|
[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0]
|
||||||
];
|
];
|
||||||
|
|
||||||
const MP3_SAMPLERATES = [44100, 48000, 32000, 0, 22050, 24000, 16000, 0, 11025, 12000, 8000, 0];
|
const MP3_SAMPLERATES = [44100, 48000, 32000, 0, 22050, 24000, 16000, 0, 11025, 12000, 8000, 0];
|
||||||
|
|
||||||
const MP3_CHANNELS = ['Stereo', 'Joint Stereo', 'Dual Channel', 'Mono'];
|
const MP3_CHANNELS = ['Stereo', 'Joint Stereo', 'Dual Channel', 'Mono'];
|
||||||
|
|
||||||
function isMp3Sync(b) {
|
function isMp3Sync(b) {
|
||||||
return b[0] === 0xFF && (b[1] & 0xE0) === 0xE0 && ((b[1] >> 3) & 3) !== 1 && ((b[1] >> 1) & 3) !== 0;
|
return b[0] === 0xFF && (b[1] & 0xE0) === 0xE0 && ((b[1] >> 3) & 3) !== 1 && ((b[1] >> 1) & 3) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMp3Header(data, off) {
|
function findMp3Header(data, off) {
|
||||||
for (let i = off; i < data.length - 4; i++) {
|
for (let i = off; i < data.length - 4; i++) {
|
||||||
if (isMp3Sync(data.slice(i, i + 4))) return i;
|
if (isMp3Sync(data.slice(i, i + 4))) return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readAudioInfo(file) {
|
async function readAudioInfo(file) {
|
||||||
const info = {};
|
const info = {};
|
||||||
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.'));
|
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.'));
|
||||||
|
|
@ -860,7 +812,6 @@ async function readAudioInfo(file) {
|
||||||
} catch {}
|
} catch {}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFiles(files, paths) {
|
async function processFiles(files, paths) {
|
||||||
scanning.style.display = 'flex';
|
scanning.style.display = 'flex';
|
||||||
const total = files.length;
|
const total = files.length;
|
||||||
|
|
@ -895,7 +846,6 @@ async function processFiles(files, paths) {
|
||||||
setTimeout(() => { scanning.style.display = 'none'; }, 800);
|
setTimeout(() => { scanning.style.display = 'none'; }, 800);
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFolder() {
|
async function addFolder() {
|
||||||
if ('showDirectoryPicker' in window) {
|
if ('showDirectoryPicker' in window) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -924,7 +874,6 @@ async function addFolder() {
|
||||||
}
|
}
|
||||||
folderPicker.click();
|
folderPicker.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPicker.addEventListener('change', async (e) => {
|
folderPicker.addEventListener('change', async (e) => {
|
||||||
const fileList = e.target.files;
|
const fileList = e.target.files;
|
||||||
if (!fileList || fileList.length === 0) return;
|
if (!fileList || fileList.length === 0) return;
|
||||||
|
|
@ -934,7 +883,6 @@ folderPicker.addEventListener('change', async (e) => {
|
||||||
addTracks(processed);
|
addTracks(processed);
|
||||||
folderPicker.value = '';
|
folderPicker.value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
async function addFiles() {
|
async function addFiles() {
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
|
|
@ -950,7 +898,6 @@ async function addFiles() {
|
||||||
});
|
});
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTracks(newTracks) {
|
function addTracks(newTracks) {
|
||||||
if (newTracks.length === 0) {
|
if (newTracks.length === 0) {
|
||||||
showError('No audio files found.');
|
showError('No audio files found.');
|
||||||
|
|
@ -959,7 +906,6 @@ function addTracks(newTracks) {
|
||||||
tracks = tracks.concat(newTracks);
|
tracks = tracks.concat(newTracks);
|
||||||
renderTracks();
|
renderTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isolateTracks() {
|
function isolateTracks() {
|
||||||
tracks.forEach((t, i) => {
|
tracks.forEach((t, i) => {
|
||||||
if (i !== currentIndex) URL.revokeObjectURL(t.url);
|
if (i !== currentIndex) URL.revokeObjectURL(t.url);
|
||||||
|
|
@ -975,7 +921,6 @@ function isolateTracks() {
|
||||||
}
|
}
|
||||||
renderTracks();
|
renderTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteTrack() {
|
function deleteTrack() {
|
||||||
if (selectedIndices.size === 0) return;
|
if (selectedIndices.size === 0) return;
|
||||||
if (selectedIndices.size === tracks.length) {
|
if (selectedIndices.size === tracks.length) {
|
||||||
|
|
@ -1006,7 +951,6 @@ function deleteTrack() {
|
||||||
renderTracks();
|
renderTracks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSortFn(field) {
|
function getSortFn(field) {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
|
case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
|
||||||
|
|
@ -1018,7 +962,6 @@ function getSortFn(field) {
|
||||||
default: return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
|
default: return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTracks() {
|
function renderTracks() {
|
||||||
const sorted = [...tracks].sort(getSortFn(sortField));
|
const sorted = [...tracks].sort(getSortFn(sortField));
|
||||||
if (!sortAsc) sorted.reverse();
|
if (!sortAsc) sorted.reverse();
|
||||||
|
|
@ -1120,13 +1063,11 @@ function renderTracks() {
|
||||||
canvas.style.display = hasT ? 'block' : 'none';
|
canvas.style.display = hasT ? 'block' : 'none';
|
||||||
progressRow.style.display = hasT ? 'flex' : 'none';
|
progressRow.style.display = hasT ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function escHtml(s) {
|
function escHtml(s) {
|
||||||
const d = document.createElement('div');
|
const d = document.createElement('div');
|
||||||
d.textContent = s;
|
d.textContent = s;
|
||||||
return d.innerHTML;
|
return d.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function playTrack(i) {
|
function playTrack(i) {
|
||||||
if (i < 0 || i >= tracks.length) return;
|
if (i < 0 || i >= tracks.length) return;
|
||||||
const prevIdx = currentIndex;
|
const prevIdx = currentIndex;
|
||||||
|
|
@ -1173,7 +1114,6 @@ function playTrack(i) {
|
||||||
renderTracks();
|
renderTracks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUI() {
|
function updateUI() {
|
||||||
const t = tracks[currentIndex];
|
const t = tracks[currentIndex];
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
|
|
@ -1191,7 +1131,6 @@ function updateUI() {
|
||||||
else if (ai.channels) parts.push(ai.channels === 1 ? 'Mono' : 'Stereo');
|
else if (ai.channels) parts.push(ai.channels === 1 ? 'Mono' : 'Stereo');
|
||||||
audioInfo.textContent = parts.join(' \u00B7 ');
|
audioInfo.textContent = parts.join(' \u00B7 ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLoadedmetadata(e) {
|
function onLoadedmetadata(e) {
|
||||||
if (e.target !== audio) return;
|
if (e.target !== audio) return;
|
||||||
const t = tracks[currentIndex];
|
const t = tracks[currentIndex];
|
||||||
|
|
@ -1245,7 +1184,6 @@ audioElA.addEventListener('play', onPlay);
|
||||||
audioElB.addEventListener('play', onPlay);
|
audioElB.addEventListener('play', onPlay);
|
||||||
audioElA.addEventListener('pause', onPause);
|
audioElA.addEventListener('pause', onPause);
|
||||||
audioElB.addEventListener('pause', onPause);
|
audioElB.addEventListener('pause', onPause);
|
||||||
|
|
||||||
playBtn.addEventListener('click', () => {
|
playBtn.addEventListener('click', () => {
|
||||||
if (tracks.length === 0) return;
|
if (tracks.length === 0) return;
|
||||||
const selArr = [...selectedIndices];
|
const selArr = [...selectedIndices];
|
||||||
|
|
@ -1262,10 +1200,8 @@ playBtn.addEventListener('click', () => {
|
||||||
audio.pause();
|
audio.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
prevBtn.addEventListener('click', prevTrack);
|
prevBtn.addEventListener('click', prevTrack);
|
||||||
nextBtn.addEventListener('click', nextTrack);
|
nextBtn.addEventListener('click', nextTrack);
|
||||||
|
|
||||||
function prevTrack() {
|
function prevTrack() {
|
||||||
if (tracks.length === 0) return;
|
if (tracks.length === 0) return;
|
||||||
if (audio.currentTime > 3) { audio.currentTime = 0; return; }
|
if (audio.currentTime > 3) { audio.currentTime = 0; return; }
|
||||||
|
|
@ -1277,7 +1213,6 @@ function prevTrack() {
|
||||||
playTrack(currentIndex <= 0 ? tracks.length - 1 : currentIndex - 1);
|
playTrack(currentIndex <= 0 ? tracks.length - 1 : currentIndex - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextTrack() {
|
function nextTrack() {
|
||||||
if (tracks.length === 0) return;
|
if (tracks.length === 0) return;
|
||||||
if (shuffleOn) {
|
if (shuffleOn) {
|
||||||
|
|
@ -1290,31 +1225,26 @@ function nextTrack() {
|
||||||
playTrack(currentIndex >= tracks.length - 1 ? 0 : currentIndex + 1);
|
playTrack(currentIndex >= tracks.length - 1 ? 0 : currentIndex + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.addEventListener('click', (e) => {
|
progressBar.addEventListener('click', (e) => {
|
||||||
if (!audio.duration) return;
|
if (!audio.duration) return;
|
||||||
const rect = progressBar.getBoundingClientRect();
|
const rect = progressBar.getBoundingClientRect();
|
||||||
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
|
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
|
||||||
});
|
});
|
||||||
|
|
||||||
volumeSlider.addEventListener('input', () => {
|
volumeSlider.addEventListener('input', () => {
|
||||||
const v = parseFloat(volumeSlider.value);
|
const v = parseFloat(volumeSlider.value);
|
||||||
audioElA.volume = v;
|
audioElA.volume = v;
|
||||||
audioElB.volume = v;
|
audioElB.volume = v;
|
||||||
volDisplay.textContent = Math.round(v * 100);
|
volDisplay.textContent = Math.round(v * 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
addFolderBtn.addEventListener('click', addFolder);
|
addFolderBtn.addEventListener('click', addFolder);
|
||||||
addFilesBtn.addEventListener('click', addFiles);
|
addFilesBtn.addEventListener('click', addFiles);
|
||||||
isolateBtn.addEventListener('click', isolateTracks);
|
isolateBtn.addEventListener('click', isolateTracks);
|
||||||
deleteBtn.addEventListener('click', deleteTrack);
|
deleteBtn.addEventListener('click', deleteTrack);
|
||||||
|
|
||||||
sortSelect.addEventListener('change', () => {
|
sortSelect.addEventListener('change', () => {
|
||||||
if (sortSelect.value === sortField) { sortAsc = !sortAsc; }
|
if (sortSelect.value === sortField) { sortAsc = !sortAsc; }
|
||||||
else { sortField = sortSelect.value; sortAsc = true; }
|
else { sortField = sortSelect.value; sortAsc = true; }
|
||||||
renderTracks();
|
renderTracks();
|
||||||
});
|
});
|
||||||
|
|
||||||
windowSelect.addEventListener('change', () => { windowType = windowSelect.value; });
|
windowSelect.addEventListener('change', () => { windowType = windowSelect.value; });
|
||||||
decaySelect.addEventListener('change', () => { peakDecay = parseFloat(decaySelect.value); });
|
decaySelect.addEventListener('change', () => { peakDecay = parseFloat(decaySelect.value); });
|
||||||
barDecaySelect.addEventListener('change', () => { barDecay = parseFloat(barDecaySelect.value); });
|
barDecaySelect.addEventListener('change', () => { barDecay = parseFloat(barDecaySelect.value); });
|
||||||
|
|
@ -1323,11 +1253,9 @@ crossfadeDuration = parseFloat(crossfadeSelect.value);
|
||||||
repeatSelect.addEventListener('change', () => { repeatMode = repeatSelect.value; });
|
repeatSelect.addEventListener('change', () => { repeatMode = repeatSelect.value; });
|
||||||
shuffleSelect.addEventListener('change', () => { shuffleOn = shuffleSelect.value === 'on'; });
|
shuffleSelect.addEventListener('change', () => { shuffleOn = shuffleSelect.value === 'on'; });
|
||||||
volDisplay.textContent = Math.round(parseFloat(volumeSlider.value) * 100);
|
volDisplay.textContent = Math.round(parseFloat(volumeSlider.value) * 100);
|
||||||
|
|
||||||
let dragCounter = 0;
|
let dragCounter = 0;
|
||||||
let isInternalDrag = false;
|
let isInternalDrag = false;
|
||||||
const content = document.querySelector('.content');
|
const content = document.querySelector('.content');
|
||||||
|
|
||||||
content.addEventListener('dragenter', (e) => {
|
content.addEventListener('dragenter', (e) => {
|
||||||
if (isInternalDrag) return;
|
if (isInternalDrag) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -1335,19 +1263,16 @@ content.addEventListener('dragenter', (e) => {
|
||||||
content.style.outline = '2px dashed #84a0c6';
|
content.style.outline = '2px dashed #84a0c6';
|
||||||
content.style.outlineOffset = '-12px';
|
content.style.outlineOffset = '-12px';
|
||||||
});
|
});
|
||||||
|
|
||||||
content.addEventListener('dragleave', (e) => {
|
content.addEventListener('dragleave', (e) => {
|
||||||
if (isInternalDrag) return;
|
if (isInternalDrag) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dragCounter--;
|
dragCounter--;
|
||||||
if (dragCounter <= 0) { dragCounter = 0; content.style.outline = 'none'; }
|
if (dragCounter <= 0) { dragCounter = 0; content.style.outline = 'none'; }
|
||||||
});
|
});
|
||||||
|
|
||||||
content.addEventListener('dragover', (e) => {
|
content.addEventListener('dragover', (e) => {
|
||||||
if (isInternalDrag) { e.preventDefault(); return; }
|
if (isInternalDrag) { e.preventDefault(); return; }
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
content.addEventListener('drop', async (e) => {
|
content.addEventListener('drop', async (e) => {
|
||||||
if (isInternalDrag) return;
|
if (isInternalDrag) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -1386,7 +1311,6 @@ content.addEventListener('drop', async (e) => {
|
||||||
const processed = await processFiles(files, paths);
|
const processed = await processFiles(files, paths);
|
||||||
addTracks(processed);
|
addTracks(processed);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function collectFiles(entry, files, paths, path, onFile) {
|
async function collectFiles(entry, files, paths, path, onFile) {
|
||||||
if (entry.isFile) {
|
if (entry.isFile) {
|
||||||
const file = await new Promise((resolve) => entry.file(resolve));
|
const file = await new Promise((resolve) => entry.file(resolve));
|
||||||
|
|
@ -1409,14 +1333,12 @@ async function collectFiles(entry, files, paths, path, onFile) {
|
||||||
await readAll();
|
await readAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
|
||||||
if (e.code === 'Space') { e.preventDefault(); playBtn.click(); }
|
if (e.code === 'Space') { e.preventDefault(); playBtn.click(); }
|
||||||
if (e.code === 'ArrowRight') nextBtn.click();
|
if (e.code === 'ArrowRight') nextBtn.click();
|
||||||
if (e.code === 'ArrowLeft') prevBtn.click();
|
if (e.code === 'ArrowLeft') prevBtn.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('focus', () => {
|
window.addEventListener('focus', () => {
|
||||||
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
|
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue