added player
This commit is contained in:
+66
-7
@@ -61,10 +61,18 @@
|
||||
td.actions { text-align: right; white-space: nowrap; }
|
||||
td .muted { color: var(--muted); }
|
||||
#toast {
|
||||
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
|
||||
position: fixed; bottom: 76px; left: 50%; transform: translateX(-50%);
|
||||
background: var(--danger); color: #fff; padding: 10px 18px; border-radius: 8px;
|
||||
display: none; max-width: 80vw;
|
||||
display: none; max-width: 80vw; z-index: 10;
|
||||
}
|
||||
#player {
|
||||
position: fixed; bottom: 0; left: 0; right: 0; z-index: 9;
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 8px 20px; background: var(--panel); border-top: 1px solid var(--border);
|
||||
}
|
||||
#player-title { min-width: 200px; max-width: 40vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
#player audio { flex: 1; max-width: 640px; height: 36px; }
|
||||
main { padding-bottom: 90px; }
|
||||
#toast.ok { background: #3f9d5f; }
|
||||
dialog {
|
||||
background: var(--panel); color: var(--text); border: 1px solid var(--border);
|
||||
@@ -76,7 +84,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>furumi-fd</h1><span>менеджер музыкальной библиотеки (только индекс — файлы не трогает)</span></header>
|
||||
<header><h1>furumi-fd</h1><span>менеджер музыкальной библиотеки (индекс + прослушивание; файлы никогда не изменяются)</span></header>
|
||||
<nav id="tabs">
|
||||
<button data-tab="artists" class="active">Артисты</button>
|
||||
<button data-tab="releases">Релизы</button>
|
||||
@@ -190,12 +198,17 @@
|
||||
<span id="fed-search-meta" style="color:var(--muted);align-self:center"></span>
|
||||
</div>
|
||||
<table><thead><tr>
|
||||
<th>Тип</th><th>Название</th><th>Артисты</th><th class="num">Год</th><th>Детали</th><th>Владелец</th>
|
||||
<th>Тип</th><th>Название</th><th>Артисты</th><th class="num">Год</th><th>Детали</th><th>Владелец</th><th></th>
|
||||
</tr></thead><tbody id="fed-results-body"></tbody></table>
|
||||
<div class="empty" id="fed-results-empty" hidden>Ничего не найдено</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="player" hidden>
|
||||
<span id="player-title" class="muted"></span>
|
||||
<audio id="player-audio" controls></audio>
|
||||
</div>
|
||||
|
||||
<div id="toast"></div>
|
||||
<dialog id="detail-dialog">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
@@ -250,13 +263,55 @@ async function showDetail(title, path) {
|
||||
} catch (err) { toast(err.message); }
|
||||
}
|
||||
|
||||
function rowButtons(kind, id) {
|
||||
return `<td class="actions">
|
||||
function rowButtons(kind, id, extra = '') {
|
||||
return `<td class="actions">${extra}
|
||||
<button class="btn secondary small" onclick="viewItem('${kind}', ${id})">инфо</button>
|
||||
<button class="btn danger" onclick="deleteItem('${kind}', ${id})">удалить</button>
|
||||
</td>`;
|
||||
}
|
||||
|
||||
function escAttr(value) {
|
||||
return esc(value).replaceAll('"', '"');
|
||||
}
|
||||
|
||||
// ---------- player ----------
|
||||
function playUrl(url, title) {
|
||||
document.getElementById('player').hidden = false;
|
||||
document.getElementById('player-title').textContent = title;
|
||||
const audio = document.getElementById('player-audio');
|
||||
audio.src = url;
|
||||
audio.play().catch(err => toast('Не удалось запустить воспроизведение: ' + err.message));
|
||||
}
|
||||
|
||||
document.getElementById('player-audio').addEventListener('error', async () => {
|
||||
const audio = document.getElementById('player-audio');
|
||||
if (!audio.src) return;
|
||||
// The stream endpoint reports problems as JSON; surface them.
|
||||
try {
|
||||
const res = await fetch(audio.src, { headers: { range: 'bytes=0-' } });
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
toast(data.error || `Ошибка воспроизведения (${res.status})`);
|
||||
return;
|
||||
}
|
||||
} catch { /* fall through to the generic message */ }
|
||||
toast('Ошибка воспроизведения: формат не поддерживается браузером или поток оборвался');
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
const local = event.target.closest('button.play-track');
|
||||
if (local) {
|
||||
playUrl(`${API}/tracks/${local.dataset.id}/audio`, local.dataset.title);
|
||||
return;
|
||||
}
|
||||
const fed = event.target.closest('button.play-fed');
|
||||
if (fed) {
|
||||
const params = new URLSearchParams({ owner: fed.dataset.owner, item_id: fed.dataset.item });
|
||||
const from = fed.dataset.own === '1' ? 'своя библиотека' : `пир ${fed.dataset.owner.slice(0, 12)}…`;
|
||||
playUrl(`${API}/federation/stream?${params}`, `${fed.dataset.title} — ${from}`);
|
||||
}
|
||||
});
|
||||
|
||||
const KIND_PATHS = { artist: 'artists', release: 'releases', track: 'tracks', file: 'media-files' };
|
||||
const KIND_NAMES = { artist: 'Артист', release: 'Релиз', track: 'Трек', file: 'Файл' };
|
||||
const KIND_CONFIRM = {
|
||||
@@ -355,7 +410,8 @@ async function loadTracks() {
|
||||
<td class="muted">${esc(releaseTitles[t.release_id] ?? '#' + t.release_id)}</td>
|
||||
<td class="num">${t.track_number ?? ''}</td>
|
||||
<td class="num">${fmtDuration(t.duration_seconds)}</td>
|
||||
${rowButtons('track', t.id)}
|
||||
${rowButtons('track', t.id,
|
||||
`<button class="btn secondary small play-track" data-id="${t.id}" data-title="${escAttr(t.title)}">▶</button>`)}
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
@@ -511,6 +567,9 @@ async function fedSearch() {
|
||||
<td class="num">${r.year ?? ''}</td>
|
||||
<td class="muted">${esc(fedDetails(r))}</td>
|
||||
<td class="muted" title="${esc(r.owner)}">${r.own ? 'вы' : esc(shortId(r.owner))}</td>
|
||||
<td class="actions">${r.kind === 'track'
|
||||
? `<button class="btn secondary small play-fed" data-owner="${escAttr(r.owner)}" data-item="${escAttr(r.id)}" data-own="${r.own ? 1 : 0}" data-title="${escAttr(r.name)}">▶</button>`
|
||||
: ''}</td>
|
||||
</tr>`);
|
||||
meta.textContent = `узлов опрошено: ${data.queried_nodes}, ${data.duration_ms} мс`;
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user