4. Distribució - maarcnavarro9/WorldAnimals GitHub Wiki
Processament de vídeos per especificar-ne els keyframes
ffmpeg -i WorldAnimalsV1_4K.mp4 -filter_complex "[0:v]split=4[v1][v2][v3][v4]; [v1]scale=w=3840:h=2160[v1out]; [v2]scale=w=1920:h=1080[v2out]; [v3]scale=w=1280:h=720[v3out]; [v4]scale=w=640:h=360[v4out]" -map "[v1out]" -c:v libx264 -crf 23 -b:v 15M -maxrate 15M -bufsize 30M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_4k.mp4 -map "[v2out]" -c:v libx264 -crf 23 -b:v 7M -maxrate 7M -bufsize 14M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_1080p.mp4 -map "[v3out]" -c:v libx264 -crf 23 -b:v 3M -maxrate 3M -bufsize 6M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_720p.mp4 -map "[v4out]" -c:v libx264 -crf 23 -b:v 1M -maxrate 1M -bufsize 2M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_360p.mp4 -map a:0 -c:a aac -b:a 320k -ac 2 audio_320k.aac -map a:0 -c:a aac -b:a 128k -ac 2 audio_128k.aac
ffmpeg -i WorldAnimalsV2_4K.mp4 -filter_complex "[0:v]split=4[v1][v2][v3][v4]; [v1]scale=w=3840:h=2160[v1out]; [v2]scale=w=1920:h=1080[v2out]; [v3]scale=w=1280:h=720[v3out]; [v4]scale=w=640:h=360[v4out]" -map "[v1out]" -c:v libx264 -crf 23 -b:v 15M -maxrate 15M -bufsize 30M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_4k.mp4 -map "[v2out]" -c:v libx264 -crf 23 -b:v 7M -maxrate 7M -bufsize 14M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_1080p.mp4 -map "[v3out]" -c:v libx264 -crf 23 -b:v 3M -maxrate 3M -bufsize 6M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_720p.mp4 -map "[v4out]" -c:v libx264 -crf 23 -b:v 1M -maxrate 1M -bufsize 2M -preset slow -g 30 -sc_threshold 0 -keyint_min 30 video_360p.mp4 -map a:0 -c:a aac -b:a 320k -ac 2 audio_320k.aac -map a:0 -c:a aac -b:a 128k -ac 2 audio_128k.aac
Streaming adaptatiu
mp4box -dash 4000 -profile onDemand .\video_4k.mp4 .\video_1080p.mp4 .\video_720p.mp4 .\video_360p.mp4 .\audio_320k.aac .\audio_128k.aac -out out/manifest.mpd --dual --cmaf
<video style="width: 800px" controls></video>
<script src="//cdn.dashjs.org/latest/dash.all.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
init();
});
function init() {
var video, player, url = "media/manifest.mpd";
video = document.querySelector("video");
player = dashjs.MediaPlayer().create();
player.initialize(video, url, true);
}
</script>
<video style="width: 800px" controls></video>
<script src="//cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
init();
});
function init() {
var video, player, url = "media/mp4box/manifest.m3u8";
if (Hls.isSupported()) {
video = document.querySelector('video’);
player = new Hls();
player.on(Hls.Events.MEDIA_ATTACHED, function () { /* ... */ });
player.loadSource(url);
player.attachMedia(video);
}
}
</script>
S'han pujat els dos vídeos a Theta Edgecloud
Omple el selector de qualitats de vídeo disponibles per DASH
function populateDashQualities() {
const sel = document.getElementById('qualitySelector');
sel.querySelectorAll('option:not([value="-1"])').forEach(o => o.remove());
dashPlayer.getRepresentationsByType('video').forEach((b, i) => {
const o = document.createElement('option');
o.value = i;
o.text = `${b.height}p`;
sel.appendChild(o);
});
}
Omple el selector de qualitats de vídeo disponibles per HLS
function populateHlsQualities() {
const sel = document.getElementById('qualitySelector');
sel.querySelectorAll('option:not([value="-1"])').forEach(o => o.remove());
hlsPlayer.levels.forEach((lvl, i) => {
const o = document.createElement('option');
o.value = i;
o.text = `${lvl.height}p`;
sel.appendChild(o);
});
}
Omple el selector d'àudio amb les diferents pistes disponibles en DASH
function populateDashAudioQualities() {
const sel = document.getElementById('audioSelector');
sel.querySelectorAll('option:not([value="-1"])').forEach(o => o.remove());
dashPlayer.getRepresentationsByType('audio').forEach((b, i) => {
const o = document.createElement('option');
o.value = i;
// Solo mostrar Hz si está definido
o.text = b.audioSamplingRate
? `${b.audioSamplingRate} Hz — ${(b.bandwidth / 1000).toFixed(0)} kbps`
: `${(b.bandwidth / 1000).toFixed(0)} kbps`;
sel.appendChild(o);
});
}
Omple el selector d'àudio amb les pistes disponibles en HLS
function populateHlsAudioQualities() {
const sel = document.getElementById('audioSelector');
sel.querySelectorAll('option:not([value="-1"])').forEach(o => o.remove());
hlsPlayer.audioTracks.forEach((track, i) => {
const o = document.createElement('option');
o.value = i;
o.text = track.name || `Audio Track ${i + 1}`;
sel.appendChild(o);
});
}
Permet canviar manualment la qualitat del vídeo (DASH o HLS)
function changeQuality() {
const idx = parseInt(document.getElementById('qualitySelector').value, 10);
if (dashPlayer) {
dashPlayer.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: idx === -1 } } } });
if (idx !== -1) {
dashPlayer.setRepresentationForTypeByIndex('video', idx);
}
}
if (hlsPlayer) {
hlsPlayer.currentLevel = idx;
}
}
Canviar manualment la qualitat/pista d'àudio seleccionada
function changeAudioQuality() {
const idx = parseInt(document.getElementById('audioSelector').value, 10);
if (dashPlayer) {
dashPlayer.updateSettings({ streaming: { abr: { autoSwitchBitrate: { audio: idx === -1 } } } });
if (idx !== -1) {
dashPlayer.setRepresentationForTypeByIndex('audio', idx);
}
// Mostrar info de la pista seleccionada
const reps = dashPlayer.getRepresentationsByType('audio');
console.log('DASH audio seleccionado:', idx, reps[idx]);
}
if (hlsPlayer) {
hlsPlayer.audioTrack = idx;
// Mostrar info de la pista seleccionada
const tracks = hlsPlayer.audioTracks;
console.log('HLS audio seleccionado:', idx, tracks && tracks[idx]);
}
}
Afeger pistes de subtítols i metadades al vídeo
function addTracks(video, videoNum) {
const langs = [
{ code: 'En', label: 'English', srclang: 'en' },
{ code: 'Es', label: 'Spanish', srclang: 'es' },
{ code: 'Ca', label: 'Catalan', srclang: 'ca' }
];
langs.forEach((lang, i) => {
const tr = document.createElement("track");
tr.kind = "subtitles";
tr.label = lang.label;
tr.srclang = lang.srclang;
tr.src = `./media/descriptionsV${videoNum}_${lang.code}.vtt`;
if (i === 0) tr.default = true;
video.appendChild(tr);
});
const meta = document.createElement("track");
meta.kind = "metadata";
meta.label = "Metadata";
meta.src = `./media/metadataV${videoNum}.vtt`;
video.appendChild(meta);
}
Configurar els tracks de text i activar la gestió de metadades
function setupTracksAndChapters(video, videoNum) {
addTracks(video, videoNum);
console.log('➤ Tracks añadidos para vídeo', videoNum);
const tracks = video.textTracks;
for (let i = 0; i < tracks.length; i++) {
const t = tracks[i];
console.log(` Track[${i}]: kind=${t.kind} label=${t.label} mode=${t.mode}`);
if (t.kind === 'subtitles') {
t.mode = 'disabled';
}
if (t.kind === 'metadata') {
t.mode = 'hidden';
console.log(' → Metadata track encontrada, cues totales:', t.cues.length);
t.addEventListener('cuechange', () => {
console.log(' ✶ cuechange disparado, activeCues=', t.activeCues.length);
if (t.activeCues.length > 0) {
const text = t.activeCues[0].text;
let data;
try {
data = JSON.parse(text);
} catch (e) {
console.error(' ¡JSON inválido en cue!', text);
return;
}
console.log(' → Metadata:', data);
document.getElementById('fotoAnimal').src = data.link_imagen;
document.getElementById('nombre').textContent = data.Nombre;
document.getElementById('especie').textContent = data.Especie;
document.getElementById('descripcion').textContent = data.Descripcion;
if (data.coordenadas_geográficas) {
const lat = +data.coordenadas_geográficas.latitud;
const lng = +data.coordenadas_geográficas.longitud;
console.log(` → Centrar mapa en ${lat},${lng}`);
mapa.setCenter({ lat, lng });
marcador.setPosition({ lat, lng });
}
const allMetadata = Array.from(t.cues).map(c => {
try { return JSON.parse(c.text); }
catch { return null; }
}).filter(x => x);
console.log(' → Generando botones para', allMetadata.length, 'capítulos');
generarBotonesCapitulos(allMetadata);
}
});
}
}
}
Crear botons per navegar entre capítols definits en les metadades
function generarBotonesCapitulos(data) {
const container = document.getElementById('capitulos');
container.innerHTML = '';
data.forEach((item, i) => {
const b = document.createElement('button');
b.textContent = item.Nombre;
b.onclick = () => {
const video = document.getElementById('myVideo');
const metadataTrack = Array.from(video.textTracks).find(track => track.kind === 'metadata');
if (!metadataTrack) {
console.error('Pista metadata no encontrada');
return;
}
const cue = metadataTrack.cues[i];
if (cue) {
video.currentTime = cue.startTime - 0.001;
video.play();
}
};
container.appendChild(b);
});
}