if (!diagnose()) log('cannot start, see warnings under the player');
$('log').innerHTML = '';
markCurrentRow();
- const asset = encodeURIComponent($('asset').value);
+ const asset = encodeURIComponent(assetId());
if ($('proto').value === 'hls') {
const url = `${base()}/hls/${asset}/master.m3u8`;
log('HLS ' + url);
@@ -197,28 +197,45 @@
segmentor control panel
}
/// Plays a row's asset id when a cache or catalog row is clicked, so browsing needs no typing.
-function playAsset(assetId) {
- $('asset').value = assetId;
+function playAsset(id) {
+ selectAsset(id);
load();
}
function markCurrentRow() {
- const current = $('asset').value;
+ const current = assetId();
for (const row of document.querySelectorAll('#cache tbody tr'))
row.classList.toggle('current', row.dataset.asset === current);
- // The picker only ever shows a known asset; typing a custom name falls back to its
- // placeholder rather than pretending one of the listed assets is selected.
- const picker = $('asset-picker');
- picker.value = [...picker.options].some(o => o.value === current) ? current : '';
}
-/// The dropdown next to the Asset field: every asset ID the cache table lists. Picking one plays
-/// it; typing a name that isn't listed (a mapper asset nobody has requested yet, for example)
-/// keeps working in the text field regardless, since the picker is a convenience, not a gate.
-function renderAssetPicker(ids) {
- $('asset-picker').innerHTML = ''
- + ids.map(id => ``).join('');
- markCurrentRow();
+// ---------- asset dropdown ----------
+// Every asset the server can name (its static catalog, or a mapper's optional listing) plus
+// whatever it has loaded, then "Custom…" for any other name. Asset IDs are never empty, so the
+// empty value marks "Custom…" without colliding with one.
+const assetId = () => $('asset-pick').value || $('asset').value.trim();
+
+function showCustomField() {
+ $('asset-custom').hidden = $('asset-pick').value !== '';
+}
+
+/// Rebuilds the options from `ids`, keeping the current choice: a selected asset stays selected
+/// even when the list no longer names it, and "Custom…" stays chosen with its text intact.
+function renderAssetOptions(ids) {
+ const pick = $('asset-pick'), selected = pick.value;
+ const all = [...new Set(selected ? [...ids, selected] : ids)].sort();
+ pick.innerHTML = all.map(id => ``).join('')
+ + '';
+ pick.value = selected;
+ showCustomField();
+}
+
+/// Selects `id`, adding it to the dropdown if it is not listed yet.
+function selectAsset(id) {
+ const pick = $('asset-pick');
+ if (![...pick.options].some(option => option.value === id))
+ pick.add(new Option(id, id), pick.options.length - 1);
+ pick.value = id;
+ showCustomField();
}
function renderPlayerStats() {
@@ -349,7 +366,8 @@
segmentor control panel
: '';
for (const row of $('cache').tBodies[0].rows)
row.onclick = () => playAsset(row.dataset.asset);
- renderAssetPicker([...rows.keys()].sort());
+ renderAssetOptions([...rows.keys()]);
+ markCurrentRow();
}
function escapeHtml(s) {
@@ -406,7 +424,11 @@
Server
- Asset
+Asset
+
+
+ Name Protocol
@@ -48,7 +48,6 @@
segmentor demo
-
@@ -106,23 +105,45 @@
Resolver & cache
const tile = (k, v, cls='') => `
${k}
${v}
`;
const escapeHtml = s => String(s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c]));
-// ---------- assets to try ----------
-// A quick way in besides typing a name: whatever the static catalog lists (if any) plus whatever
-// has already been played (any resolver). /admin/status has the same trust model as /metrics,
-// which this page already reads, so nothing new is exposed by also reading this.
-async function refreshTryRow() {
+// ---------- asset dropdown ----------
+// Every asset the server can name (its static catalog, or a mapper's optional listing) plus
+// whatever it has loaded, then "Custom…" for any other name. Asset IDs are never empty, so the
+// empty value marks "Custom…" without colliding with one.
+const assetId = () => $('asset-pick').value || $('asset').value.trim();
+
+function showCustomField() {
+ $('asset-custom').hidden = $('asset-pick').value !== '';
+}
+
+/// Rebuilds the options from `ids`, keeping the current choice: a selected asset stays selected
+/// even when the list no longer names it, and "Custom…" stays chosen with its text intact.
+function renderAssetOptions(ids) {
+ const pick = $('asset-pick'), selected = pick.value;
+ const all = [...new Set(selected ? [...ids, selected] : ids)].sort();
+ pick.innerHTML = all.map(id => ``).join('')
+ + '';
+ pick.value = selected;
+ showCustomField();
+}
+
+/// Selects `id`, adding it to the dropdown if it is not listed yet.
+function selectAsset(id) {
+ const pick = $('asset-pick');
+ if (![...pick.options].some(option => option.value === id))
+ pick.add(new Option(id, id), pick.options.length - 1);
+ pick.value = id;
+ showCustomField();
+}
+
+// /admin/status has the same trust model as /metrics, which this page already reads, so nothing
+// new is exposed by also reading it.
+async function refreshAssets() {
try {
const status = await (await fetch(base() + '/admin/status')).json();
- const ids = [...new Set([...(status.resolver.known_assets || []), ...status.cache.assets.map(a => a.asset_id)])].sort();
- const row = $('try-row');
- if (!ids.length) { row.textContent = ''; return; }
- const current = $('asset').value;
- row.innerHTML = 'Try: ' + ids.map(id =>
- ``).join(' ');
- for (const chip of row.querySelectorAll('.chip')) chip.onclick = () => { $('asset').value = chip.dataset.asset; load(); };
- } catch { /* the try row is a convenience; leave it as it was if the origin is unreachable */ }
+ renderAssetOptions([...(status.resolver.known_assets || []), ...status.cache.assets.map(a => a.asset_id)]);
+ } catch { /* the list is a convenience; keep it as it was if the origin is unreachable */ }
}
-setInterval(refreshTryRow, 10000);
+setInterval(refreshAssets, 10000);
// ---------- player ----------
function teardown() {
@@ -149,7 +170,7 @@
Resolver & cache
teardown();
if (!diagnose()) log('cannot start, see warnings under the player');
$('log').innerHTML = '';
- const asset = encodeURIComponent($('asset').value);
+ const asset = encodeURIComponent(assetId());
if ($('proto').value === 'hls') {
const url = `${base()}/hls/${asset}/master.m3u8`;
log('HLS ' + url);
@@ -300,8 +321,11 @@