<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Jaroslav Polakovič on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Jaroslav Polakovič</name>
  </author>
  <link href="https://web.dev/authors/dero/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/8L1ESx211yWBm8uNAgqvUc2GwNk1/wj3d5QqXh3gIUuR7Ob7w.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Senior Engineer at XWP</subtitle>
  
  
  <entry>
    <title>Media streaming basics</title>
    <link href="https://web.dev/media-streaming-basics/"/>
    <updated>2021-07-05T00:00:00Z</updated>
    <id>https://web.dev/media-streaming-basics/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this article, you are going to learn about the more advanced concept of media
streaming and by the end should have a good understanding of the various
streaming use cases, protocols, and extensions. Let&#39;s start with an
explanation of what streaming actually is.&lt;/p&gt;
&lt;p&gt;Media streaming is a way of delivering and playing back media content piece by
piece. Instead of loading a single file, which can be slow if not optimized for
the network, the player reads a manifest file describing how the target media is
split into individual chunks of data. Media chunks are later dynamically stitched
back together at runtime—probably at different &lt;a href=&quot;https://web.dev/bitrate/&quot;&gt;bitrates&lt;/a&gt;, which you&#39;ll learn
about later.&lt;/p&gt;
&lt;p&gt;Keep in mind that to provide streaming on your website the server
must support the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Range&quot; rel=&quot;noopener&quot;&gt;Range&lt;/a&gt; HTTP request header. Learn more about the &lt;code&gt;Accept-Ranges&lt;/code&gt;
header in &lt;a href=&quot;https://web.dev/video-and-source-tags/#specify-start-and-end-times&quot;&gt;The &amp;lt;video&amp;gt; and &amp;lt;source&amp;gt; tags&lt;/a&gt; article.&lt;/p&gt;
&lt;h2 id=&quot;streaming-use-cases&quot;&gt;Streaming use cases &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#streaming-use-cases&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Producing media chunks and the necessary manifests describing the stream is not
exactly straightforward, but streaming unlocks some interesting use cases that
are not possible to achieve just by pointing a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element
to a set of static source files. You&#39;ll learn more about how to
&lt;a href=&quot;https://web.dev/add-media/&quot;&gt;add media to a web page&lt;/a&gt; in a later section. First, you should know about a
few use cases for streaming multimedia if you want to go further than just
loading multiple files into the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Adaptive streaming&lt;/strong&gt; is where media chunks are encoded in several
bitrates, and the highest quality media chunk that &lt;strong&gt;&lt;em&gt;fits&lt;/em&gt;&lt;/strong&gt; the client&#39;s
currently available bandwidth is returned to the media player.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live broadcast&lt;/strong&gt; is where media chunks are encoded and made available in
real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Injecting media&lt;/strong&gt; is where other media like advertisements are injected into
a stream without the player having to change the media source.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;streaming-protocols&quot;&gt;Streaming protocols &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#streaming-protocols&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The two most commonly used streaming protocols on the web are &lt;strong&gt;Dynamic
Adaptive Streaming over HTTP&lt;/strong&gt; (&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/DASH_Adaptive_Streaming_for_HTML_5_Video&quot; rel=&quot;noopener&quot;&gt;DASH&lt;/a&gt;) and &lt;strong&gt;HTTP Live Streaming&lt;/strong&gt; (&lt;a href=&quot;https://developer.apple.com/documentation/http_live_streaming&quot; rel=&quot;noopener&quot;&gt;HLS&lt;/a&gt;).
Players that support these protocols will fetch the generated manifest file,
figure out which media chunks to request, and then combine them into the final
media experience.&lt;/p&gt;
&lt;h3 id=&quot;using-lessvideogreater-to-play-a-stream&quot;&gt;Using &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; to play a stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#using-lessvideogreater-to-play-a-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many browsers are not going to play your stream natively. While there is some
native &lt;a href=&quot;https://caniuse.com/http-live-streaming&quot; rel=&quot;noopener&quot;&gt;support for HLS&lt;/a&gt; playback, browsers generally &lt;a href=&quot;https://caniuse.com/mpeg-dash&quot; rel=&quot;noopener&quot;&gt;don&#39;t support native DASH&lt;/a&gt;
stream playback. This means often it&#39;s not enough to simply point the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;
in the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element to a manifest file.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;controls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;manifest.mpd&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;application/dash+xml&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; This is valid HTML, but doesn&#39;t actually work. Browsers don&#39;t natively support DASH manifest playback added to the &lt;code&gt;src&lt;/code&gt; property. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;What may seem as a deficit is actually a strength in disguise. Streams are
powerful and applications that consume streams have different needs.&lt;/p&gt;
&lt;p&gt;Manifest files usually describe many variants of single media. Think different
bitrates, several audio tracks, and even the same media encoded in different
formats.&lt;/p&gt;
&lt;p&gt;Some applications may want to keep a larger amount of video in the buffer,
others may want to prefetch the first few seconds of video from an upcoming
episode, and some want to implement their own logic for adaptive streaming.
This is where you would want to have some sort of built-in browser feature
to generate media streams for playback, and it just so happens there is one.&lt;/p&gt;
&lt;h3 id=&quot;media-source-extensions&quot;&gt;Media Source Extensions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#media-source-extensions&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Thankfully, the W3C defined something called &lt;a href=&quot;https://w3c.github.io/media-source/&quot; rel=&quot;noopener&quot;&gt;Media Source Extensions (MSE)&lt;/a&gt;
that will let JavaScript generate our media streams. In a nutshell, MSE allows
developers to attach a &lt;code&gt;MediaSource&lt;/code&gt; object to a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element and have
it play back whatever media data is pumped into the buffers attached to the
&lt;code&gt;MediaSource&lt;/code&gt; instance.&lt;/p&gt;
&lt;h3 id=&quot;basic-example&quot;&gt;Basic example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#basic-example&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoEl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;video&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MediaSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mediaSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;sourceopen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mimeString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/mp4; codecs=&quot;avc1.42E01E, mp4a.40.2&quot;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addSourceBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mimeString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Video data as `ArrayBuffer` object. */&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The simplified example above illustrates a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As far as &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; is concerned, it is receiving media data from a URL.&lt;/li&gt;
&lt;li&gt;The generated URL is just a pointer to a &lt;code&gt;MediaSource&lt;/code&gt; instance.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;MediaSource&lt;/code&gt; instance creates one or more &lt;code&gt;SourceBuffer&lt;/code&gt; instances.&lt;/li&gt;
&lt;li&gt;We then just append binary media data into the buffer, e.g. using &lt;code&gt;fetch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While these basic concepts are simple, and it is certainly possible to write a
DASH and HLS compatible video player from scratch, most people usually pick one
of the mature open source solutions that already exist, such as &lt;a href=&quot;https://github.com/google/shaka-player&quot; rel=&quot;noopener&quot;&gt;Shaka Player&lt;/a&gt;,
&lt;a href=&quot;https://developer.jwplayer.com/&quot; rel=&quot;noopener&quot;&gt;JW Player&lt;/a&gt;, or &lt;a href=&quot;http://videojs.com/&quot; rel=&quot;noopener&quot;&gt;Video.js&lt;/a&gt; to name a few.&lt;/p&gt;
&lt;p&gt;However, we have created a demo Media PWA called Kino that demonstrates how you
would go about developing your own basic streaming media website that provides
offline media playback using just the simple &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element. There are plans
in our roadmap to support frameworks and digital rights management, among other
features. So check back for updates from time to time, or request a feature.
Read more about it in the &lt;a href=&quot;https://web.dev/pwa-with-offline-streaming/&quot;&gt;PWA with offline streaming&lt;/a&gt; article.&lt;/p&gt;
&lt;h2 id=&quot;media-chunks-format&quot;&gt;Media chunks format &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-streaming-basics/#media-chunks-format&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a long time, DASH and HLS required media chunks to be encoded in different
formats. In 2016, however, support for standard &lt;strong&gt;fragmented MP4&lt;/strong&gt; (fMP4) files
was added to HLS, a format that DASH also supports.&lt;/p&gt;
&lt;p&gt;Video chunks using the &lt;code&gt;fMP4&lt;/code&gt; container and the &lt;code&gt;H.264&lt;/code&gt; codec are supported
by both protocols and playable by a vast majority of players. This allows
content producers to encode their videos just once, which in turn &lt;strong&gt;saves time
and disk space&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To achieve better quality and lower files sizes, you may want to choose to
encode several sets of media chunks using more efficient formats like &lt;code&gt;VP9&lt;/code&gt;,
though before we get to far ahead you will first need to learn how to
&lt;a href=&quot;https://web.dev/prepare-media/&quot;&gt;Prepare media files for the web&lt;/a&gt;, and that&#39;s up next.&lt;/p&gt;
</content>
    <author>
      <name>Derek Herman</name>
    </author><author>
      <name>Jaroslav Polakovič</name>
    </author>
  </entry>
  
  <entry>
    <title>PWA with offline streaming</title>
    <link href="https://web.dev/pwa-with-offline-streaming/"/>
    <updated>2021-07-05T00:00:00Z</updated>
    <id>https://web.dev/pwa-with-offline-streaming/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://web.dev/progressive-web-apps/&quot;&gt;Progressive Web Apps&lt;/a&gt; bring a lot of features previously reserved for native
applications to the web. One of the most prominent features associated with
PWAs is an offline experience.&lt;/p&gt;
&lt;p&gt;Even better would be an offline streaming media experience, which is an
enhancement you could offer to your users in a few different ways. However,
this creates a truly unique problem—media files can be &lt;em&gt;very&lt;/em&gt; large. So
you might be asking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do I download and store a large video file?&lt;/li&gt;
&lt;li&gt;And how do I serve it to the user?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article we will discuss answers to these questions, while
referencing the &lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt; demo PWA we built that provides you with practical
examples of how you can implement an offline streaming media experience without
using any functional or presentational frameworks. The following examples are
mainly for educational purposes, because in most cases you should probably use
one of the existing &lt;a href=&quot;https://web.dev/media-frameworks/&quot;&gt;Media Frameworks&lt;/a&gt; to provide these features.&lt;/p&gt;
&lt;p&gt;Unless you have a good business case for developing your own, building a PWA
with offline streaming has its challenges. In this article you will learn about
the APIs and techniques used to provide users with a high-quality offline media
experience.&lt;/p&gt;
&lt;h2 id=&quot;downloading-and-storing-a-large-media-file&quot;&gt;Downloading and storing a large media file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#downloading-and-storing-a-large-media-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Progressive Web Apps usually use the convenient &lt;a href=&quot;https://web.dev/cache-api-quick-guide/&quot;&gt;Cache API&lt;/a&gt; to both download
and store the assets required to provide the offline experience: documents,
stylesheets, images, and others.&lt;/p&gt;
&lt;p&gt;Here is a basic example of using the Cache API within a Service Worker:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cacheStorageName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;v1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;install&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheStorageName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;index.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;style.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;scripts.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;        &lt;span class=&quot;token comment&quot;&gt;// Don&#39;t do this.&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;very-large-video.mp4&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;While the example above does technically work, using the Cache API has several
limitations that makes its use with large files impractical.&lt;/p&gt;
&lt;p&gt;For example, the Cache API doesn&#39;t:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow you to easily pause and resume downloads&lt;/li&gt;
&lt;li&gt;Let you track the progress of downloads&lt;/li&gt;
&lt;li&gt;Offer a way to properly respond to &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Range_requests&quot; rel=&quot;noopener&quot;&gt;HTTP range requests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these issues are pretty serious limitations for any video application.
Let&#39;s review some other options that might be more appropriate.&lt;/p&gt;
&lt;p&gt;Nowadays, the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot; rel=&quot;noopener&quot;&gt;Fetch API&lt;/a&gt; is a cross-browser way to asynchronously access remote
files. In our use case it allows you to access large video files as a stream and
store them incrementally as chunks using an HTTP range request.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Check out the [Background Fetch API] as a candidate for a progressive enhancement of the Fetch API in browsers that [support Background Fetch]. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Now that you can read the chunks of data with the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot; rel=&quot;noopener&quot;&gt;Fetch API&lt;/a&gt; you also need to
store them. Chances are there is a bunch of metadata associated with your media
file such as: name, description, runtime length, category, etc.&lt;/p&gt;
&lt;p&gt;You&#39;re not storing just the one media file, you are storing a structured object,
and the media file is just one of its properties.&lt;/p&gt;
&lt;p&gt;In this case the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB API&lt;/a&gt; provides an excellent solution to store both the
media data and metadata. It can hold huge amounts of binary data easily, and it
also offers indexes that allow you to perform very fast data lookups.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; There is also a [File System Access API] that you could use in some browsers to store the media files directly on the client device. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;downloading-media-files-using-the-fetch-api&quot;&gt;Downloading media files using the Fetch API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#downloading-media-files-using-the-fetch-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We built a couple of interesting features around the Fetch API in our demo PWA,
which we named &lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt;—the &lt;a href=&quot;https://github.com/GoogleChrome/kino&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt; is public so feel free to review it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ability to pause and resume incomplete downloads.&lt;/li&gt;
&lt;li&gt;A custom buffer for storing chunks of data in the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before showing how those features are implemented, we&#39;ll first do a
quick recap of how you can use the Fetch API to download files.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * Downloads a single file.&lt;br /&gt; *&lt;br /&gt; * @param {string} url URL of the file to be downloaded.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;downloadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dataChunk &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Store the `dataChunk` to IndexedDB.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Notice that &lt;code&gt;await reader.read()&lt;/code&gt; is in a loop? That&#39;s how you&#39;ll receive chunks
of data from a readable stream as they arrive from the network. Consider how
useful this is: you can start processing your data even before it all arrives
from the network.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; If you find the concept of streams confusing, check out [Streams–The definitive guide] before you continue. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;resuming-downloads&quot;&gt;Resuming downloads &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#resuming-downloads&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a download is paused or interrupted, the data chunks that have arrived will
be safely stored in an IndexedDB database. You can then display a button to
resume a download in your application. Because the &lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt; demo PWA server
supports &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Range_requests&quot; rel=&quot;noopener&quot;&gt;HTTP range requests&lt;/a&gt; resuming a download is somewhat straightforward:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;downloadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// this.currentFileMeta contains data from IndexedDB.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; bytesDownloaded&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; downloadUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentFileMeta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fetchOpts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If we already have some data downloaded,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// request everything from that position on.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytesDownloaded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    fetchOpts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bytes=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;bytesDownloaded&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;downloadUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fetchOpts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dataChunk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dataChunk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;dataChunk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataChunk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;dataChunk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;done &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paused&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;custom-write-buffer-for-indexeddb&quot;&gt;Custom write buffer for IndexedDB &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#custom-write-buffer-for-indexeddb&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On paper, the process of writing &lt;code&gt;dataChunk&lt;/code&gt; values into an IndexedDB database
is simple. Those values already are &lt;code&gt;ArrayBuffer&lt;/code&gt; instances, which are storable
in IndexedDB directly, so we can just create an object of an appropriate shape
and store it.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dataItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fileUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;rangeStart&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; dataStartByte&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;rangeEnd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; dataEndByte&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; dataChunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Name of the store that will hold your data.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storeName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;fileChunksStorage&#39;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// `db` is an instance of `IDBDatabase`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; transaction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;storeName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;readwrite&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; store &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;objectStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storeName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; putRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;putRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;While this approach works, you will likely discover that your IndexedDB writes
are significantly slower than your download. This isn&#39;t because IndexedDB writes
are slow, it&#39;s because we are adding a lot of transactional overhead by creating
a new transaction for every data chunk that we receive from a network.&lt;/p&gt;
&lt;p&gt;The downloaded chunks can be rather small and can be emitted by the stream in
rapid succession. You need to limit the rate of IndexedDB writes. In the
&lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt; demo PWA we do this by implementing an &lt;strong&gt;intermediary write buffer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As data chunks arrive from the network, we append them to our buffer first. If
the incoming data doesn&#39;t fit, we flush the full buffer into the database and
clear it before appending the rest of the data. As a result our IndexedDB
writes are less frequent, which leads to significantly improved write
performance.&lt;/p&gt;
&lt;h2 id=&quot;serving-a-media-file-from-offline-storage&quot;&gt;Serving a media file from offline storage &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#serving-a-media-file-from-offline-storage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you have a media file downloaded, you probably want your service worker to
serve it from IndexedDB instead of fetching the file from the network.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * The main service worker fetch handler.&lt;br /&gt; *&lt;br /&gt; * @param {FetchEvent} event Fetch event.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;fetchHandler&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getResponse&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Omitted Cache API code used to serve static assets.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getVideoResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoResponse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; videoResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Fallback to network.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fetchHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;So what do you need to do in &lt;code&gt;getVideoResponse()&lt;/code&gt;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;event.respondWith()&lt;/code&gt; method expects a &lt;code&gt;Response&lt;/code&gt; object as a parameter.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Response/Response&quot; rel=&quot;noopener&quot;&gt;Response() constructor&lt;/a&gt; tells us that there are several types of objects we
could use to instantiate a &lt;code&gt;Response&lt;/code&gt; object: a &lt;code&gt;Blob&lt;/code&gt;, &lt;code&gt;BufferSource&lt;/code&gt;,
&lt;code&gt;ReadableStream&lt;/code&gt;, and more.&lt;/li&gt;
&lt;li&gt;We need an object that doesn&#39;t hold all of its data in memory, so we&#39;ll
probably want to choose the &lt;code&gt;ReadableStream&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, because we&#39;re dealing with large files, and we wanted to allow browsers to
only request the part of the file they currently need, we needed to implement
some basic support for &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Range_requests&quot; rel=&quot;noopener&quot;&gt;HTTP range requests&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * Respond to a request to fetch offline video file and construct a response&lt;br /&gt; * stream.&lt;br /&gt; *&lt;br /&gt; * Includes support for `Range` requests.&lt;br /&gt; *&lt;br /&gt; * @param {Request} request  Request object.&lt;br /&gt; * @param {Object}  fileMeta File meta object.&lt;br /&gt; *&lt;br /&gt; * @returns {Response} Response object.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getVideoResponse&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileMeta&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rangeRequest &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;range&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; byteRanges &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rangeRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;bytes=&lt;span class=&quot;token group punctuation&quot;&gt;(?&amp;lt;&lt;span class=&quot;token group-name variable&quot;&gt;from&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token char-class&quot;&gt;&lt;span class=&quot;token char-class-punctuation punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token range&quot;&gt;0&lt;span class=&quot;token range-punctuation operator&quot;&gt;-&lt;/span&gt;9&lt;/span&gt;&lt;span class=&quot;token char-class-punctuation punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;?&lt;/span&gt;-&lt;span class=&quot;token group punctuation&quot;&gt;(?&amp;lt;&lt;span class=&quot;token group-name variable&quot;&gt;to&lt;/span&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token char-class&quot;&gt;&lt;span class=&quot;token char-class-punctuation punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token range&quot;&gt;0&lt;span class=&quot;token range-punctuation operator&quot;&gt;-&lt;/span&gt;9&lt;/span&gt;&lt;span class=&quot;token char-class-punctuation punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;?&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Using the optional chaining here to access properties of&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// possibly nullish objects.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rangeFrom &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;byteRanges&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;groups&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;from &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rangeTo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;byteRanges&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;groups&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;to &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; fileMeta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bytesTotal &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Omitting implementation for brevity.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streamSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;token function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;token comment&quot;&gt;// Read file data here and call `controller.enqueue`&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;token comment&quot;&gt;// with every retrieved chunk, then `controller.close`&lt;/span&gt;&lt;br /&gt;       &lt;span class=&quot;token comment&quot;&gt;// once all data is read.&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Make sure to set proper headers when supporting range requests.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responseOpts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; rangeRequest &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;206&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;statusText&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; rangeRequest &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Partial Content&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OK&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;Accept-Ranges&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bytes&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Length&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; rangeTo &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; rangeFrom &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rangeRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    responseOpts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Content-Range&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bytes &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;rangeFrom&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;rangeTo&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;fileMeta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bytesTotal&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; responseOpts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Feel free to check out the &lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt; demo PWA &lt;a href=&quot;https://github.com/GoogleChrome/kino/blob/72055bdc7c8ad3de943a55293b2bf882e467c814/src/js/sw/sw.js#L53-L122&quot; rel=&quot;noopener&quot;&gt;service worker source code&lt;/a&gt; to find
out how we are reading file data from IndexedDB and constructing a stream in
a real application.&lt;/p&gt;
&lt;h2 id=&quot;other-considerations&quot;&gt;Other considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwa-with-offline-streaming/#other-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the main obstacles out of your way, you can now start adding some
nice-to-have features to your video application. Here are a few examples of
features you would find in the &lt;a href=&quot;https://kinoweb.dev/&quot; rel=&quot;noopener&quot;&gt;Kino&lt;/a&gt; demo PWA:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/media-session/&quot;&gt;Media Session API&lt;/a&gt; integration that allows your users to control media
playback using dedicated hardware media keys or from media notification
popups.&lt;/li&gt;
&lt;li&gt;Caching of other assets associated with the media files like subtitles, and
poster images using the good old &lt;a href=&quot;https://web.dev/cache-api-quick-guide/&quot;&gt;Cache API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Support for video streams (DASH, HLS) download within the app. Because stream
manifests generally declare multiple sources of different bitrates, you need to
transform the manifest file and only download one media version before storing
it for offline viewing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Up next, you will learn about &lt;a href=&quot;https://web.dev/fast-playback-with-preload/&quot;&gt;Fast playback with audio and video preload&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Jaroslav Polakovič</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
</feed>
