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 comprendere la MSE se vuoi incorporare nel tuo sito video che includono:
- Streaming adattivo, ovvero un altro modo per adattarsi alle funzionalità del dispositivo e alle condizioni della rete
- Accoppiamento adattivo, ad esempio l'inserimento di annunci
- Variazione temporale
- Controllo del rendimento e delle dimensioni del download
Possiamo quasi pensare a MSE come a una catena. Come illustrato nella figura, tra il file scaricato e gli elementi multimediali sono presenti diversi livelli.
- Un elemento
<audio>
o<video>
per riprodurre i contenuti multimediali. - Un'istanza
MediaSource
con unSourceBuffer
per il feed dell'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 ha il seguente aspetto:
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 risolvere il problema tra le spiegazioni fornite finora, non esitare a smettere di leggere ora. Per una spiegazione più dettagliata, continua a leggere. Analizzerò questa catena creando un esempio di MSE di base. Ciascuno dei passaggi di build aggiungerà codice al passaggio precedente.
Una nota sulla chiarezza
Questo articolo ti spiega tutto ciò che devi sapere sulla riproduzione di contenuti multimediali su una pagina web? No, è destinata solo ad aiutarti a comprendere codice più complicato che potresti trovare altrove. Per maggiore chiarezza, questo documento semplifica ed esclude molti aspetti. Riteniamo che possiamo farla franca perché ti consigliamo di usare anche una libreria come Google's Shaka Player. Ne avrò volutamente nota la semplificazione.
Alcuni aspetti non trattati
Qui, senza ordine particolare, ci sono alcuni degli aspetti che non tratterò.
- Controlli di riproduzione. Le ottieni senza costi grazie all'utilizzo degli elementi HTML5
<audio>
e<video>
. - Gestione degli errori:
Per l'utilizzo in ambienti di produzione
Ecco alcuni suggerimenti per un utilizzo in produzione di API relative a MSE:
- Prima di effettuare chiamate su queste API, gestisci eventuali eventi di errore o eccezioni alle API e controlla i criteri
HTMLMediaElement.readyState
eMediaSource.readyState
. Questi valori possono cambiare prima della pubblicazione degli eventi associati. - Assicurati che le chiamate
appendBuffer()
eremove()
precedenti non siano ancora in corso controllando il valore booleanoSourceBuffer.updating
prima di aggiornaremode
,timestampOffset
,appendWindowStart
,appendWindowEnd
eSourceBuffer
diSourceBuffer
o chiamareappendBuffer()
oremove()
suSourceBuffer
. - Per tutte le istanze
SourceBuffer
aggiunte aMediaSource
, assicurati che nessuno dei rispettivi valoriupdating
sia vero prima di chiamareMediaSource.endOfStream()
o aggiornareMediaSource.duration
. - Se il valore di
MediaSource.readyState
èended
, chiamate comeappendBuffer()
eremove()
o l'impostazione diSourceBuffer.mode
oSourceBuffer.timestampOffset
causeranno la transizione di questo valore aopen
. Ciò significa che dovresti essere pronto a gestire più eventisourceopen
. - Quando gestisci gli eventi
HTMLMediaElement error
, i contenuti diMediaError.message
possono essere utili per determinare la causa principale dell'errore, in particolare per gli errori difficili da riprodurre negli ambienti di test.
Collega un'istanza MediaSource a un elemento multimediale
Come per molte altre cose nello sviluppo web oggigiorno, il rilevamento delle funzionalità inizia. A questo punto, ottieni un elemento multimediale, che sia <audio>
o <video>
.
Infine, crea un'istanza di MediaSource
. Viene trasformato in un URL e trasmesso
all'attributo sorgente 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 trasmesso 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()
è a sua volta sincrono, ma elabora l'allegato in modo asincrono. Questo causa un leggero ritardo prima di poter eseguire qualsiasi operazione con l'istanza MediaSource
. Fortunatamente, esistono dei modi per farlo.
Il modo più semplice consiste nell'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 o ricevere dati.ended
. L'istanzaMediaSource
è collegata a un elemento multimediale e tutti i suoi dati sono stati trasmessi a questo elemento.
L'esecuzione di query dirette su queste opzioni può influire negativamente sulle prestazioni. Fortunatamente,
MediaSource
attiva anche gli eventi quando readyState
cambia, in particolare
sourceopen
, sourceclosed
, sourceended
. Per l'esempio che sto creando, utilizzerò l'evento sourceopen
per indicarmi quando recuperare e eseguire il buffering del 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>
Noterai che ho chiamato anche revokeObjectURL()
. So che potrebbe sembrare prematuro, ma posso farlo in qualsiasi momento dopo che l'attributo src
dell'elemento multimediale è stato connesso a un'istanza MediaSource
. La chiamata di questo metodo non elimina alcun oggetto. Consente alla piattaforma di gestire la garbage collection in un momento appropriato, motivo per cui la chiamo immediatamente.
Crea un buffer di origine
Ora è il momento di creare SourceBuffer
, l'oggetto che di fatto esegue
il trasferimento dei dati tra le origini multimediali e gli elementi multimediali. Un elemento SourceBuffer
deve essere specifico per il tipo di file multimediale che stai caricando.
In pratica, puoi eseguire questa operazione chiamando addSourceBuffer()
con il valore
appropriato. Nota che nell'esempio seguente la stringa di tipo MIME contiene un tipo MIME e due codec. Questa è una stringa MIME per un file video, ma utilizza codec separati per le parti audio e video.
La versione 1 della specifica MSE consente agli user agent di differire in base alla necessità o meno di un tipo MIME e un codec. Alcuni user agent non richiedono, ma consentono solo il tipo MIME. Alcuni user agent, Chrome ad esempio, richiedono un codec per i tipi MIME che non descrivono autonomamente i relativi codec. Invece di cercare di risolvere tutto questo, è meglio includerli 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>;
}
Scarica il file multimediale
Se cerchi esempi di MSE su Internet, ne troverai moltissimi che recuperano i file multimediali utilizzando XHR. Per essere più all'avanguardia, utilizzerò l'API Fetch e la funzionalità Promise 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 della qualità in 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 di file multimediali a risoluzioni diverse, in modo da potersi adattare alle diverse funzionalità del dispositivo e alle diverse condizioni della rete. Un'applicazione di questo tipo è in grado di caricare e riprodurre i video in blocchi utilizzando richieste di intervalli o segmenti. Ciò consente l'adattamento alle condizioni della rete durante la riproduzione di contenuti multimediali. Potresti aver già sentito i termini DASH o HLS, che sono due metodi per raggiungere questo obiettivo. Una discussione completa su questo argomento va oltre l'ambito di questa introduzione.
Elabora l'oggetto di risposta
Il codice sembra quasi fatto, ma i contenuti multimediali non vengono riprodotti. Dobbiamo ottenere i dati multimediali dall'oggetto Response
all'oggetto SourceBuffer
.
Il modo tipico per passare i dati dall'oggetto risposta all'istanza MediaSource
è ottenere un ArrayBuffer
dall'oggetto di risposta e passarlo
a SourceBuffer
. Inizia chiamando response.arrayBuffer()
, che restituisce una promessa al buffer. Nel mio codice ho trasmesso questa promessa a una seconda clausola then()
, in cui la aggiungo a 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 dopo che non sono previsti ulteriori dati dei contenuti multimediali, chiama
MediaSource.endOfStream()
. 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 sulle estensioni Media Source.
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);
});
}