Media Source Extensions (MSE) è un'API JavaScript che consente di creare stream per la riproduzione da segmenti di audio o video. Anche se non è trattato in questo articolo, è necessario conoscere MSE se vuoi incorporare nel tuo sito video che, ad esempio:
- Streaming adattivo, ovvero un altro modo per dire adattamento alle funzionalità del dispositivo e alle condizioni della rete
- Splicing adattivo, ad esempio l'inserimento di annunci
- Spostamento temporale
- Controllo delle prestazioni e delle dimensioni del download
L'MSE è quasi come una catena. Come illustrato in figura, tra il file scaricato e gli elementi multimediali ci sono diversi livelli.
- Un elemento
<audio>
o<video>
per riprodurre i contenuti multimediali. - Un'istanza
MediaSource
con unSourceBuffer
per alimentare l'elemento multimediale. - Una chiamata
fetch()
o XHR per recuperare i dati multimediali in un oggettoResponse
. - Una chiamata a
Response.arrayBuffer()
per nutrireMediaSource.SourceBuffer
.
In pratica, la catena si presenta così:
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Se riesci a capire cosa fare dalle spiegazioni finora fornite, puoi interrompere la lettura. Per una spiegazione più dettagliata, continua a leggere. Analizzerò questa catena creando un esempio di MSE di base. Ogni passaggio di compilazione aggiungerà codice al passaggio precedente.
Una nota sulla chiarezza
Questo articolo ti fornirà tutte le informazioni necessarie per riprodurre contenuti multimediali su una pagina web? No, ha lo scopo di aiutarti a comprendere il codice più complicato che potresti trovare altrove. Per maggiore chiarezza, questo documento semplifica ed esclude molte cose. Pensiamo di poter fare l'una sull'altra, perché consigliamo anche di usare una libreria come Shaka Player di Google. Farò notare dove sto semplificando deliberatamente.
Aspetti non coperti
Ecco, in nessun ordine particolare, alcune cose che non tratterò.
- Controlli di riproduzione Li riceviamo senza costi grazie all'utilizzo degli elementi HTML5
<audio>
e<video>
. - Gestione degli errori:
Per l'utilizzo in ambienti di produzione
Ecco alcuni consigli per l'utilizzo in produzione delle API correlate a MSE:
- Prima di effettuare chiamate a queste API, gestisci eventuali eventi di errore o eccezioni API e controlla
HTMLMediaElement.readyState
eMediaSource.readyState
. Questi valori possono cambiare prima che gli eventi associati vengano pubblicati. - Assicurati che le chiamate
appendBuffer()
eremove()
precedenti non siano ancora in corso controllando il valore booleanoSourceBuffer.updating
prima di aggiornaremode
,timestampOffset
,appendWindowStart
,appendWindowEnd
diSourceBuffer
o di chiamareappendBuffer()
oremove()
suSourceBuffer
. - Per tutte le istanze
SourceBuffer
aggiunte aMediaSource
, assicurati che nessuno dei valoriupdating
sia true prima di chiamareMediaSource.endOfStream()
o aggiornareMediaSource.duration
. - Se il valore
MediaSource.readyState
èended
, chiamate comeappendBuffer()
eremove()
o l'impostazione diSourceBuffer.mode
oSourceBuffer.timestampOffset
causeranno la transizione di questo valore aopen
. Ciò significa che devi essere preparato a gestire più eventisourceopen
. - Quando gestisci gli eventi
HTMLMediaElement error
, i contenuti diMediaError.message
possono essere utili per determinare la causa principale dell'errore, soprattutto per gli errori difficili da riprodurre negli ambienti di test.
Collegamento di un'istanza MediaSource a un elemento multimediale
Come per molti aspetti dello sviluppo web di questi tempi, si inizia con il rilevamento
delle funzionalità. Poi, recupera un elemento multimediale, che può essere un elemento <audio>
o <video>
.
Infine, crea un'istanza di MediaSource
. Viene trasformato in un URL e passato
all'attributo source dell'elemento multimediale.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
// Is the MediaSource instance ready?
} else {
console.log('The Media Source Extensions API is not supported.');
}
Il fatto che un oggetto MediaSource
possa essere passato a un attributo src
potrebbe sembrare un po' strano. Di solito sono stringhe, ma possono anche essere blob.
Se esamini una pagina con contenuti multimediali incorporati ed esamini il relativo elemento multimediale, capirai cosa intendo.
L'istanza MediaSource è pronta?
URL.createObjectURL()
è sincrono, ma elabora l'allegato in modo asincrono. Ciò causa un leggero ritardo prima che tu possa eseguire qualsiasi operazione con l'istanza MediaSource
. Fortunatamente, esistono dei modi per verificare.
Il modo più semplice è utilizzare una proprietà MediaSource
denominata readyState
. La proprietà readyState
descrive la relazione tra un'istanza MediaSource
e un elemento multimediale. Può avere uno dei seguenti valori:
closed
: l'istanzaMediaSource
non è collegata a un elemento multimediale.open
: l'istanzaMediaSource
è collegata a un elemento multimediale ed è pronta a ricevere dati o sta ricevendo dati.ended
: l'istanzaMediaSource
è collegata a un elemento multimediale e tutti i suoi dati sono stati passati a quell'elemento.
Eseguire query direttamente su queste opzioni può influire negativamente sul rendimento. Fortunatamente,
MediaSource
attiva anche eventi quando readyState
cambia, in particolare
sourceopen
, sourceclosed
, sourceended
. Per l'esempio che sto creando, utilizzerò l'evento sourceopen
per dirmi quando recuperare e buffering
il video.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
<strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
console.log("The Media Source Extensions API is not supported.")
}
<strong>function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
// Create a SourceBuffer and get the media file.
}</strong>
Tieni presente che ho chiamato anche revokeObjectURL()
. So che sembra prematuro, ma posso farlo in qualsiasi momento dopo che l'attributo src
dell'elemento multimediale è stato collegato a un'istanza MediaSource
. La chiamata di questo metodo non distrugge alcun
oggetto. Consente alla piattaforma di gestire la raccolta dei rifiuti in un momento appropriato, motivo per cui la richiamo immediatamente.
Creare un SourceBuffer
Ora è il momento di creare SourceBuffer
, l'oggetto che effettivamente si occupa di trasferire i dati tra origini multimediali ed elementi multimediali. Un valore SourceBuffer
deve essere specifico per il tipo di file multimediale che stai caricando.
In pratica, puoi farlo chiamando addSourceBuffer()
con il valore
appropriato. Tieni presente che nell'esempio seguente la stringa del tipo MIME contiene un tipo MIME e due codec. Si tratta di una stringa MIME per un file video, ma utilizza codec distinti per le parti video e audio del file.
La versione 1 della specifica MSE consente agli user agent di differire in base al fatto che richiedono o meno sia un tipo MIME sia un codec. Alcuni user agent non richiedono, ma consentono solo il tipo MIME. Alcuni user agent, ad esempio Chrome, richiedono un codec per i tipi MIME che non descrivono autonomamente i propri codec. Piuttosto che cercare di risolvere tutti i problemi, è meglio includere entrambi.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
<strong>
var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
the mediaSource instance. // Store it in a variable so it can be used in a
closure. var mediaSource = e.target; var sourceBuffer =
mediaSource.addSourceBuffer(mime); // Fetch and process the video.
</strong>;
}
Recuperare il file multimediale
Se cerchi esempi di MSE su internet, ne troverai molti che recuperano
i file multimediali utilizzando XHR. Per essere più all'avanguardia, utilizzerò l'API Fetch e la promessa che restituisce. Se stai cercando di farlo in Safari, non funzionerà senza un
polyfill fetch()
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
<strong>
fetch(videoUrl) .then(function(response){' '}
{
// Process the response object.
}
);
</strong>;
}
Un player di qualità di produzione avrebbe lo stesso file in più versioni per supportare browser diversi. Potrebbe utilizzare file separati per audio e video per consentire la selezione dell'audio in base alle impostazioni della lingua.
Il codice reale avrebbe anche più copie dei file multimediali a risoluzioni diverse, in modo da potersi adattare alle diverse funzionalità dei dispositivi e alle condizioni della rete. Questa applicazione è in grado di caricare e riprodurre i video in blocchi utilizzando richieste di intervallo o segmenti. Ciò consente l'adattamento alle condizioni della rete durante la riproduzione di contenuti multimediali. Potresti aver sentito i termini DASH o HLS, che sono due metodi per farlo. Una discussione completa di questo argomento esula dall'ambito di questa introduzione.
Elabora l'oggetto di risposta
Il codice sembra quasi completo, ma i contenuti multimediali non vengono riprodotti. Dobbiamo trasferire i dati media dall'oggetto Response
all'oggetto SourceBuffer
.
Il modo tipico per passare i dati dall'oggetto risposta all'istanza MediaSource
è ottenere un ArrayBuffer
dall'oggetto risposta e passarlo a
SourceBuffer
. Inizia chiamando response.arrayBuffer()
, che restituisce una promessa al buffer. Nel mio codice, ho passato questa promessa a una seconda clausola then()
in cui la aggiungo alla SourceBuffer
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
<strong>return response.arrayBuffer();</strong>
})
<strong>.then(function(arrayBuffer) {
sourceBuffer.appendBuffer(arrayBuffer);
});</strong>
}
Chiama endOfStream()
Dopo aver aggiunto tutti i ArrayBuffers
e non sono previsti ulteriori dati multimediali, chiama
MediaSource.endOfStream()
. In questo modo, MediaSource.readyState
diventerà
ended
e verrà attivato l'evento sourceended
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
<strong>sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});</strong>
sourceBuffer.appendBuffer(arrayBuffer);
});
}
La versione finale
Ecco l'esempio di codice completo. Spero che tu abbia imparato qualcosa su Media Source Extensions.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}