dhtml5_player (arkenidar/dirlist-suite) — lo stesso player che hai visto su github.arkenidar.comdocument.addEventListener("DOMContentLoaded", player_initialize)
Niente bundler, niente build step: un solo <script src="player.js"> nella pagina, che aspetta che il DOM sia pronto e poi parte. È la stessa filosofia "esplicita, niente magia" già vista nel PHP — qui applicata al frontend.
var links = document.querySelectorAll("a[href]")
const video_extensions = ["mp4", "webm"]
const audio_extensions = ["mp3", "3gp", "ogg", "opus", "m4a", "flac", "wav", "mid", "xm"]
const playable_extensions = audio_extensions.concat(video_extensions)
const linksFilterCriterion = link =>
playable_extensions.some(ext => link.href.endsWith(`.${ext}`))
links = Array.from(links).filter(linksFilterCriterion)
Pattern chiave: invece di costruire la playlist a mano, il player scansiona la pagina e riconosce quali link <a> puntano a file riproducibili, in base all'estensione. La pagina HTML resta semplice (solo link), tutta l'intelligenza è nello script.
if (audio_extensions.some(ext => link.href.endsWith(`.${ext}`))) {
link.dataset.mediaType = "audio"
}
if (video_extensions.some(ext => link.href.endsWith(`.${ext}`))) {
link.dataset.mediaType = "video"
}
element.dataset è l'API per leggere/scrivere attributi data-* sull'HTML. Qui serve a "etichettare" ogni link col tipo di media, così il resto del codice può decidere quale player (audio/video) mostrare senza ricontrollare l'estensione ogni volta.
const download_link = document.createElement("a")
download_link.download = ""
download_link.classList.add("download_link")
download_link.innerText = "⬇ copy"
download_link.href = link.href
link.after(download_link)
createElement + after() costruiscono e inseriscono nodi DOM al volo — qui un link di download accanto a ogni traccia, generato per ciascun elemento della playlist senza toccare l'HTML originale.
link.next_link = links[link_index + 1]
link.onclick = player_link_click
function player_link_click(event) {
const current_link_or_null = typeof current_link != "undefined" ? current_link : null;
const same_link_clicked = this == current_link_or_null;
const something_playing = !player_audio.paused || !player_video.paused
if (same_link_clicked && something_playing) {
player_audio.pause(); player_video.pause();
} else link_play(event.currentTarget);
return !player_intercept.checked;
}
Nota di stile: current_link è una variabile globale implicita (mai dichiarata con let/const in questo scope), condivisa tra tutte le funzioni del file. È una scelta pragmatica da script "single file, single purpose" — diversa da come strutturerebbe un modulo ES6 con stato incapsulato, ma coerente con la semplicità voluta qui.
if (typeof current_link != "undefined")
current_link.classList.remove("current_link")
current_link = link
current_link.classList.add("current_link")
playing.innerText = link.innerText
document.title = link.innerText
classList.add/remove per evidenziare il link attivo, innerText per aggiornare sia un'area di stato in pagina (playing) sia il titolo del tab del browser — un piccolo dettaglio UX che costa tre righe.
if (media_type == "audio") {
player_video.pause()
player_video.style.display = "none"
player_audio.style.display = "block"
player_active = player_audio
} else if (media_type == "video") {
player_audio.pause()
player_audio.style.display = "none"
player_video.style.display = "block"
player_active = player_video
}
Mostra/nasconde i due elementi <audio>/<video> già presenti nell'HTML invece di crearli e distruggerli — più semplice e meno soggetto a race condition rispetto a costruire l'elemento media al volo.
function json_link() {
var json_data = { keyword: current_link.href, after: after.value }
var URI = '#' + 'JSON:' + JSON.stringify(json_data)
link_to_current_playable.href = encodeURI(URI)
}
Idea elegante: lo stato corrente del player (cosa sta suonando, cosa fare dopo) viene serializzato in JSON e infilato nell'hash dell'URL. Il link "Link to the current playable media content" diventa quindi condivisibile — chi lo apre riparte dallo stesso punto, senza nessun backend coinvolto.
if (location.hash.startsWith("#")) {
var hash = decodeURIComponent(location.hash.slice(1))
if (hash.startsWith("JSON:")) {
hash = hash.slice(5)
try {
var hash_json = JSON.parse(hash)
if (typeof hash_json.keyword != "undefined") {
first_link_override = links.filter(l => l.href.includes(hash_json.keyword))[0]
}
} catch (error) { console.error(hash, error) }
}
}
Simmetrico alla slide precedente: al caricamento, se l'URL contiene quello stato JSON, lo riapplica trovando il link corrispondente. Il try/catch attorno al JSON.parse protegge da URL malformati senza far crashare il player.
function download_links_display(shown) {
localStorage.download_links_display = shown
const verb = shown ? "remove" : "add"
document.body.classList[verb]("download_links_hide")
}
localStorage ricorda una preferenza (mostrare o no i link di download) tra una visita e l'altra — senza account, senza server, senza cookie. classList[verb] è un piccolo trucco: sceglie dinamicamente il metodo (add o remove) da chiamare in base a una stringa.
function on_media_end() {
link_play({
repeat: current_link,
next: current_link.next_link,
playlist: current_link.next_link || first_link_in_playlist,
}[after.value])
}
Invece di uno switch, un oggetto letterale usato come tabella di lookup: la chiave è il valore della <select> "After this playable media do...", il valore è il link da riprodurre dopo. Più compatto di un if/else if a catena, e facile da estendere aggiungendo una chiave.
utente clicca un link <a>
→ player_link_click(event)
→ stesso link e qualcosa in riproduzione? → pausa
→ altrimenti → link_play(link)
→ aggiorna classList, "playing", document.title
→ mostra audio o video player in base a dataset.mediaType
→ imposta .src se necessario, poi .play()
→ json_link() aggiorna l'hash dell'URL
→ al termine del media → on_media_end()
→ legge "after" (next/repeat/stop/playlist) → richiama link_play()
Tutto il comportamento del player nasce dalla combinazione di poche primitive DOM (querySelectorAll, dataset, classList, createElement, addEventListener implicito via onclick) più due piccoli "database" client-side: location.hash per lo stato condivisibile, localStorage per le preferenze.
Questo è "DHTML5" applicato nel senso che intendi tu: HTML minimale (solo una lista di link), tutto il comportamento aggiunto via JS vanilla che legge e modifica il DOM esistente — nessun framework, nessuna build, un file che si legge dall'inizio alla fine.
Il repository: github.com/arkenidar/dirlist-suite (percorso: web/dhtml/dhtml5_player/)