Schnelle Wiedergabe durch Vorabladen von Audio- und Videoinhalten

Hier erfahren Sie, wie Sie die Medienwiedergabe durch aktives Vorabladen von Ressourcen beschleunigen.

François Beaufort
François Beaufort

<ph type="x-smartling-placeholder">

Ein schnellerer Start der Wiedergabe bedeutet, dass sich mehr Nutzer Ihr Video ansehen oder sich Ihre Musik anhören Audio. Das ist bekannt. In diesem Artikel geht es um mit denen Sie die Audio- und Videowiedergabe beschleunigen können, je nach Anwendungsfall Ressourcen vorab laden.

Quelle: Copyright Blender Foundation | www.blender.org

Ich werde drei Methoden zum Vorabladen von Mediendateien beschreiben, angefangen mit den Profis. und Nachteile.

Das ist großartig... Aber...
Attribut „Video-Preload“ Einfache Verwendung für eine eindeutige Datei, die auf einem Webserver gehostet wird. Browser ignorieren das Attribut möglicherweise vollständig.
Der Ressourcenabruf beginnt, wenn das HTML-Dokument vollständig geladen wurde und geparst.
Media Source Extensions (MSE) ignorieren das preload-Attribut für Medienelemente, da die App für der Medienbereitstellung für MSE.
Link vorab laden Erzwingt, dass der Browser eine Videoressource anfordert, ohne zu blockieren Das onload-Ereignis des Dokuments. HTTP-Bereichsanfragen sind nicht kompatibel.
Kompatibel mit MSE- und Dateisegmenten. Sollte nur für kleine Mediendateien (< 5 MB) verwendet werden, wenn vollständige Ressourcen abgerufen werden.
Manuelle Zwischenspeicherung Uneingeschränkter Zugriff Die Website ist für die Behebung komplexer Fehler verantwortlich.

Attribut für das Vorabladen des Videos

Handelt es sich bei der Videoquelle um eine eindeutige Datei, die auf einem Webserver gehostet wird, empfiehlt es sich, Verwende das preload-Attribut des Videos, um dem Browser einen Hinweis darauf zu geben, wie viele Informationen oder Inhalte vorab laden. Das bedeutet, dass Erweiterungen für Medienquellen (MSE) ist nicht mit preload kompatibel.

Das Abrufen der Ressourcen wird erst gestartet, wenn das ursprüngliche HTML-Dokument zuvor vollständig geladen und geparst (z.B. das Ereignis DOMContentLoaded ausgelöst wurde) während das sehr unterschiedliche load-Ereignis ausgelöst wird, wenn die Ressource abgerufen wurde.

Wenn Sie das Attribut preload auf metadata festlegen, ist der Nutzer nicht erwartet, dass sie das Video benötigen, aber dass die Metadaten (Dimensionen, Tracks Liste, Dauer usw.) wünschenswert ist. Hinweis: Ab Chrome 64 eingeben, ist der Standardwert für preload metadata. (Es war auto zuvor).

<video id="video" preload="metadata" src="file.mp4" controls></video>

<script>
  video.addEventListener('loadedmetadata', function() {
    if (video.buffered.length === 0) return;

    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);
  });
</script>

Wenn das Attribut preload auf auto gesetzt wird, bedeutet das, dass der Browser möglicherweise dass genügend Daten vorhanden sind, um die Wiedergabe zu beenden. eine weitere Pufferung aus.

<video id="video" preload="auto" src="file.mp4" controls></video>

<script>
  video.addEventListener('loadedmetadata', function() {
    if (video.buffered.length === 0) return;

    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);
  });
</script>

Es gibt jedoch einige Einschränkungen. Da dies nur ein Hinweis ist, kann es sein, dass der Browser das Attribut preload ignorieren. Bei der Erstellung dieses Dokuments finden Sie hier einige Regeln, in Chrome angewendet:

  • Wenn der Datensparmodus aktiviert ist, erzwingt Chrome den Wert preload: none.
  • In Android 4.3 erzwingt Chrome den Wert preload aufgrund eines Android-none Fehler.
  • Bei einer Mobilfunkverbindung (2G, 3G und 4G) erzwingt Chrome den preload-Wert, metadata.

Tipps

Wenn Ihre Website viele Videoressourcen in derselben Domain enthält, empfehlen, den Wert von preload auf metadata festzulegen oder die poster zu definieren und legen Sie preload auf none fest. So vermeiden Sie es, maximale Anzahl an HTTP-Verbindungen zur selben Domain (6 gemäß dem HTTP 1.1-Spezifikation), die sich beim Laden von Ressourcen aufhängen kann. Beachten Sie, dass auch hier die Seitengeschwindigkeit zu verbessern, wenn Videos nicht zu den Hauptfunktionen gehören.

<ph type="x-smartling-placeholder">

Wie in anderen Artikeln beschrieben, ist das Vorabladen von Links ein deklarativer Abruf, können Sie den Browser zwingen, eine Ressource anzufordern, das load-Ereignis blockiert wird und während die Seite heruntergeladen wird. Ressourcen über <link rel="preload"> geladen, werden lokal im Browser gespeichert und werden inaktiv, bis explizit im DOM, in JavaScript, oder CSS.

Der Unterschied zum Vorabruf liegt insofern vor, als der Schwerpunkt auf der aktuellen Navigation und ruft Ressourcen mit Priorität basierend auf ihrem Typ (Skript, Stil, Schriftart, Video, Audio usw.). Damit kann der Browser-Cache auf die aktuellen Sitzungen.

Vollständiges Video vorab laden

So laden Sie vorab ein vollständiges Video auf Ihrer Website, JavaScript fordert zum Abrufen von Videoinhalten an und wird als Ressource aus dem Cache gelesen. wurde möglicherweise bereits vom Browser im Cache gespeichert. Wenn bei der Vorabladeanfrage abgeschlossen ist, findet ein regelmäßiger Netzwerkabruf statt.

<link rel="preload" as="video" href="https://cdn.com/small-file.mp4">

<video id="video" controls></video>

<script>
  // Later on, after some condition has been met, set video source to the
  // preloaded video URL.
  video.src = 'https://cdn.com/small-file.mp4';
  video.play().then(() => {
    // If preloaded video URL was already cached, playback started immediately.
  });
</script>

Da die vorab geladene Ressource von einem Videoelement in Im Beispiel lautet der Wert des as-Links für den Vorabladen video. Wenn es sich um eine Audioanzeige handelt Element enthält, wäre es as="audio".

Erstes Segment vorab laden

Das folgende Beispiel zeigt, wie das erste Segment eines Videos mit <link rel="preload"> vorab geladen und mit Media Source Extensions verwendet wird. Wenn Sie nicht vertraut sind, mit der MSE JavaScript API erhalten Sie unter MSE-Grundlagen.

Nehmen wir der Einfachheit halber an, dass das gesamte Video in kleinere Dateien wie file_1.webm, file_2.webm, file_3.webm usw.

<link rel="preload" as="fetch" href="https://cdn.com/file_1.webm">

<video id="video" controls></video>

<script>
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

  function sourceOpen() {
    URL.revokeObjectURL(video.src);
    const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

    // If video is preloaded already, fetch will return immediately a response
    // from the browser cache (memory cache). Otherwise, it will perform a
    // regular network fetch.
    fetch('https://cdn.com/file_1.webm')
    .then(response => response.arrayBuffer())
    .then(data => {
      // Append the data into the new sourceBuffer.
      sourceBuffer.appendBuffer(data);
      // TODO: Fetch file_2.webm when user starts playing video.
    })
    .catch(error => {
      // TODO: Show "Video is not available" message to user.
    });
  }
</script>

Support

<ph type="x-smartling-placeholder">

Ob verschiedene as-Typen für <link rel=preload> unterstützt werden, erkennen Sie mit dem Ausschnitte unten:

function preloadFullVideoSupported() {
  const link = document.createElement('link');
  link.as = 'video';
  return (link.as === 'video');
}

function preloadFirstSegmentSupported() {
  const link = document.createElement('link');
  link.as = 'fetch';
  return (link.as === 'fetch');
}

Manuelle Zwischenspeicherung

Bevor wir uns mit der Cache API und den Service Workern befassen, sehen wir uns Videos mit MSE manuell zwischengespeichert. Im Beispiel unten wird davon ausgegangen, Server unterstützt HTTP Range aber das wäre ähnlich wie bei Segmente. Beachten Sie, dass einige Middleware-Bibliotheken wie die Shaka-Bibliothek von Google Player, JW Player und Video.js sind die dies für Sie übernimmt.

<video id="video" controls></video>

<script>
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

  function sourceOpen() {
    URL.revokeObjectURL(video.src);
    const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');

    // Fetch beginning of the video by setting the Range HTTP request header.
    fetch('file.webm', { headers: { range: 'bytes=0-567139' } })
    .then(response => response.arrayBuffer())
    .then(data => {
      sourceBuffer.appendBuffer(data);
      sourceBuffer.addEventListener('updateend', updateEnd, { once: true });
    });
  }

  function updateEnd() {
    // Video is now ready to play!
    const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
    console.log(`${bufferedSeconds} seconds of video are ready to play.`);

    // Fetch the next segment of video when user starts playing the video.
    video.addEventListener('playing', fetchNextSegment, { once: true });
  }

  function fetchNextSegment() {
    fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })
    .then(response => response.arrayBuffer())
    .then(data => {
      const sourceBuffer = mediaSource.sourceBuffers[0];
      sourceBuffer.appendBuffer(data);
      // TODO: Fetch further segment and append it.
    });
  }
</script>

Hinweise

Da Sie jetzt die Kontrolle über die Zwischenspeicherung der Medien haben, empfehle ich Ihnen, den Akkuladestand des Geräts, den „Datensparmodus“ die Nutzereinstellungen und Netzwerkinformationen beim Vorabladen.

Akkuerkennung

Berücksichtige den Akkuladestand der Nutzer Geräte, bevor Sie darüber nachdenken, zum Vorabladen eines Videos. Dadurch wird die Akkulaufzeit verlängert, ist niedrig.

Deaktivieren Sie das Vorabladen oder laden Sie zumindest ein Video mit niedrigerer Auflösung vorab, wenn das der Akku Ihres Geräts leer ist.

if ('getBattery' in navigator) {
  navigator.getBattery()
  .then(battery => {
    // If battery is charging or battery level is high enough
    if (battery.charging || battery.level > 0.15) {
      // TODO: Preload the first segment of a video.
    }
  });
}

„Datensparmodus“ erkennen

Verwende den Save-Data-Anfrageheader für Clienthinweise, um schnelle und leichte Übermittlungen zu ermöglichen Anwendungen für Nutzer*innen, die „Dateneinsparungen“ aktiviert haben in ihrem Browser. Durch die Identifizierung dieses Anfrage-Headers kann Ihre Anwendung Nutzererfahrung bei Kosten- und Leistungsbegrenzungen optimieren Nutzenden.

Weitere Informationen finden Sie unter Delivering Fast and Light Applications with Save-Data.

Intelligentes Laden basierend auf Netzwerkinformationen

Wir empfehlen dir, navigator.connection.type vor dem Vorabladen zu prüfen. Wann? auf cellular gesetzt ist, könnten Sie das Vorabladen verhindern und Nutzer darüber informieren, berechnet ihr Mobilfunkanbieter möglicherweise die Bandbreite Automatische Wiedergabe von zuvor im Cache gespeicherten Inhalten

if ('connection' in navigator) {
  if (navigator.connection.type == 'cellular') {
    // TODO: Prompt user before preloading video
  } else {
    // TODO: Preload the first segment of a video.
  }
}

Im Beispiel mit den Netzwerkinformationen erfährst du, wie du auf ein Netzwerk reagieren kannst. ändert sich auch.

Mehrere erste Segmente vorab im Cache speichern

Was ist, wenn ich Medieninhalte spekulativ vorab laden möchte, zu wissen, für welche Medien der Nutzer sich letztendlich entscheiden wird? Wenn der Nutzer eine mit 10 Videos angezeigt wird, haben wir wahrscheinlich genug Speicher, für jede Segmentdatei, aber wir sollten auf keinen Fall 10 versteckte <video> erstellen. -Elementen und 10 MediaSource-Objekten und fangen an, diese Daten einzuspeisen.

Das zweiteilige Beispiel unten zeigt, wie Sie mehrere erste Segmente können Sie mithilfe der leistungsstarken und nutzerfreundlichen Cache API Videos ansehen. Etwas Ähnliches kann auch mit IndexedDB erreicht werden. Wir verwenden noch keine Service Worker, Die Cache API ist auch über das Objekt window zugänglich.

Abrufen und zwischenspeichern

const videoFileUrls = [
  'bat_video_file_1.webm',
  'cow_video_file_1.webm',
  'dog_video_file_1.webm',
  'fox_video_file_1.webm',
];

// Let's create a video pre-cache and store all first segments of videos inside.
window.caches.open('video-pre-cache')
.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));

function fetchAndCache(videoFileUrl, cache) {
  // Check first if video is in the cache.
  return cache.match(videoFileUrl)
  .then(cacheResponse => {
    // Let's return cached response if video is already in the cache.
    if (cacheResponse) {
      return cacheResponse;
    }
    // Otherwise, fetch the video from the network.
    return fetch(videoFileUrl)
    .then(networkResponse => {
      // Add the response to the cache and return network response in parallel.
      cache.put(videoFileUrl, networkResponse.clone());
      return networkResponse;
    });
  });
}

Hinweis: Wenn ich HTTP-Range-Anfragen verwenden würde, müsste ich das Programm manuell neu erstellen. Ein Response-Objekt, da die Cache API noch keine Range-Antworten unterstützt. Seien Beachten Sie, dass durch das Aufrufen von networkResponse.arrayBuffer() der gesamte Inhalt der Antwort gleichzeitig im Arbeitsspeicher des Renderers zu speichern. Daher bietet es sich an, und kleine Bereiche.

Ich habe einen Teil des obigen Beispiels geändert, um den HTTP-Bereich zu speichern. an den Pre-Cache für Videos senden.

    ...
    return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })
    .then(networkResponse => networkResponse.arrayBuffer())
    .then(data => {
      const response = new Response(data);
      // Add the response to the cache and return network response in parallel.
      cache.put(videoFileUrl, response.clone());
      return response;
    });

Video abspielen

Wenn ein Nutzer auf eine Wiedergabeschaltfläche klickt, wird das erste Segment des Videos abgerufen. in der Cache-API verfügbar, sodass die Wiedergabe sofort startet, falls verfügbar. Andernfalls rufen wir sie einfach aus dem Netzwerk ab. Denken Sie daran, dass Browser Nutzer können sich entscheiden, den Cache zu leeren.

Wie bereits erwähnt, nutzen wir MSE, um das erste Videosegment in das Video einzuspeisen. -Elements.

function onPlayButtonClick(videoFileUrl) {
  video.load(); // Used to be able to play video later.

  window.caches.open('video-pre-cache')
  .then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.
  .then(response => response.arrayBuffer())
  .then(data => {
    const mediaSource = new MediaSource();
    video.src = URL.createObjectURL(mediaSource);
    mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });

    function sourceOpen() {
      URL.revokeObjectURL(video.src);

      const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
      sourceBuffer.appendBuffer(data);

      video.play().then(() => {
        // TODO: Fetch the rest of the video when user starts playing video.
      });
    }
  });
}

Bereichsantworten mit einem Service Worker erstellen

Was ist, wenn ihr eine ganze Videodatei abgerufen und die Cache-API? Wenn der Browser eine HTTP-Range-Anfrage sendet, das gesamte Video in den Arbeitsspeicher des Renderers übertragen werden, da die Cache API unterstützen noch Range-Antworten.

Ich zeige Ihnen nun, wie Sie diese Anfragen abfangen und eine benutzerdefinierte Range-Anfrage zurückgeben können. von einem Service Worker erhalten.

addEventListener('fetch', event => {
  event.respondWith(loadFromCacheOrFetch(event.request));
});

function loadFromCacheOrFetch(request) {
  // Search through all available caches for this request.
  return caches.match(request)
  .then(response => {

    // Fetch from network if it's not already in the cache.
    if (!response) {
      return fetch(request);
      // Note that we may want to add the response to the cache and return
      // network response in parallel as well.
    }

    // Browser sends a HTTP Range request. Let's provide one reconstructed
    // manually from the cache.
    if (request.headers.has('range')) {
      return response.blob()
      .then(data => {

        // Get start position from Range request header.
        const pos = Number(/^bytes\=(\d+)\-/g.exec(request.headers.get('range'))[1]);
        const options = {
          status: 206,
          statusText: 'Partial Content',
          headers: response.headers
        }
        const slicedResponse = new Response(data.slice(pos), options);
        slicedResponse.setHeaders('Content-Range': 'bytes ' + pos + '-' +
            (data.size - 1) + '/' + data.size);
        slicedResponse.setHeaders('X-From-Cache': 'true');

        return slicedResponse;
      });
    }

    return response;
  }
}

Wichtig: Ich habe response.blob() verwendet, um diese segmentierte Datei neu zu erstellen. Dadurch erhalte ich einen Handle auf die Datei, während Mit response.arrayBuffer() wird die gesamte Datei in den Arbeitsspeicher des Renderers übertragen.

Mit meinem benutzerdefinierten X-From-Cache-HTTP-Header kann ermittelt werden, ob diese Anfrage aus dem Cache oder aus dem Netzwerk stammt. Es kann von einem Player wie ShakaPlayer, um die Antwortzeit als Indikator für Netzwerkgeschwindigkeit.

Sieh dir die offizielle Beispielmedien-App und insbesondere die ranged-response.js enthält eine Komplettlösung für den Umgang mit Range -Anfragen.