<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Joe Medley on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Joe Medley</name>
  </author>
  <link href="https://web.dev/authors/joemedley/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/admin/ynJFmvKEbD9diZZsTdkD.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>If an API&amp;#39;s not documented it doesn&amp;#39;t exist</subtitle>
  
  
  <entry>
    <title>How to copy text</title>
    <link href="https://web.dev/patterns/clipboard/copy-text/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/clipboard/copy-text/</id>
    <content type="html" mode="escaped">&lt;p&gt;Cutting and pasting text is one of the most commonly used features of apps in general and desktop applications in particular. How do I copy text on the web? There&#39;s an old way and a new way. And it depends on which browser is used.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-async-clipboard-api&quot;&gt;Using the Async Clipboard API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#using-the-async-clipboard-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/patterns/clipboard/copy-text/%E2%80%8B%E2%80%8Bhttps://developer.mozilla.org/docs/Web/API/Clipboard/writeText&quot;&gt;&lt;code&gt;Clipboard.writeText()&lt;/code&gt;&lt;/a&gt; method takes a string and returns a Promise that resolves when text is successfully written to the clipboard. &lt;code&gt;Clipboard.writeText()&lt;/code&gt; can only be used from the &lt;code&gt;window&lt;/code&gt; object that has focus.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 63, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      63
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/writeText#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-documentexeccommand&quot;&gt;Using &lt;code&gt;document.execCommand()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#using-documentexeccommand&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Calling &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/execCommand#copy&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;document.execCommand(&#39;copy&#39;)&lt;/code&gt;&lt;/a&gt; returns a boolean value that indicates whether the copy was successful. Call this command inside of a user gesture such as a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Element/click_event#javascript&quot; rel=&quot;noopener&quot;&gt;click handler&lt;/a&gt;. This approach has limitations when compared to the Async Clipboard API. The &lt;code&gt;execCommand()&lt;/code&gt; method only works with DOM elements. Because it&#39;s synchronous, copying large amounts of data, especially data that must be transformed or sanitized in some way, can block the page.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 1.3, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1.3
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Document/execCommand#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; copyButton &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;#copyButton&#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; out &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;#out&#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;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 string&quot;&gt;&#39;clipboard&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;  copyButton&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;click&#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;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;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&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 function&quot;&gt;then&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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Text copied&#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 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 function&quot;&gt;catch&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 parameter&quot;&gt;err&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; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  copyButton&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;click&#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;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; textArea &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;textarea&#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;    textArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    textArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opacity &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;br /&gt;    document&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;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;textArea&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;    textArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;focus&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;    textArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&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;try&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; success &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;execCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;copy&#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;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&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;Text copy was &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;successful&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unsuccessful&#39;&lt;/span&gt;&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;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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;    document&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;removeChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;textArea&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;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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/async-clipboard/&quot;&gt;Unblocking clipboard access&lt;/a&gt; (the modern way)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/blog/cut-and-copy-commands/&quot; rel=&quot;noopener&quot;&gt;Cut and copy commands&lt;/a&gt; (the classic way)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-text/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Media encryption</title>
    <link href="https://web.dev/media-encryption/"/>
    <updated>2021-07-05T00:00:00Z</updated>
    <id>https://web.dev/media-encryption/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this section we are going to cover two different strategies for encrypting
your media, and some practical examples on how to use them with FFmpeg and
Shaka Packager. The two strategies for encryption we&#39;ll discuss are &lt;a href=&quot;https://w3c.github.io/encrypted-media/index.html#clear-key&quot; rel=&quot;noopener&quot;&gt;Clear Key&lt;/a&gt; and
using a service like &lt;a href=&quot;https://www.widevine.com/&quot; rel=&quot;noopener&quot;&gt;Google Widevine&lt;/a&gt;. Both strategies are a form of digital
rights management (&lt;a href=&quot;https://en.wikipedia.org/wiki/Digital_rights_management&quot; rel=&quot;noopener&quot;&gt;DRM&lt;/a&gt;) to control what users can do with your media. However,
one is inherently less secure than the other due to the way keys are passed for
authentication and is why a DRM service might make more sense.&lt;/p&gt;
&lt;p&gt;The primary DRM services for the web are &lt;a href=&quot;https://www.widevine.com/&quot; rel=&quot;noopener&quot;&gt;Google Widevine&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/playready/&quot; rel=&quot;noopener&quot;&gt;Microsoft PlayReady&lt;/a&gt;
and &lt;a href=&quot;https://developer.apple.com/streaming/fps/&quot; rel=&quot;noopener&quot;&gt;Apple FairPlay&lt;/a&gt;, but we will not be covering all of them in this article.
However, if you are targeting all the modern browsers you are likely going
to be using all three DRM services.&lt;/p&gt;
&lt;p&gt;Conversion and encryption is done with these applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/shaka-packager&quot; rel=&quot;noopener&quot;&gt;Shaka Packager&lt;/a&gt; (&lt;a href=&quot;https://stackoverflow.com/questions/tagged/shaka&quot; rel=&quot;noopener&quot;&gt;#shaka on Stack Overflow&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/download.html&quot; rel=&quot;noopener&quot;&gt;FFmpeg&lt;/a&gt; (&lt;a href=&quot;https://stackoverflow.com/questions/tagged/ffmpeg&quot; rel=&quot;noopener&quot;&gt;#ffmpeg on Stack Overflow&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.openssl.org/&quot; rel=&quot;noopener&quot;&gt;OpenSSL&lt;/a&gt; (&lt;a href=&quot;https://stackoverflow.com/questions/tagged/openssl&quot; rel=&quot;noopener&quot;&gt;#openssl on Stack Overflow&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;clear-key-encryption&quot;&gt;Clear Key encryption &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#clear-key-encryption&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, you should have a good understanding of what Clear Key is and isn&#39;t before
using it. When you do &lt;em&gt;&lt;strong&gt;not&lt;/strong&gt;&lt;/em&gt; want to use an existing DRM service and feel
basic encryption of you media is a viable option, you would use Clear Key. But,
keep in mind that this type of encryption does not provide the same level of
security as using one of the DRM services. This is because the key value pair is
not encrypted under another key, unlike encrypted keys which are generated by a
decryption key that is stored on a licence server. Additionally, Clear Key sends
the key value pair as plain text, so while you are encrypting your media the key to
decrypt it is not a secret.&lt;/p&gt;
&lt;h3 id=&quot;create-a-key&quot;&gt;Create a key &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#create-a-key&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can use the same method to create a key for both DASH and HLS. Do this using
&lt;a href=&quot;https://www.openssl.org/&quot; rel=&quot;noopener&quot;&gt;OpenSSL&lt;/a&gt;. The following will create an encryption key made of 16 hex values.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl rand -hex &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; media.key&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 command creates a file that could contain white space and new line characters, which are not allowed by Shaka Packager. You&#39;ll need to open the key file and manually remove all whitespace including the final carriage return. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;create-an-iv&quot;&gt;Create an IV &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#create-an-iv&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Next we can generate an initialization vector (IV).&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl rand -hex &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt;6143b5373a51cb46209cfed0d747da66&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Make a note of the output, you&#39;ll need this later. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;encrypt-with-clear-key&quot;&gt;Encrypt with Clear Key &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#encrypt-with-clear-key&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following example uses &lt;a href=&quot;https://google.github.io/shaka-packager/html/tutorials/raw_key.html&quot; rel=&quot;noopener&quot;&gt;Shaka Packager with raw keys&lt;/a&gt;, where &lt;code&gt;keys&lt;/code&gt; and
&lt;code&gt;key_ids&lt;/code&gt; are provided to Shaka Packager directly. Read the documentation for
more examples.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;key&lt;/code&gt; flag, use the key created earlier, which is stored in the &lt;code&gt;media.key&lt;/code&gt;
file. However, when entering it on the command line, be sure you&#39;ve removed its
whitespace. For the &lt;code&gt;key_id&lt;/code&gt; flag, repeat the &lt;code&gt;media.id&lt;/code&gt; value or use the IV value
generated above.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glockena.m4a &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glockenv.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --keys &lt;span class=&quot;token assign-left variable&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_AUDIO_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_AUDIO_KEY_ID_HERE,label&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_VIDEO_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_VIDEO_KEY_ID_HERE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Note that we can also encrypt both &lt;code&gt;video&lt;/code&gt; and &lt;code&gt;audio&lt;/code&gt; streams using the same &lt;code&gt;keys&lt;/code&gt; by omitting the label argument instead of defining a different set of &lt;code&gt;keys&lt;/code&gt; per-stream. For example, &lt;code&gt;--keys label=:key=INSERT_KEY_HERE:key_id=INSERT_KEY_ID_HERE&lt;/code&gt;. However, this would decrease the level of security when protecting your streams. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;create-a-key-information-file&quot;&gt;Create a key information file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#create-a-key-information-file&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To encrypt for HLS you need a key information file in addition to a key file. A
key information file is a text file with the format below. It should have the
extension &lt;code&gt;.keyinfo&lt;/code&gt;. For example: &lt;code&gt;encrypt.keyinfo&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;key URI&lt;br /&gt;key &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path&lt;br /&gt;private key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The key URI is where the &lt;code&gt;media.key&lt;/code&gt; (&lt;a href=&quot;https://web.dev/media-encryption/#create-a-key&quot;&gt;created above&lt;/a&gt; will be
located on your server. The key file path is its location relative to the key
information file. Finally, the private key is the contents of the &lt;code&gt;media.key&lt;/code&gt;
file itself, or the IV you created before. For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;https://example.com/keys/media.key&lt;br /&gt;/path/to/media.key&lt;br /&gt;6143b5373a51cb46209cfed0d747da66&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;encrypt-for-hls&quot;&gt;Encrypt for HLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#encrypt-for-hls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;input=input.mp4,stream=video,segment_template=output$Number$.ts,playlist_name=video_playlist.m3u8&#39;&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;input=input.mp4,stream=audio,segment_template=output_audio$Number$.ts,playlist_name=audio_playlist.m3u8,hls_group_id=audio,hls_name=ENGLISH&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --hls_master_playlist_output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;master_playlist.m3u8&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --hls_base_url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:5000/&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This command will accept a key with either 16 or 32 characters.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mov -c:v libx264 -c:a aac -hls_key_info_file encrypt.keyinfo myvideo.m3u8&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;widevine-encryption&quot;&gt;Widevine encryption &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#widevine-encryption&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now you know what Clear Key encryption is and when to use it. But, when should you use a DRM service for additional security? This is where Widevine, or another service,
would be used to securely encrypt and decrypt your media. Widevine supports MPEG-DASH and
HLS and is a DRM from Google. Widevine is used by the Google Chrome and Firefox web
browsers, Android MediaDRM, Android TV, and other consumer electronics devices that use
Encrypted Media Extensions and Media Source Extensions, where Widevine decrypts content.&lt;/p&gt;
&lt;h3 id=&quot;encrypt-with-widevine&quot;&gt;Encrypt with Widevine &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#encrypt-with-widevine&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Most of the examples in this article used Clear Key encryption. However, for Widevine you
will want to replace the following options.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;--enable_fixed_key_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;--enable_fixed_key_decryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;--keys &lt;span class=&quot;token assign-left variable&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_ID_HERE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Everything in the demultiplexer (demux) command except the name of your files and the
&lt;code&gt;--content-id&lt;/code&gt; flag should be copied exactly from the example. The &lt;code&gt;--content-id&lt;/code&gt; is 16
or 32 random hex digits. Use the keys provided here instead of your own. Read the Shaka
Packager documentation on using the &lt;a href=&quot;https://google.github.io/shaka-packager/html/tutorials/widevine.html&quot; rel=&quot;noopener&quot;&gt;Widevine Key Server&lt;/a&gt; for more examples.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Demux (separate) the audio and video, encrypt the new files, and output a
media presentation description (MPD) file.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_video.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_audio.m4a &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_widevine_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --key_server_url &lt;span class=&quot;token string&quot;&gt;&quot;https://license.uat.widevine.com/cenc/getcontentkey/widevine_test&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --content_id &lt;span class=&quot;token string&quot;&gt;&quot;fd385d9f9a14bb09&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --signer &lt;span class=&quot;token string&quot;&gt;&quot;widevine_test&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --aes_signing_key &lt;span class=&quot;token string&quot;&gt;&quot;1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --aes_signing_iv &lt;span class=&quot;token string&quot;&gt;&quot;d58ce954203b7c9a9a9d467f59839249&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remux (combine) the audio and video streams. If you&#39;re using a video
framework, you may not need to do this.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken_video.mp4 -i glocken_audio.m4a -c copy glocke.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The default pixel format &lt;code&gt;yuv420p&lt;/code&gt; is used in all the &lt;code&gt;ffmpeg&lt;/code&gt; remux examples in this article because one isn&#39;t supplied in the command line. The &lt;code&gt;ffmpeg&lt;/code&gt; command will give you an error message that it is deprecated. We&#39;ve chosen not to override the default because, though deprecated &lt;code&gt;yuv420p&lt;/code&gt; is still the most widely supported. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;media-conversion-sequence&quot;&gt;Media conversion sequence &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#media-conversion-sequence&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This section shows in order commands needed to get from a raw &lt;code&gt;.mov&lt;/code&gt; file to
encrypted assets packaged for DASH or HLS. For the sake of having a goal to
illustrate, we&#39;re converting a source file to a bitrate of 8Mbs at a resolution
of 1080p (1920 x 1080). Adjust these values as your needs dictate.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &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; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Not all steps are possible with Shaka Packager alone, so we&#39;ll also use FFmpeg when needed. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;dashwebm&quot;&gt;DASH/WebM &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#dashwebm&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Convert the file type and codec.&lt;/p&gt;
&lt;p&gt;For this command you can use either &lt;code&gt;liborbis&lt;/code&gt; or &lt;code&gt;libopus&lt;/code&gt; for the audio codec.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken.mov -c:v libvpx-vp9 -c:a libvorbis -b:v 8M -vf &lt;span class=&quot;token assign-left variable&quot;&gt;setsar&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;:1 -f webm tmp_glocken.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a Clear Key encryption key.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl rand -hex &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; media.key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Demux (separate) the audio and video, encrypt the new files, and output a
media presentation description (MPD) file.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.webm,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_video.webm &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.webm,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_audio.webm &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_decryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --keys &lt;span class=&quot;token assign-left variable&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_ID_HERE &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --mpd_output glocken_webm_vod.mpd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remux (combine) the audio and video streams. If you&#39;re using a video
framework, you may not need to do this.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken_video.webm -i glocken_audio.webm -c copy glocken.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;dashmp4&quot;&gt;DASH/MP4 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#dashmp4&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Convert the file type, video codec and bitrate.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken.mov -c:v libx264 -c:a aac -b:v 8M -strict -2 tmp_glocken.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a Clear Key encryption key.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl rand -hex &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; media.key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Demux (separate) the audio and video, encrypt the new files, and output a
media presentation description (MPD) file.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_video.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tmp_glocken.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken_audio.m4a &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_decryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --keys &lt;span class=&quot;token assign-left variable&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_ID_HERE &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --mpd_output glocken_mp4_vod.mpd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remux (combine) the audio and video streams. If you&#39;re using a video
framework, you may not need to do this.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken_video.mp4 -i glocken_audio.m4a -c copy glocken.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;hlsmp4&quot;&gt;HLS/MP4 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-encryption/#hlsmp4&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HLS only supports MP4, so first you&#39;ll need to convert to the MP4 container and
supported codecs.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Convert the file type, video codec, and bitrate.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken.mov -c:v libx264 -c:a aac -b:v 8M -strict -2 glocken.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a Clear Key encryption key.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;openssl rand -hex &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; media.key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a key information file&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;input=glocken.mp4,stream=video,segment_template=output$Number$.ts,playlist_name=video_playlist.m3u8&#39;&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;input=glocken.mp4,stream=audio,segment_template=output_audio$Number$.ts,playlist_name=audio_playlist.m3u8,hls_group_id=audio,hls_name=ENGLISH&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --hls_master_playlist_output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;master_playlist.m3u8&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --hls_base_url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:5000/&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_encryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --enable_fixed_key_decryption &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --keys &lt;span class=&quot;token assign-left variable&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;:key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_HERE:key_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;INSERT_KEY_ID_HERE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That was a lot to digest, but hopefully you are now able to encrypt you media
with confidence. Next we&#39;ll show you how to &lt;a href=&quot;https://web.dev/add-media/&quot;&gt;add media&lt;/a&gt; to
your site.&lt;/p&gt;
</content>
    <author>
      <name>Derek Herman</name>
    </author><author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Introducing &lt;model-viewer&gt; 1.1</title>
    <link href="https://web.dev/introducing-model-viewer/"/>
    <updated>2020-09-03T00:00:00Z</updated>
    <id>https://web.dev/introducing-model-viewer/</id>
    <content type="html" mode="escaped">&lt;p&gt;3D models are more relevant than ever. Retailers bring in-store shopping
experiences to customers&#39; homes. Museums are making 3D models of their artifacts
available to everyone on the web. Unfortunately, it can be difficult to add a 3D
model to a website in a way that provides a great user experience without a deep
knowledge of 3D technologies or resorting to hosting 3D content on a third-party
site. The &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; web component, &lt;a href=&quot;https://web.dev/model-viewer&quot;&gt;introduced in early
2019&lt;/a&gt;, seeks to make putting 3D models on the web as easy as
writing a few lines of HTML. Since then, the team has been working to address
feedback and requests from the community. The culmination of that work was
&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; version 1.0, released earlier this year. We&#39;re now announcing
the release of &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; 1.1. You can &lt;a href=&quot;https://github.com/google/model-viewer/releases/tag/v1.1.0&quot; rel=&quot;noopener&quot;&gt;read the release
notes&lt;/a&gt; in GitHub.&lt;/p&gt;
&lt;h2 id=&quot;whats-new-since-last-year&quot;&gt;What&#39;s new since last year? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#whats-new-since-last-year&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Version 1.1 includes built-in support for augmented reality (AR) on the web,
improvements to speed and fidelity, and other frequently-requested features.&lt;/p&gt;
&lt;h3 id=&quot;augmented-reality&quot;&gt;Augmented reality &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#augmented-reality&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Viewing a 3D model on a blank canvas is great, but being able to view it in your
space is even better. For an entirely-within-the-browser 3D and AR Chrome
Android supports &lt;a href=&quot;https://modelviewer.dev/examples/augmented-reality.html&quot; rel=&quot;noopener&quot;&gt;augmented
reality&lt;/a&gt; using WebXR .&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/introducing-model-viewer/modelviewer1-0.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/introducing-model-viewer/modelviewer1-0.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    A demonstration of the &lt;code&gt;&amp;lt;model-viewer&gt;&lt;/code&gt; AR capability.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When it&#39;s ready, you&#39;ll be able to use it by add an &lt;code&gt;ar&lt;/code&gt; attribute to the
&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; tag. Other attributes allow you to customize the WebXR AR
experience, as shown in &lt;a href=&quot;https://modelviewer.dev/examples/webxr.html&quot; rel=&quot;noopener&quot;&gt;the WebXR sample on
modelviewer.dev&lt;/a&gt;. The code sample
below shows what this might look like.&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;model-viewer&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;Chair.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;ar&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ar-scale&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;auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;camera-controls&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;alt&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;A 3D model of an office chair.&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;model-viewer&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;!-- Hide until model-viewer&#39;s iframe bug is fixed --&gt;
&lt;!-- &lt;iframe style=&quot;width:100%; height: 100%;position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);&quot; src=&quot;https://modelviewer.dev/examples/webxr.html&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt; --&gt;
&lt;p&gt;It looks something like the embedded video shown under this heading.&lt;/p&gt;
&lt;h3 id=&quot;camera-controls&quot;&gt;Camera controls &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#camera-controls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; now gives full control over the view&#39;s virtual camera (the
perspective of the viewer). This includes the camera target, orbit (position
relative to the model), and field of view. You can also enable auto-rotation and
set limits on user interaction (e.g. maximum and minimum fields of view).&lt;/p&gt;
&lt;h3 id=&quot;annotations&quot;&gt;Annotations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#annotations&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can also annotate your models using HTML and CSS. This capability is often
used to &amp;quot;attach&amp;quot; labels to parts of the model in a way that moves with the model
as it&#39;s manipulated. The annotations are customizable, including their
appearance and the extent to which they&#39;re hidden by
the model. Annotations also work in AR.&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;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 6px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 6px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3px solid blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; border-box&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 selector&quot;&gt;#annotation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #dddddd&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;10px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 10px&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 property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&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&gt;&lt;/span&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;style&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;model-viewer&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;https://modelviewer.dev/assets/ShopifyModels/ToyTrain.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&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;A 3D model of a Toy Train&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;camera-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;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;slot&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;hotspot-hand&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-position&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;-0.023 0.0594 0.0714&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-normal&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;-0.3792 0.0004 0.9253&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;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&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;annotation&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;Whistle&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;div&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;button&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;model-viewer&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;!-- Hide until model-viewer&#39;s iframe bug is fixed --&gt;
&lt;!-- &lt;iframe style=&quot;width:100%; height: 100%;position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);&quot; src=&quot;https://modelviewer.dev/webdotdev/annotations.html&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt; --&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;A space suit with an annotation.&quot; decoding=&quot;async&quot; height=&quot;839&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/CG1JQXZP9Gncr7qLz5JZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;A space suit with an annotation.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;See the &lt;a href=&quot;https://modelviewer.dev/examples/annotations.html&quot; rel=&quot;noopener&quot;&gt;annotations documentation
page&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h3 id=&quot;editor&quot;&gt;Editor &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#editor&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Version 1.1 introduces and hosts a &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; &lt;a href=&quot;https://modelviewer.dev/editor/&quot; rel=&quot;noopener&quot;&gt;&amp;quot;editing&amp;quot;
tool&lt;/a&gt;, which enables you to
quickly preview your model, try out different &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; configurations
(e.g. exposure and shadow softness), generate a poster image, and interactively
get coordinates for annotations.&lt;/p&gt;
&lt;h3 id=&quot;rendering-and-performance-improvements&quot;&gt;Rendering and performance improvements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#rendering-and-performance-improvements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rendering fidelity is greatly improved, especially for high dynamic range (HDR)
environments. &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; now also uses a direct render path when only one
&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; element is in the viewport, which increases performance
(especially on Firefox). Lastly, dynamically scaling the render resolution
improved frame rate dramatically. The example below shows off some of these
recent improvements.&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;model-viewer&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;camera-controls&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token attr-name&quot;&gt;skybox-image&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;spruit_sunrise_1k_HDR.hdr&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token attr-name&quot;&gt;alt&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;A 3D model of a well-worn  helmet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;DamagedHelmet.glb&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;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;model-viewer&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;!-- Hide until model-viewer&#39;s iframe bug is fixed --&gt;
&lt;!-- &lt;iframe style=&quot;width:100%; height: 100%;position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);&quot; src=&quot;https://modelviewer.dev/webdotdev/rendering.html&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt; --&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;A 3D model of a well-worn  helmet.&quot; decoding=&quot;async&quot; height=&quot;809&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/ZAID4J5TsJTcJs3qPNTp.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;A 3D model of a well-worn  helmet.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;stability&quot;&gt;Stability &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#stability&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; reaching its first major version, API stability is a
priority, so breaking changes will be avoided until version 2.0 is released.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/introducing-model-viewer/#whats-next&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; version 1.0 includes the most-requested capabilities, but the
team is not done yet. More features will be added, as will improvements in
performance, stability, documentation, and tooling. If you have suggestions,
file an &lt;a href=&quot;https://github.com/google/model-viewer/issues&quot; rel=&quot;noopener&quot;&gt;issue in GitHub&lt;/a&gt;; also,
PRs are always welcome. You can stay connected by following &lt;a href=&quot;https://twitter.com/modelviewer&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; on
Twitter&lt;/a&gt; and checking out the &lt;a href=&quot;https://spectrum.chat/model-viewer?tab=posts&quot; rel=&quot;noopener&quot;&gt;community chat
on Spectrum&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Rob Kochman</name>
    </author><author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Media accessibility</title>
    <link href="https://web.dev/media-accessibility/"/>
    <updated>2020-08-20T00:00:00Z</updated>
    <id>https://web.dev/media-accessibility/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this article you will learn about the WebVTT (Web Video Text Tracks) format,
which is used to describe timed text data such as closed captions or subtitles
in order to make videos more accessible to your audience.&lt;/p&gt;
&lt;p&gt;Accessibility isn&#39;t like icing on a cake. It&#39;s never anything you put on a
backlog with the hope of introducing it later. Captions and screen reader
descriptions are the only way many users can experience your videos, and in some
jurisdictions, they&#39;re even required by law or regulation.&lt;/p&gt;
&lt;h2 id=&quot;add-lesstrackgreater-tags&quot;&gt;Add &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tags &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-accessibility/#add-lesstrackgreater-tags&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To add captions or screen reader descriptions to a web video, add a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/track&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt;&lt;/a&gt;
tag within a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag. In addition to captions and screen reader
descriptions, &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tags may also be used for subtitles and chapter titles.
The &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tag can also help search engines understand what&#39;s in a video.
However, those capabilities are outside the scope of this article.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshot showing captions displayed using the track element in Chrome on Android&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vbNDp5R05MwQmsxZ0RLI.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Screenshot showing captions displayed using the
track element in Chrome on Android&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tag is similar to the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element in that both have a &lt;code&gt;src&lt;/code&gt;
attribute that points to referenced content. For a &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tag, it points to a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebVTT_API&quot; rel=&quot;noopener&quot;&gt;WebVTT file&lt;/a&gt;. The &lt;code&gt;label&lt;/code&gt; attribute specifies how a particular track will be
identified in the interface.&lt;/p&gt;
&lt;p&gt;To provide tracks for multiple languages add a separate &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tag for each
WebVTT file you&#39;re providing and indicate the language using the &lt;code&gt;srclang&lt;/code&gt;
attribute.&lt;/p&gt;
&lt;p&gt;An example &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag with two &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; tags is shown below. There&#39;s also a
sample you can &lt;a href=&quot;https://track-demonstration.glitch.me/&quot; rel=&quot;noopener&quot;&gt;view on Glitch&lt;/a&gt; (&lt;a href=&quot;https://glitch.com/edit/#!/track-demonstration&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Add a &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; element as a child of the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element:&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;highlight-line&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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;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;https://storage.googleapis.com/webfundamentals-assets/videos/chrome.webm&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;video/webm&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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;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;https://storage.googleapis.com/webfundamentals-assets/videos/chrome.mp4&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;video/mp4&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;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&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;track&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;chrome-subtitles-en.vtt&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;label&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;English captions&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;kind&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;captions&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srclang&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;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;track&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;chrome-subtitles-zh.vtt&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;label&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;中文字幕&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;kind&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;captions&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srclang&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;zh&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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This browser does not support the video element.&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Notice in the example on &lt;strong&gt;line 4&lt;/strong&gt; that the &lt;code&gt;default&lt;/code&gt; attribute indicates which language track is the default. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;webvtt-file-structure&quot;&gt;WebVTT file structure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-accessibility/#webvtt-file-structure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Below is a hypothetical WebVTT file for the demo linked to above. The file is a
text file containing a series of &lt;em&gt;cues&lt;/em&gt;. Each cue is a block of text to display
on screen, and the time range during which it will be displayed.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;WEBVTT&lt;br /&gt;&lt;br /&gt;00:00.000 --&gt; 00:04.999&lt;br /&gt;Man sitting on a tree branch, using a laptop.&lt;br /&gt;&lt;br /&gt;00:05.000 --&gt; 00:08.000&lt;br /&gt;The branch breaks, and he starts to fall.&lt;br /&gt;&lt;br /&gt;...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Each item within the track file is called a &lt;em&gt;cue&lt;/em&gt;. Each cue has a start time and
end time separated by an arrow, with cue text in the line below. Cues can
optionally also have IDs like &lt;code&gt;railroad&lt;/code&gt; and &lt;code&gt;manuscript&lt;/code&gt; in the example below.
Cues are separated by an empty line.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;WEBVTT&lt;br /&gt;&lt;br /&gt;railroad&lt;br /&gt;00:00:10.000 --&gt; 00:00:12.500&lt;br /&gt;Left uninspired by the crust of railroad earth&lt;br /&gt;&lt;br /&gt;manuscript&lt;br /&gt;00:00:13.200 --&gt; 00:00:16.900&lt;br /&gt;that touched the lead to the pages of your manuscript.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Cue times are in &lt;code&gt;hours:minutes:seconds.milliseconds&lt;/code&gt; format. Parsing is strict.
Meaning, numbers must be zero padded if necessary: hours, minutes, and seconds
must have two digits (00 for a zero value) and milliseconds must have three
digits (000 for a zero value). There is an excellent WebVTT validator at
&lt;a href=&quot;https://quuz.org/webvtt/&quot; rel=&quot;noopener&quot;&gt;Live WebVTT Validator&lt;/a&gt;, which checks for errors in time formatting, and
problems such as non-sequential times.&lt;/p&gt;
&lt;p&gt;You can create a VTT file by hand, thought there are &lt;a href=&quot;https://www.google.com/search?q=webvtt+services&quot; rel=&quot;noopener&quot;&gt;many services&lt;/a&gt; that will
create them for you.&lt;/p&gt;
&lt;p&gt;As you can see in our previous examples, the WebVTT format is pretty simple.
Just add your text data along with timings.&lt;/p&gt;
&lt;p&gt;However, what if you want your captions to render in a different position with
left or right alignment? Perhaps to align the captions with the current speaker
position, or to stay out of the way of in-camera text. WebVTT defines settings to do that,
and more, directly inside the
&lt;code&gt;.vtt&lt;/code&gt; file. Take note of how the caption placement is defined by adding
settings after the time interval definitions.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;WEBVTT&lt;br /&gt;&lt;br /&gt;00:00:05.000 --&gt; 00:00:10.000 line:0 position:20% size:60% align:start&lt;br /&gt;The first line of the subtitles.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Another handy feature is the ability to style cues using CSS. Perhaps you want
to use a gray linear gradient as the background, with a foreground color of
&lt;code&gt;papayawhip&lt;/code&gt; for all captions and all bold text colored &lt;code&gt;peachpuff&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;video::cue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;to bottom&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dimgray&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lightgray&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 property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; papayawhip&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 selector&quot;&gt;video::cue(b)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; peachpuff&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;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &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; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Be careful when coloring text to keep to high-contrast colors so as not to degrade readability. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If you&#39;re interested in learning more about styling and tagging of individual
cues, the &lt;a href=&quot;https://w3c.github.io/webvtt/&quot; rel=&quot;noopener&quot;&gt;WebVTT specification&lt;/a&gt; is a good source for advanced examples.&lt;/p&gt;
&lt;h2 id=&quot;kinds-of-text-tracks&quot;&gt;Kinds of text tracks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-accessibility/#kinds-of-text-tracks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you notice the &lt;code&gt;kind&lt;/code&gt; attribute of the &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; element? It&#39;s used to
indicate what relation the particular text track has to the video. The
possible values of the &lt;code&gt;kind&lt;/code&gt; attribute are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;captions&lt;/code&gt;: For closed captions from transcripts and possibly translations
of any audio. Suitable for hearing impaired and in cases when the video is
playing muted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subtitles&lt;/code&gt;: For subtitles, that is, translations of speech and text in a
language different from the main language of the video.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;descriptions&lt;/code&gt;: For descriptions of visual parts of the video content.
Suitable for visually impaired people.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chapters&lt;/code&gt;: Intended to be displayed when the user is navigating within the
video.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata&lt;/code&gt;: Not visible, and may be used by scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you understand the basics of making a video available and accessible
on your web page, you might wonder about more complex use cases. Next up, you&#39;ll
lean about &lt;a href=&quot;https://web.dev/media-frameworks/&quot;&gt;Media frameworks&lt;/a&gt; and how they can help you add
videos to your web page while providing advanced features.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Add media to a web page</title>
    <link href="https://web.dev/add-media/"/>
    <updated>2020-06-19T00:00:00Z</updated>
    <id>https://web.dev/add-media/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this section you&#39;ll learn how to embed a media file in a web page using the
&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tags, how to add captions to a video for the
hearing-impaired, and some general concepts for using media frameworks.
Additionally, you&#39;ll learn about using preload to speed up playback start, and
we will discuss how we built an updated demo PWA with offline adaptive
streaming capabilities called Kino.&lt;/p&gt;
&lt;p&gt;This section assumes you have a video file that is ready for embedding in a web
page. A &lt;code&gt;.mov&lt;/code&gt; file downloaded from a camera will not work. If that&#39;s all you
have, see &lt;a href=&quot;https://web.dev/prepare-media/&quot;&gt;Prepare media files for the web&lt;/a&gt; then come back.&lt;/p&gt;
&lt;p&gt;This section covers these topics.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;a href=&quot;https://web.dev/video-and-source-tags/&quot;&gt;The &amp;lt;video&amp;gt; and &amp;lt;source&amp;gt; tags&lt;/a&gt; you&#39;ll
learn specifically how to embed a media file in a web page.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;https://web.dev/media-accessibility/&quot;&gt;Media accessibility&lt;/a&gt; you&#39;ll learn to add captions
to a media file for hearing impaired.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;https://web.dev/media-frameworks/&quot;&gt;Media frameworks&lt;/a&gt; you&#39;ll learn basics about using
media frameworks like Shaka Player, JW Player, and Video.js.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;https://web.dev/fast-playback-with-preload/&quot;&gt;Fast playback with audio and video preload&lt;/a&gt;
you&#39;ll learn to accelerate your media playback by actively preloading
resources.&lt;/li&gt;
&lt;li&gt;In &lt;a href=&quot;https://web.dev/pwa-with-offline-streaming/&quot;&gt;PWA with offline streaming&lt;/a&gt; you&#39;ll learn how
we built an updated demo PWA that is capable of adaptive streaming and offline
playback without using frameworks. Plus you can play with source code.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Be sure not to skip the accessibility section. The technical aspects of supporting accessibility are not that difficult. They are also a regulatory or legal requirement in many places. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;There is a lot of ground to cover in this section, get started by learning
how to use the &lt;a href=&quot;https://web.dev/video-and-source-tags/&quot;&gt;&amp;lt;video&amp;gt; and &amp;lt;source&amp;gt; tags&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Media file basics</title>
    <link href="https://web.dev/media-file-basics/"/>
    <updated>2020-06-19T00:00:00Z</updated>
    <id>https://web.dev/media-file-basics/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this article you will learn about media file basics such as the concepts of a
container, and a few of the many available codec formats that you can use in a
stream. Plus lightly touch on topics such as adaptive streaming, bitrate, and
resolution—but we&#39;ll dive deeper into all these in later sections.&lt;/p&gt;
&lt;h2 id=&quot;serving-video-files&quot;&gt;Serving video files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-file-basics/#serving-video-files&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You might think that you can take a raw file from a video camera and just upload
it to the web as-is. Indeed, video streaming sites such as &lt;a href=&quot;https://www.youtube.com/&quot; rel=&quot;noopener&quot;&gt;YouTube&lt;/a&gt; or &lt;a href=&quot;https://vimeo.com/&quot; rel=&quot;noopener&quot;&gt;Vimeo&lt;/a&gt;
let you do just that, and even provide live-streaming capabilities—typically by
connecting to your camera&#39;s HDMI port and then processing it through a capture
card. These services greatly simplify video processing and uploading, which
includes generating the many files and manifests needed for adaptive streaming
and various resolutions. Plus the many other complicated and nuanced requirements
that make self-hosting a bit of a chore. Preparing and serving a video from your
own site, and likely a separate media server, is a bit more complicated than just
uploading a raw camera file if you care how your users experience your site.&lt;/p&gt;
&lt;p&gt;Video files come in a variety of formats. The format that comes off your camera is
typically a &lt;code&gt;.mov&lt;/code&gt; file, or an &lt;code&gt;.mp4&lt;/code&gt; if you have a good modern mirrorless camera.
However, while a &lt;code&gt;.mov&lt;/code&gt; is good for recording and for editing and other early
post-production processes, the file size means it&#39;s not good for streaming over
the web. As well, the file size of a raw &lt;code&gt;.mp4&lt;/code&gt; in 4K is going to make playing that
file on mobile very prohibitive. Because browsers support different file formats,
you&#39;ll need to create multiple optimized files and potentially a manifest if you
plan to support adaptive streaming. Before converting files you need to understand
a few basics about them and about their characteristics.&lt;/p&gt;
&lt;h2 id=&quot;containers-and-codecs,-and-streams&quot;&gt;Containers and codecs, and streams? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-file-basics/#containers-and-codecs,-and-streams&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The file that you see in your operating system shell is a &lt;em&gt;container&lt;/em&gt;,
identified by a file extension (&lt;code&gt;.mp4&lt;/code&gt;, &lt;code&gt;.webm&lt;/code&gt;, &lt;code&gt;.ogg&lt;/code&gt; etc.). The container
houses one or more &lt;em&gt;streams&lt;/em&gt;. A media file can have any number of streams, of
many more &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats&quot; rel=&quot;noopener&quot;&gt;formats&lt;/a&gt; than we&#39;ll go into here.&lt;/p&gt;
&lt;p&gt;The sample files used later in this section contain at most two streams: an
audio stream and a video stream. Among the other types you might encounter are
captions and data, both of which are beyond the scope of this article. There are
instances where audio and video streams are dealt with separately. Most files
you&#39;ll encounter will only contain a single audio stream and a single video
stream.&lt;/p&gt;
&lt;p&gt;Within the audio and video streams, the actual data is compressed using a codec.
A &lt;em&gt;codec&lt;/em&gt;, or coder/decoder, is a compression format for video or audio data. The
distinction between a container and a codec is important because files with the
same container can have their contents encoded with different codecs.&lt;/p&gt;
&lt;p&gt;The image below illustrates this structure. On the left is the basic container
structure with two streams. On the right are the specifics of that structure for
a single WebM file.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Comparing media file structure with a hypothetical media file.&quot; decoding=&quot;async&quot; height=&quot;250&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 560px) 560px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QwNEBBa8LEMpedJh5imG.png?auto=format&amp;w=1120 1120w&quot; width=&quot;560&quot; /&gt;
  &lt;figcaption&gt;Parts of a media file.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Files in WebM containers can be orders of magnitude smaller than other formats,
making them a good choice for mobile sites to stream. Unfortunately, not all
browsers support up-to-date containers and codecs. For example, WebM was created
specifically for the web as a high-quality and open source option, but its support
is not yet universal. Safari in particular does not, according to &lt;a href=&quot;https://caniuse.com/#feat=webm&quot; rel=&quot;noopener&quot;&gt;Can I use&lt;/a&gt; at
the time of this writing, support WebM for embedded video. However, WebM does have
partial support with the VP8 and VP9 codec used in WebRTC. So your best option is
to provide a fallback video.&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; All modern browsers support MP4 files, making them a good general choice for a media container and the best choice as the backup container for WebM. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;codec-formats&quot;&gt;Codec formats &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-file-basics/#codec-formats&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many file types support multiple codecs within the same container. A complete list
of available &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs&quot; rel=&quot;noopener&quot;&gt;video codecs&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Audio_codecs&quot; rel=&quot;noopener&quot;&gt;audio codecs&lt;/a&gt; would be a whole website to itself.
The links just provided are for MDN&#39;s practical lists of what&#39;s usable on the web.
Listed below are the currently preferred file types, and the codecs they might use.
Follow the file type links to view the browsers that support them.&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File type&lt;/th&gt;
&lt;th&gt;Video Codec&lt;/th&gt;
&lt;th&gt;Audio Codec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://caniuse.com/#search=mp4&quot; rel=&quot;noopener&quot;&gt;MP4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs#AV1&quot; rel=&quot;noopener&quot;&gt;AV1&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs#AVC_H.264&quot; rel=&quot;noopener&quot;&gt;AVC (H.264)&lt;/a&gt;*, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs#VP9&quot; rel=&quot;noopener&quot;&gt;VP9&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Audio_codecs#AAC&quot; rel=&quot;noopener&quot;&gt;AAC&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://caniuse.com/#feat=webm&quot; rel=&quot;noopener&quot;&gt;WebM&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs#AV1&quot; rel=&quot;noopener&quot;&gt;AV1&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Video_codecs#VP9&quot; rel=&quot;noopener&quot;&gt;VP9&lt;/a&gt;*&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Audio_codecs#Vorbis&quot; rel=&quot;noopener&quot;&gt;Vorbis&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Media/Formats/Audio_codecs#Opus&quot; rel=&quot;noopener&quot;&gt;Opus&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;p&gt;* Indicates the preferred video codec.&lt;/p&gt;
&lt;h2 id=&quot;bitrate-and-resolution&quot;&gt;Bitrate and resolution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-file-basics/#bitrate-and-resolution&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Bitrate&lt;/strong&gt; is the maximum number of bits used to encode one second of a stream.
The more bits used to encode a second of stream, the higher the potential detail
and fidelity. We provide more information about this concept in &lt;a href=&quot;https://web.dev/bitrate/&quot;&gt;Bitrate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt; is the amount of information in a single frame of video, given as
the number of logical pixels in each dimension. We provide more information about
this concept in &lt;a href=&quot;https://web.dev/resolution/&quot;&gt;Resolution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Up next, in &lt;a href=&quot;https://web.dev/media-application-basics/&quot;&gt;Media application basics&lt;/a&gt;, we&#39;ll show you
how to examine these characteristics using two command line tools: Shaka Packager
and FFmpeg.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Positioning virtual objects in real-world views</title>
    <link href="https://web.dev/ar-hit-test/"/>
    <updated>2020-02-13T00:00:00Z</updated>
    <id>https://web.dev/ar-hit-test/</id>
    <content type="html" mode="escaped">&lt;p&gt;The WebXR Device API shipped last fall in Chrome 79. As stated then, Chrome&#39;s
implementation of the API is a work in progress. Chrome is happy to announce
that some of the work is finished. In Chrome 81, two new features have arrived:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5450241148977152&quot; rel=&quot;noopener&quot;&gt;Augmented reality session types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/4755348300759040&quot; rel=&quot;noopener&quot;&gt;Hit testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article covers the &lt;a href=&quot;https://github.com/immersive-web/hit-test/blob/master/hit-testing-explainer.md&quot; rel=&quot;noopener&quot;&gt;WebXR Hit Test
API&lt;/a&gt;, a
means of placing virtual objects in a real-world camera view.&lt;/p&gt;
&lt;p&gt;In this article I assume you already know how to create an augmented reality
session and that you know how to run a frame loop. If you&#39;re not familiar with
these concepts, you should read the earlier articles in this series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/vr-comes-to-the-web/&quot;&gt;Virtual reality comes to the web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/&quot;&gt;Virtual reality comes to the web, part II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/web-ar&quot;&gt;Web AR: you may already know how to use it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-immersive-ar-session-sample&quot;&gt;The immersive AR session sample &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#the-immersive-ar-session-sample&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code in this article is based on, but not identical to, that found in the
Immersive Web Working Group&#39;s Hit Test sample
(&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/hit-test.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;,
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/hit-test.html&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;).
This example lets you place virtual sunflowers on surfaces in the real world.&lt;/p&gt;
&lt;p&gt;When you first open the app, you&#39;ll see a blue circle with a dot in the middle.
The dot is the intersection between an imaginary line from your device to the
point in the environment. It moves as you move the device. As it finds
intersection points, it appears to snap to surfaces such as floors, table tops,
and walls. It does this because hit testing provides the position and
orientation of the intersection point, but nothing about the surfaces
themselves.&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; A reminder for those of you are new: in the WebXR device API &amp;quot;position and orientation&amp;quot; are covered by the term pose. I will be using that term from here on. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;This circle is called a &lt;em&gt;reticle&lt;/em&gt;, which is a temporary image that aids in
placing an object in augmented reality. If you tap the screen, a sunflower is
placed on the surface at the reticle location and orientation of the reticle
point, regardless of where you tapped the screen. The reticle continues to move
with your device.&lt;/p&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;A reticle rendered on a wall, Lax, or Strict depending on their context&quot; decoding=&quot;async&quot; height=&quot;1422&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/42fbB0q2WTa9ytTm8NZN.png?auto=format&amp;w=1600 1600w&quot; style=&quot;max-width: 60vw;&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The reticle is a temporary image that aids in placing an object in augmented reality.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;create-the-reticle&quot;&gt;Create the reticle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#create-the-reticle&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You must create the reticle image yourself since it is not provided by the
browser or the API. The method of loading and drawing it is framework specific.
If you&#39;re not drawing it directly using WebGL or WebGL2 consult your framework
documentation. For this reason, I won&#39;t go into detail about how the reticle is
drawn in the sample. Below I show one line of it for one reason only: so that in
later code samples, you&#39;ll know what I&#39;m referring to when I use the &lt;code&gt;reticle&lt;/code&gt;
variable.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; reticle &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;Gltf2Node&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 literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;media/gltf/reticle/reticle.gltf&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;request-a-session&quot;&gt;Request a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#request-a-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When requesting a session, you must request &lt;code&gt;&#39;hit-test&#39;&lt;/code&gt; in the
&lt;code&gt;requiredFeatures&lt;/code&gt; array as shown below.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-ar&#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 literal-property property&quot;&gt;requiredFeatures&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 string&quot;&gt;&#39;local&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hit-test&#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;br /&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&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;// Do something with the session&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;entering-a-session&quot;&gt;Entering a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#entering-a-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In previous articles I&#39;ve presented code for entering an XR session. I&#39;ve shown
a version of this below with some additions. First I&#39;ve added the &lt;code&gt;select&lt;/code&gt; event
listener. When the user taps the screen, a flower will be placed in the camera
view based on the pose of the reticle. I&#39;ll describe that event listener later.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  xrSession&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;select&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSelect&lt;span class=&quot;token punctuation&quot;&gt;)&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  gl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&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;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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;viewer&#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;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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;refSpace&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    xrViewerSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refSpace&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestHitTestSource&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 literal-property property&quot;&gt;space&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; xrViewerSpace &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&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 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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hitTestSource&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      xrHitTestSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hitTestSource&lt;span class=&quot;token punctuation&quot;&gt;;&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 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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;local&#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;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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;refSpace&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    xrRefSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refSpace&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&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 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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;multiple-reference-spaces&quot;&gt;Multiple reference spaces &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#multiple-reference-spaces&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Notice that the highlighted code calls &lt;code&gt;XRSession.requestReferenceSpace()&lt;/code&gt;
twice. I initially found this confusing. I asked why does the hit test code not
request an animation frame (starting the frame loop) and why does the frame loop
seem to not involve hit tests. The source of the confusion was a
misunderstanding of reference spaces. Reference spaces express relationships
between an origin and the world.&lt;/p&gt;
&lt;p&gt;To understand what this code is doing, pretend that you&#39;re viewing this sample
using a standalone rig, and you have both a headset and a controller. To measure
distances from the controller, you would use a controller-centered frame of
reference. But to draw something to the screen you would use user-centered
coordinates.&lt;/p&gt;
&lt;p&gt;In this sample, the viewer and the controller are the same device. But I have a
problem. What I draw must be stable with regard to the environment, but the
&#39;controller&#39; I&#39;m drawing with is moving.&lt;/p&gt;
&lt;p&gt;For image drawing, I use the &lt;code&gt;local&lt;/code&gt; reference space, which gives me stability
in terms of the environment. After getting this I start the frame loop by
calling &lt;code&gt;requestAnimationFrame()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For hit testing, I use the &lt;code&gt;viewer&lt;/code&gt; reference space, which is based on the
device&#39;s pose at the time of the hit test. The label &#39;viewer&#39; is somewhat
confusing in this context because I&#39;m talking about a controller. It makes sense
if you think of the controller as an electronic viewer. After getting this, I
call &lt;code&gt;xrSession.requestHitTestSource()&lt;/code&gt;, which creates the source of hit test
data that I&#39;ll use when drawing.&lt;/p&gt;
&lt;h2 id=&quot;running-a-frame-loop&quot;&gt;Running a frame loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#running-a-frame-loop&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;requestAnimationFrame()&lt;/code&gt; callback also gets new code to handle hit testing.&lt;/p&gt;
&lt;p&gt;As you move your device, the reticle needs to move with it as it tries to find
surfaces. To create the illusion of movement, redraw the reticle in every frame.
But don&#39;t show the reticle if the hit test fails. So, for the reticle I created
earlier, I set it&#39;s &lt;code&gt;visible&lt;/code&gt; property to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// Reminder: the hitTestSource was acquired during onSessionStart()&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; xrViewerPose&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;let&lt;/span&gt; hitTestResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHitTestResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hitTestResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hitTestResults&lt;span class=&quot;token punctuation&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;span class=&quot;token function&quot;&gt;getPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix&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&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&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 comment&quot;&gt;// Draw to the screen&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;To draw anything in AR, I need to know where the viewer is and where they&#39;re
looking. So I test that &lt;code&gt;hitTestSource&lt;/code&gt; and the &lt;code&gt;xrViewerPose&lt;/code&gt; are still valid.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&lt;span class=&quot;token punctuation&quot;&gt;)&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&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 comment&quot;&gt;// Reminder: the hitTestSource was acquired during onSessionStart()&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;)&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 keyword&quot;&gt;let&lt;/span&gt; hitTestResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHitTestResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hitTestResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hitTestResults&lt;span class=&quot;token punctuation&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;span class=&quot;token function&quot;&gt;getPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix&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&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&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 comment&quot;&gt;// Draw to the screen&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now I call &lt;code&gt;getHitTestResults()&lt;/code&gt;. It takes the &lt;code&gt;hitTestSource&lt;/code&gt; as an argument
and returns an array of &lt;code&gt;HitTestResult&lt;/code&gt; instances. The hit test may find
multiple surfaces. The first one in the array is the one closest to the camera.
Most of the time you will use it, but an array is returned for advanced use
cases. For example, imagine your camera is pointed at a box on a table on a
floor. It&#39;s possible that the hit test will return all three surfaces in the
array. In most cases, it will be the box that I care about. If the length of the
returned array is 0, in other words, if no hit test is returned, continue
onward. Try again in the next frame.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&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 comment&quot;&gt;// Reminder: the hitTestSource was acquired during onSessionStart()&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; xrViewerPose&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; hitTestResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHitTestResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hitTestResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hitTestResults&lt;span class=&quot;token punctuation&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;span class=&quot;token function&quot;&gt;getPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix&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&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&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 comment&quot;&gt;// Draw to the screen&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Finally, I need to process the hit test results. The basic process is this. Get
a pose from the hit test result, transform (move) the reticle image to the hit
test position, then set its &lt;code&gt;visible&lt;/code&gt; property to true.  The pose represents the
pose of a point on a surface.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;  reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&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;// Reminder: the hitTestSource was acquired during onSessionStart()&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;xrHitTestSource &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; xrViewerPose&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;let&lt;/span&gt; hitTestResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHitTestResults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrHitTestSource&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;hitTestResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hitTestResults&lt;span class=&quot;token punctuation&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;span class=&quot;token function&quot;&gt;getPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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 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;// Draw to the screen&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;placing-an-object&quot;&gt;Placing an object &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#placing-an-object&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An object is placed in AR when the user taps the screen. I already added a
&lt;code&gt;select&lt;/code&gt; event handler to the session. (&lt;a href=&quot;https://web.dev/ar-hit-test/#entering-a-session&quot;&gt;See above&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;The important thing in this step is knowing where to place it. Since the moving
reticle gives you a constant source of hit tests, the simplest way to place an
object is to draw it at the location of the reticle at the last hit test.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSelect&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;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;reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible&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;// The reticle should already be positioned at the latest hit point,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// so we can just use its matrix to save an unnecessary call to&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// event.frame.getHitTestResults.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;addARObjectAt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reticle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matrix&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/ar-hit-test/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best way to get a handle on this is to step through the &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-ar-session.html&quot; rel=&quot;noopener&quot;&gt;sample
code&lt;/a&gt; or
try out the
&lt;a href=&quot;https://codelabs.developers.google.com/ar-with-webxr&quot; rel=&quot;noopener&quot;&gt;codelab&lt;/a&gt;. I hope
I&#39;ve given you enough background to make sense of both.&lt;/p&gt;
&lt;p&gt;We&#39;re not done building immersive web APIs, not by a long shot. We&#39;ll publish
new articles here as we make progress.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@fr3nks&quot; rel=&quot;noopener&quot;&gt;Daniel Frank&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Virtual reality comes to the web, part II</title>
    <link href="https://web.dev/vr-comes-to-the-web-pt-ii/"/>
    <updated>2020-02-13T00:00:00Z</updated>
    <id>https://web.dev/vr-comes-to-the-web-pt-ii/</id>
    <content type="html" mode="escaped">&lt;p&gt;Recently, I published &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/&quot;&gt;Virtual reality comes to the
web&lt;/a&gt;, an article that introduced basic
concepts behind the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebXR_Device_API&quot; rel=&quot;noopener&quot;&gt;WebXR Device
API&lt;/a&gt;. I
also provided instructions for requesting, entering, and ending an XR session.&lt;/p&gt;
&lt;p&gt;This article describes the frame loop, which is a user-agent controlled
infinite loop in which content is repeatedly drawn to the screen. Content is
drawn in discrete blocks called frames. The succession of frames creates the
illusion of movement.&lt;/p&gt;
&lt;h2 id=&quot;what-this-article-is-not&quot;&gt;What this article is not &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#what-this-article-is-not&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WebGL and WebGL2 are the only means of rendering content during a frame loop in
a WebXR App. Fortunately many frameworks provide a layer of abstraction on top
of WebGL and WebGL2. Such frameworks include &lt;a href=&quot;https://threejs.org/&quot; rel=&quot;noopener&quot;&gt;three.js&lt;/a&gt;,
&lt;a href=&quot;https://www.babylonjs.com/&quot; rel=&quot;noopener&quot;&gt;babylonjs&lt;/a&gt;, and
&lt;a href=&quot;https://playcanvas.com/&quot; rel=&quot;noopener&quot;&gt;PlayCanvas&lt;/a&gt;, while &lt;a href=&quot;https://aframe.io/&quot; rel=&quot;noopener&quot;&gt;A-Frame&lt;/a&gt; and
&lt;a href=&quot;https://github.com/facebookarchive/react-360&quot; rel=&quot;noopener&quot;&gt;React 360&lt;/a&gt; was designed for interacting
with WebXR.&lt;/p&gt;
&lt;p&gt;This article is neither a WebGL nor a framework tutorial. It explains basics of
a frame loop using the Immersive Web Working Group&#39;s Immersive VR Session sample
(&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-vr-session.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;,
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/immersive-vr-session.html&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;).
If you want to dive into WebGL or one of the frameworks, the internet provides a
growing list of articles.&lt;/p&gt;
&lt;h2 id=&quot;the-players-and-the-game&quot;&gt;The players and the game &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#the-players-and-the-game&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When trying to understand the frame loop, I kept getting lost in the details.
There&#39;s a lot of objects in play, and some of them are only named by reference
properties on other objects. To help you keep it straight, I&#39;ll describe the
objects, which I&#39;m calling &#39;players&#39;. Then I&#39;ll describe how they interact,
which I&#39;m calling &#39;the game&#39;.&lt;/p&gt;
&lt;h2 id=&quot;the-players&quot;&gt;The players &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#the-players&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;xrviewerpose&quot;&gt;XRViewerPose &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#xrviewerpose&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A pose is the position and orientation of something in 3D space. Both viewers
and input devices have a pose, but it&#39;s the viewer&#39;s pose we&#39;re concerned with
here. Both viewer and input device poses have a &lt;code&gt;transform&lt;/code&gt; attribute describing
its position as a vector and its orientation as a quaternion relative to the
origin. The origin is specified based on the requested reference space type when
calling &lt;code&gt;XRSession.requestReferenceSpace()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Reference spaces take a bit to explain. I cover them in depth in &lt;a href=&quot;https://web.dev/web-ar/&quot;&gt;Augmented
reality&lt;/a&gt;. The sample I&#39;m using as the basis for this
article uses a &lt;code&gt;&#39;local&#39;&lt;/code&gt; reference space which means the origin is at the
viewer&#39;s position at the time of session creation without a well-defined floor,
and its precise position may vary by platform.&lt;/p&gt;
&lt;h3 id=&quot;xrview&quot;&gt;XRView &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#xrview&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A view corresponds to a camera viewing the virtual scene. A view also has a
&lt;code&gt;transform&lt;/code&gt; attribute describing it&#39;s position as a vector and its orientation.
These are provided both as a vector/quaternion pair and as an equivalent matrix,
you can use either representation depending on which best fits your code. Each
view corresponds to a display or a portion of a display used by a device to
present imagery to the viewer. &lt;code&gt;XRView&lt;/code&gt; objects are returned in an array from
the &lt;code&gt;XRViewerPose&lt;/code&gt; object. The number of views in the array varies. On mobile
devices an AR scene has one view, which may or may not cover the device screen.
Headsets typically have two views, one for each eye.&lt;/p&gt;
&lt;h3 id=&quot;xrwebgllayer&quot;&gt;XRWebGLLayer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#xrwebgllayer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Layers provide a source of bitmap images and descriptions of how those images
are to be rendered in the device. This description doesn&#39;t quite capture what
this player does. I&#39;ve come to think of it as a middleman between a device and a
&lt;code&gt;WebGLRenderingContext&lt;/code&gt;. MDN takes much the same view, stating that it &#39;provides
a linkage&#39; between the two. As such, it provides access to the other players.&lt;/p&gt;
&lt;p&gt;In general, WebGL objects store state information for rendering 2D and 3D
graphics.&lt;/p&gt;
&lt;h3 id=&quot;webglframebuffer&quot;&gt;WebGLFramebuffer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#webglframebuffer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A framebuffer provides image data to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;. After
retrieving it from the &lt;code&gt;XRWebGLLayer&lt;/code&gt;, you simply pass it to the current
&lt;code&gt;WebGLRenderingContext&lt;/code&gt;. Other than calling &lt;code&gt;bindFramebuffer()&lt;/code&gt; (more about that
later) you will never access this object directly. You will merely pass it from
the &lt;code&gt;XRWebGLLayer&lt;/code&gt; to the WebGLRenderingContext.&lt;/p&gt;
&lt;h3 id=&quot;xrviewport&quot;&gt;XRViewport &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#xrviewport&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A viewport provides the coordinates and dimensions of a rectangular region in
the &lt;code&gt;WebGLFramebuffer&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;webglrenderingcontext&quot;&gt;WebGLRenderingContext &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#webglrenderingcontext&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A rendering context is a programmatic access point for a canvas (the space we&#39;re
drawing on). To do this, it needs both a &lt;code&gt;WebGLFramebuffer&lt;/code&gt; and an XRViewport.&lt;/p&gt;
&lt;p&gt;Notice the relationship between &lt;code&gt;XRWebGLLayer&lt;/code&gt; and &lt;code&gt;WebGLRenderingContext&lt;/code&gt;. One
corresponds to the viewer&#39;s device and the other corresponds to the web page.
&lt;code&gt;WebGLFramebuffer&lt;/code&gt; and &lt;code&gt;XRViewport&lt;/code&gt; are passed from the former to the latter.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The relationship between XRWebGLLayer and WebGLRenderingContext&quot; decoding=&quot;async&quot; height=&quot;94&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 711px) 711px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/ZdH8cIApe8jUr7F1WNgC.png?auto=format&amp;w=1422 1422w&quot; width=&quot;711&quot; /&gt;
  &lt;figcaption&gt;
    The relationship between &lt;code&gt;XRWebGLLayer&lt;/code&gt; and &lt;code&gt;WebGLRenderingContext&lt;/code&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-game&quot;&gt;The game &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#the-game&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we know who the players are, let&#39;s look at the game they play. It&#39;s a
game that starts over with every frame. Recall that frames are part of a &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/#running-a-frame-loop&quot;&gt;frame
loop&lt;/a&gt; that happens at
a rate that depends on the underlying hardware. For VR applications the frames
per second can be anywhere from 60 to 144. AR for Android runs at 30 frames per
second. Your code should not assume any particular frame rate.&lt;/p&gt;
&lt;p&gt;The basic process for the frame loop looks like this:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt;. In response, the user agent invokes the &lt;code&gt;XRFrameRequestCallback&lt;/code&gt;, which is defined by you.&lt;/li&gt;
  &lt;li&gt;Inside your callback function:
    &lt;ol&gt;
      &lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt; again.&lt;/li&gt;
      &lt;li&gt;Get the viewer&#39;s pose.&lt;/li&gt;
      &lt;li&gt;Pass (&#39;bind&#39;) the &lt;code&gt;WebGLFramebuffer&lt;/code&gt; from the &lt;code&gt;XRWebGLLayer&lt;/code&gt; to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Iterate over each &lt;code&gt;XRView&lt;/code&gt; object, retrieving its &lt;code&gt;XRViewport&lt;/code&gt; from the &lt;code&gt;XRWebGLLayer&lt;/code&gt; and passing it to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Draw something to the framebuffer.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because steps 1 and 2a were covered in the previous article, I&#39;ll start at step
2b.&lt;/p&gt;
&lt;h3 id=&quot;get-the-viewers-pose&quot;&gt;Get the viewer&#39;s pose &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#get-the-viewers-pose&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It probably goes without saying. To draw anything in AR or VR, I need to know
where the viewer is and where they&#39;re looking. The viewer&#39;s position and
orientation are provided by an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XRViewerPose&quot; rel=&quot;noopener&quot;&gt;XRViewerPose
object&lt;/a&gt;. I
get the viewer&#39;s pose by calling &lt;code&gt;XRFrame.getViewerPose()&lt;/code&gt; on the current
animation frame. I pass it the reference space I acquired when I set up the
session. The values returned by this object are always relative to the reference
space I requested when I &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/#entering-a-session&quot;&gt;entered the current
session&lt;/a&gt;. As you may
recall, I have to pass the current reference space when requesting the pose.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;)&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 comment&quot;&gt;// Render based on the pose.&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;There&#39;s one viewer pose that represents the user&#39;s overall position, meaning
either the viewer&#39;s head or the phone camera in the case of a smartphone.
The pose tells your application where the viewer is. Actual image rendering uses
&lt;code&gt;XRView&lt;/code&gt; objects, which I&#39;ll get to in a bit.&lt;/p&gt;
&lt;p&gt;Before moving on, I test whether the viewer pose was returned in case the system
loses tracking or blocks the pose for privacy reasons. Tracking is the XR
device&#39;s ability to know where it and/or it&#39;s input devices are relative to the
environment. Tracking can be lost in several ways, and varies depending on the
method used for tracking. For example, if cameras on the headset or phone are
used for tracking the device may lose its ability to determine where it is in
situations with low or no light, or if the cameras are covered.&lt;/p&gt;
&lt;p&gt;An example of blocking the pose for privacy reasons is if the headset is showing
a security dialog such as a permission prompt, the browser may stop providing
poses to the application while this is happening. But I&amp;quot;ve already called
&lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt; so that if the system can recover, the frame
loop will continue. If not, the user agent will end the session and call the
&lt;code&gt;end&lt;/code&gt; event handler.&lt;/p&gt;
&lt;h3 id=&quot;a-short-detour&quot;&gt;A short detour &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#a-short-detour&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next step requires objects created during &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/#requesting-a-session&quot;&gt;session
set-up&lt;/a&gt;. Recall that
I created a canvas and instructed it to create an XR-compatible Web GL rendering
context, which I got by calling &lt;code&gt;canvas.getContext()&lt;/code&gt;. All drawing is done using
the WebGL API, the WebGL2 API, or a WebGL-based framework such as Three.js. This
context was passed to the session object via &lt;code&gt;updateRenderState()&lt;/code&gt;, along with a
new instance of &lt;code&gt;XRWebGLLayer&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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 comment&quot;&gt;// The rendering context must be based on WebGL or WebGL2&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; webGLRenContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webGLRenContext&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;pass-bind-the-webglframebuffer&quot;&gt;Pass (&#39;bind&#39;) the WebGLFramebuffer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#pass-bind-the-webglframebuffer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;XRWebGLLayer&lt;/code&gt; provides a framebuffer for the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;
provided specifically for use with WebXR and replacing the rendering contexts
default framebuffer. This is called &#39;binding&#39; in the language of WebGL.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; glLayer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;renderState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseLayer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindFramebuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FRAMEBUFFER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;framebuffer&lt;span class=&quot;token punctuation&quot;&gt;)&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 comment&quot;&gt;// Iterate over the views&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;iterate-over-each-xrview-object&quot;&gt;Iterate over each XRView object &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#iterate-over-each-xrview-object&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After getting the pose and binding the framebuffer, it&#39;s time to get the
viewports. The &lt;code&gt;XRViewerPose&lt;/code&gt; contains an array of XRView interfaces each of
which represents a display or a portion of a display. They contain information
that&#39;s needed to render content that&#39;s correctly positioned for the device and
the viewer such as the field of view, eye offset, and other optical properties.
Since I&#39;m drawing for two eyes, I have two views, which I loop through and draw
a separate image for each.&lt;/p&gt;
&lt;p&gt;When implementing for phone-based augmented reality, I would have only one view
but I&#39;d still use a loop. Though it may seem pointless to iterate through one
view, doing so allows you to have a single rendering path for a spectrum of
immersive experiences. This is an important difference between WebXR and other
immersive systems.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&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;let&lt;/span&gt; glLayer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;renderState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseLayer&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;    webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindFramebuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FRAMEBUFFER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;framebuffer&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrView &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&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 comment&quot;&gt;// Pass viewports to the context&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 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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;pass-the-xrviewport-object-to-the-webglrenderingcontext&quot;&gt;Pass the XRViewport object to the WebGLRenderingContext &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#pass-the-xrviewport-object-to-the-webglrenderingcontext&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An &lt;code&gt;XRView&lt;/code&gt; object refers to what&#39;s observable on a screen. But to draw to that
view I need coordinates and dimensions that are specific to my device. As with
the framebuffer, I request them from the &lt;code&gt;XRWebGLLayer&lt;/code&gt; and pass them to the
&lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrRefSpace&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&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;let&lt;/span&gt; glLayer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;renderState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseLayer&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;    webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindFramebuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FRAMEBUFFER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;framebuffer&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrView &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; xrViewerPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&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 keyword&quot;&gt;let&lt;/span&gt; viewport &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; glLayer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      webGLRenContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;viewport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; viewport&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; viewport&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; viewport&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&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 comment&quot;&gt;// Draw something to the framebuffer&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&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-webglrencontext&quot;&gt;The webGLRenContext &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#the-webglrencontext&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In writing this article I had a debate with a few collegues over the naming of
the &lt;code&gt;webGLRenContext&lt;/code&gt; object. The sample scripts and most WebXR code simpley
calls this variable &lt;code&gt;gl&lt;/code&gt;. When I was working to understand the samples, I kept
forgetting what &lt;code&gt;gl&lt;/code&gt; referred to. I&#39;ve called it &lt;code&gt;webGLRenContext&lt;/code&gt; to remind you
while your learning that this is an instance of &lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reason is that using &lt;code&gt;gl&lt;/code&gt; allows method names to look like their
counterparts in the OpenGL ES 2.0 API, used for creating VR in compiled
languages. This fact is obvious if you&#39;ve written VR apps using OpenGL, but
confusing if you&#39;re completely new to this technology.&lt;/p&gt;
&lt;h3 id=&quot;draw-something-to-the-framebuffer&quot;&gt;Draw something to the framebuffer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#draw-something-to-the-framebuffer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re feeling really ambitious, you can use WebGL directly, but I don&#39;t
recommend that. It&#39;s much simpler to use one of the frameworks &lt;a href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#what-this-article-is-not&quot;&gt;listed at the
top&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is not the end of WebXR updates or articles. You can find a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebXR_Device_API&quot; rel=&quot;noopener&quot;&gt;reference for
all of WebXR&#39;s interfaces and
members&lt;/a&gt;
at MDN. For upcoming enhancements to the interfaces themselves, follow
individual features on &lt;a href=&quot;https://www.chromestatus.com/features#WebXR&quot; rel=&quot;noopener&quot;&gt;Chrome
Status&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@jeshoots&quot; rel=&quot;noopener&quot;&gt;JESHOOTS.COM&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Augmented reality: You may already know it</title>
    <link href="https://web.dev/web-ar/"/>
    <updated>2020-02-13T00:00:00Z</updated>
    <id>https://web.dev/web-ar/</id>
    <content type="html" mode="escaped">&lt;p&gt;The WebXR Device API shipped last fall in Chrome 79. As stated then, Chrome&#39;s
implementation of the API is a work in progress. Chrome is happy to announce
that some of the work is finished. In Chrome 81, two new features have arrived:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5450241148977152&quot; rel=&quot;noopener&quot;&gt;Augmented reality session types&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/4755348300759040&quot; rel=&quot;noopener&quot;&gt;Hit testing&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article covers augmented reality. If you&#39;ve already used the WebXR Device
API, you&#39;ll be happy to know there&#39;s very little new to learn. Entering a WebXR
session is largely the same. Running a frame loop is largely the same. The
differences lie in configurations that allow content to be shown appropriately
for augmented reality. If you&#39;re not familiar with the basic concepts of WebXR,
you should read my earlier posts on the WebXR Device API, or at least be
familiar with the topics covered therein. You should know how to &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/&quot;&gt;request and
enter a session&lt;/a&gt; and you should know how
to run &lt;a href=&quot;https://web.dev/vr-comes-to-the-web-pt-ii&quot;&gt;a frame loop&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For information on hit testing, see the companion article &lt;a href=&quot;https://web.dev/ar-hit-test&quot;&gt;Positioning virtual
objects in real-world views&lt;/a&gt;. The code in this
article is based on the Immersive AR Session sample
(&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-ar-session.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/immersive-vr-session.html&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;) from
the Immersive Web Working Group&#39;s &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/&quot; rel=&quot;noopener&quot;&gt;WebXR Device API
samples&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before diving into the code you should use the &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-ar-session.html&quot; rel=&quot;noopener&quot;&gt;Immersive AR Session
sample&lt;/a&gt;
at least once. You&#39;ll need a modern Android phone with Chrome 81 or later.&lt;/p&gt;
&lt;h2 id=&quot;whats-it-useful-for&quot;&gt;What&#39;s it useful for? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#whats-it-useful-for&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Augmented reality will be a valuable addition to a lot of existing or new web
pages by allowing them to implement AR use cases without leaving the browser.
For example, it can help people learn on education sites, and allow potential
buyers to visualize objects in their home while shopping.&lt;/p&gt;
&lt;p&gt;Consider the second use case. Imagine simulating placing a life-size
representation of a virtual object in a real scene. Once placed, the image stays
on the selected surface, appears the size it would be if the actual item were on
that surface, and allows the user to move around it as well as closer to it or
farther from it. This gives viewers a deeper understanding of the object than is
possible with a two dimensional image.&lt;/p&gt;
&lt;p&gt;I&#39;m getting a little ahead of myself. To actually do what I&#39;ve described, you
need AR functionality and some means of detecting surfaces. This article covers
the former. The accompanying article on the WebXR Hit Test API (linked to above)
covers the latter.&lt;/p&gt;
&lt;h2 id=&quot;requesting-a-session&quot;&gt;Requesting a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#requesting-a-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Requesting a session is very much like what you&#39;ve seen before. First find out
if the session type you want is available on the current device by calling
&lt;code&gt;xr.isSessionSupported()&lt;/code&gt;. Instead of requesting &lt;code&gt;&#39;immersive-vr&#39;&lt;/code&gt; as before,
request &lt;code&gt;&#39;immersive-ar&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supported &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSessionSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-ar&#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;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supported&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;    xrButton&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;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onButtonClicked&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;    xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Enter AR&#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;    xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; supported&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// supported is Boolean&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As before, this enables an &#39;Enter AR&#39; button. When the user clicks it, call
&lt;code&gt;xr.requestSession()&lt;/code&gt;, also passing &lt;code&gt;&#39;immersive-ar&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&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;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onButtonClicked&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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;xrSession&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-ar&#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 function&quot;&gt;then&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 parameter&quot;&gt;session&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isImmersive &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;      xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Exit AR&#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 function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrSession&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;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 keyword&quot;&gt;else&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;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&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&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;a-convenience-property&quot;&gt;A convenience property &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#a-convenience-property&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You&#39;ve probably noticed that I highlighted two lines in the last code sample.
The &lt;code&gt;XRSession&lt;/code&gt; object would seem to have a property called &lt;code&gt;isImmersive&lt;/code&gt;. This
is a convenience property I&#39;ve created myself, and not part of the spec. I&#39;ll
use it later to make decisions about what to show the viewer. Why isn&#39;t this
property part of the API? Because your app may need to track this property
differently so the spec authors decided to keep the API clean.&lt;/p&gt;
&lt;h2 id=&quot;entering-a-session&quot;&gt;Entering a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#entering-a-session&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recall what &lt;code&gt;onSessionStarted()&lt;/code&gt; looked like in my earlier article:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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;  gl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;local-floor&#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 function&quot;&gt;then&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 parameter&quot;&gt;refSpace&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;    xrRefSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refSpace&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;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;I need to add a few things to account for rendering augmented reality. Turn off
the background First, I&#39;m going to determine whether I need the background. This
is the first place I&#39;m going to use my convenience property.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isImmersive&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&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 function&quot;&gt;removeBackground&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  gl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&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;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  refSpaceType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isImmersive &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;local&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;viewer&#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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;refSpaceType&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;refSpace&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;reference-spaces&quot;&gt;Reference spaces &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#reference-spaces&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My earlier articles skimmed over reference spaces. The sample I&#39;m describing
uses two of them, so it&#39;s time to correct that omission.&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; A full explanation of reference spaces would be longer than I can provide here. I&#39;m only going to discuss reference spaces in regards to augmented reality. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;A reference space describes the relationship between the virtual world and the
user&#39;s physical environment. It does this by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specifying the origin for the coordinate system used for expressing positions
in the virtual world.&lt;/li&gt;
&lt;li&gt;Specifying whether the user is expected to move within that coordinate system.&lt;/li&gt;
&lt;li&gt;Whether that coordinate system has pre-established boundaries. (The examples
shown here do not use coordinate systems with pre-established boundaries.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For all reference spaces, the X coordinate expresses left and right, the Y
expresses up and down, and Z expresses forward and backward. Positive values are
right, up, and backward, respectively.&lt;/p&gt;
&lt;p&gt;The coordinates returned by &lt;code&gt;XRFrame.getViewerPose()&lt;/code&gt; depend on the requested
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XRReferenceSpace#Reference_space_types&quot; rel=&quot;noopener&quot;&gt;reference space
type&lt;/a&gt;.
More about that when we get to the frame loop. Right now we need to select a
reference type that&#39;s appropriate for augmented reality. Again, this uses my
convenience property.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; refSpaceType&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isImmersive&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 function&quot;&gt;removeBackground&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&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&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;let&lt;/span&gt; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  gl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&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;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  refSpaceType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isImmersive &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;local&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;viewer&#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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;refSpaceType&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;refSpace&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;br /&gt;&lt;span class=&quot;highlight-line&quot;&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;If you&#39;ve visited the &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-ar-session.html&quot; rel=&quot;noopener&quot;&gt;Immersive AR Session
Sample&lt;/a&gt;
you&#39;ll notice that initially the scene is static and not at all augmented
reality. You can drag and swipe with your finger to move around the scene. If
you click &amp;quot;START AR&amp;quot;, the background drops out and you can move around the scene
by moving the device. The modes use different reference space types. The
highlighted text above shows how this is selected. It uses the following
reference types:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;local&lt;/code&gt; - The origin is at the viewer&#39;s position at the time of session
creation. This means the experience doesn&#39;t necessarily have a well-defined
floor and the exact position of the origin may vary by platform. Though there
are no pre-established boundaries to the space, it&#39;s expected that content can
be viewed with no movement other than rotation. As you can see from our own AR
example, some movement within the space may be possible.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;viewer&lt;/code&gt; - Used most frequently for content presented inline in the page, this
space follows the viewing device. When passed to getViewerPose it provides no
tracking, and thus always reports a pose at the origin unless the application
modifies it with &lt;code&gt;XRReferenceSpace.getOffsetReferenceSpace()&lt;/code&gt;. The sample uses
this to enable touch-based panning of the camera.&lt;/p&gt;
&lt;h2 id=&quot;running-a-frame-loop&quot;&gt;Running a frame loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#running-a-frame-loop&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Conceptually, nothing changes from what I did in the VR session described in my
earlier articles. Pass the reference space type to &lt;code&gt;XRFrame.getViewerPose()&lt;/code&gt;.
The returned &lt;code&gt;XRViewerPose&lt;/code&gt; will be for the current reference space type. Using
&lt;code&gt;viewer&lt;/code&gt; as the default allows a page to show content previews before user
consent is requested for AR or VR. This illustrates an important point: the
inline content uses the same frame loop as the immersive content, cutting down
the amount of code that needs to be maintained.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrViewerPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getViewerPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;refSpaceType&lt;span class=&quot;token punctuation&quot;&gt;)&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrViewerPose&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 comment&quot;&gt;// Render based on the pose.&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&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-ar/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This series of articles only covers the basics of implementing immersive content
on the web. Many more capabilities and use cases are presented by the Immersive
Web Working Group&#39;s &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/&quot; rel=&quot;noopener&quot;&gt;WebXR Device API
samples&lt;/a&gt;. We&#39;ve also just
published a &lt;a href=&quot;https://web.dev/ar-hit-test/&quot;&gt;hit test article&lt;/a&gt; which explains an API
for detecting surfaces and placing virtual items in a real-world camera view.
Check them out and watch The web.dev blog for more
articles in the year to come.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@davidgrdm&quot; rel=&quot;noopener&quot;&gt;David Grandmougin&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Integrate PWAs into built-in sharing UIs with Workbox</title>
    <link href="https://web.dev/workbox-share-targets/"/>
    <updated>2019-12-19T00:00:00Z</updated>
    <id>https://web.dev/workbox-share-targets/</id>
    <content type="html" mode="escaped">&lt;p&gt;The &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt; lets you display
your &lt;a href=&quot;https://web.dev/pwa-checklist/&quot;&gt;Progressive Web App&lt;/a&gt; in a
user&#39;s system-level share &lt;a href=&quot;https://material.io/develop/android/components/bottom-sheet-behavior/&quot; rel=&quot;noopener&quot;&gt;sheet&lt;/a&gt; after it&#39;s been installed. While it works great if you have a server
available to receive the request, it&#39;s much harder to get working if you don&#39;t.&lt;/p&gt;
&lt;p&gt;In this article we&#39;ll use
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;, a set of JavaScript
libraries for adding offline support to web apps, to create a share target URL
that lives entirely inside your &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service worker&lt;/a&gt;. This lets static sites and
single-page apps serve as share targets without a dedicated server endpoint.&lt;/p&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;Android phone with the &amp;#x27;Share via&amp;#x27; drawer open.&quot; decoding=&quot;async&quot; height=&quot;377&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;
    System-level share target picker with an installed PWA called
    &lt;code&gt;Share Target Test&lt;/code&gt; as an option.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;on-the-same-page&quot;&gt;On the same page &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#on-the-same-page&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re unfamiliar with how Web Share Target Works, &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Receiving shared data with the Web Share
Target API&lt;/a&gt; gives you an in-depth introduction.
Here&#39;s a quick review.&lt;/p&gt;
&lt;p&gt;There are two parts to implementing web share target functionality. First,
update your &lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web app manifest&lt;/a&gt; to indicate that you want your app to be a share
target when installed. The following example directs shares to the &lt;code&gt;/share&lt;/code&gt; url
via a &lt;code&gt;POST&lt;/code&gt; request. It is encoded as a multipart form, with title being called
&lt;code&gt;name&lt;/code&gt;, text being called &lt;code&gt;description&lt;/code&gt;, and JPEG images being called &lt;code&gt;photos&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;…&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;&quot;share_target&quot;&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 property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/share&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;enctype&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;params&quot;&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 property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;files&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;photos&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token property&quot;&gt;&quot;accept&quot;&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 string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.jpg&quot;&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;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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;service-worker-share-targets-with-workbox&quot;&gt;Service worker share targets with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#service-worker-share-targets-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While normally handled by a server endpoint, a neat trick you can do for a share
target is to register a route directly in your service worker to handle the
request. This will let your app be a share target without a backend.&lt;/p&gt;
&lt;p&gt;You do this in &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt; by
registering a route that&#39;s handled by your service worker. Start by importing
&lt;code&gt;registerRoute&lt;/code&gt; from &lt;code&gt;&#39;workbox-routing&#39;&lt;/code&gt;. Notice that it&#39;s registered for the
&lt;code&gt;/share&lt;/code&gt; route, the same one listed in the example web app manifest. In
response it calls &lt;code&gt;shareTargetHandler()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; registerRoute &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-routing&#39;&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 function&quot;&gt;registerRoute&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;/share&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  shareTargetHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&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 &lt;code&gt;shareTargetHandler()&lt;/code&gt; function is asynchronous and takes the event, awaits
the form data, then retrieves the media files from that.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&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;shareTargetHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&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&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; formData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&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 function&quot;&gt;formData&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;const&lt;/span&gt; mediaFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;media&#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;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mediaFile &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; mediaFiles&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;// Do something with mediaFile&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Maybe cache it or post it back to a server&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do something with the rest of formData as you need&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Maybe save it to IndexedDB&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;You can then do whatever you&#39;d like with these files. You can cache them. You
can send them somewhere with a fetch request. You can even use the other
manifest options, maybe serving a page with some query parameters for the other
shared items or storing the data and pointers to the media in the &lt;a href=&quot;https://web.dev/cache-api-quick-guide/&quot;&gt;Cache Storage
API&lt;/a&gt;
or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can try it out on the sample app &lt;a href=&quot;https://fugu-journal.web.app/&quot; rel=&quot;noopener&quot;&gt;Fugu
Journal&lt;/a&gt; and see its service worker
implementation in its &lt;a href=&quot;https://github.com/chromeos/bridging-the-native-app-gap/blob/master/fugu-journal/src/js/service-worker.js&quot; rel=&quot;noopener&quot;&gt;source
code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One common thing you might do is hold shared resources until better network
connections are available. Workbox also supports &lt;a href=&quot;https://web.dev/periodic-background-sync/&quot;&gt;periodic background
sync&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Share Target API is a simple way to deeply integrate your Progressive Web
App into user&#39;s devices, putting them on-par with platform-specific applications for the
critical task of sharing content between apps. But doing so usually requires a
server available to receive the request. By leveraging Workbox to create a share
target route directly in your service worker, your app is free of this
constraint, allowing Share Target to work for apps while offline and without
backends.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@ecasap?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Elaine Casap&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/share?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Sam Richard</name>
    </author><author>
      <name>Joe Medley</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Improving page dismissal in synchronous XMLHttpRequest()</title>
    <link href="https://web.dev/disallow-synchronous-xhr/"/>
    <updated>2019-12-18T00:00:00Z</updated>
    <id>https://web.dev/disallow-synchronous-xhr/</id>
    <content type="html" mode="escaped">&lt;p&gt;It&#39;s common for a page or app to have unsubmitted analytics or other data at the
time a user closes it. To prevent data loss, some sites use a synchronous call
to &lt;code&gt;XMLHttpRequest()&lt;/code&gt; to keep the page or app open until its data is passed to
the server. Not only are there better ways to save data, but this technique creates
a bad user experience by delaying closing of the page for up to several seconds.&lt;/p&gt;
&lt;p&gt;This practice needs to change, and browsers are responding. The &lt;code&gt;XMLHttpRequest()&lt;/code&gt;
specification is already &lt;a href=&quot;https://xhr.spec.whatwg.org/#sync-warning&quot; rel=&quot;noopener&quot;&gt;slated for deprecation and
removal&lt;/a&gt;. Chrome 80 takes the first
step by disallowing synchronous calls inside several event handlers,
specifically &lt;code&gt;beforeunload&lt;/code&gt;, &lt;code&gt;unload&lt;/code&gt;, &lt;code&gt;pagehide&lt;/code&gt;, and &lt;code&gt;visibilitychange&lt;/code&gt; when
they are fired in the dismissal. WebKit also recently landed &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=204912&quot; rel=&quot;noopener&quot;&gt;a commit implementing
the same behavior change&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this article I&#39;ll briefly describe options for those who need time to update
their sites and outline the alternatives to &lt;code&gt;XMLHttpRequest()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;temporary-opt-outs&quot;&gt;Temporary opt-outs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/disallow-synchronous-xhr/#temporary-opt-outs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome does not simply want to pull the plug on &lt;code&gt;XMLHttpRequest()&lt;/code&gt;, which is why a few
temporary opt-out options are available. For sites on the internet, &lt;a href=&quot;https://developers.chrome.com/origintrials/#/view_trial/4391009636686233601&quot; rel=&quot;noopener&quot;&gt;an origin
trial is
available&lt;/a&gt;.
With this, you add an origin-specific token to your page headers that enables
synchronous &lt;code&gt;XMLHttpRequest()&lt;/code&gt; calls. This option ends shortly before Chrome 89
ships, sometime in March 2021. Enterprise Chrome customers can also
use the  &lt;code&gt;AllowSyncXHRInPageDismissal&lt;/code&gt; policy flag, which ends at the same time.&lt;/p&gt;
&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/disallow-synchronous-xhr/#alternatives&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Regardless of how you send data back to the server, it&#39;s best to avoid waiting
until page unload to send all the data at once. Aside from creating a bad user
experience, unload is unreliable on modern browsers and risks data loss if
something goes wrong. Specifically, unload events &lt;a href=&quot;https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/&quot; rel=&quot;noopener&quot;&gt;often don&#39;t fire on mobile
browsers&lt;/a&gt;
because there are &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/&quot; rel=&quot;noopener&quot;&gt;many ways to
close&lt;/a&gt; a
tab or browser on mobile operating systems without the &lt;code&gt;unload&lt;/code&gt; event firing.
With &lt;code&gt;XMLHttpRequest()&lt;/code&gt;, using small payloads was a choice. Now it&#39;s a
requirement. Both of its alternatives have an upload limit of 64 KB per
context, as required by the specification.&lt;/p&gt;
&lt;h3 id=&quot;fetch-keepalive&quot;&gt;Fetch keepalive &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/disallow-synchronous-xhr/#fetch-keepalive&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;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;
provides a robust means of dealing with server interactions and &lt;a href=&quot;https://fetch.spec.whatwg.org/#preface&quot; rel=&quot;noopener&quot;&gt;a consistent
interface&lt;/a&gt; for use across different
platform APIs. Among its options is &lt;code&gt;keepalive&lt;/code&gt;, which ensures that a request
continues whether or not the page that made it stays open:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;window&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;unload&#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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/siteAnalytics&#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;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#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 literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStatistics&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&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;keepalive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;fetch()&lt;/code&gt; method has the advantage of greater control over what&#39;s sent to
the server. What I don&#39;t show in the example is that &lt;code&gt;fetch()&lt;/code&gt; also returns a
promise that resolves with a &lt;code&gt;Response&lt;/code&gt; object. Since I&#39;m trying to get out of the
way of the page&#39;s unloading, I chose not to do anything with it.&lt;/p&gt;
&lt;h3 id=&quot;sendbeacon&quot;&gt;SendBeacon() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/disallow-synchronous-xhr/#sendbeacon&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;SendBeacon()&lt;/code&gt;&lt;/a&gt;
actually uses the Fetch API under the hood, which is why it has the same
64 KB payload limitation and why it also ensures that a request continues
after a page unload. Its primary advantage is its simplicity. It lets you
submit your data with a single line of code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&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;unload&#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;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/siteAnalytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStatistics&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/disallow-synchronous-xhr/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;increased availability of
&lt;code&gt;fetch()&lt;/code&gt;&lt;/a&gt;
across browsers, &lt;code&gt;XMLHttpRequest()&lt;/code&gt; will hopefully be removed
from the web platform at some point. Browser vendors agree it should be removed, but it will
take time. Deprecating one of its worst use cases is a first step that improves
the user experience for everyone.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@thatsmrbio?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Matthew Hamilton&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/roadblock?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Integrate with the OS sharing UI with the Web Share API</title>
    <link href="https://web.dev/web-share/"/>
    <updated>2019-11-08T00:00:00Z</updated>
    <id>https://web.dev/web-share/</id>
    <content type="html" mode="escaped">&lt;p&gt;With the Web Share API, web apps are able to use the same system-provided share
capabilities as platform-specific apps. The Web Share API makes it possible for web apps to
share links, text, and files to other apps installed on the device in the same
way as platform-specific apps.&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; Sharing is only half of the magic. Web apps can also be share targets, meaning they can receive data, links, text, and files from platform-specific or web apps. Read &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Receive shared data&lt;/a&gt; to learn how to register your app as a share target. &lt;/div&gt;&lt;/aside&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;System-level share target picker with an installed PWA as an option.&quot; decoding=&quot;async&quot; height=&quot;349&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 370px) 370px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/cCXNoHbXAfkAQzTTuS0Z.png?auto=format&amp;w=740 740w&quot; width=&quot;370&quot; /&gt;
  &lt;figcaption&gt;
    System-level share target picker with an installed PWA as an option.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;capabilities-and-limitations&quot;&gt;Capabilities and limitations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#capabilities-and-limitations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Web share has the following capabilities and limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It can only be used on a site that is &lt;a href=&quot;https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features&quot; rel=&quot;noopener&quot;&gt;accessed via HTTPS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If the share happens in a third-party iframe, the &lt;code&gt;allow&lt;/code&gt; attribute must be used.&lt;/li&gt;
&lt;li&gt;It must be invoked in response to a user action such as a click. Invoking it
through the &lt;code&gt;onload&lt;/code&gt; handler is impossible.&lt;/li&gt;
&lt;li&gt;It can share URLs, text, or files.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 71, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 93, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
93
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 12.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
12.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/share#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;sharing-links-and-text&quot;&gt;Sharing links and text &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#sharing-links-and-text&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To share links and text, use the &lt;code&gt;share()&lt;/code&gt; method, which is a promise-based
method with a required properties object.
To keep the browser from throwing a &lt;code&gt;TypeError&lt;/code&gt;,
the object must contain at least one
of the following properties: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; or &lt;code&gt;files&lt;/code&gt;. You
can, for example, share text without a URL or vice versa. Allowing all three
members expands the flexibility of use cases. Imagine if after running the code
below, the user chose an email application as the target. The &lt;code&gt;title&lt;/code&gt; parameter
might become the email subject, the &lt;code&gt;text&lt;/code&gt;, the message body, and the files, the
attachments.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;share&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;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&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 literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web.dev&#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;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Check out web.dev.&#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;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://web.dev/&#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;br /&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 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; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Successful share&#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 punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&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 parameter&quot;&gt;error&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; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Error sharing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If your site has multiple URLs for the same content, share the page&#39;s
canonical URL instead of the current URL. Instead of sharing
&lt;code&gt;document.location.href&lt;/code&gt;, you would check for a canonical URL &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag in
the page&#39;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and share that. This will provide a better experience to the
user. Not only does it avoid redirects, but it also ensures that a shared URL serves
the correct user experience for a particular client. For example, if a friend
shares a mobile URL and you look at it on a desktop computer,
you should see a desktop version:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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; canonicalElement &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;link[rel=canonical]&#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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canonicalElement &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&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;    url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canonicalElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&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;url&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;sharing-files&quot;&gt;Sharing files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#sharing-files&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To share files, first test for and call &lt;code&gt;navigator.canShare()&lt;/code&gt;. Then include an
array of files in the call to &lt;code&gt;navigator.share()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;canShare &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canShare&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 literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filesArray &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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filesArray&lt;span class=&quot;token punctuation&quot;&gt;,&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 literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Vacation Pictures&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&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 literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Photos from September 27 to October 14.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&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 punctuation&quot;&gt;}&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 function&quot;&gt;then&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 operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Share was successful.&#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;/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 function&quot;&gt;catch&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 parameter&quot;&gt;error&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; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Sharing failed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&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 keyword&quot;&gt;else&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;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&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;Your system doesn&#39;t support sharing files.&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;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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Notice that the sample handles feature detection by testing for
&lt;code&gt;navigator.canShare()&lt;/code&gt; rather than for &lt;code&gt;navigator.share()&lt;/code&gt;.
The data object passed to &lt;code&gt;canShare()&lt;/code&gt; only supports the &lt;code&gt;files&lt;/code&gt; property.
Certain types of audio, image, pdf, video, and text files can be shared.
See &lt;a href=&quot;https://docs.google.com/document/d/1tKPkHA5nnJtmh2TgqWmGSREUzXgMUFDL6yMdVZHqUsg/edit?usp=sharing&quot; rel=&quot;noopener&quot;&gt;Permitted File Extensions in Chromium&lt;/a&gt;
for a complete list. More file types may be added in the future.&lt;/p&gt;
&lt;h2 id=&quot;sharing-in-third-party-iframes&quot;&gt;Sharing in third-party iframes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#sharing-in-third-party-iframes&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To trigger the share action from within a third-party iframe,
embed the iframe with the &lt;code&gt;allow&lt;/code&gt; attribute with a value of &lt;code&gt;web-share&lt;/code&gt;:&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 comment&quot;&gt;&amp;lt;!-- On https://example.com/index.html --&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;iframe&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;allow&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;web-share&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;https://third-party.example.com/iframe.html&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;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;iframe&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;p&gt;You can see this in action in a &lt;a href=&quot;https://web-share-in-third-party-iframe.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo on Glitch&lt;/a&gt;
and view the &lt;a href=&quot;https://glitch.com/edit/#!/web-share-in-third-party-iframe?path=index.html%3A17%3A44&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt;.
Failing to provide the attribute will result in a &lt;code&gt;NotAllowedError&lt;/code&gt; with the message
&lt;code&gt;Failed to execute &#39;share&#39; on &#39;Navigator&#39;: Permission denied&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;santa-tracker-case-study&quot;&gt;Santa Tracker case study &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#santa-tracker-case-study&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;The Santa Tracker app showing a share button.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 343px) 343px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/2I5iOXaOpzEJlEbM694n.png?auto=format&amp;w=686 686w&quot; width=&quot;343&quot; /&gt;
  &lt;figcaption&gt;
    Santa Tracker share button.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://santatracker.google.com/&quot; rel=&quot;noopener&quot;&gt;Santa Tracker&lt;/a&gt;, an open-source project, is a
holiday tradition at Google. Every December, you can celebrate the season
with games and educational experiences.&lt;/p&gt;
&lt;p&gt;In 2016, the Santa Tracker team used the Web Share API on Android.
This API was a perfect fit for mobile.
In previous years, the team removed share buttons on mobile because space is
at a premium, and they couldn&#39;t justify having several share targets.&lt;/p&gt;
&lt;p&gt;But with the Web Share API, they were able to present one button,
saving precious pixels.
They also found that users shared with Web Share around 20% more than
users without the API enabled. Head to
&lt;a href=&quot;https://santatracker.google.com/&quot; rel=&quot;noopener&quot;&gt;Santa Tracker&lt;/a&gt; to see Web Share in action.&lt;/p&gt;
&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Browser support for the Web Share API is nuanced, and it&#39;s recommended that you use feature
detection (as described in the earlier code samples) instead of assuming that a particular method is
supported.&lt;/p&gt;
&lt;p&gt;Here&#39;s a rough outline of support for this feature. For detailed information, follow either of the support links.&lt;/p&gt;
&lt;dl&gt;
  &lt;dt&gt;&lt;code&gt;navigator.canShare()&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 96, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 93, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
93
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 14, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
14
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/canShare#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;&lt;/dd&gt;
  &lt;dt&gt;&lt;code&gt;navigator.share()&lt;/code&gt;&lt;/dt&gt;
  &lt;dd&gt;&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 71, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 93, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
93
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 12.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
12.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/share#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;h2 id=&quot;show-support-for-the-api&quot;&gt;Show support for the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#show-support-for-the-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Are you planning to use the Web Share API? Your public support helps the Chromium team
prioritize features and shows other browser vendors how critical it is to support them.&lt;/p&gt;
&lt;p&gt;Send a tweet to &lt;a href=&quot;https://twitter.com/ChromiumDev&quot; rel=&quot;noopener&quot;&gt;@ChromiumDev&lt;/a&gt; using the hashtag
&lt;a href=&quot;https://twitter.com/search?q=%23WebShare&amp;amp;src=recent_search_click&amp;amp;f=live&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;#WebShare&lt;/code&gt;&lt;/a&gt;
and let us know where and how you&#39;re using it.&lt;/p&gt;
&lt;h2 id=&quot;helpful-links&quot;&gt;Helpful Links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/web-share/#helpful-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/web-share/demos/share-files.html&quot; rel=&quot;noopener&quot;&gt;Web Share Demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/samples/blob/gh-pages/web-share/README.md#web-share-demo&quot; rel=&quot;noopener&quot;&gt;Scrapbook PWA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Virtual reality comes to the web</title>
    <link href="https://web.dev/vr-comes-to-the-web/"/>
    <updated>2019-10-31T00:00:00Z</updated>
    <id>https://web.dev/vr-comes-to-the-web/</id>
    <content type="html" mode="escaped">&lt;p&gt;Immersive experiences came to the web in Chrome 79. The WebXR Device API brings
virtual reality brought virtual reality, while support for augmented reality
arrives in Chrome 81. While an update to the GamePad API extends the advanced
use of controlls to VR. Other browsers will be supporting these specs soon,
including Firefox Reality, Oculus Browser, Edge and Magic Leap&#39;s Helio browser,
among others.&lt;/p&gt;
&lt;p&gt;This article begins a series on the immersive web. This installment covers
setting up a basic WebXR application as well as entering and exiting an XR
session. Later articles will cover the frame loop (the workhorse of WebXR
experience), the specifics of augmented reality, and the WebXR Hit Test API, a
means of detecting surfaces in an AR session. Unless stated otherwise,
everything I cover in this and succeeding articles applies equally to both AR
and VR.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-immersive-web&quot;&gt;What is the immersive web? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#what-is-the-immersive-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Though we use two terms to describe immersive experiences—augmented
reality and virtual reality—many think of them on a spectrum from complete
reality to completely virtual, with degrees of immersion in between. The &#39;X&#39; in
XR is intended to reflect that thinking by being a sort of algebraic variable
that stands for anything in the spectrum of immersive experiences.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A graph illustrating the spectrum of visual experiences from complete reality to completely immersive.&quot; decoding=&quot;async&quot; height=&quot;204&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/iQ99APUTFIgjdRPyS1C4.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The spectrum of immersive experiences
  &lt;/figcaption&gt;
&lt;/figure&gt;Examples of immersive experiences include:&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Games&lt;/li&gt;
&lt;li&gt;360° videos&lt;/li&gt;
&lt;li&gt;Traditional 2D (or 3D) videos presented in immersive surroundings&lt;/li&gt;
&lt;li&gt;Home buying&lt;/li&gt;
&lt;li&gt;Viewing products in your home before you buy them&lt;/li&gt;
&lt;li&gt;Immersive art&lt;/li&gt;
&lt;li&gt;Something cool nobody&#39;s thought of yet&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;concepts-and-usage&quot;&gt;Concepts and usage &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#concepts-and-usage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ll explain a few basics of using the WebXR Device API. If you need more depth
than I&#39;ve provided, check out the Immersive Web Working Group&#39;s &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/&quot; rel=&quot;noopener&quot;&gt;WebXR
samples&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebXR_Device_API&quot; rel=&quot;noopener&quot;&gt;MDN&#39;s growing
reference
materials&lt;/a&gt;.
If you&#39;re familiar with early versions of the WebXR Device API, you should
glance over all of this material. There have been changes.&lt;/p&gt;
&lt;p&gt;The code in this article is based on the Immersive Web Working Group&#39;s barebones
sample (&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/vr-barebones.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;,
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/vr-barebones.html&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;),
but is edited for clarity and simplicity.&lt;/p&gt;
&lt;p&gt;Part of creating the WebXR specification has been fleshing out security and
privacy measures to protect users. Consequently, implementations must adhere to
certain requirements. A web page or app must be active and focused before it can
request anything sensitive from the viewer. Web pages or apps must be served
over HTTPS. The API itself is designed to protect information obtained from
sensors and cameras, which it needs in order to function.&lt;/p&gt;
&lt;h3 id=&quot;request-a-session&quot;&gt;Request a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#request-a-session&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Entering an XR session requires a user gesture. To get that, use feature
detection to test for &lt;code&gt;XRSystem&lt;/code&gt; (via &lt;code&gt;navigator.xr&lt;/code&gt;) and make a call to
&lt;code&gt;XRSystem.isSessionSupported()&lt;/code&gt;. Be aware that in Chrome versions 79 and 80 the
&lt;code&gt;XRSystem&lt;/code&gt; object was called &lt;code&gt;XR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the example below, I&#39;ve indicated that I
want a virtual reality session with the &lt;code&gt;&#39;immersive-vr&#39;&lt;/code&gt; session type. The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XR/isSessionSupported#Syntax&quot; rel=&quot;noopener&quot;&gt;other session
types&lt;/a&gt;
are &lt;code&gt;&#39;immersive-ar&#39;&lt;/code&gt; and &lt;code&gt;&#39;inline&#39;&lt;/code&gt;. An inline session is for presenting content
within HTML and is mainly used for teaser content. The &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-ar-session.html&quot; rel=&quot;noopener&quot;&gt;Immersive AR
Session&lt;/a&gt;
sample demonstrates this. I&#39;ll explain that in a later article.&lt;/p&gt;
&lt;p&gt;Once I know that virtual reality sessions are supported, I enable a button that
lets me acquire a user gesture.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&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; supported &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSessionSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-vr&#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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supported&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;    xrButton&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;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onButtonClicked&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;    xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Enter VR&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; supported&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// supported is Boolean&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;After enabling the button, I wait for a click event then request a session.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&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;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onButtonClicked&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;xrSession&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;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;immersive-vr&#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 function&quot;&gt;then&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 parameter&quot;&gt;session&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;      xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Exit XR&#39;&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;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrSession&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;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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&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;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 the object hierarchy in this code. It moves from &lt;code&gt;navigator&lt;/code&gt; to &lt;code&gt;xr&lt;/code&gt; to
an &lt;code&gt;XRSession&lt;/code&gt; instance. In early versions of the API, a script had to request a
device before requesting a session. Now, the device is acquired implicitly.&lt;/p&gt;
&lt;h3 id=&quot;enter-a-session&quot;&gt;Enter a session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#enter-a-session&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After getting a session, I need to start it and enter it. But first, I need to
set up a few things. A session needs an &lt;code&gt;onend&lt;/code&gt; event handler so that the app or
web page can be reset when the user exits.&lt;/p&gt;
&lt;p&gt;I&#39;ll also need a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element to draw my scene on. It needs to be an
XR-compatible
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebGLRenderingContext&quot; rel=&quot;noopener&quot;&gt;WebGLRenderingContext&lt;/a&gt;
or
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebGL2RenderingContext&quot; rel=&quot;noopener&quot;&gt;WebGL2RenderingContext&lt;/a&gt;.
All drawing is done using them or a WebGL-based framework such as
&lt;a href=&quot;https://threejs.org/&quot; rel=&quot;noopener&quot;&gt;Three.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that I have a place to draw, I need a source of content to draw on
it. For that, I create an instance of &lt;code&gt;XRWebGLLayer&lt;/code&gt;. I associate it with the
canvas by calling &lt;code&gt;XRSession.updateRenderState()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once I&#39;m in a session, I need a way of determining where things are in virtual
reality. I&#39;ll need a reference space. A &lt;code&gt;&#39;local-floor&#39;&lt;/code&gt; reference space is one
where the origin is located near the viewer and the y-axis is 0 at floor level
and is not expected to move. There are &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XRSession/requestReferenceSpace&quot; rel=&quot;noopener&quot;&gt;other types of reference
spaces&lt;/a&gt;,
but that is a more complicated topic than I can go into here. I save the
reference space to a variable because I&#39;ll need it when I draw to the screen.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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; canvas &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;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#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;  webGLRenContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;webgl&#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;span class=&quot;token literal-property property&quot;&gt;xrCompatible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateRenderState&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 literal-property property&quot;&gt;baseLayer&lt;/span&gt;&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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; webGLRenContext&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestReferenceSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;local-floor&#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 function&quot;&gt;then&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 parameter&quot;&gt;refSpace&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;    xrRefSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; refSpace&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;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;After getting a reference space, I call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt;.
This is the start of presenting virtual content, which is done in the frame
loop.&lt;/p&gt;
&lt;h3 id=&quot;run-a-frame-loop&quot;&gt;Run a frame loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#run-a-frame-loop&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The frame loop is a user-agent controlled infinite loop in which content is
repeatedly drawn to the screen. Content is drawn in discrete blocks called
frames. The succession of frames creates the illusion of movement. For VR
applications the frames per second can be anywhere from 60 to 144. AR for
Android runs at 30 frames per second. Your code should not assume any particular
frame rate.&lt;/p&gt;
&lt;p&gt;The basic process for the frame loop is:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt;. In response, the user agent invokes the &lt;code&gt;XRFrameRequestCallback&lt;/code&gt;, which is defined by you.&lt;/li&gt;
  &lt;li&gt;Inside your callback function:
    &lt;ol&gt;
      &lt;li&gt;Call &lt;code&gt;XRSession.requestAnimationFrame()&lt;/code&gt; again.&lt;/li&gt;
      &lt;li&gt;Get the viewer&#39;s pose.&lt;/li&gt;
      &lt;li&gt;Pass (&#39;bind&#39;) the &lt;code&gt;WebGLFramebuffer&lt;/code&gt; from the &lt;code&gt;XRWebGLLayer&lt;/code&gt; to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Iterate over each &lt;code&gt;XRView&lt;/code&gt; object, retrieving its &lt;code&gt;XRViewport&lt;/code&gt; from the &lt;code&gt;XRWebGLLayer&lt;/code&gt; and passing it to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Draw something to the framebuffer.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The remainder of this article describes step 1 and part of step 2,  setting up
and calling the &lt;code&gt;XRFrameRequestCallback&lt;/code&gt;. The remaining items of step 2 are
covered in part II.&lt;/p&gt;
&lt;h4 id=&quot;the-xrframerequestcallback&quot;&gt;The XRFrameRequestCallback &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#the-xrframerequestcallback&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;XRFrameRequestCallback&lt;/code&gt; is defined by you. It takes two parameters: a
&lt;code&gt;DOMHighResTimeStamp&lt;/code&gt; and an &lt;code&gt;XRFrame&lt;/code&gt; instance. The &lt;code&gt;XRFrame&lt;/code&gt; object provides
the information needed to render a single frame to the display. The
&lt;code&gt;DOMHighResTimeStamp&lt;/code&gt; argument is for future use.&lt;/p&gt;
&lt;p&gt;Before doing anything else, I&#39;m going to request the next animation frame. As
previously stated, the timing of frames is determined by the user agent based on
the underlying hardware. Requesting the next frame first ensures that
the frame loop continues if something during the callback throws an error.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onXRFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hrTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;let&lt;/span&gt; xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onXRFrame&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;// Render a frame.&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;At this point, it&#39;s time to draw something for the viewer. That&#39;s a discussion
for part II. Before going there, let me show you how to end a session.&lt;/p&gt;
&lt;h3 id=&quot;end-the-session&quot;&gt;End the session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#end-the-session&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An immersive session may end for several reasons including ending by your own
code through a call to &lt;code&gt;XRSession.end()&lt;/code&gt;. Other causes include the headset being
disconnected or another application taking control of it. This is why a
well-behaved application should monitor the &lt;code&gt;end&lt;/code&gt; event. When it occurs, discard
the session and its related render objects. An ended immersive session cannot be
resumed. To reenter the immersive experience, my app needs to start a new
session.&lt;/p&gt;
&lt;p&gt;Recall from &lt;a href=&quot;https://web.dev/vr-comes-to-the-web/#entering-a-session&quot;&gt;Entering a session&lt;/a&gt; that during setup, I added
an &lt;code&gt;onend&lt;/code&gt; event handler.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;  xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnded&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;// More setup…&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;Inside the event handler, restore the state of the app before the user entered a
session.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionEnded&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;br /&gt;  xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  xrButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Enter VR&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vr-comes-to-the-web/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I haven&#39;t explained everthing you need to write a Web XR or AR application.
Hopefull, I&#39;ve give  you enought to start making sense of the code for yourself,
and enough to start experimenting. In the next article, I&#39;ll explain the frame
loop, which is where content is drawn to the screen.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@jeshoots&quot; rel=&quot;noopener&quot;&gt;JESHOOTS.COM&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Create OS-style backgrounds with backdrop-filter</title>
    <link href="https://web.dev/backdrop-filter/"/>
    <updated>2019-07-26T00:00:00Z</updated>
    <id>https://web.dev/backdrop-filter/</id>
    <content type="html" mode="escaped">&lt;p&gt;Translucence, blurring, and other effects are useful ways of creating depth while keeping the context of the background content. They support a host of use cases such as frosted glass, video overlays, translucent navigation headers, inappropriate image censoring, image loading, and so on. You may recognize these effects from two popular operating systems: &lt;a href=&quot;https://i.kinja-img.com/gawker-media/image/upload/s--9RLXARU4--/c_scale,dpr_2.0,f_auto,fl_progressive,q_80,w_800/trgz8yivyyqrpcnwscu5.png&quot; rel=&quot;noopener&quot;&gt;Windows 10&lt;/a&gt; and &lt;a href=&quot;https://static.businessinsider.com/image/51fd2822eab8eae16e00000b-750.jpg&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An example of a frosted glass effect.&quot; decoding=&quot;async&quot; height=&quot;300&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/mEc6bdwB2ZX6VSXvyJEn.jpg?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;An example of a frosted glass effect. &lt;a href=&quot;https://dribbble.com/shots/733714-Weather-App?list=tags&amp;tag=android&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Source&lt;/a&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Historically, these techniques were difficult to implement on the web, requiring less than perfect &lt;a href=&quot;https://stackoverflow.com/questions/38145368/css-workaround-to-backdrop-filter&quot; rel=&quot;noopener&quot;&gt;hacks or workarounds&lt;/a&gt;. In recent years both &lt;a href=&quot;https://webkit.org/blog/3632/introducing-backdrop-filters/&quot; rel=&quot;noopener&quot;&gt;Safari&lt;/a&gt; and Edge have provided these capabilities through the &lt;code&gt;background-filter&lt;/code&gt; (and alternatively, the &lt;code&gt;-webkit-backdrop-filter&lt;/code&gt;) property, which dynamically blends foreground and background colors based on filter functions. Now Chrome supports &lt;code&gt;background-filter&lt;/code&gt;, starting in version 76.&lt;/p&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-kitchen_sink2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-kitchen_sink2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    A demonstration of the filter functions for &lt;code&gt;backdrop-filter&lt;/code&gt;. Try the example on &lt;a href=&quot;https://codepen.io/robinrendle/pen/LmzLEL&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CodePen&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 76, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      76
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 103, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      103
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 17, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      17
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 9, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      9
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/CSS/backdrop-filter#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;For performance reasons, fall back to an image instead of a polyfill when &lt;code&gt;backdrop-filter&lt;/code&gt; isn&#39;t supported. The example below shows this.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&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 selector&quot;&gt;.background&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;10px&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&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 selector&quot;&gt;.background&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blurred-hero.png&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;br /&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;## Basics&lt;br /&gt;&lt;br /&gt;- The `backdrop-filter` property applies one or more filters to an element, changing the appearance of anything behind the element.&lt;br /&gt;- The overlaying element must be at least partially transparent.&lt;br /&gt;- The overlaying element will get a new stacking context.&lt;br /&gt;&lt;br /&gt;&amp;lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&amp;lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&amp;lt;span class=&quot;aside__icon box-block &quot;&gt;&amp;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;   &amp;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; &amp;lt;/svg&gt;&amp;lt;/span&gt;&amp;lt;strong&gt;Caution&amp;lt;/strong&gt;&amp;lt;/p&gt;&amp;lt;div class=&quot; flow&quot;&gt; &amp;lt;code&gt;backdrop-filter&amp;lt;/code&gt; may harm performance. Test it before deploying. &amp;lt;/div&gt;&amp;lt;/aside&gt;&lt;br /&gt;&lt;br /&gt;CSS `backdrop-filter` applies one or more effects to an element that is translucent or transparent. To understand that, consider the images below.&lt;br /&gt;&lt;br /&gt;&amp;lt;div class=&quot;switcher&quot;&gt;&lt;br /&gt;&amp;lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&amp;lt;p class=&quot;compare__label&quot;&gt;No foreground transparency&amp;lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;img     alt=&quot;A triangle superimposed on a circle. The circle can&amp;amp;#x27;t be seen through the triangle.&quot;          decoding=&quot;async&quot;          height=&quot;283&quot;          loading=&quot;lazy&quot;     sizes=&quot;(min-width: 480px) 480px, calc(100vw - 48px)&quot;     src=&quot;https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&quot;     srcset=&quot;https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=200 200w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=228 228w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=260 260w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=296 296w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=338 338w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=385 385w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=439 439w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=500 500w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=571 571w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=650 650w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=741 741w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=845 845w,     https://web-dev.imgix.net/image/admin/LOqxvB3qqVkbZBmxMmKS.png?auto=format&amp;amp;w=960 960w&quot;          width=&quot;480&quot;   /&gt;&lt;br /&gt;&lt;br /&gt;```css&lt;br /&gt;.frosty-glass-pane&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;2px&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;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Foreground transparency&lt;/p&gt;
&lt;img alt=&quot;A triangle superimposed on a circle. The triangle is translucent, allowing the circle to be seen through it.&quot; decoding=&quot;async&quot; height=&quot;283&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 480px) 480px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/VbyjpS6Td39E4FudeiVg.png?auto=format&amp;w=960 960w&quot; width=&quot;480&quot; /&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.frosty-glass-pane&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; .9&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 property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;2px&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;

&lt;p&gt;The image on the left shows how overlapping elements would be rendered if &lt;code&gt;backdrop-filter&lt;/code&gt; were not used or supported. The image on the right applies a blurring effect using &lt;code&gt;backdrop-filter&lt;/code&gt;. Notice that it uses &lt;code&gt;opacity&lt;/code&gt; in addition to &lt;code&gt;backdrop-filter&lt;/code&gt;. Without &lt;code&gt;opacity&lt;/code&gt;, there would be nothing to apply blurring to. It almost goes without saying that if &lt;code&gt;opacity&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt; (fully opaque) there will be no effect on the background.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;backdrop-filter&lt;/code&gt; property is like CSS &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/filter&quot; rel=&quot;noopener&quot;&gt;filters&lt;/a&gt; in that all your favorite &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/filter#Filter_functions&quot; rel=&quot;noopener&quot;&gt;filter functions&lt;/a&gt; are supported: &lt;code&gt;blur()&lt;/code&gt;, &lt;code&gt;brightness()&lt;/code&gt;, &lt;code&gt;contrast()&lt;/code&gt;, &lt;code&gt;opacity()&lt;/code&gt;, &lt;code&gt;drop-shadow()&lt;/code&gt;, and so on. It also supports the &lt;code&gt;url()&lt;/code&gt; function if you want to use an external image as the filter, as well as the keywords &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;inherit&lt;/code&gt;, &lt;code&gt;initial&lt;/code&gt;, and &lt;code&gt;unset&lt;/code&gt;. There are explanations for all of this on &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/backdrop-filter&quot; rel=&quot;noopener&quot;&gt;MDN&lt;/a&gt;, including descriptions of syntax, filters, and values.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;backdrop-filter&lt;/code&gt; is set to anything other than &lt;code&gt;none&lt;/code&gt;, the browser creates a new &lt;a href=&quot;https://www.w3.org/TR/CSS21/zindex.html&quot; rel=&quot;noopener&quot;&gt;stacking context&lt;/a&gt;. A &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Containing_block&quot; rel=&quot;noopener&quot;&gt;containing block&lt;/a&gt; may also be created, but only if the element has absolute and fixed position descendants.&lt;/p&gt;
&lt;p&gt;You can combine filters for rich and clever effects, or use just one filter for more subtle or precise effects. You can even combine them with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/SVG/Element/filter&quot; rel=&quot;noopener&quot;&gt;SVG filters&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;examples&quot;&gt;Examples &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#examples&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Design techniques and styles previously reserved for operating systems are now performant and achievable with a single CSS declaration. Let&#39;s look at some examples.&lt;/p&gt;
&lt;h3 id=&quot;single-filter&quot;&gt;Single filter &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#single-filter&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the following example, the frosted effect is achieved by combining color and blur. The blur is supplied by &lt;code&gt;backdrop-filter&lt;/code&gt;, while the color comes from the element&#39;s semi-transparent background color.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.blur-behind-me&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.3&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 property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;.5rem&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-rgb2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-rgb2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Try this example for yourself in &lt;a href=&quot;https://codepen.io/netsi1964/pen/JqBLPK&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CodePen&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;multiple-filters&quot;&gt;Multiple filters &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#multiple-filters&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes you&#39;ll need multiple filters to achieve the desired effect. To do this, provide a list of filters separated by a space. For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.brighten-saturate-and-blur-behind-me&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;brightness&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;150%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saturate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;150%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1rem&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;In the following example, each of the four panes has a different combination of backdrop filters while the same set of shapes are animated behind them.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-ambient_blur2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-ambient_blur2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Try this example for yourself in &lt;a href=&quot;https://codepen.io/pepf/pen/GqZkdj&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CodePen&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;overlays&quot;&gt;Overlays &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#overlays&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This example shows how to blur a semi-transparent background to make text readable while stylistically blending with a page&#39;s background.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.modal&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blur&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;10px&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 property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.5&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-modal2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-modal2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Try this &lt;a href=&quot;https://mfreed7.github.io/backdrop-filter-feature/examples/scrollable.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt; for yourself.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;text-contrast-on-dynamic-backgrounds&quot;&gt;Text contrast on dynamic backgrounds &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#text-contrast-on-dynamic-backgrounds&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As stated earlier, &lt;code&gt;backdrop-filter&lt;/code&gt; allows performant effects that would be difficult or impossible on the web. An example of this is changing a background in response to an animation. In this example, &lt;code&gt;backdrop-filter&lt;/code&gt; maintains the high contrast between the text and its background in spite of what&#39;s going on behind the text. It starts with the default background color &lt;code&gt;darkslategray&lt;/code&gt; and uses &lt;code&gt;backdrop-filter&lt;/code&gt; to invert the colors after the transformation.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.container::before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; darkslategray&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&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 selector&quot;&gt;.container::after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token property&quot;&gt;backdrop-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&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 property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-invert_color2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/backdrop-filter/backdrop_filter-invert_color2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Try this example from &lt;a href=&quot;https://www.chenhuijing.com/#%F0%9F%91%9F&quot;&gt;Chen Hui Jing&lt;/a&gt; in &lt;a href=&quot;https://tympanus.net/codrops-playground/huijing/Qqpwg5Iy/editor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Codrops&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;More than 560 of you have upvoted the &lt;a href=&quot;https://crbug.com/497522&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt; over the past few years, clearly marking this as a long awaited CSS feature. Chrome&#39;s release of &lt;code&gt;backdrop-filter&lt;/code&gt; in version 76 brings the web a step closer to truly OS-like UI presentation.&lt;/p&gt;
&lt;h3 id=&quot;additional-resources&quot;&gt;Additional resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/backdrop-filter/#additional-resources&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty&quot; rel=&quot;noopener&quot;&gt;Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromestatus.com/feature/5679432723333120&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/backdrop-filter&quot; rel=&quot;noopener&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/the-backdrop-filter-css-property/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;background-filter&lt;/code&gt; at CSS Tricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/tag/backdrop-filter/#&quot; rel=&quot;noopener&quot;&gt;Samples on Codepen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Adam Argyle</name>
    </author><author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Visual searching with the Web Perception Toolkit</title>
    <link href="https://web.dev/perception-toolkit/"/>
    <updated>2019-05-07T00:00:00Z</updated>
    <id>https://web.dev/perception-toolkit/</id>
    <content type="html" mode="escaped">&lt;p&gt;Wouldn&#39;t it be great if users could search your site using their camera? Imagine
this. Your site is Razor McShaveyface. Your customers tell you they have trouble
finding the right cartridges for their razor when they reorder. They don&#39;t know
the right keywords for your product search. And let&#39;s be honest, they probably
never will.&lt;/p&gt;
&lt;p&gt;What if they never need to? What if they could point their phone&#39;s camera at the
UPC code on package and your site could present them with the right cartridge
and a big red &amp;quot;reorder&amp;quot; button?&lt;/p&gt;
&lt;p&gt;Think of other ways you can use a camera on a site. Imagine a site that
supports in-store price checking. Imagine getting information about a museum
exhibit or historical marker. Imagine identifying real-world landmarks in games
like geocaching or scavenger hunts.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/perception-toolkit&quot; rel=&quot;noopener&quot;&gt;The Web Perception Toolkit&lt;/a&gt;
makes these camera-based scenarios possible. In some cases you can even create
an experience without writing code.&lt;/p&gt;
&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/perception-toolkit/#how-does-it-work&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The open-source Web Perception Toolkit helps you add visual search to your
website. It passes a device camera stream through a set of detectors that map
real-world objects, here called &amp;quot;targets&amp;quot;, to content on your site. This mapping is defined
using Structured Data (JSON-LD) on your site. With this data, you can present the right information in a customizable
UI.&lt;/p&gt;
&lt;p&gt;I&#39;ll show you enough of this to give you a taste of how it works. For a complete
explanation, check out the &lt;a href=&quot;https://perceptiontoolkit.dev/getting-started/&quot; rel=&quot;noopener&quot;&gt;Getting
Started&lt;/a&gt; guide, the &lt;a href=&quot;https://perceptiontoolkit.dev/documentation/&quot; rel=&quot;noopener&quot;&gt;toolkit
reference&lt;/a&gt;, the &lt;a href=&quot;https://io.perceptiontoolkit.dev/&quot; rel=&quot;noopener&quot;&gt;I/O Sandbox demo&lt;/a&gt; and the &lt;a href=&quot;https://github.com/GoogleChromeLabs/perception-toolkit/tree/master/demo&quot; rel=&quot;noopener&quot;&gt;sample demos&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;structured-data&quot;&gt;Structured data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/perception-toolkit/#structured-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The toolkit can&#39;t find just any target in the camera&#39;s view. You must provide it
with linked JSON data for the targets you want it to recognize. This data also
contains information about those targets that will be shown to the user.&lt;/p&gt;
&lt;p&gt;The data is all you need to create a user experience like the one in the image
below. If you do nothing else, the Web Perception Toolkit can identify targets,
then show and hide cards based on the information provided in the data. Try this
for yourself using our &lt;a href=&quot;https://github.com/GoogleChromeLabs/perception-toolkit/tree/master/demo/artifact-map&quot; rel=&quot;noopener&quot;&gt;artifact-map
demo&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The default interface is available by using just the linked data.&quot; decoding=&quot;async&quot; height=&quot;518&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UD5t3RD9HZ7OIHVaAg31.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The default interface.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Add data to your site with a JSON linked data file, included using a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;
tag and the &lt;code&gt;&amp;quot;application/ld+json&amp;quot;&lt;/code&gt; MIME type.&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;script&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/ld+json&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;//path/to/your/sitemap.jsonld&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The file itself looks something like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&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 string-property property&quot;&gt;&quot;@context&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://schema.googleapis.com/&quot;&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;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ARArtifact&quot;&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;&quot;arTarget&quot;&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;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Barcode&quot;&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;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;012345678912&quot;&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 string-property property&quot;&gt;&quot;arContent&quot;&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;&quot;@type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WebPage&quot;&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;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/demo/artifact-map/products/product1.html&quot;&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;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Product 1&quot;&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is a product with a barcode&quot;&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;&quot;image&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8080/demo/artifact-map/products/product1.png&quot;&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 punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;the-user-experience&quot;&gt;The user experience &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/perception-toolkit/#the-user-experience&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What if you want more than the default user experience? The toolkit gives you
lifecycle events, Card and Button objects for crafting the user experience
around those events, and an easy way to style the cards. I&#39;m going to show a
little of this with code based losely on our &lt;a href=&quot;https://perceptiontoolkit.dev/getting-started/&quot; rel=&quot;noopener&quot;&gt;Getting
Started&lt;/a&gt; guide.&lt;/p&gt;
&lt;p&gt;The most important lifecycle event is &lt;code&gt;PerceivedResults&lt;/code&gt;, which is fired every
time a target is found. A target can be a real-world object or a marker such as
a bar code or QR code.&lt;/p&gt;
&lt;p&gt;The process for responding to this event is the same as for any other event with
an exception already alluded to. If you don&#39;t implement the event, a user
interface is automatically created using structured data. To override this
behavior start your event handler by calling&lt;code&gt;event.preventDefault()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &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;.container&#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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPerceivedResults&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// preventDefault() to stop default result Card from showing.&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;preventDefault&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;// Process the event.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;window&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;PerceptionToolkit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PerceivedResults&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onPerceivedResults&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;Let&#39;s look at the event more closely. The event itself contains arrays of
markers and targets that it has both found and lost. When targets are found in
the world, the even fires and passes found objects in &lt;code&gt;event.found&lt;/code&gt;. Similarly,
when targets pass from the camera view the event fires again, passing lost
objects in &lt;code&gt;event.lost&lt;/code&gt;. This helps account for hand and marker movements:
cameras not held steadily enough, dropped markers, that kind of thing.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&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;onPerceivedResults&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;  &lt;span class=&quot;token comment&quot;&gt;// preventDefault() to stop default result Card from showing&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;preventDefault&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;childNodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;span class=&quot;token keyword&quot;&gt;return&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;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; found&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lost &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;detail&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 comment&quot;&gt;// Deal with lost and found objects.&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Next, you show an appropriate card based on what the toolkit found.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&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;onPerceivedResults&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;preventDefault&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;childNodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;span class=&quot;token keyword&quot;&gt;return&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;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; found&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lost &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;detail&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;found&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &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 operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; lost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// Object not found.&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 comment&quot;&gt;// Show a card with an offer to show the catalog.&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 punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;found&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// Object found.&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 comment&quot;&gt;// Show a card with a reorder button.&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 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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Adding cards and buttons is simply a matter of instantiating them and appending
them to a parent object. For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Card &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PerceptionToolkit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Elements&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; card &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;Card&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;card&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 string&quot;&gt;&#39;Your message here.&#39;&lt;/span&gt;&lt;br /&gt;container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;card&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Finally, here&#39;s what the whole thing looks like. Notice the conveniences I&#39;ve
added to the user experience. Whether the marker is found or not, I provide
one-click access to what I think is most useful in the circumstances.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&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;onPerceivedResults&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// preventDefault() to stop default result Card from showing&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;preventDefault&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;container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;childNodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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;span class=&quot;token keyword&quot;&gt;return&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; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; found&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lost &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;detail&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; ActionButton&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Card &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PerceptionToolkit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Elements&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;found&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &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 operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; lost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &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 comment&quot;&gt;//Make a view catalog button.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; button &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;ActionButton&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;    button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;View catalog&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    button&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;click&#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;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;      card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;//Run code to launch a catalog.&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;//Make a card for the button.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; card &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;Card&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;    card&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 string&quot;&gt;&#39;We wish we could help, but that\&#39;s not our razor. Would you like to see our catalog?&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;button&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;//Tell the toolkit it does not keep the card around&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// if it finds something it recognizes.&lt;/span&gt;&lt;br /&gt;    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notRecognized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;card&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;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;found&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&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 comment&quot;&gt;//Make a reorder button.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; button &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;ActionButton&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;    button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Reorder&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    botton&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;click&#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;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;      card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;//Run code to reorder.&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;const&lt;/span&gt; card &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;Card&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;    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; found&lt;span class=&quot;token punctuation&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;content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;button&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;    container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;card&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;formatting-cards&quot;&gt;Formatting cards &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/perception-toolkit/#formatting-cards&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Web Perception Toolkit provides built-in formatting for cards and buttons
with the default stylesheet. But you can easily add your own. The provided
&lt;code&gt;Card&lt;/code&gt; and &lt;code&gt;ActionButton&lt;/code&gt; objects contain &lt;code&gt;style&lt;/code&gt; properties (among many others)
that let you put your organizational stamp on the look and feel. To include the
default stylesheet, add a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element to your page.&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;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&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;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&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;//path/to/toolkit/styles/perception-toolkit.css&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/perception-toolkit/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I said at the top, this is not an exhaustive look at the &lt;a href=&quot;https://github.com/GoogleChromeLabs/perception-toolkit&quot; rel=&quot;noopener&quot;&gt;Web Perception
Toolkit&lt;/a&gt;. Hopefully it gives
you a sense of how easy it is to add visual searching to a website. Learn more
with the &lt;a href=&quot;https://perceptiontoolkit.dev/getting-started/&quot; rel=&quot;noopener&quot;&gt;Getting Started&lt;/a&gt; guide
and the &lt;a href=&quot;https://github.com/GoogleChromeLabs/perception-toolkit/tree/master/demo&quot; rel=&quot;noopener&quot;&gt;sample
demos&lt;/a&gt;.
Dig in to the &lt;a href=&quot;https://perceptiontoolkit.dev/documentation/&quot; rel=&quot;noopener&quot;&gt;toolkit
documentation&lt;/a&gt; to learn what it
can do.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>The &lt;model-viewer&gt; web component</title>
    <link href="https://web.dev/model-viewer/"/>
    <updated>2019-02-06T00:00:00Z</updated>
    <id>https://web.dev/model-viewer/</id>
    <content type="html" mode="escaped">&lt;p&gt;Adding 3D models to a website is tricky. 3D models ideally will be shown in
a viewer that can work responsively on all browsers including smartphones,
desktop, or even new head-mounted displays. The viewer should support
progressive enhancement for performance and rendering quality. It should support
use cases on all devices ranging from older, lower-powered smartphones to newer
devices that support augmented reality. It should stay up to date with current
technologies. It should be performant and accessible. However, building such a
viewer requires specialty 3D programming skills, and can be a challenge for web
developers that want to host their own models instead of using a third-party
hosting service.&lt;/p&gt;
&lt;p&gt;To help with that, the &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; web component, which just released
&lt;a href=&quot;https://modelviewer.dev/&quot; rel=&quot;noopener&quot;&gt;version 1.1&lt;/a&gt;, lets you declaratively add a 3D model
to a web page, while hosting the model on your own site. The web component
supports responsive design and use cases like augmented reality on some devices,
and it includes features for accessibility, rendering quality, and
interactivity.  The goal of the component is to enable adding 3D models to your
website without understanding the underlying technology and platforms.&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; We&#39;re always &lt;a href=&quot;https://github.com/google/model-viewer/releases&quot;&gt;updating and improving&lt;/a&gt; &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt;. Check out the &lt;a href=&quot;https://modelviewer.dev/&quot;&gt;&lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; homepage&lt;/a&gt; to explore what &amp;lt;model-viewer&amp;gt; 1.1 can do. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-a-web-component&quot;&gt;What is a web component? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#what-is-a-web-component&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A web component is a custom HTML element built from standard web platform
features. A web component behaves for all intents and purposes like a standard
element. It has a unique tag, it can have properties and methods, and it can
fire and respond to events. In short, you don&#39;t need to know anything special to
use any web component, much less &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this article, I will show you things that are particular to &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt;.
If you&#39;re interested in learning more about web components,
&lt;a href=&quot;https://www.webcomponents.org/&quot; rel=&quot;noopener&quot;&gt;webcomponents.org&lt;/a&gt; is a good place to start.&lt;/p&gt;
&lt;h2 id=&quot;what-can-lessmodel-viewergreater-do&quot;&gt;What can &amp;lt;model-viewer&amp;gt; do? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#what-can-lessmodel-viewergreater-do&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ll show you a few current capabilities of &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt;. You&#39;ll get a great
experience today, and &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; will get better over time as Google adds
new features and improves rendering quality. The examples I&#39;m provided are just
to give you a sense of what it does. If you want to try them there are
installation and usage instructions in &lt;a href=&quot;https://modelviewer.dev/&quot; rel=&quot;noopener&quot;&gt;its GitHub
repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;basic-3d-models&quot;&gt;Basic 3D models &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#basic-3d-models&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Embedding a 3D model is as simple as the markup below. By
&lt;a href=&quot;https://www.marxentlabs.com/glb-files/&quot; rel=&quot;noopener&quot;&gt;using glb files&lt;/a&gt;, we&#39;ve ensured that this component will work on any major
browser.&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 comment&quot;&gt;&amp;lt;!-- Import the component --&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;script&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;module&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js&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;span class=&quot;token script&quot;&gt;&lt;/span&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;script&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;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;nomodule&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;https://unpkg.com/@google/model-viewer/dist/model-viewer-legacy.js&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;span class=&quot;token script&quot;&gt;&lt;/span&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;script&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 comment&quot;&gt;&amp;lt;!-- Use it like any other HTML element --&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;model-viewer&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&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;mv-demo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;shadow-intensity&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;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;./spacesuit.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token attr-name&quot;&gt;alt&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;A 3D model of an astronaut&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;auto-rotate&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;camera-controls&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token attr-name&quot;&gt;poster&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;./spacesuit.jpg&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;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;model-viewer&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;p&gt;That code renders like this:&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 500px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://model-viewer-shark.glitch.me/&quot; title=&quot;&lt;model-viewer&gt; on Glitch&quot; allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media;
clipboard; clipboard-read; clipboard-write&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;adding-motion-and-interactivity&quot;&gt;Adding motion and interactivity &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#adding-motion-and-interactivity&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;auto-rotate&lt;/code&gt; and &lt;code&gt;camera-controls&lt;/code&gt; attributes provide motion and user
control. Those aren&#39;t the only possible attributes. See the documentation for &lt;a href=&quot;https://modelviewer.dev/#section-attributes&quot; rel=&quot;noopener&quot;&gt;a
complete list of attributes&lt;/a&gt;.&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;model-viewer&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;assets/Astronaut.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;auto-rotate&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;camera-controls&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;h3 id=&quot;delayed-loading-with-poster-images&quot;&gt;Delayed loading with poster images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#delayed-loading-with-poster-images&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some 3D models can be very large, so you might want to hold off loading them
until the user has requested the model. For this, the component has a built-in
means of delaying loading until the user wants it. That&#39;s what the &lt;code&gt;poster&lt;/code&gt;
attribute does.&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;model-viewer&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;assets/Astronaut.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;controls&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;auto-rotate&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token attr-name&quot;&gt;poster&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;./spacesuit.jpg&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;To show your users that it&#39;s a 3D model, and not just an image, you can provide
some preload animation by using script to switch between multiple posters.&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;model-viewer&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&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;toggle-poster&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;assets/Astronaut.glb&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;controls&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token attr-name&quot;&gt;auto-rotate&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;poster&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;assets/poster2.png&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;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;model-viewer&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;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; posters &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 string&quot;&gt;&#39;poster.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;poster2.png&#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;let&lt;/span&gt; i &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;br /&gt;    &lt;span class=&quot;token function&quot;&gt;setInterval&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 operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&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;#toggle-poster&#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;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;poster&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;assets/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;posters&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&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&gt;&lt;/span&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;script&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;h3 id=&quot;responsive-design&quot;&gt;Responsive design &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#responsive-design&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The component handles some types of &lt;a href=&quot;https://web.dev/responsive-web-design-basics&quot;&gt;responsive design&lt;/a&gt;, scaling for both mobile
and desktop. It can also manage multiple instances on a page and uses
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IntersectionObserver&quot; rel=&quot;noopener&quot;&gt;Intersection
Observer&lt;/a&gt;
to conserve battery power and GPU cycles when a model isn&#39;t visible.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Multiple spacesuit images representing responsiveness.&quot; decoding=&quot;async&quot; height=&quot;453&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/8jXl21iFxh7O3QeEeL0k.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Multiple spacesuit images representing responsiveness.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/model-viewer/#looking-forward&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://modelviewer.dev/&quot; rel=&quot;noopener&quot;&gt;Install &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; and give it a try&lt;/a&gt; The
project team wants &lt;code&gt;&amp;lt;model-viewer&amp;gt;&lt;/code&gt; to be useful to you, and wants your input on
its future. That&#39;s not to say they don&#39;t have ideas. So give it a try and let us
know what you think by &lt;a href=&quot;https://github.com/google/model-viewer/issues&quot; rel=&quot;noopener&quot;&gt;filing an issue in
GitHub&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Media conversion</title>
    <link href="https://web.dev/media-conversion/"/>
    <updated>2018-09-20T00:00:00Z</updated>
    <id>https://web.dev/media-conversion/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this article we are going to learn some common commands for converting and
manipulating specific characteristics of media files. Although we&#39;ve tried to
show equivalent operations for all procedures, not all operations are possible
in both applications.&lt;/p&gt;
&lt;p&gt;In many cases, the commands we&#39;re showing may be combined in a single command
line operation, and would be when actually used. For example, there&#39;s nothing
preventing you from setting an output file&#39;s bitrate in the same operation as
a file conversion. For this article, we often show these operations as separate
commands for the sake of clarity.&lt;/p&gt;
&lt;p&gt;Conversion is done with these applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/shaka-packager&quot; rel=&quot;noopener&quot;&gt;Shaka Packager&lt;/a&gt; (&lt;a href=&quot;https://stackoverflow.com/questions/tagged/shaka&quot; rel=&quot;noopener&quot;&gt;on Stack Overflow&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/download.html&quot; rel=&quot;noopener&quot;&gt;FFmpeg&lt;/a&gt;, (&lt;a href=&quot;https://stackoverflow.com/questions/tagged/ffmpeg&quot; rel=&quot;noopener&quot;&gt;on Stack Overflow&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;display-characteristics&quot;&gt;Display characteristics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#display-characteristics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Both Shaka Packager and FFmpeg can be used to inspect the content of a media
file and then display the characteristics of a stream. However, both provide
different output for the same media.&lt;/p&gt;
&lt;h3 id=&quot;characteristics-using-shaka-packager&quot;&gt;Characteristics using Shaka Packager &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#characteristics-using-shaka-packager&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken.mp4 --dump_stream_info&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The output looks like:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;File &lt;span class=&quot;token string&quot;&gt;&quot;glocken.mp4&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;br /&gt;Found &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;.&lt;br /&gt;Stream &lt;span class=&quot;token punctuation&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; type: Video&lt;br /&gt; codec_string: avc1.640028&lt;br /&gt; time_scale: &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;&lt;br /&gt; duration: &lt;span class=&quot;token number&quot;&gt;300300&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt; is_encrypted: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt; codec: H264&lt;br /&gt; width: &lt;span class=&quot;token number&quot;&gt;1920&lt;/span&gt;&lt;br /&gt; height: &lt;span class=&quot;token number&quot;&gt;1080&lt;/span&gt;&lt;br /&gt; pixel_aspect_ratio: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;:1&lt;br /&gt; trick_play_factor: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt; nalu_length_size: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Stream &lt;span class=&quot;token punctuation&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; type: Audio&lt;br /&gt; codec_string: mp4a.40.2&lt;br /&gt; time_scale: &lt;span class=&quot;token number&quot;&gt;48000&lt;/span&gt;&lt;br /&gt; duration: &lt;span class=&quot;token number&quot;&gt;481280&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt; is_encrypted: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt; codec: AAC&lt;br /&gt; sample_bits: &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt; num_channels: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt; sampling_frequency: &lt;span class=&quot;token number&quot;&gt;48000&lt;/span&gt;&lt;br /&gt; language: eng&lt;br /&gt; seek_preroll_ns: &lt;span class=&quot;token number&quot;&gt;20833&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;characteristics-using-ffmpeg&quot;&gt;Characteristics using FFmpeg &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#characteristics-using-ffmpeg&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i glocken.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The output looks like:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Input &lt;span class=&quot;token comment&quot;&gt;#0, mov,mp4,m4a,3gp,3g2,mj2, from &#39;glocken.mp4&#39;:&lt;/span&gt;&lt;br /&gt;  Metadata:&lt;br /&gt;    major_brand     &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; isom&lt;br /&gt;    minor_version   &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;br /&gt;    compatible_brands: isomiso2avc1mp41&lt;br /&gt;    encoder         &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; Lavf57.83.100&lt;br /&gt;  Duration: 00:00:10.03, start: &lt;span class=&quot;token number&quot;&gt;0.000000&lt;/span&gt;, bitrate: &lt;span class=&quot;token number&quot;&gt;8063&lt;/span&gt; kb/s&lt;br /&gt;    Stream &lt;span class=&quot;token comment&quot;&gt;#0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc), 1920x1080, 7939 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)&lt;/span&gt;&lt;br /&gt;    Metadata:&lt;br /&gt;      handler_name    &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; VideoHandler&lt;br /&gt;    Stream &lt;span class=&quot;token comment&quot;&gt;#0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)&lt;/span&gt;&lt;br /&gt;    Metadata:&lt;br /&gt;      handler_name    &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; SoundHandler&lt;br /&gt;At least one output &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; must be specified&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &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; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Technically, FFmpeg always requires an output file format. Calling FFmpeg this way will give you an error message explaining that; however, it lists information not available using Shaka Packager so feel free to ignore the error. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;demux-separate-the-audio-and-video-streams&quot;&gt;Demux (separate) the audio and video streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#demux-separate-the-audio-and-video-streams&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Shaka Packager requires demuxing when converting files. This is also required
for using media frameworks.&lt;/p&gt;
&lt;h3 id=&quot;shaka-packager-demuxing&quot;&gt;Shaka Packager demuxing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#shaka-packager-demuxing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MP4&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_video.mp4&lt;br /&gt;packager &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_audio.m4a&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Or:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_video.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_audio.m4a&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;WebM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.webm,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_video.webm &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.webm,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_audio.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;ffmpeg-demuxing&quot;&gt;FFmpeg demuxing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#ffmpeg-demuxing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MP4&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mp4 -vcodec copy -an myvideo_video.mp4&lt;br /&gt;ffmpeg -i myvideo.mp4 -acodec copy -vn myvideo_audio.m4a&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;WebM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.webm -vcodec copy -an myvideo_video.webm&lt;br /&gt;ffmpeg -i myvideo.webm -acodec copy -vn myvideo_audio.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;remux-combine-the-audio-and-video-streams&quot;&gt;Remux (combine) the audio and video streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#remux-combine-the-audio-and-video-streams&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In some situation you will need to combine the audio and video back into a single
container. Especially when not using a media framework. This is something FFmpeg
can handle quite well and is something Shaka Packager does not currently support.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo_video.webm -i myvideo_audio.webm -c copy myvideo.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;change-characteristics&quot;&gt;Change characteristics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#change-characteristics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;bitrate&quot;&gt;Bitrate &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#bitrate&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For FFmpeg, we can do this while converting to &lt;code&gt;.mp4&lt;/code&gt; or &lt;code&gt;.webm&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mov -b:v 350K myvideo.mp4&lt;br /&gt;ffmpeg -i myvideo.mov -vf &lt;span class=&quot;token assign-left variable&quot;&gt;setsar&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;:1 -b:v 350K myvideo.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;dimensions-resolution&quot;&gt;Dimensions (resolution) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#dimensions-resolution&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.webm -s 1920x1080 myvideo_1920x1080.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;file-type&quot;&gt;File type &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#file-type&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Shaka Packager cannot process &lt;code&gt;.mov&lt;/code&gt; files and hence cannot be used to convert
files from that format.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;.mov&lt;/code&gt; to &lt;code&gt;.mp4&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mov myvideo.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;.mov&lt;/code&gt; to &lt;code&gt;.webm&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mov myvideo.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;synchronize-audio-and-video&quot;&gt;Synchronize audio and video &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#synchronize-audio-and-video&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To ensure that audio and video synchronize during playback, insert keyframes.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mp4 -keyint_min &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt; -g &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt; -f webm -vf &lt;span class=&quot;token assign-left variable&quot;&gt;setsar&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;:1 out.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Visit &lt;em&gt;Containers and codecs&lt;/em&gt; to view the audio and video &lt;a href=&quot;https://web.dev/containers-and-codecs/#codecs&quot;&gt;&lt;strong&gt;codecs&lt;/strong&gt;&lt;/a&gt; and the associated encoding and decoding library the codec uses. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;MP4/H.264&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mp4 -c:v libx264 -c:a copy myvideo.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Audio for an MP4&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mp4 -c:v copy -c:a aac myvideo.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;WebM/VP9&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.webm -v:c libvpx-vp9 -v:a copy myvideo.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Audio for a WebM&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.webm -v:c copy -v:a libvorbis myvideo.webm&lt;br /&gt;ffmpeg -i myvideo.webm -v:c copy -v:a libopus myvideo.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;video-on-demand-and-live-streaming&quot;&gt;Video-on-demand and live-streaming &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#video-on-demand-and-live-streaming&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two types of streaming protocols we are going to demonstrate in this
article. The first is Dynamic Adaptive Streaming over HTTP (DASH), which is an
adaptive bitrate streaming technique and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/DASH_Adaptive_Streaming_for_HTML_5_Video&quot; rel=&quot;noopener&quot;&gt;web-standards-based&lt;/a&gt; method of
presenting video-on-demand. The second is HTTP Live Streaming (HLS), which is
&lt;a href=&quot;https://developer.apple.com/streaming/&quot; rel=&quot;noopener&quot;&gt;Apple&#39;s standard&lt;/a&gt; for live-streaming and video-on-demand for the web.&lt;/p&gt;
&lt;h3 id=&quot;dashmpd&quot;&gt;DASH/MPD &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#dashmpd&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This example generates the Media Presentation Description (MPD) output file
from the audio and video streams.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;audio,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_audio.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo.mp4,stream&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;video,output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;myvideo_video.mp4 &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --mpd_output myvideo_vod.mpd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;hls&quot;&gt;HLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-conversion/#hls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These examples generate an &lt;code&gt;M3U8&lt;/code&gt; output file from the audio and video streams,
which is a UTF-8 encoded multimedia playlist.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg -i myvideo.mp4 -c:a copy -b:v 8M -c:v copy -f hls &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  -hls_time &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; -hls_list_size &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; myvideo.m3u8&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;OR:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;input=myvideo.mp4,stream=video,segment_template=output$Number$.ts,playlist_name=video_playlist.m3u8&#39;&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;input=myvideo.mp4,stream=audio,segment_template=output_audio$Number$.ts,playlist_name=audio_playlist.m3u8,hls_group_id=audio,hls_name=ENGLISH&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;br /&gt;  --hls_master_playlist_output&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;master_playlist.m3u8&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now that we hopefully have a good grasp on how to convert files, we can build on
what we&#39;ve learned in this article and go learn about
&lt;a href=&quot;https://web.dev/media-encryption/&quot;&gt;Media encryption&lt;/a&gt; next.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Welcome to the immersive web</title>
    <link href="https://web.dev/welcome-to-immersive/"/>
    <updated>2018-05-08T00:00:00Z</updated>
    <id>https://web.dev/welcome-to-immersive/</id>
    <content type="html" mode="escaped">&lt;p&gt;The immersive web means virtual world experiences hosted through the
browser. This covers entire virtual reality (VR) experiences surfaced in the
browser or in VR enabled headsets like Google&#39;s Daydream, Oculus Rift, Samsung
Gear VR, HTC Vive, and Windows Mixed Reality Headsets, as well as augmented
reality experiences developed for AR-enabled mobile devices.&lt;/p&gt;
&lt;p&gt;Though we use two terms to describe immersive experiences, they should be
thought of as a spectrum from complete reality to a completely immersive VR
environment, with various levels of AR in between.&lt;/p&gt;
&lt;p&gt;Examples of immersive experiences include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immersive 360° videos&lt;/li&gt;
&lt;li&gt;Traditional 2D (or 3D) videos presented in immersive surroundings&lt;/li&gt;
&lt;li&gt;Data visualizations&lt;/li&gt;
&lt;li&gt;Home shopping&lt;/li&gt;
&lt;li&gt;Art&lt;/li&gt;
&lt;li&gt;Something cool nobody&#39;s thought of yet&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-do-i-get-there&quot;&gt;How do I get there? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#how-do-i-get-there&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The immersive web has been available for nearly a year now in embryonic form.
This was done through the &lt;a href=&quot;https://www.chromestatus.com/features/4532810371039232&quot; rel=&quot;noopener&quot;&gt;WebVR 1.1
API&lt;/a&gt;, which has been
available in an &lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials&quot; rel=&quot;noopener&quot;&gt;origin trial&lt;/a&gt;
since Chrome 62. That API is also supported by Firefox and Edge as well as a
polyfill for Safari.&lt;/p&gt;
&lt;p&gt;But it&#39;s time to move on.&lt;/p&gt;
&lt;p&gt;The origin trial ended on July 24, 2018, and the spec has been superseded
by the &lt;a href=&quot;https://www.chromestatus.com/features/5680169905815552&quot; rel=&quot;noopener&quot;&gt;WebXR Device API&lt;/a&gt;
and a new origin trial.&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&#39;re participating in the WebVR origin trial, you need a separate registration for the WebXR Origin Trial (&lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md&quot;&gt;explainer&lt;/a&gt;, &lt;a href=&quot;http://bit.ly/OriginTrialSignup&quot;&gt;sign-up form&lt;/a&gt;). &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-happened-to-webvr-11&quot;&gt;What happened to WebVR 1.1? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#what-happened-to-webvr-11&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We learned a lot from WebVR 1.1, but over time, it became clear that some major
changes were needed to support the types of applications developers want to
build. The full list of lessons learned is too long to go into here, but
includes issues like the API being explicitly tied to the main JavaScript
thread, too many opportunities  for developers to set up obviously wrong
configurations, and common uses like magic window being a side effect rather
than an intentional feature. (Magic window is a technique for viewing immersive
content without a headset wherein the app renders a single view based on the
device&#39;s orientation sensor.)&lt;/p&gt;
&lt;p&gt;The new design facilitates simpler implementations and large performance
improvements. At the same time, AR and other use cases were emerging and it
became important that the API be extensible to support those in the future.&lt;/p&gt;
&lt;p&gt;The WebXR Device API was designed and named with these expanded use cases in
mind and provides a better path forward. Implementors of WebVR have committed
to migrating to the WebXR Device API.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-webxr-device-api&quot;&gt;What is the WebXR Device API? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#what-is-the-webxr-device-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like the WebVR spec before it, the WebXR Device API is a product of the
&lt;a href=&quot;https://github.com/immersive-web&quot; rel=&quot;noopener&quot;&gt;Immersive Web Community Group&lt;/a&gt; which has
contributors from Google, Microsoft, Mozilla, and others. The &#39;X in XR is
intended as a sort of algebraic variable that stands for anything in the
spectrum of immersive experiences. It&#39;s available in the previously mentioned
&lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials&quot; rel=&quot;noopener&quot;&gt;origin trial&lt;/a&gt; as well as through
a &lt;a href=&quot;https://github.com/immersive-web/webxr-polyfill&quot; rel=&quot;noopener&quot;&gt;polyfill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When this article was originally published during the Chrome 67 beta period,
only VR capabilities were enabled. Augmented reality arrived in Chrome 69. Read
about it in &lt;a href=&quot;https://developer.chrome.com/blog/ar-for-the-web/&quot; rel=&quot;noopener&quot;&gt;Augmented reality for the
web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&#39;s more to this new API than I can go to in an article like this. I want
to give you enough to start making sense of the &lt;a href=&quot;https://github.com/immersive-web/webxr-samples&quot; rel=&quot;noopener&quot;&gt;WebXR
samples&lt;/a&gt;. You can find more
information in both the &lt;a href=&quot;https://github.com/immersive-web/webxr/blob/master/explainer.md&quot; rel=&quot;noopener&quot;&gt;original
explainer&lt;/a&gt; and
our &lt;a href=&quot;https://immersive-web.github.io/webxr-reference/&quot; rel=&quot;noopener&quot;&gt;Immersive Web Early Adopters
Guide&lt;/a&gt;. I&#39;ll be expanding the
latter as the origin trial progresses. Feel free to open issues or submit pull
requests.&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; The Early Adopters Guide is updated when spec changes land in Chrome. To be notified of those updates watch &lt;a href=&quot;https://github.com/immersive-web/webxr-reference&quot;&gt;its repo on GitHub&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;For this article, I&#39;m going to discuss starting, stopping and running
an XR session, plus a few basics about processing input.&lt;/p&gt;
&lt;p&gt;What I&#39;m not going to cover is how to draw AR/VR content to the screen. The
WebXR Device API does not provide image rendering features. That&#39;s up to you.
Drawing is done using WebGL APIs. You can do that if you&#39;re really ambitious.
Though, we recommend using a framework. The immersive web samples use one
created just for the demos called
&lt;a href=&quot;https://github.com/immersive-web/webxr-samples/tree/master/js/cottontail&quot; rel=&quot;noopener&quot;&gt;Cottontail&lt;/a&gt;.
Three.js has supported WebXR since May. I&#39;ve heard nothing about
A-Frame.&lt;/p&gt;
&lt;h2 id=&quot;starting-and-running-an-app&quot;&gt;Starting and running an app &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#starting-and-running-an-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The basic process is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request an XR device.&lt;/li&gt;
&lt;li&gt;If it&#39;s available, request an XR session. If you want the user to put their
phone in a headset, it&#39;s called an immersive session and requires a user
gesture to enter.&lt;/li&gt;
&lt;li&gt;Use the session to run a render loop which provides 60 image frames per
second. Draw appropriate content to the screen in each frame.&lt;/li&gt;
&lt;li&gt;Run the render loop until the user decides to exit.&lt;/li&gt;
&lt;li&gt;End the XR session.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s look at this in a little more detail and include some code. You won&#39;t be
able to run an app from what I&#39;m about to show you. But again, this is just to
give a sense of it.&lt;/p&gt;
&lt;h3 id=&quot;request-an-xr-device&quot;&gt;Request an XR device &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#request-an-xr-device&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here, you&#39;ll recognize the standard feature detection code. You could wrap this
in a function called something like &lt;code&gt;checkForXR()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re not using an immersive session you can skip advertising the
functionality and getting a user gesture and go straight to requesting a
session. An immersive session is one that requires a headset. A non-immersive
session simply shows content on the device screen. The former is what most
people think of when you refer to virtual reality or augmented reality. The
latter is sometimes called a &#39;magic window&#39;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&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;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestDevice&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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrDevice&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;// Advertise the AR/VR functionality to get a user gesture.&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 function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NotFoundError&#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 comment&quot;&gt;// No XRDevices available.&lt;/span&gt;&lt;br /&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;No XR devices available:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&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;else&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;// An error occurred while requesting an XRDevice.&lt;/span&gt;&lt;br /&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Requesting XR device failed:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&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;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;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This browser does not support the WebXR API.&quot;&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;request-an-xr-session&quot;&gt;Request an XR session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#request-an-xr-session&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we have our device and our user gesture, it&#39;s time to get a session.
To create a session, the browser needs a canvas on which to draw.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;xrPresentationContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; htmlCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;xrpresent&#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;let&lt;/span&gt; sessionOptions &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 comment&quot;&gt;// The immersive option is optional for non-immersive sessions; the value&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;//   defaults to false.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;immersive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&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;outputContext&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; xrPresentationContext&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;xrDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionOptions&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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;// Use a WebGL context as a base layer.&lt;/span&gt;&lt;br /&gt;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseLayer &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;XRWebGLLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gl&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;// Start the render loop&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;h3 id=&quot;run-the-render-loop&quot;&gt;Run the render loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#run-the-render-loop&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code for this step takes a bit of untangling. To untangle it, I&#39;m about to
throw a bunch of words at you. If you want a peek at the final code, &lt;a href=&quot;https://web.dev/welcome-to-immersive/#the-whole-render-loop&quot;&gt;jump
ahead&lt;/a&gt; to have a quick look then come back for the full
explanation. There&#39;s quite a bit that you may not be able to infer.&lt;/p&gt;
&lt;p&gt;The basic process for a render loop is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request an animation frame.&lt;/li&gt;
&lt;li&gt;Query for the position of the device.&lt;/li&gt;
&lt;li&gt;Draw content to the position of the device based on it&#39;s position.&lt;/li&gt;
&lt;li&gt;Do work needed for the input devices.&lt;/li&gt;
&lt;li&gt;Repeat 60 times a second until the user decides to quit.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;request-a-presentation-frame&quot;&gt;Request a presentation frame &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#request-a-presentation-frame&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The word &#39;frame&#39; has several meanings in a Web XR context. The first is the
&lt;em&gt;frame of reference&lt;/em&gt; which defines where the origin of the coordinate system is
calculated from, and what happens to that origin when the device moves. (Does
the view stay the same when the user moves or does it shift as it would in real
life?)&lt;/p&gt;
&lt;p&gt;The second type of frame is the &lt;em&gt;presentation frame&lt;/em&gt;, represented by an
&lt;code&gt;XRFrame&lt;/code&gt; object. This object contains the information needed to
render a single frame of an AR/VR scene to the device. This is a bit confusing
because a presentation frame is retrieved by calling &lt;code&gt;requestAnimationFrame()&lt;/code&gt;.
This makes it compatible with &lt;code&gt;window.requestAnimationFrame()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Before I give you any more to digest, I&#39;ll offer some code. The sample below
shows how the render loop is started and maintained. Notice the dual use of the
word frame. And notice the recursive call to &lt;code&gt;requestAnimationFrame()&lt;/code&gt;. This
function will be called 60 times a second.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestFrameOfReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;eye-level&#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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrFrameOfRef&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;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;// The time argument is for future use and not implemented at this time.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Process the frame.&lt;/span&gt;&lt;br /&gt;    xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onFrame&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;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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;poses&quot;&gt;Poses &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#poses&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Before drawing anything to the screen, you need to know where the display
device is pointing and you need access to the screen. In general, the position
and orientation of a thing in AR/VR is called a pose. Both viewers and input
devices have a pose. (I cover input devices later.) Both viewer and input
device poses are defined as a 4 by 4 matrix stored in a &lt;code&gt;Float32Array&lt;/code&gt; in column
major order. You get the viewer&#39;s pose by calling
&lt;code&gt;XRFrame.getDevicePose()&lt;/code&gt; on the current animation frame object.
Always test to see if you got a pose back. If something went wrong you don&#39;t
want to draw to the screen.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDevicePose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrFrameOfRef&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;pose&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;// Draw something to the screen.&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;h4 id=&quot;views&quot;&gt;Views &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#views&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;After checking the pose, it&#39;s time to draw something. The object you draw to is
called a view (&lt;code&gt;XRView&lt;/code&gt;). This is where the session type becomes important. Views
are retrieved from the &lt;code&gt;XRFrame&lt;/code&gt; object as an array. If you&#39;re in a
non-immersive session the array has one view. If you&#39;re in an immersive
session, the array has two, one for each eye.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; view &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views&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;// Draw something to the screen.&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;This is an important difference between WebXR and other immersive systems.
Though it may seem pointless to iterate through one view, doing so allows you
to have a single rendering path for a variety of devices.&lt;/p&gt;
&lt;h4 id=&quot;the-whole-render-loop&quot;&gt;The whole render loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#the-whole-render-loop&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If I put all this together, I get the code below. I&#39;ve left a placeholder for
the input devices, which I&#39;ll cover in a later section.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestFrameOfReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;eye-level&#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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrFrameOfRef&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;    xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrame&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;// The time argument is for future use and not implemented at this time.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDevicePose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xrFrameOfRef&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;pose&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;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; view &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; xrFrame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views&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;// Draw something to the screen.&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 comment&quot;&gt;// Input device code will go here.&lt;/span&gt;&lt;br /&gt;    frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onFrame&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;end-the-xr-session&quot;&gt;End the XR session &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#end-the-xr-session&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An XR session may end for several reasons, including ending by your own code
through a call to &lt;code&gt;XRSession.end()&lt;/code&gt;. Other causes include the headset being
disconnected or another application taking control of it. This is why a
well-behaved application should monitor the end event and when it occurs,
discard the session and renderer objects. An XR session once ended cannot be
resumed.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;xrDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionOptions&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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;// Create a WebGL layer and initialize the render loop.&lt;/span&gt;&lt;br /&gt;    xrSession&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;end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSessionEnd&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Restore the page to normal after immersive access has been released.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSessionEnd&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;    xrSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&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;// Ending the session stops executing callbacks passed to the XRSession&#39;s&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// requestAnimationFrame(). To continue rendering, use the window&#39;s&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// requestAnimationFrame() function.&lt;/span&gt;&lt;br /&gt;    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onDrawFrame&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;h2 id=&quot;how-does-interaction-work&quot;&gt;How does interaction work? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#how-does-interaction-work&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As with the application lifetime, I&#39;m just going to give you a taste for how to
interact with objects in AR or VR.&lt;/p&gt;
&lt;p&gt;The WebXR Device API adopts a &amp;quot;point and click&amp;quot; approach to user input. With
this approach every input source has a defined &lt;em&gt;pointer ray&lt;/em&gt; to indicate where
an input device is pointing and events to indicate when something was selected.
Your app draws the pointer ray and shows where it&#39;s pointed. When the user
clicks the input device, events are fired - &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;selectStart&lt;/code&gt;, and
&lt;code&gt;selectEnd&lt;/code&gt;, specifically. Your app determines what was clicked and responds
appropriately.&lt;/p&gt;
&lt;h3 id=&quot;the-input-device-and-the-pointer-ray&quot;&gt;The input device and the pointer ray &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#the-input-device-and-the-pointer-ray&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To users, the pointer ray is just a faint line between the controller and
whatever they&#39;re pointing at. But your app has to draw it. That means getting
the pose of the input device and drawing a line from its location to an object
in AR/VR space. That process looks roughly like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; inputSources &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xrSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputSources&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;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; xrInputSource &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; inputSources&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;let&lt;/span&gt; inputPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputSource&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrameOfRef&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;inputPose&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;continue&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gripMatrix&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;// Render a virtual version of the input device&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;//   at the correct position and orientation.&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pointerMatrix&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;// Draw a ray from the gripMatrix to the pointerMatrix.&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This is a stripped down version of the &lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/input-tracking.html#L198&quot; rel=&quot;noopener&quot;&gt;Input Tracking
sample&lt;/a&gt;
from the Immersive Web Community Group. As with frame rendering, drawing the
pointer ray and the device is up to you. As alluded to earlier, this code must
be run as part of the render loop.&lt;/p&gt;
&lt;h3 id=&quot;selecting-items-in-virtual-space&quot;&gt;Selecting items in virtual space &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#selecting-items-in-virtual-space&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Merely pointing at things in AR/VR is pretty useless. To do anything useful,
users need the ability to select things. The WebXR Device API provides three
events for responding to user interactions: &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;selectStart&lt;/code&gt;, and
&lt;code&gt;selectEnd&lt;/code&gt;. They have a quirk I didn&#39;t expect: they only tell you that an
input device was clicked. They don&#39;t tell you what item in the environment was
clicked. Event handlers are added to the &lt;code&gt;XRSession&lt;/code&gt; object and should be added
as soon as its available.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;xrDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionOptions&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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;xrSession&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;// Create a WebGL layer and initialize the render loop.&lt;/span&gt;&lt;br /&gt;    xrSession&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;selectstart&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSelectStart&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;    xrSession&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;selectend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSelectEnd&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;    xrSession&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;select&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onSelect&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This code is based on an &lt;a href=&quot;https://github.com/immersive-web/webxr-samples/blob/master/input-selection.html&quot; rel=&quot;noopener&quot;&gt;Input Selection
example&lt;/a&gt;
, in case you want more context.&lt;/p&gt;
&lt;p&gt;To figure out what was clicked you use a pose. (Are you surprised? I didn&#39;t
think so.) The details of that are specific to your app or whatever framework
you&#39;re using, and hence beyond the scope of this article. Cottontail&#39;s approach
is in the Input Selection example.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSelect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;ev&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;let&lt;/span&gt; inputPose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ev&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frame&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInputPose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ev&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputSource&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xrFrameOfRef&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;inputPose&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;&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputPose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pointerMatrix&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;// Figure out what was clicked and respond.&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion-looking-ahead&quot;&gt;Conclusion: looking ahead &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/welcome-to-immersive/#conclusion-looking-ahead&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I said earlier, augmented reality is expected in Chrome 69 (Canary some time
in June 2018). Nevertheless, I encourage you try what we&#39;ve got so far. We
need feedback to make it better. Follow it&#39;s progress by watching
ChromeStatus.com for &lt;a href=&quot;https://www.chromestatus.com/features/4755348300759040&quot; rel=&quot;noopener&quot;&gt;WebXR Hit
Test&lt;/a&gt;. You can also
follow &lt;a href=&quot;https://www.chromestatus.com/features/5129925015109632&quot; rel=&quot;noopener&quot;&gt;WebXR Anchors&lt;/a&gt;
which will improve pose tracking.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>Bitrate</title>
    <link href="https://web.dev/bitrate/"/>
    <updated>2017-06-30T00:00:00Z</updated>
    <id>https://web.dev/bitrate/</id>
    <content type="html" mode="escaped">&lt;p&gt;In the previous &lt;a href=&quot;https://web.dev/containers-and-codecs/&quot;&gt;Containers and codecs&lt;/a&gt; article, you
learned how to change the container (extension) and codec of a media file. In
this article, we&#39;ll show you how to change bitrate before explaining
&lt;a href=&quot;https://web.dev/resolution/&quot;&gt;resolution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bitrate and resolution correlate to the amount of data in a media file. It
probably goes without saying, but we&#39;re going to say it anyway. You can always
lower bitrate and resolution, but increasing them is a problem. Without special
software and algorithms, quality is going to take a hit.&lt;/p&gt;
&lt;p&gt;So always start your conversion process with the highest quality source file you
can get your hands on. Before doing anything, even before changing the codec or
container, check the file&#39;s
&lt;a href=&quot;https://web.dev/media-conversion/#display-characteristics&quot;&gt;display characteristics&lt;/a&gt; and verify
that your source file has a higher bitrate or resolution than your desired result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bitrate&lt;/strong&gt; is the maximum number of bits used to encode one second of a media
stream. The more bits used to encode a second of stream, the higher the
fidelity.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, the different bitrates the web can handle are low. The table
below shows you what bitrate you should target for common network conditions. For
the sake of comparison, we&#39;ve thrown in values for Blu-rays and DVDs.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The web numbers are approximations. This chart should &lt;strong&gt;not&lt;/strong&gt; be a substitute for doing your own testing. &lt;/div&gt;&lt;/aside&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Delivery method&lt;/th&gt;
&lt;th&gt;Bitrate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blu-ray&lt;/td&gt;
&lt;td&gt;20Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DVD&lt;/td&gt;
&lt;td&gt;6 Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop web&lt;/td&gt;
&lt;td&gt;2 Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4G mobile&lt;/td&gt;
&lt;td&gt;0.7 Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3G mobile&lt;/td&gt;
&lt;td&gt;0.35 Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2G mobile&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Depends on network type.&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;EDGE: 0.4 Mbs&lt;br /&gt;GPRS: 0.04Mbs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;p&gt;Which value should I use for video on my web pages? The short answer is at
least: desktop, 4G, and 3G. If you&#39;re serving video in one of the markets
referred to as &amp;quot;the next billion users&amp;quot;, say India, for example, you&#39;ll want to
include 2G as well. For demonstration purposes, we&#39;re going to target 3G.&lt;/p&gt;
&lt;p&gt;Using FFmpeg you set the bitrate with the (surprise!) bitrate (&lt;code&gt;-b&lt;/code&gt;) flag.&lt;/p&gt;
&lt;p&gt;If you don&#39;t have FFmpeg installed read
&lt;a href=&quot;https://web.dev/media-application-basics/#installing-applications-with-docker&quot;&gt;Media application basics&lt;/a&gt;
to get it set up with Docker.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;MP4&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -b:v 350k -b:a 64k glocken_3g.mp4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebM&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -b:v 350k -b:a 64k glocken_3g.webm&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice that there are two bitrate flags, &lt;code&gt;-b:a&lt;/code&gt; and &lt;code&gt;-b:v&lt;/code&gt;. One is for the audio
stream, and the other is for the video stream.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ls -l&lt;/span&gt;&lt;br /&gt;-rw-r--r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root  &lt;span class=&quot;token number&quot;&gt;12080306&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:16 glocken.mov&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;531117&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;:42 glocken_3g.mp4&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;706119&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;:46 glocken_3g.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now that your files are prepared, it&#39;s time to &lt;a href=&quot;https://web.dev/resolution&quot;&gt;adjust their resolutions&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Containers and codecs</title>
    <link href="https://web.dev/containers-and-codecs/"/>
    <updated>2017-06-30T00:00:00Z</updated>
    <id>https://web.dev/containers-and-codecs/</id>
    <content type="html" mode="escaped">&lt;p&gt;To support multiple browsers, you&#39;ll need to use FFmpeg to convert your &lt;code&gt;.mov&lt;/code&gt;
file to two different containers: an MP4 container and a WebM container. In
actual practice, you would likely specify a codec at the same time. For now,
we&#39;re letting FFmpeg use its defaults.&lt;/p&gt;
&lt;p&gt;If these concepts are new to you, you should read
&lt;a href=&quot;https://web.dev/media-file-basics/#containers-and-codecs-and-streams&quot;&gt;Media file basics&lt;/a&gt;
before going further. Additionally, if you don&#39;t have FFmpeg installed read
&lt;a href=&quot;https://web.dev/media-application-basics/#installing-applications-with-docker&quot;&gt;Media application basics&lt;/a&gt;
to get it set up with Docker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We are using the suggested Docker install and
the &lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/prepare-media/glocken.mov&quot; rel=&quot;noopener&quot;&gt;glocken.mov&lt;/a&gt; file from &lt;a href=&quot;https://web.dev/prepare-media/&quot;&gt;Prepare media files for the web&lt;/a&gt;
added inside the &lt;code&gt;media&lt;/code&gt; directory. We used FFmpeg version 4.3.2 for all the
commands in this section.&lt;/strong&gt;&lt;/p&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; If the commands don&#39;t work for your version of FFmpeg, consult the FFmpeg documentation. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;containers&quot;&gt;Containers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/containers-and-codecs/#containers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, we need to create our two containers from the &lt;code&gt;.mov&lt;/code&gt; file with the &lt;code&gt;.mp4&lt;/code&gt;
and &lt;code&gt;.webm&lt;/code&gt; file extensions with both an audio and video stream inside the file.
Review &lt;a href=&quot;https://web.dev/media-file-basics/#containers-and-codecs-and-streams&quot;&gt;Media file basics&lt;/a&gt;
for more about containers and streams if you don&#39;t know the different between
them.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;MP4&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov glocken.mp4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebM&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov glocken.webm&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;WebM takes longer to create than MP4. This isn&#39;t surprising when you look at
the results. While MP4 compresses to about &lt;code&gt;83%&lt;/code&gt; of the original file&#39;s
size, WebM is down to &lt;code&gt;78%&lt;/code&gt; of the original&#39;s size, but can be much smaller.
Your results will vary. It&#39;s important to call out that FFmpeg &lt;code&gt;4.2.2&lt;/code&gt; set the
default video bitrate to &lt;code&gt;200k&lt;/code&gt; and in &lt;code&gt;4.3.2&lt;/code&gt; it does not set a default bitrate.
So the video is no longer  &lt;strong&gt;a mere&lt;code&gt;4%&lt;/code&gt; of the original&lt;/strong&gt;. You can see this for yourself
using the &lt;code&gt;ls -a&lt;/code&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Ls&quot; rel=&quot;noopener&quot;&gt;bash command&lt;/a&gt; in the folder where your media files are located.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ls -l&lt;/span&gt;&lt;br /&gt;-rw-r--r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root  &lt;span class=&quot;token number&quot;&gt;12080306&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:16 glocken.mov&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root  &lt;span class=&quot;token number&quot;&gt;10106446&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:33 glocken.mp4&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root   &lt;span class=&quot;token number&quot;&gt;9503301&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:30 glocken.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;To get a tiny file, you would do this instead:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -b:v 200k glocken.webm&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 assign-left variable&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;fps&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3.6&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;Lsize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;     483kB &lt;span class=&quot;token assign-left variable&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;00:00:10.01 &lt;span class=&quot;token assign-left variable&quot;&gt;bitrate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;395&lt;/span&gt;.0kbits/s &lt;span class=&quot;token assign-left variable&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.121x&lt;br /&gt;video:359kB audio:117kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: &lt;span class=&quot;token number&quot;&gt;1.356068&lt;/span&gt;%&lt;br /&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ls -l&lt;/span&gt;&lt;br /&gt;-rw-r--r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root  &lt;span class=&quot;token number&quot;&gt;12080306&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:16 glocken.mov&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root  &lt;span class=&quot;token number&quot;&gt;10106446&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:33 glocken.mp4&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root  root    &lt;span class=&quot;token number&quot;&gt;494497&lt;/span&gt; Mar &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;:45 glocken.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;check-your-work&quot;&gt;Check your work &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/containers-and-codecs/#check-your-work&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To verify your results, use FFmpeg and Shaka Packager as already shown in
&lt;a href=&quot;https://web.dev/media-application-basics&quot;&gt;Media Application basics&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# packager input=glocken.mp4 --dump_stream_info&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mp4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;codecs&quot;&gt;Codecs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/containers-and-codecs/#codecs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, the codec. As stated in &lt;a href=&quot;https://web.dev/media-file-basics&quot;&gt;Media file basics&lt;/a&gt;, a codec
is &lt;em&gt;not&lt;/em&gt; the same thing as a container (file type). Two files of the same container
type could hold data compressed using different codecs. The WebM format for example
allows audio to be encoded using either &lt;a href=&quot;https://en.wikipedia.org/wiki/Vorbis&quot; rel=&quot;noopener&quot;&gt;Vorbis&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Opus_(audio_format)&quot; rel=&quot;noopener&quot;&gt;Opus&lt;/a&gt;. To change the codec we
use FFmpeg. For example, this command outputs an &lt;code&gt;.mkv&lt;/code&gt; file with a &lt;code&gt;vorbis&lt;/code&gt; audio
codec and an &lt;code&gt;av1&lt;/code&gt; video codec.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -c:a vorbis -c:v av1 glocken.mkv&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In this example, the &lt;code&gt;-c:a&lt;/code&gt; flag and the &lt;code&gt;-c:v&lt;/code&gt; are for specifying the audio and
video codecs respectively.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/media-conversion/#change-characteristics&quot;&gt;Media conversion&lt;/a&gt; page lists
commands needed to convert codecs. The tables below summarize the libraries used
in FFmpeg to perform the codec conversions for WebM and MP4 files. These are the
formats recommended for DASH and HLS respectively.&lt;/p&gt;
&lt;h3 id=&quot;video&quot;&gt;Video &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/containers-and-codecs/#video&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;av1&lt;/td&gt;
&lt;td&gt;WebM, mkv&lt;/td&gt;
&lt;td&gt;libaom-av1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;h264&lt;/td&gt;
&lt;td&gt;MP4&lt;/td&gt;
&lt;td&gt;libx264&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vp9&lt;/td&gt;
&lt;td&gt;WebM&lt;/td&gt;
&lt;td&gt;libvpx-vp9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h3 id=&quot;audio&quot;&gt;Audio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/containers-and-codecs/#audio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;aac&lt;/td&gt;
&lt;td&gt;MP4&lt;/td&gt;
&lt;td&gt;aac&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opus&lt;/td&gt;
&lt;td&gt;WebM&lt;/td&gt;
&lt;td&gt;libopus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vorbis&lt;/td&gt;
&lt;td&gt;WebM&lt;/td&gt;
&lt;td&gt;libvorbis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;p&gt;Next, we&#39;ll show you how to change the &lt;a href=&quot;https://web.dev/bitrate/&quot;&gt;bitrate&lt;/a&gt; of your newly created files.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>What is a media experience?</title>
    <link href="https://web.dev/media-experience/"/>
    <updated>2017-06-30T00:00:00Z</updated>
    <id>https://web.dev/media-experience/</id>
    <content type="html" mode="escaped">&lt;p&gt;Users like media, especially videos; they can be fun and informative. On mobile
devices, videos can be an easier way to consume information than text. For a
good user experience, videos should not need more than the available bandwidth.
Users should be able to use them no matter what device they&#39;re viewing them
with. Users should never need to wait for media to download. Nobody likes it when
they press play and nothing happens.&lt;/p&gt;
&lt;p&gt;You&#39;ve no doubt consumed video on your own device, which means that nothing in
that last paragraph surprises you. Now you need to learn how to put a video or
other media file on your own website. The technical requirements of adding media
should be in service to the user experience.&lt;/p&gt;
&lt;h2 id=&quot;the-technical-requirements&quot;&gt;The technical requirements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-experience/#the-technical-requirements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Versions of a media file are in common web-friendly formats containing both audio
and video streams.&lt;/li&gt;
&lt;li&gt;The resolution is appropriate for your users&#39; devices.&lt;/li&gt;
&lt;li&gt;The bitrate doesn&#39;t overload your users&#39; network bandwidth.&lt;/li&gt;
&lt;li&gt;The result is viewable on all major browsers using appropriate technologies.&lt;/li&gt;
&lt;li&gt;The file is encrypted (Optional).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/media/&quot;&gt;media&lt;/a&gt; section of this website will help you achieve these technical
requirements. Don&#39;t worry if these concepts are still a bit abstract. We&#39;ll
explain them progressively throughout all the articles. In the first section you
will learn about the basic concepts of media, then how to add media to the web in
the second section, and in the final section the practical applications, with some
advanced techniques, of using media on the web.&lt;/p&gt;
&lt;h2 id=&quot;displaying-video-on-the-web&quot;&gt;Displaying video on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-experience/#displaying-video-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are four approaches you can take when displaying video on a site.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Upload your videos to a media hosting provider such as &lt;a href=&quot;https://www.youtube.com/&quot; rel=&quot;noopener&quot;&gt;YouTube&lt;/a&gt; or &lt;a href=&quot;https://vimeo.com/&quot; rel=&quot;noopener&quot;&gt;Vimeo&lt;/a&gt;.
These options require you to embed their players within your site.&lt;/li&gt;
&lt;li&gt;Basic self-hosted embedding using the HTML &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; elements.&lt;/li&gt;
&lt;li&gt;More full-featured embedding using a video library 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;.&lt;/li&gt;
&lt;li&gt;Building your own media server and streaming application.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This site mainly covers the basics of embedding media. However, we do cover
some more advanced topics to get you started on the path towards building your
own media streaming application. The effort to do this is not trivial, so we&#39;ve
built a &lt;a href=&quot;https://github.com/xwp/web-dev-media&quot; rel=&quot;noopener&quot;&gt;Media PWA&lt;/a&gt; with offline support to use as a reference, which
should both show you ways this can be accomplished and just how much
effort it requires. The application is by no means a production ready offering
or competitor to services like YouTube or Vimeo, but it will provide you with a
starting point to learn something challenging and new.&lt;/p&gt;
&lt;p&gt;Frankly, building a competitor to hosted media services would require a team
of expert engineers and thousands of human-hours of work. Unless your goal is
to enter that market as a competitor, you&#39;re better off using one of the other
methods. It&#39;s good to understand the technology and while you may not roll out
your own application or video player, there is utility in exploring and
experimenting on the cutting edge of browser support, or at the very least
using one of the existing video libraries.&lt;/p&gt;
&lt;h2 id=&quot;what-were-going-to-learn&quot;&gt;What we&#39;re going to learn &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-experience/#what-were-going-to-learn&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/media/&quot;&gt;media&lt;/a&gt; collection has three parts. In this first section, we&#39;ll
provide concepts and prerequisite information to adding media to your site.
This includes explaining how media files are put together, basics about the
applications you&#39;ll need to prepare your files for the web, and streaming
concepts. The second section explains how to prepare your files and convert
them to various formats and optionally add encryption. In the last section,
we&#39;ll show you how to embed a media file in a web page, discuss autoplay best
practices, using media frameworks, taking videos offline, and making your videos
accessible.&lt;/p&gt;
&lt;p&gt;There&#39;s a lot of ground to cover, so let&#39;s get started with &lt;a href=&quot;https://web.dev/media-file-basics/&quot;&gt;Media file
basics&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Prepare media files for the web</title>
    <link href="https://web.dev/prepare-media/"/>
    <updated>2017-06-30T00:00:00Z</updated>
    <id>https://web.dev/prepare-media/</id>
    <content type="html" mode="escaped">&lt;p&gt;Now that we&#39;ve introduced you to the &lt;a href=&quot;https://web.dev/media-application-basics/&quot;&gt;applications&lt;/a&gt;
we use when manipulating media files, over the next few pages, we&#39;re going to
take a raw video file from a camera and transform it into a resource that you
can embed in a web page. We&#39;re going to show you how to format your
video for mobile web playback, and how to create multiple files to cover a
range of browsers. Specifically, we&#39;ll create a WebM file for use on Chrome and
an MP4 file for use on other browsers.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The result of this section will be media resources with the following characteristics:  * Versions of a media file in common web-friendly formats containing both audio and video streams. * A &lt;a href=&quot;https://web.dev/resolution/&quot;&gt;&lt;strong&gt;Resolution&lt;/strong&gt;&lt;/a&gt; appropriate for your users&#39; devices. * A &lt;a href=&quot;https://web.dev/bitrate/&quot;&gt;&lt;strong&gt;Bitrate&lt;/strong&gt;&lt;/a&gt; that doesn&#39;t overload your users&#39; network bandwidth. * A result that&#39;s viewable on all major browsers using &lt;strong&gt;appropriate technologies&lt;/strong&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Plus we will dive deeper into common commands used for
&lt;a href=&quot;https://web.dev/media-conversion/&quot;&gt;Media conversion&lt;/a&gt; and &lt;a href=&quot;https://web.dev/media-encryption/&quot;&gt;Media encryption&lt;/a&gt;
that will serve as a good reference for later.&lt;/p&gt;
&lt;p&gt;By &amp;quot;&lt;strong&gt;appropriate technologies&lt;/strong&gt;&amp;quot; we mean Dynamic Adaptive Streaming over HTTP
(&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;) or HTTP Live Streaming (&lt;a href=&quot;https://developer.apple.com/documentation/http_live_streaming&quot; rel=&quot;noopener&quot;&gt;HLS&lt;/a&gt;), which are the two primary means of
providing video in HTML on the major browsers. By the end of this section,
you&#39;ll be able to create media files that are ready for use in DASH and HLS.&lt;/p&gt;
&lt;p&gt;If you want to play along at home, you&#39;ll need a raw video file off a camera,
preferably one that contains both audio and video. If you don&#39;t have one handy,
then here&#39;s ten seconds of an &lt;code&gt;.mov&lt;/code&gt; file named &lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/prepare-media/glocken.mov&quot; rel=&quot;noopener&quot;&gt;glocken.mov&lt;/a&gt; that was taken of
the &lt;a href=&quot;https://en.wikipedia.org/wiki/Rathaus-Glockenspiel&quot; rel=&quot;noopener&quot;&gt;Rathaus-Glockenspiel&lt;/a&gt; in Munich&#39;s MarienPlatz.&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; The selection of the file formats, bitrate, and resolution are not arbitrary. We&#39;ve selected these values for speedy web playback on mobile devices. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Next, let&#39;s get started with &lt;a href=&quot;https://web.dev/containers-and-codecs/&quot;&gt;Containers and codecs&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Resolution</title>
    <link href="https://web.dev/resolution/"/>
    <updated>2017-06-30T00:00:00Z</updated>
    <id>https://web.dev/resolution/</id>
    <content type="html" mode="escaped">&lt;p&gt;In the previous articles you learned how to change the
&lt;a href=&quot;https://web.dev/containers-and-codecs/&quot;&gt;containers, codecs&lt;/a&gt;, and &lt;a href=&quot;https://web.dev/bitrate/&quot;&gt;bitrate&lt;/a&gt; of the
&lt;a href=&quot;https://storage.googleapis.com/web-dev-assets/prepare-media/glocken.mov&quot; rel=&quot;noopener&quot;&gt;glocken.mov&lt;/a&gt; media file. This article focuses on changing the resolution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution&lt;/strong&gt; is the amount of information in a single frame of video, given as
the number of logical pixels in each dimension. For example, a resolution of
1920 by 1080 works out to 1080 stacked horizontal lines, each of which is one
logical pixel high and 1920 logical pixels wide. This resolution is frequently
abbreviated 1080p because technically the width can vary. The dimensions 1080 by
1920 produce an &lt;a href=&quot;https://en.wikipedia.org/wiki/Aspect_ratio_(image)&quot; rel=&quot;noopener&quot;&gt;aspect ratio&lt;/a&gt; of 16:9, which is the ratio of movie screens and
modern television sets. By the way this is the resolution defined as &lt;a href=&quot;https://en.wikipedia.org/wiki/1080p&quot; rel=&quot;noopener&quot;&gt;full HD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://support.google.com/youtube/answer/6375112&quot; rel=&quot;noopener&quot;&gt;YouTube recommends&lt;/a&gt; the following resolutions for video uploads, all in the 16:9
aspect ratio. There&#39;s nothing specific to YouTube about this list. It&#39;s just a
list of common 16:9 video resolutions.&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Abbreviation&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2160p&lt;/td&gt;
&lt;td&gt;3840 x 2160&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1440p&lt;/td&gt;
&lt;td&gt;2560 x 1440&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1080p&lt;/td&gt;
&lt;td&gt;1920 x 1080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;720p&lt;/td&gt;
&lt;td&gt;1280 x 720&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;480p&lt;/td&gt;
&lt;td&gt;854 x 480&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;360p&lt;/td&gt;
&lt;td&gt;640 x 360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;240p&lt;/td&gt;
&lt;td&gt;426 x 240&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;p&gt;Which one should you use? That depends on your application. For simple embedding
you may decide to chose only a single resolution. If you&#39;re preparing files for
DASH or HLS, you may chose one, several, or all. Fortunately, this is one of the
simplest transformations you&#39;ll make with FFmpeg.&lt;/p&gt;
&lt;p&gt;If you don&#39;t have FFmpeg installed read
&lt;a href=&quot;https://web.dev/media-application-basics/#installing-applications-with-docker&quot;&gt;Media application basics&lt;/a&gt;
to get it set up with Docker.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;MP4&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -b:v 350k -b:a 64k -s 1280x720 glocken_3g_720p.mp4&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebM&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -i glocken.mov -b:v 350k -b:a 64k -s 1280x720 glocken_3g_720p.webm&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following files should now exist:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ls -l&lt;/span&gt;&lt;br /&gt;-rw-r--r-- &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root  &lt;span class=&quot;token number&quot;&gt;12080306&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;:16 glocken.mov&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;531117&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;:42 glocken_3g.mp4&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;706119&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;:46 glocken_3g.webm&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;539414&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:15 glocken_3g_720p.mp4&lt;br /&gt;-rwx------ &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; root root    &lt;span class=&quot;token number&quot;&gt;735930&lt;/span&gt; Mar  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:19 glocken_3g_720p.webm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It&#39;s worth reiterating that you should start from the highest resolution and
bitrate file you have available. If you&#39;re upgrading an older site, you&#39;ll want
to find your original camera or other high resolution sources and convert from
that rather than from older web files.&lt;/p&gt;
&lt;p&gt;Now that your files are prepared, you can either &lt;a href=&quot;https://web.dev/add-media/&quot;&gt;add them to a web page&lt;/a&gt;
as they are now or dive a bit deeper and continue to learn more command line options
by reading the &lt;a href=&quot;https://web.dev/media-conversion/&quot;&gt;Media conversion&lt;/a&gt; page, and then close out the
section with &lt;a href=&quot;https://web.dev/media-encryption/&quot;&gt;Media encryption&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Media application basics</title>
    <link href="https://web.dev/media-application-basics/"/>
    <updated>2017-06-09T00:00:00Z</updated>
    <id>https://web.dev/media-application-basics/</id>
    <content type="html" mode="escaped">&lt;p&gt;Working with media often requires changing the characteristics of media files,
such as bitrate or resolution. Finding a straightforward way to get started can
be pretty intimidating. On this page, you will learn about the tools used and how
to install them quickly.&lt;/p&gt;
&lt;p&gt;First, we describe the basic usage for two common command-line media utilities:
&lt;a href=&quot;https://github.com/google/shaka-packager&quot; rel=&quot;noopener&quot;&gt;Shaka Packager&lt;/a&gt; and &lt;a href=&quot;https://ffmpeg.org/download.html&quot; rel=&quot;noopener&quot;&gt;FFmpeg&lt;/a&gt; and then we help you quickly get the tools installed.
Why cover two applications? While both are powerful and useful by themselves,
neither does everything needed to prepare media for the web. We also created the
&lt;a href=&quot;https://web.dev/media-conversion/&quot;&gt;Media conversion&lt;/a&gt; and &lt;a href=&quot;https://web.dev/media-encryption/&quot;&gt;Media encryption&lt;/a&gt;
pages which show many more common operations with these two applications.&lt;/p&gt;
&lt;p&gt;These applications aren&#39;t the only options available for file manipulation tasks,
but they are two of the most common and powerful. Other options include the GUI
applications &lt;a href=&quot;http://www.mirovideoconverter.com/&quot; rel=&quot;noopener&quot;&gt;Miro&lt;/a&gt;, &lt;a href=&quot;https://handbrake.fr/&quot; rel=&quot;noopener&quot;&gt;HandBrake&lt;/a&gt;, and &lt;a href=&quot;https://www.videolan.org/&quot; rel=&quot;noopener&quot;&gt;VLC&lt;/a&gt;. There are also encoding/transcoding
services such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Zencoder&quot; rel=&quot;noopener&quot;&gt;Zencoder&lt;/a&gt;, &lt;a href=&quot;https://aws.amazon.com/elastictranscoder&quot; rel=&quot;noopener&quot;&gt;Amazon Elastic Encoder&lt;/a&gt;, and &lt;a href=&quot;https://cloud.google.com/transcoder/docs&quot; rel=&quot;noopener&quot;&gt;Google Transcoder API&lt;/a&gt;
(currently in beta).&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; You&#39;ll notice in what follows that the word &#39;resolution&#39; doesn&#39;t appear. What the two applications output are just the dimensions; the numbers themselves. That&#39;s because resolution is just an informal shorthand for the dimensions of a video. In every case that follows, We talk about specific numbers. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;shaka-packager&quot;&gt;Shaka Packager &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-application-basics/#shaka-packager&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/google/shaka-packager&quot; rel=&quot;noopener&quot;&gt;Shaka Packager&lt;/a&gt; is a free media packaging SDK. If you were using a media player
on your site, Shaka Packager is what you would use to prepare the files. It
supports conversion for the two most common video streaming protocols: Dynamic
Adaptive Streaming over HTTP (&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;) or HTTP Live Streaming (&lt;a href=&quot;https://developer.apple.com/documentation/http_live_streaming&quot; rel=&quot;noopener&quot;&gt;HLS&lt;/a&gt;). Shaka
Packager supports key security features: common encryption and Widevine digital
rights management (DRM). It can also handle live-streaming, and video-on-demand.&lt;/p&gt;
&lt;p&gt;Despite what it says on the package, this utility is for more than C++
developers. You can use it as both a library for building media software and as
a command-line utility for preparing media files for web playback. It&#39;s the
latter capacity that&#39;s useful for us here. In fact, for web media creators,
Shaka Packager is the only way to do some tasks without spending money on
expensive commercial applications.&lt;/p&gt;
&lt;p&gt;Here&#39;s the basic pattern for a Shaka Packager command:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager stream_descriptor &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;stream_descriptor-2 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;stream_descriptor-n&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;flags&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This isn&#39;t quite what you get if you type &lt;code&gt;packager -help&lt;/code&gt;. This example is
easier to reason about, and this reflects the examples in the
&lt;a href=&quot;https://google.github.io/shaka-packager/html/&quot; rel=&quot;noopener&quot;&gt;Shaka Packager documentation&lt;/a&gt;. Note that there are multiple &lt;code&gt;stream_descriptor&lt;/code&gt;
items in the pattern. Though we don&#39;t show it, you could manipulate the video
and audio streams of a file separately in a single command.&lt;/p&gt;
&lt;p&gt;Compare this basic pattern with a simple use that displays file characteristics.
In the example, We&#39;ve lined up equivalent parts.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;packager stream_descriptor &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;stream_descriptor-n&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;flags&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;packager &lt;span class=&quot;token assign-left variable&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;glocken.mp4                       --dump_stream_info&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The command outputs this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;File &lt;span class=&quot;token string&quot;&gt;&quot;glocken.mp4&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;br /&gt;Found &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;.&lt;br /&gt;Stream &lt;span class=&quot;token punctuation&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; type: Video&lt;br /&gt; codec_string: avc1.640028&lt;br /&gt; time_scale: &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;&lt;br /&gt; duration: &lt;span class=&quot;token number&quot;&gt;300300&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt; is_encrypted: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt; codec: H264&lt;br /&gt; width: &lt;span class=&quot;token number&quot;&gt;1920&lt;/span&gt;&lt;br /&gt; height: &lt;span class=&quot;token number&quot;&gt;1080&lt;/span&gt;&lt;br /&gt; pixel_aspect_ratio: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;:1&lt;br /&gt; trick_play_factor: &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt; nalu_length_size: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Stream &lt;span class=&quot;token punctuation&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; type: Audio&lt;br /&gt; codec_string: mp4a.40.2&lt;br /&gt; time_scale: &lt;span class=&quot;token number&quot;&gt;48000&lt;/span&gt;&lt;br /&gt; duration: &lt;span class=&quot;token number&quot;&gt;481280&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt; seconds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt; is_encrypted: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt; codec: AAC&lt;br /&gt; sample_bits: &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;br /&gt; num_channels: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt; sampling_frequency: &lt;span class=&quot;token number&quot;&gt;48000&lt;/span&gt;&lt;br /&gt; language: eng&lt;br /&gt; seek_preroll_ns: &lt;span class=&quot;token number&quot;&gt;20833&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Look for the characteristics discussed in &lt;a href=&quot;https://web.dev/media-file-basics/&quot;&gt;Media file basics&lt;/a&gt;
and notice a few things. The height and width are correct for full HD, and the
audio and video codecs are among the preferred codecs for their container types,
AAC for audio and H264 for video. Notice also that streams are identified with
numbers. These are useful for operations that manipulate the audio and video
separately.&lt;/p&gt;
&lt;p&gt;Notice that the output above doesn&#39;t show the bitrate. Despite what&#39;s missing,
this output is easier to read, so we use it whenever we can. When we need
information that Shaka Packager can&#39;t get, such as the bitrate, we use FFmpeg.&lt;/p&gt;
&lt;h2 id=&quot;ffmpeg&quot;&gt;FFmpeg &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-application-basics/#ffmpeg&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ffmpeg.org/download.html&quot; rel=&quot;noopener&quot;&gt;FFmpeg&lt;/a&gt; is also a free application for recording, converting, and streaming
media files. Its capabilities aren&#39;t better or worse than Shaka Packager&#39;s.
They&#39;re just different.&lt;/p&gt;
&lt;p&gt;The basic pattern for an FFmpeg command looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ffmpeg &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;GeneralOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;InputFileOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; -i input &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;OutputFileOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; output&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Like Shaka Packager this application can handle multiple streams. Some of its
options are used in multiple locations and manipulate the file output differently
depending on where they are in the command. Be aware of this as you
look at &lt;a href=&quot;https://stackoverflow.com/questions/tagged/ffmpeg&quot; rel=&quot;noopener&quot;&gt;FFmpeg questions on Stack Overflow&lt;/a&gt; and similar sites.&lt;/p&gt;
&lt;p&gt;We&#39;ll again compare the basic pattern to the example for displaying file
characteristics.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;    ffmpeg &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;GeneralOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;InputFileOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; -i input        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;OutputFileOptions&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; output&lt;br /&gt;&lt;br /&gt;    ffmpeg                                     -i glocken.mp4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In addition to the information we requested, this also prints an error message
as shown in the example below. That&#39;s because this is technically an incorrect
usage of FFmpeg. We use it because it displays information we care about.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Input &lt;span class=&quot;token comment&quot;&gt;#0, mov,mp4,m4a,3gp,3g2,mj2, from &#39;glocken.mp4&#39;:&lt;/span&gt;&lt;br /&gt;  Metadata:&lt;br /&gt;    major_brand     &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; isom&lt;br /&gt;    minor_version   &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt;&lt;br /&gt;    compatible_brands: isomiso2avc1mp41&lt;br /&gt;    encoder         &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; Lavf58.17.100&lt;br /&gt;  Duration: 00:01:47.53, start: &lt;span class=&quot;token number&quot;&gt;0.000000&lt;/span&gt;, bitrate: &lt;span class=&quot;token number&quot;&gt;10715&lt;/span&gt; kb/s&lt;br /&gt;    Stream &lt;span class=&quot;token comment&quot;&gt;#0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc), 1920x1080, 10579 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)&lt;/span&gt;&lt;br /&gt;    Metadata:&lt;br /&gt;      handler_name    &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; VideoHandler&lt;br /&gt;    Stream &lt;span class=&quot;token comment&quot;&gt;#0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)&lt;/span&gt;&lt;br /&gt;    Metadata:&lt;br /&gt;      handler_name    &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; SoundHandler&lt;br /&gt;At least one output &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; must be specified&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;installing-applications-with-docker&quot;&gt;Installing applications with Docker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-application-basics/#installing-applications-with-docker&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you plan to follow along and try our commands you could either install the
required tools manually, or take the easy path and use &lt;a href=&quot;https://www.docker.com/whatisdocker&quot; rel=&quot;noopener&quot;&gt;Docker&lt;/a&gt;. We suggest using
Docker, because this is going to save you a lot of time. On top of that we&#39;ve
provided the instructions to get you set up quickly.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start by creating a new directory somewhere on your computer named &lt;code&gt;media-tools&lt;/code&gt;;
you can use whatever name you like, just note that the following instructions
assume you are using &lt;code&gt;media-tools&lt;/code&gt; as the directory name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;media&lt;/code&gt; directory inside of &lt;code&gt;media-tools&lt;/code&gt;.
This will keep your &lt;code&gt;media&lt;/code&gt; directory out of the build context. This is important
because &lt;code&gt;media&lt;/code&gt; is where files are stored that we plan to do operations on, and
some of them could be quite large. Putting the &lt;code&gt;Dockerfile&lt;/code&gt; directly in
&lt;code&gt;media-tools&lt;/code&gt; would slow down building the image if you ever rebuild it down the
road—perhaps to change the versions installed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create &lt;code&gt;/media-tools/docker/Dockerfile&lt;/code&gt;, and add the following build instructions:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; google/shaka-packager:release-v2.4.3 &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; packager&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; jrottenberg/ffmpeg:4.3.2-alpine38&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; &lt;span class=&quot;token options&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;packager&lt;/span&gt;&lt;/span&gt; /usr/bin /usr/bin&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt;  [&lt;span class=&quot;token string&quot;&gt;&quot;sh&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the image:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; build -t media-tools ./docker&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the image as an interactive shell. On Linux:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run -w /media -v &lt;span class=&quot;token variable&quot;&gt;${&lt;span class=&quot;token environment constant&quot;&gt;PWD&lt;/span&gt;}&lt;/span&gt;/media:/media -it --rm media-tools&lt;br /&gt;/media &lt;span class=&quot;token comment&quot;&gt;#&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;On Windows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run -w /media -v %cd%/media:/media -it --rm media-tools&lt;br /&gt;/media &lt;span class=&quot;token comment&quot;&gt;#&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &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; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Make sure you run the previous &lt;code&gt;docker&lt;/code&gt; commands from inside the &lt;code&gt;media-tools&lt;/code&gt; directory. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;While running the image you can check versions for both FFmpeg and Shaka Packager
to validate everything was successful by running &lt;code&gt;ffmpeg -version&lt;/code&gt; and
&lt;code&gt;packager --version&lt;/code&gt;. The output should look like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# ffmpeg -version&lt;/span&gt;&lt;br /&gt;ffmpeg version &lt;span class=&quot;token number&quot;&gt;4.3&lt;/span&gt;.2 Copyright &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;-2021 the FFmpeg developers&lt;br /&gt;built with gcc &lt;span class=&quot;token number&quot;&gt;6.4&lt;/span&gt;.0 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Alpine &lt;span class=&quot;token number&quot;&gt;6.4&lt;/span&gt;.0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;configuration: --disable-debug --disable-doc --disable-ffplay --enable-shared --enable-avresample --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-gpl --enable-libass --enable-fontconfig --enable-libfreetype --enable-libvidstab --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libxcb --enable-libx265 --enable-libxvid --enable-libx264 --enable-nonfree --enable-openssl --enable-libfdk_aac --enable-postproc --enable-small --enable-version3 --enable-libbluray --enable-libzmq --extra-libs&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;-ldl --prefix&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/opt/ffmpeg --enable-libopenjpeg --enable-libkvazaar --enable-libaom --extra-libs&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;-lpthread --enable-libsrt --enable-libaribb24 --extra-cflags&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;-I/opt/ffmpeg/include --extra-ldflags&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;-L/opt/ffmpeg/lib&lt;br /&gt;libavutil      &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;51.100&lt;/span&gt; / &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;51.100&lt;/span&gt;&lt;br /&gt;libavcodec     &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;91.100&lt;/span&gt; / &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;91.100&lt;/span&gt;&lt;br /&gt;libavformat    &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;45.100&lt;/span&gt; / &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;45.100&lt;/span&gt;&lt;br /&gt;libavdevice    &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;10.100&lt;/span&gt; / &lt;span class=&quot;token number&quot;&gt;58&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;10.100&lt;/span&gt;&lt;br /&gt;libavfilter     &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;85.100&lt;/span&gt; /  &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;. &lt;span class=&quot;token number&quot;&gt;85.100&lt;/span&gt;&lt;br /&gt;libavresample   &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; /  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;libswscale      &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt; /  &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt;&lt;br /&gt;libswresample   &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt; /  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt;&lt;br /&gt;libpostproc    &lt;span class=&quot;token number&quot;&gt;55&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt; / &lt;span class=&quot;token number&quot;&gt;55&lt;/span&gt;.  &lt;span class=&quot;token number&quot;&gt;7.100&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/media &lt;span class=&quot;token comment&quot;&gt;# packager --version&lt;/span&gt;&lt;br /&gt;packager version v2.4.3-dd9870075f-release&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now that you&#39;ve tried your hand at using Shaka Packager and FFmpeg, you can continue
learning the basic concepts, next up &lt;a href=&quot;https://web.dev/media-streaming-basics/&quot;&gt;Media streaming basics&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Media Source Extensions</title>
    <link href="https://web.dev/media-mse-basics/"/>
    <updated>2017-02-08T00:00:00Z</updated>
    <id>https://web.dev/media-mse-basics/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/media-source/&quot; rel=&quot;noopener&quot;&gt;Media Source Extensions (MSE)&lt;/a&gt;
is a JavaScript API that lets you build streams for playback from segments of
audio or video. Although not covered in this article, understanding MSE is
needed if you want to embed videos in your site that do things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adaptive streaming, which is another way of saying adapting to device
capabilities and network conditions&lt;/li&gt;
&lt;li&gt;Adaptive splicing, such as ad insertion&lt;/li&gt;
&lt;li&gt;Time shifting&lt;/li&gt;
&lt;li&gt;Control of performance and download size&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Basic MSE data flow&quot; decoding=&quot;async&quot; height=&quot;250&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 305px) 305px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/SMMXsJT0g2ivhkxfB9Rc.png?auto=format&amp;w=610 610w&quot; width=&quot;305&quot; /&gt;
  &lt;figcaption&gt;&lt;b&gt;Figure 1&lt;/b&gt;: Basic MSE data flow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can almost think of MSE as a chain. As illustrated in the figure, between
the downloaded file and the media elements are several layers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element to play the media.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;MediaSource&lt;/code&gt; instance with a &lt;code&gt;SourceBuffer&lt;/code&gt; to feed the media element.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;fetch()&lt;/code&gt; or XHR call to retrieve media data in a &lt;code&gt;Response&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;A call to &lt;code&gt;Response.arrayBuffer()&lt;/code&gt; to feed &lt;code&gt;MediaSource.SourceBuffer&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, the chain looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; vidElement &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;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;window&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;  &lt;span class=&quot;token keyword&quot;&gt;var&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;  vidElement&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;span class=&quot;token string&quot;&gt;&#39;sourceopen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sourceOpen&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The Media Source Extensions API is not supported.&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&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;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &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;mime&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;var&lt;/span&gt; videoUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;droid.webm&#39;&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;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoUrl&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 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;response&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;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;    &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;arrayBuffer&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;      sourceBuffer&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;updateend&#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;e&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;sourceBuffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updating &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;open&#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;          mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endOfStream&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;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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      sourceBuffer&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;arrayBuffer&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;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;If you can sort things out from the explanations so far, feel free to stop
reading now. If you want a more detailed explanation, then please keep reading.
I&#39;m going to walk through this chain by building a basic MSE example. Each of
the build steps will add code to the previous step.&lt;/p&gt;
&lt;h2 id=&quot;a-note-about-clarity&quot;&gt;A note about clarity &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#a-note-about-clarity&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Will this article tell you everything you need to know about playing media on a
web page? No, it&#39;s only intended to help you understand more complicated code
you might find elsewhere. For the sake of clarity, this document simplifies and
excludes many things. We think we can get away with this because we also
recommend using a library such as &lt;a href=&quot;https://shaka-player-demo.appspot.com/demo/&quot; rel=&quot;noopener&quot;&gt;Google&#39;s Shaka Player&lt;/a&gt;.
I will note throughout where I&#39;m deliberately simplifying.&lt;/p&gt;
&lt;h3 id=&quot;a-few-things-not-covered&quot;&gt;A few things not covered &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#a-few-things-not-covered&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here, in no particular order, are a few things I won&#39;t cover.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Playback controls. We get those for free by virtue of using the HTML5
&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements.&lt;/li&gt;
&lt;li&gt;Error handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;for-use-in-production-environments&quot;&gt;For use in production environments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#for-use-in-production-environments&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here are some things I&#39;d recommend in a production usage of MSE related APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Before making calls on these APIs, handle any error events or API
exceptions, and check &lt;code&gt;HTMLMediaElement.readyState&lt;/code&gt; and
&lt;code&gt;MediaSource.readyState&lt;/code&gt;. These values can change before associated events are
delivered.&lt;/li&gt;
&lt;li&gt;Make sure previous &lt;code&gt;appendBuffer()&lt;/code&gt; and &lt;code&gt;remove()&lt;/code&gt; calls are not still in
progress by checking the &lt;code&gt;SourceBuffer.updating&lt;/code&gt; boolean value before
updating the &lt;code&gt;SourceBuffer&lt;/code&gt;&#39;s &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;timestampOffset&lt;/code&gt;, &lt;code&gt;appendWindowStart&lt;/code&gt;,
&lt;code&gt;appendWindowEnd&lt;/code&gt;, or calling &lt;code&gt;appendBuffer()&lt;/code&gt; or &lt;code&gt;remove()&lt;/code&gt; on the
&lt;code&gt;SourceBuffer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For all &lt;code&gt;SourceBuffer&lt;/code&gt; instances added to your &lt;code&gt;MediaSource&lt;/code&gt;, ensure none of
their &lt;code&gt;updating&lt;/code&gt; values are true before calling &lt;code&gt;MediaSource.endOfStream()&lt;/code&gt;
or updating the &lt;code&gt;MediaSource.duration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;MediaSource.readyState&lt;/code&gt; value is &lt;code&gt;ended&lt;/code&gt;, calls like &lt;code&gt;appendBuffer()&lt;/code&gt; and
&lt;code&gt;remove()&lt;/code&gt;, or setting &lt;code&gt;SourceBuffer.mode&lt;/code&gt; or &lt;code&gt;SourceBuffer.timestampOffset&lt;/code&gt;
will cause this value to transition to &lt;code&gt;open&lt;/code&gt;. This means you should be
prepared to handle multiple &lt;code&gt;sourceopen&lt;/code&gt; events.&lt;/li&gt;
&lt;li&gt;When handling &lt;code&gt;HTMLMediaElement error&lt;/code&gt; events, the contents of
&lt;a href=&quot;https://googlechrome.github.io/samples/media/error-message.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;MediaError.message&lt;/code&gt;&lt;/a&gt;
can be useful to determine the root cause of the failure, especially for
errors that are hard to reproduce in test environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;attach-a-mediasource-instance-to-a-media-element&quot;&gt;Attach a MediaSource instance to a media element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#attach-a-mediasource-instance-to-a-media-element&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As with many things in web development these days, you start with feature
detection. Next, get a media element, either an &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element.
Finally create an instance of &lt;code&gt;MediaSource&lt;/code&gt;. It gets turned into a URL and passed
to the media element&#39;s source attribute.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; vidElement &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;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;window&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;  &lt;span class=&quot;token keyword&quot;&gt;var&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;  vidElement&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;  &lt;span class=&quot;token comment&quot;&gt;// Is the MediaSource instance ready?&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The Media Source Extensions API is not supported.&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Note: Each incomplete code example contains a comment that gives you a hint of what I&#39;ll add in the next step. In the example above, this comment says, &#39;Is the MediaSource instance ready?&#39;, which matches the title of the next section. &lt;/div&gt;&lt;/aside&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A source attribute as a blob&quot; decoding=&quot;async&quot; height=&quot;251&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 433px) 433px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/C47gYyWYVMMhDmtYSLOWazuyePF2/ue5eqd9gCmKRnBzs36hn.png?auto=format&amp;w=866 866w&quot; width=&quot;433&quot; /&gt;
  &lt;figcaption&gt;&lt;b&gt;Figure 1&lt;/b&gt;: A source attribute as a blob&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That a &lt;code&gt;MediaSource&lt;/code&gt; object can be passed to a &lt;code&gt;src&lt;/code&gt; attribute might seem a bit
odd. They&#39;re usually strings, but
&lt;a href=&quot;https://www.w3.org/TR/FileAPI/#url&quot; rel=&quot;noopener&quot;&gt;they can also be blobs&lt;/a&gt;.
If you inspect a page with embedded media and examine its media element, you&#39;ll
see what I mean.&lt;/p&gt;
&lt;h3 id=&quot;is-the-mediasource-instance-ready&quot;&gt;Is the MediaSource instance ready? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#is-the-mediasource-instance-ready&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;URL.createObjectURL()&lt;/code&gt; is itself synchronous; however, it processes the
attachment asynchronously. This causes a slight delay before you can do anything
with the &lt;code&gt;MediaSource&lt;/code&gt; instance. Fortunately, there are ways to test for this.
The simplest way is with a &lt;code&gt;MediaSource&lt;/code&gt; property called &lt;code&gt;readyState&lt;/code&gt;. The
&lt;code&gt;readyState&lt;/code&gt; property describes the relation between a &lt;code&gt;MediaSource&lt;/code&gt; instance and
a media element. It can have one of the following values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;closed&lt;/code&gt; - The &lt;code&gt;MediaSource&lt;/code&gt; instance is not attached to a media element.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; - The &lt;code&gt;MediaSource&lt;/code&gt; instance is attached to a media element and is
ready to receive data or is receiving data.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ended&lt;/code&gt; - The &lt;code&gt;MediaSource&lt;/code&gt; instance is attached to a media element and all of
its data has been passed to that element.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Querying these options directly can negatively affect performance. Fortunately,
&lt;code&gt;MediaSource&lt;/code&gt; also fires events when &lt;code&gt;readyState&lt;/code&gt; changes, specifically
&lt;code&gt;sourceopen&lt;/code&gt;, &lt;code&gt;sourceclosed&lt;/code&gt;, &lt;code&gt;sourceended&lt;/code&gt;. For the example I&#39;m building, I&#39;m
going to use the &lt;code&gt;sourceopen&lt;/code&gt; event to tell me when to fetch and buffer the
video.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; vidElement &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;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;window&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;  &lt;span class=&quot;token keyword&quot;&gt;var&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;  vidElement&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;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&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;span class=&quot;token string&quot;&gt;&#39;sourceopen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sourceOpen&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;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;The Media Source Extensions API is not supported.&quot;&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;// Create a SourceBuffer and get the media file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Notice that I&#39;ve also called &lt;code&gt;revokeObjectURL()&lt;/code&gt;. I know this seems premature,
but I can do this any time after the media element&#39;s &lt;code&gt;src&lt;/code&gt; attribute is
connected to a &lt;code&gt;MediaSource&lt;/code&gt; instance. Calling this method doesn&#39;t destroy any
objects. It &lt;em&gt;does&lt;/em&gt; allow the platform to handle garbage collection at an
appropriate time, which is why I&#39;m calling it immediately.&lt;/p&gt;
&lt;h3 id=&quot;create-a-sourcebuffer&quot;&gt;Create a SourceBuffer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#create-a-sourcebuffer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now it&#39;s time to create the &lt;code&gt;SourceBuffer&lt;/code&gt;, which is the object that actually
does the work of shuttling data between media sources and media elements. A
&lt;code&gt;SourceBuffer&lt;/code&gt; has to be specific to the type of media file you&#39;re loading.&lt;/p&gt;
&lt;p&gt;In practice you can do this by calling &lt;code&gt;addSourceBuffer()&lt;/code&gt; with the appropriate
value. Notice that in the example below the mime type string contains a mime
type and &lt;em&gt;two&lt;/em&gt; codecs. This is a mime string for a video file, but it uses
separate codecs for the video and audio portions of the file.&lt;/p&gt;
&lt;p&gt;Version 1 of the MSE spec allows user agents to differ on whether to require
both a mime type and a codec. Some user agents don&#39;t require, but do allow just
the mime type. Some user agents, Chrome for example, require a codec for mime
types that don&#39;t self-describe their codecs. Rather than trying to sort all this
out, it&#39;s better to just include both.&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; Note: For simplicity, the example only shows a single segment of media though in practice, MSE only makes sense for scenarios with multiple segments. &lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; vidElement &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;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;window&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;  &lt;span class=&quot;token keyword&quot;&gt;var&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;  vidElement&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;span class=&quot;token string&quot;&gt;&#39;sourceopen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sourceOpen&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The Media Source Extensions API is not supported.&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&quot;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// e.target refers to&lt;/span&gt;&lt;br /&gt;    the mediaSource instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Store it in a variable so it can be used in a&lt;/span&gt;&lt;br /&gt;    closure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &lt;span class=&quot;token operator&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;addSourceBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mime&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 comment&quot;&gt;// Fetch and process the video.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;get-the-media-file&quot;&gt;Get the media file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#get-the-media-file&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you do an internet search for MSE examples, you&#39;ll find plenty that retrieve
media files using XHR. To be more cutting edge,
I&#39;m going to use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/GlobalFetch&quot; rel=&quot;noopener&quot;&gt;Fetch&lt;/a&gt;
API and the &lt;a href=&quot;https://web.dev/web/fundamentals/getting-started/primers/promises&quot;&gt;Promise&lt;/a&gt; it
returns. If you&#39;re trying to do this in Safari, it won&#39;t work without a
&lt;code&gt;fetch()&lt;/code&gt; polyfill.&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; Note: Just to help things fit on the screen, from here to the end I&#39;m only going to show part of the example we&#39;re building. If you want to see it in context, &lt;a href=&quot;https://web.dev/media-mse-basics/#the-final-version&quot;&gt;jump to the end&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&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;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &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;mime&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;var&lt;/span&gt; videoUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;droid.webm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoUrl&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;response&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 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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Process the response object.&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;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;A production quality player would have the same file in multiple versions to
support different browsers. It could use separate files for audio and video to
allow audio to be selected based on language settings.&lt;/p&gt;
&lt;p&gt;Real world code would also have multiple copies of media files at different
resolutions so that it could adapt to different device capabilities and network
conditions. Such an application is able to load and play videos in chunks either
using range requests or segments. This allows for adaption to network conditions
&lt;em&gt;while media are playing&lt;/em&gt;. You may have heard the terms DASH or HLS, which are
two methods of accomplishing this. A full discussion of this topic is beyond the
scope of this introduction.&lt;/p&gt;
&lt;h3 id=&quot;process-the-response-object&quot;&gt;Process the response object &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#process-the-response-object&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code looks almost done, but the media doesn&#39;t play. We need to get media
data from the &lt;code&gt;Response&lt;/code&gt; object to the &lt;code&gt;SourceBuffer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The typical way to pass data from the response object to the &lt;code&gt;MediaSource&lt;/code&gt;
instance is to get an &lt;code&gt;ArrayBuffer&lt;/code&gt; from the response object and pass it to the
&lt;code&gt;SourceBuffer&lt;/code&gt;. Start by calling &lt;code&gt;response.arrayBuffer()&lt;/code&gt;, which returns a
promise to the buffer. In my code, I&#39;ve passed this promise to a second &lt;code&gt;then()&lt;/code&gt;
clause where I append it to the &lt;code&gt;SourceBuffer&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&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;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &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;mime&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;var&lt;/span&gt; videoUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;droid.webm&#39;&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;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoUrl&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 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;response&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&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;span class=&quot;token function&quot;&gt;arrayBuffer&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&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 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;arrayBuffer&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;      sourceBuffer&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;arrayBuffer&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&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;h4 id=&quot;call-endofstream&quot;&gt;Call endOfStream() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#call-endofstream&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;After all &lt;code&gt;ArrayBuffers&lt;/code&gt; are appended, and no further media data is expected, call
&lt;code&gt;MediaSource.endOfStream()&lt;/code&gt;. This will change &lt;code&gt;MediaSource.readyState&lt;/code&gt; to
&lt;code&gt;ended&lt;/code&gt; and fire the &lt;code&gt;sourceended&lt;/code&gt; event.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&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;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &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;mime&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;var&lt;/span&gt; videoUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;droid.webm&#39;&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;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoUrl&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 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;response&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;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;    &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;arrayBuffer&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 operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;sourceBuffer&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;updateend&#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;e&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;sourceBuffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updating &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;open&#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;          mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endOfStream&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;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 punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      sourceBuffer&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;arrayBuffer&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;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;h4 id=&quot;the-final-version&quot;&gt;The final version &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#the-final-version&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Here&#39;s the complete code example. I hope you have learned something about Media
Source Extensions.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; vidElement &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;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;window&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;  &lt;span class=&quot;token keyword&quot;&gt;var&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;  vidElement&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;span class=&quot;token string&quot;&gt;&#39;sourceopen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sourceOpen&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The Media Source Extensions API is not supported.&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sourceOpen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&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 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;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;vidElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;var&lt;/span&gt; mime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;video/webm; codecs=&quot;opus, vp09.00.10.08&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;var&lt;/span&gt; mediaSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; sourceBuffer &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;mime&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;var&lt;/span&gt; videoUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;droid.webm&#39;&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;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoUrl&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 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;response&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;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;    &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;arrayBuffer&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;      sourceBuffer&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;updateend&#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;e&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;sourceBuffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updating &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;open&#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;          mediaSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endOfStream&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;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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      sourceBuffer&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;arrayBuffer&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;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;h4 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/media-mse-basics/#feedback&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
</content>
    <author>
      <name>Joe Medley</name>
    </author><author>
      <name>François Beaufort</name>
    </author>
  </entry>
  
  <entry>
    <title>ResizeObserver: it’s like document.onresize for elements</title>
    <link href="https://web.dev/resize-observer/"/>
    <updated>2016-10-07T00:00:00Z</updated>
    <id>https://web.dev/resize-observer/</id>
    <content type="html" mode="escaped">&lt;p&gt;Before &lt;code&gt;ResizeObserver&lt;/code&gt;, you had to attach a listener to the document&#39;s &lt;code&gt;resize&lt;/code&gt;
event to get notified of any change of the viewport&#39;s dimensions. In the event
handler, you would then have to figure out which elements have been affected by
that change and call a specific routine to react appropriately. If you needed
the new dimensions of an element after a resize, you had to call
&lt;code&gt;getBoundingClientRect()&lt;/code&gt; or &lt;code&gt;getComputedStyle()&lt;/code&gt;, which can cause layout
thrashing if you don&#39;t take care of batching &lt;em&gt;all&lt;/em&gt; your reads and &lt;em&gt;all&lt;/em&gt; your
writes.&lt;/p&gt;
&lt;p&gt;This didn&#39;t even cover cases where elements change their size without the main
window having been resized. For example, appending new children, setting an
element&#39;s &lt;code&gt;display&lt;/code&gt; style to &lt;code&gt;none&lt;/code&gt;, or similar actions can change the size of
an element, its siblings, or its ancestors.&lt;/p&gt;
&lt;p&gt;This is why &lt;code&gt;ResizeObserver&lt;/code&gt; is a useful primitive. It reacts to changes in
size of any of the observed elements, independent of what caused the change.
It provides access to the new size of the observed elements too.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 64, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      64
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 69, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      69
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/ResizeObserver#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;api&quot;&gt;API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All the APIs with the &lt;code&gt;Observer&lt;/code&gt; suffix we mentioned above share a simple API
design. &lt;code&gt;ResizeObserver&lt;/code&gt; is no exception. You create a &lt;code&gt;ResizeObserver&lt;/code&gt; object
and pass a callback to the constructor. The callback is passed an array of
&lt;code&gt;ResizeObserverEntry&lt;/code&gt; objects—one entry per observed element—which
contains the new dimensions for the element.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; ro &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;ResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&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;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&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; cr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentRect&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&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;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&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;Element size: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px x &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&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;Element padding: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px ; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&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;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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Observe one or multiple elements&lt;/span&gt;&lt;br /&gt;ro&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;someElement&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;h2 id=&quot;some-details&quot;&gt;Some details &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#some-details&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;what-is-being-reported&quot;&gt;What is being reported? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#what-is-being-reported&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Generally, a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ResizeObserverEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ResizeObserverEntry&lt;/code&gt;&lt;/a&gt;
reports the content box of an element through a property called
&lt;code&gt;contentRect&lt;/code&gt;, which returns a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DOMRectReadOnly&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DOMRectReadOnly&lt;/code&gt;&lt;/a&gt;
object. The content box is the box in which content can be placed. It is
the border box minus the padding.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A diagram of the CSS box model.&quot; decoding=&quot;async&quot; height=&quot;562&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 727px) 727px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CKxpe8LNq2CMPFdtLtVK.png?auto=format&amp;w=1454 1454w&quot; width=&quot;727&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;It&#39;s important to note that while &lt;code&gt;ResizeObserver&lt;/code&gt; &lt;em&gt;reports&lt;/em&gt; both the dimensions
of the &lt;code&gt;contentRect&lt;/code&gt; and the padding, it only &lt;em&gt;watches&lt;/em&gt; the &lt;code&gt;contentRect&lt;/code&gt;.
&lt;em&gt;Don&#39;t&lt;/em&gt; confuse &lt;code&gt;contentRect&lt;/code&gt; with the bounding box of the element. The bounding
box, as reported by &lt;code&gt;getBoundingClientRect()&lt;/code&gt;, is the box that contains the
entire element and its descendants. SVGs are an exception to the rule, where
&lt;code&gt;ResizeObserver&lt;/code&gt; will report the dimensions of the bounding box.&lt;/p&gt;
&lt;p&gt;As of Chrome 84, &lt;code&gt;ResizeObserverEntry&lt;/code&gt; has three new properties to provide more
detailed information. Each of these properties returns a &lt;code&gt;ResizeObserverSize&lt;/code&gt;
object containing a &lt;code&gt;blockSize&lt;/code&gt; property and an &lt;code&gt;inlineSize&lt;/code&gt; property. This
information is about the observered element at the time the callback is invoked.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;borderBoxSize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contentBoxSize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;devicePixelContentBoxSize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these items return read-only arrays because in the future it&#39;s hoped that
they can support elements that have multiple fragments, which occur in
multi-column scenarios. For now, these arrays will only contain one element.&lt;/p&gt;
&lt;p&gt;Platform support for these properties is limited, but &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ResizeObserverEntry#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;Firefox already
supports&lt;/a&gt;
the first two.&lt;/p&gt;
&lt;h3 id=&quot;when-is-it-being-reported&quot;&gt;When is it being reported? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#when-is-it-being-reported&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The spec proscribes that &lt;code&gt;ResizeObserver&lt;/code&gt; should process all resize events
before paint and after layout. This makes the callback of a &lt;code&gt;ResizeObserver&lt;/code&gt; the
ideal place to make changes to your page&#39;s layout. Because &lt;code&gt;ResizeObserver&lt;/code&gt;
processing happens between layout and paint, doing so will only invalidate
layout, not paint.&lt;/p&gt;
&lt;h3 id=&quot;gotcha&quot;&gt;Gotcha &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#gotcha&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You might be asking yourself: what happens if I change the size of an observed
element inside the callback to &lt;code&gt;ResizeObserver&lt;/code&gt;? The answer is: you will trigger
another call to the callback right away. Fortunately, &lt;code&gt;ResizeObserver&lt;/code&gt; has a
mechanism to avoid infinite callback loops and cyclic dependencies. Changes will
only be processed in the same frame if the resized element is deeper in the DOM
tree than the &lt;em&gt;shallowest&lt;/em&gt; element processed in the previous callback.
Otherwise, they&#39;ll get deferred to the next frame.&lt;/p&gt;
&lt;h2 id=&quot;application&quot;&gt;Application &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#application&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One thing that &lt;code&gt;ResizeObserver&lt;/code&gt; allows you to do is to implement per-element
media queries. By observing elements, you can imperatively define your
design breakpoints and change an element&#39;s styles. In the following
&lt;a href=&quot;https://googlechrome.github.io/samples/resizeobserver/&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;, the second box
will change its border radius according to its width.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/webfundamentals-assets/resizeobserver/elem-mq_vp8.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/webfundamentals-assets/resizeobserver/elem-mq_x264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ro &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;ResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&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;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&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;    entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;borderRadius &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;        Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&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 number&quot;&gt;250&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&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;px&#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;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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Only observe the second box&lt;/span&gt;&lt;br /&gt;ro&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&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;.box:nth-child(2)&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Another interesting example to look at is a chat window. The problem that arises
in a typical top-to-bottom conversation layout is scroll positioning. To avoid
confusing the user, it is helpful if the window sticks to the bottom of the
conversation, where the newest messages appear. Additionally, any kind of layout
change (think of a phone going from landscape to portrait or vice versa) should
achieve the same.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ResizeObserver&lt;/code&gt; allows you to write a &lt;em&gt;single&lt;/em&gt; piece of code that takes care of
&lt;em&gt;both&lt;/em&gt; scenarios. Resizing the window is an event that a &lt;code&gt;ResizeObserver&lt;/code&gt; can
capture by definition, but calling &lt;code&gt;appendChild()&lt;/code&gt; also resizes that element
(unless&lt;code&gt;overflow: hidden&lt;/code&gt; is set), because it needs to make space for the new
elements. With this in mind, it takes very few lines to achieve the desired
effect:&lt;/p&gt;
&lt;figure&gt;
 &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
   &lt;source src=&quot;https://storage.googleapis.com/webfundamentals-assets/resizeobserver/chat_vp8.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
   &lt;source src=&quot;https://storage.googleapis.com/webfundamentals-assets/resizeobserver/chat_x264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
 &lt;/video&gt;
&lt;/figure&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ro &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;ResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&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;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scrollingElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scrollTop &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scrollingElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scrollHeight&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Observe the scrollingElement for when the window gets resized&lt;/span&gt;&lt;br /&gt;ro&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scrollingElement&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;// Observe the timeline to process new messages&lt;/span&gt;&lt;br /&gt;ro&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeline&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;Pretty neat, huh?&lt;/p&gt;
&lt;p&gt;From here, I could add more code to handle the case where the user has scrolled
up manually and wants scrolling to stick to &lt;em&gt;that&lt;/em&gt; message when a new message
comes in.&lt;/p&gt;
&lt;p&gt;Another use case is for any kind of custom element that is doing its own layout.
Until &lt;code&gt;ResizeObserver&lt;/code&gt;, there was no reliable way to get notified when its
dimensions change so its children can be laid out again.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resize-observer/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ResizeObserver&lt;/code&gt; is available in &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ResizeObserver#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;most major
browsers&lt;/a&gt;.
In some cases, that availability is quite recent. There are &lt;a href=&quot;https://github.com/WICG/ResizeObserver/issues/3&quot; rel=&quot;noopener&quot;&gt;some polyfills
available&lt;/a&gt; but it is not
possible to completely duplicate the functionality of &lt;code&gt;ResizeObserver&lt;/code&gt;. Current
implementations either rely on polling or on adding sentinel elements to the
DOM. The former will drain your battery on mobile by keeping the CPU busy while
the latter modifies your DOM and might mess up styling and other DOM-reliant
code.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@markusspiske?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Markus
Spiske&lt;/a&gt;
on
&lt;a href=&quot;https://unsplash.com/s/photos/observe-growth?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Surma</name>
    </author><author>
      <name>Joe Medley</name>
    </author>
  </entry>
  
  <entry>
    <title>The &lt;video&gt; and &lt;source&gt; tags</title>
    <link href="https://web.dev/video-and-source-tags/"/>
    <updated>2014-02-15T00:00:00Z</updated>
    <id>https://web.dev/video-and-source-tags/</id>
    <content type="html" mode="escaped">&lt;p&gt;You&#39;ve properly &lt;a href=&quot;https://web.dev/prepare-media/&quot;&gt;prepared a video file&lt;/a&gt; for the web. You&#39;ve given it correct
dimensions and the correct resolution. You&#39;ve even created separate WebM and
MP4 files for different browsers.&lt;/p&gt;
&lt;p&gt;For anyone to see your video, you still need to add it to a web page. Doing so
properly requires adding two HTML elements: the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/video&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;&lt;/a&gt; element and the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/source&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;&lt;/a&gt; element. In addition to basics about these tags, this
article explains attributes you should add to those tags to craft a good
user experience.&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; You always have the option of uploading your file to [YouTube] or [Vimeo]. In many cases, this is preferable to the procedure described here. Those services handle formatting and filetype conversion for you, as well as provide the means to embed a video in your web page. If you need to manage this yourself, read on. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;specify-a-single-file&quot;&gt;Specify a single file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#specify-a-single-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Although it&#39;s not recommended, you can use the video element by itself. Always
use the &lt;code&gt;type&lt;/code&gt; attribute as shown below. The browser uses this to determine if
it can play the provided video file. If it can&#39;t, the enclosed text displays.&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;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;chrome.webm&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;video/webm&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Your browser cannot play the provided video file.&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;p&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;h3 id=&quot;specify-multiple-file-formats&quot;&gt;Specify multiple file formats &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#specify-multiple-file-formats&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Recall from &lt;a href=&quot;https://web.dev/media-file-basics/&quot;&gt;Media file basics&lt;/a&gt; that not all browsers support the same video
formats. The &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element lets you specify multiple formats as a fallback
in case the user&#39;s browser doesn&#39;t support one of them.&lt;/p&gt;
&lt;p&gt;The example below produces the embedded video that is used as an example later
in this article.&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;https://storage.googleapis.com/web-dev-assets/video-and-source-tags/chrome.webm&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;video/webm&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;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;https://storage.googleapis.com/web-dev-assets/video-and-source-tags/chrome.mp4&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;video/mp4&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Your browser cannot play the provided video file.&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;p&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;p&gt;&lt;a href=&quot;https://track-demonstration.glitch.me/&quot; rel=&quot;noopener&quot;&gt;Try it on Glitch&lt;/a&gt; (&lt;a href=&quot;https://glitch.com/edit/#!/track-demonstration&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;)&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; Notice in the previous example that the &lt;code&gt;controls&lt;/code&gt; attribute was introduced. This instructs browsers to allow the user to control video playback, including volume, seeking, selecting captions, and pause/resume playback among others. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;You should always add a &lt;code&gt;type&lt;/code&gt; attribute to the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tags event though it
is optional. This ensures that the browser only downloads the file that it is
capable of playing.&lt;/p&gt;
&lt;p&gt;This approach has several advantages over serving different HTML or server-side
scripting, especially on mobile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can list formats in order of preference.&lt;/li&gt;
&lt;li&gt;Client-side switching reduces latency; only one request is made to
get content.&lt;/li&gt;
&lt;li&gt;Letting the browser choose a format is simpler, quicker, and potentially
more reliable than using a server-side support database with user-agent
detection.&lt;/li&gt;
&lt;li&gt;Specifying each file source&#39;s type improves network performance; the browser
can select a video source without having to download part of the video to
&amp;quot;sniff&amp;quot; the format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These issues are especially important in mobile contexts, where bandwidth and
latency are at a premium, and the user&#39;s patience is likely limited. Omitting
the &lt;code&gt;type&lt;/code&gt; attribute can affect performance when there are multiple sources
with unsupported types.&lt;/p&gt;
&lt;p&gt;There are a few ways you can dig into the details. Check out
&lt;a href=&quot;https://www.xiph.org/video/vid1.shtml&quot; rel=&quot;noopener&quot;&gt;A Digital Media Primer for Geeks&lt;/a&gt; to find out more about how video and audio
work on the web. You can also use &lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;remote debugging&lt;/a&gt; in DevTools to compare
network activity &lt;a href=&quot;https://googlesamples.github.io/web-fundamentals/fundamentals/media/video-main.html&quot; rel=&quot;noopener&quot;&gt;with type attributes&lt;/a&gt; and &lt;a href=&quot;https://googlesamples.github.io/web-fundamentals/fundamentals/design-and-ux/responsive/notype.html&quot; rel=&quot;noopener&quot;&gt;without type attributes&lt;/a&gt;.&lt;/p&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; Be sure to check the response headers in your browser developer tools to [ensure your server reports the right MIME type]; otherwise video source type checks won&#39;t work. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;specify-start-and-end-times&quot;&gt;Specify start and end times &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#specify-start-and-end-times&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Save bandwidth and make your site feel more responsive: use media fragments to
add start and end times to the video element.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; width=&quot;100%&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/video-and-source-tags/chrome.webm#t=5,10&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/video-and-source-tags/chrome.mp4#t=5,10&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;p&gt;This browser does not support the video element.&lt;/p&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;To use a media fragment, add &lt;code&gt;#t=[start_time][,end_time]&lt;/code&gt; to the media URL. For
example, to play the video from seconds 5 to 10, specify:&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;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;video/chrome.webm#t=5,10&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;video/webm&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can also specify the times in &lt;code&gt;&amp;lt;hours&amp;gt;:&amp;lt;minutes&amp;gt;:&amp;lt;seconds&amp;gt;&lt;/code&gt;. For example,
&lt;code&gt;#t=00:01:05&lt;/code&gt; starts the video at one minute, five seconds. To play only the
first minute of video, specify &lt;code&gt;#t=,00:01:00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can use this feature to deliver multiple views on the same video—like
cue points in a DVD–without having to encode and serve multiple files.&lt;/p&gt;
&lt;p&gt;For this feature to work, your server must support range requests and that
capability must be enabled. Most servers enable range requests by default.
Because some hosting services turn them off, you should confirm that range
requests are available for using fragments on your site.&lt;/p&gt;
&lt;p&gt;Fortunately, you can do this in your browser developer tools. In Chrome, for
instance, it&#39;s in the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/#network&quot; rel=&quot;noopener&quot;&gt;Network panel&lt;/a&gt;. Look for the &lt;code&gt;Accept-Ranges&lt;/code&gt; header and
verify that it says &lt;code&gt;bytes&lt;/code&gt;. In the image, I&#39;ve drawn a red box around this
header. If you do not see &lt;code&gt;bytes&lt;/code&gt; as the value, you&#39;ll need to contact your
hosting provider.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools screenshot: Accept-Ranges: bytes.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/20DlLyicG5PAo6TXBKh3.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Chrome DevTools screenshot: Accept-Ranges: bytes.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;include-a-poster-image&quot;&gt;Include a poster image &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#include-a-poster-image&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add a poster attribute to the &lt;code&gt;video&lt;/code&gt; element so that viewers have an idea of
the content as soon as the element loads, without needing to download the video
or start playback.&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;poster&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;poster.jpg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;p&gt;A poster can also be a fallback if the video &lt;code&gt;src&lt;/code&gt; is broken or if none of the
supplied video formats are supported. The only downside to a poster images is an
additional file request, which consumes some bandwidth and requires rendering.
For more information see &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/&quot; rel=&quot;noopener&quot;&gt;Efficiently encode images&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Without a fallback poster, the video just looks broken.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/R8VNeplKwajJhOuVkPDT.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
&lt;/figure&gt;
&lt;figcaption class=&quot;compare__caption&quot;&gt;
&lt;p&gt;Without a fallback poster, the video just looks broken.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A fallback poster makes it seem as if the first frame has been captured.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rNhydHVGeL2P0sQ0je5k.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
&lt;/figure&gt;
&lt;figcaption class=&quot;compare__caption&quot;&gt;
&lt;p&gt;A fallback poster makes it seem as if the first frame has been captured.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;h3 id=&quot;ensure-videos-dont-overflow-containers&quot;&gt;Ensure videos don&#39;t overflow containers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#ensure-videos-dont-overflow-containers&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When video elements are too big for the viewport, they may overflow their
container, making it impossible for the user to see the content or use the
controls.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Android Chrome screenshot, portrait: unstyled video element overflows viewport.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 338px) 338px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cDl2OfCE3hQivhaNvMUh.png?auto=format&amp;w=676 676w&quot; width=&quot;338&quot; /&gt;
    &lt;figcaption&gt;Android Chrome screenshot, portrait: unstyled video element overflows
    viewport.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Android Chrome screenshot, landscape: unstyled video element overflows viewport.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCiZsNkZNsAhWbOBsLCs.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;Android Chrome screenshot, landscape: unstyled video element overflows
    viewport.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;You can control video dimensions using CSS. If CSS does not meet all of your
needs, JavaScript libraries and plugins such as &lt;a href=&quot;http://fitvidsjs.com/&quot; rel=&quot;noopener&quot;&gt;FitVids&lt;/a&gt; (outside the scope
of this article) can help, even for videos from YouTube and other sources.
Unfortunately, these resources can increase your &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/total-byte-weight/&quot; rel=&quot;noopener&quot;&gt;network payload sizes&lt;/a&gt; with
negative consequences for your revenues and your users&#39; wallets.&lt;/p&gt;
&lt;p&gt;For simple uses like the ones I&#39;m describing here, use &lt;a href=&quot;https://web.dev/responsive-web-design-basics/#media-queries&quot;&gt;CSS media queries&lt;/a&gt; to
specify the size of elements depending on the viewport dimensions; &lt;code&gt;max-width: 100%&lt;/code&gt; is your friend.&lt;/p&gt;
&lt;p&gt;For media content in iframes (such as YouTube videos), try a responsive approach
(like the one &lt;a href=&quot;http://avexdesigns.com/responsive-youtube-embed/&quot; rel=&quot;noopener&quot;&gt;proposed by John Surdakowski&lt;/a&gt;).&lt;/p&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; Don&#39;t force element sizing that results in an [aspect ratio] different from the original video. Squashed or stretched videos looks awful. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;css&quot;&gt;CSS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#css&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.video-container&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding-bottom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 56.25%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding-top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&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 selector&quot;&gt;.video-container iframe,&lt;br /&gt;.video-container object,&lt;br /&gt;.video-container embed&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&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;h4 id=&quot;html&quot;&gt;HTML &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#html&quot;&gt;#&lt;/a&gt;&lt;/h4&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;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;video-container&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;iframe&lt;/span&gt;&lt;br /&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;//www.youtube.com/embed/l-BA9Ee2XuM&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;frameborder&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;0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;width&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;560&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;height&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;315&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&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;iframe&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;div&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;p&gt;&lt;a href=&quot;https://googlesamples.github.io/web-fundamentals/fundamentals/media/responsive_embed.html&quot; rel=&quot;noopener&quot;&gt;Try it&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Compare the &lt;a href=&quot;https://googlesamples.github.io/web-fundamentals/fundamentals/media/responsive_embed.html&quot; rel=&quot;noopener&quot;&gt;responsive sample&lt;/a&gt; to the &lt;a href=&quot;https://googlesamples.github.io/web-fundamentals/fundamentals/design-and-ux/responsive/unyt.html&quot; rel=&quot;noopener&quot;&gt;unresponsive version&lt;/a&gt;. As you can see,
the unresponsive version isn&#39;t a great user experience.&lt;/p&gt;
&lt;h3 id=&quot;device-orientation&quot;&gt;Device orientation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#device-orientation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Device orientation isn&#39;t an issue for desktop monitors or laptops, but it&#39;s
hugely important when considering web page design for mobile devices and
tablets.&lt;/p&gt;
&lt;p&gt;Safari on iPhone does a good job of switching between portrait and landscape
orientation:&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of video playing in Safari on iPhone, portrait.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 338px) 338px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AmHneDShMOioWZwYG2kF.png?auto=format&amp;w=676 676w&quot; width=&quot;338&quot; /&gt;
  &lt;figcaption&gt;Screenshot of video playing in Safari on iPhone, portrait.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of video playing in Safari on iPhone, landscape.&quot; decoding=&quot;async&quot; height=&quot;338&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MZwkLJaXVk4g8lruhiKZ.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Screenshot of video playing in Safari on iPhone, landscape.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Device orientation on an iPad and Chrome on Android can be problematic.
For example, without any customization a video playing on an iPad in landscape
orientation looks like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of video playing in Safari on iPad, landscape.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9FsExgY6cJFfMkxOPNkl.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Screenshot of video playing in Safari on iPad, landscape.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Setting the video &lt;code&gt;width: 100%&lt;/code&gt; or &lt;code&gt;max-width: 100%&lt;/code&gt; with CSS can resolve
many device orientation layout problems.&lt;/p&gt;
&lt;h3 id=&quot;autoplay&quot;&gt;Autoplay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#autoplay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;autoplay&lt;/code&gt; attribute controls whether the browser downloads and plays a
video immediately. The precise way it works depends on the platform and browser.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Chrome: Depends on multiple factors including but not limited to whether the
viewing is on desktop and whether the mobile user has added your site or app
to their homescreen. For details, see &lt;a href=&quot;https://web.dev/autoplay-best-practices/&quot;&gt;Autoplay best practices&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Firefox: Blocks all video and sound, but gives users the ability to relax these
restrictions for either all sites or particular sites. For details, see
&lt;a href=&quot;https://support.mozilla.org/en-US/kb/block-autoplay&quot; rel=&quot;noopener&quot;&gt;Allow or block media autoplay in Firefox&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Safari: Has historically required a user gesture, but has been relaxing that
requirement in recent versions. For details, see
&lt;a href=&quot;https://webkit.org/blog/6784/new-video-policies-for-ios/&quot; rel=&quot;noopener&quot;&gt;New &amp;lt;video&amp;gt; Policies for iOS&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even on platforms where autoplay is possible, you need to consider whether it&#39;s
a good idea to enable it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data usage can be expensive.&lt;/li&gt;
&lt;li&gt;Playing media before the user wants it can hog bandwidth and CPU, and thereby
delay page rendering.&lt;/li&gt;
&lt;li&gt;Users may be in a context where playing video or audio is intrusive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;preload&quot;&gt;Preload &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/video-and-source-tags/#preload&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;preload&lt;/code&gt; attribute provides a hint to the browser as to how much
information or content to preload.&lt;/p&gt;
&lt;table class=&quot;responsive&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td data-th=&quot;Value&quot;&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
      &lt;td data-th=&quot;Description&quot;&gt;The user might chose not to watch the video, so don&#39;t
      preload anything.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td data-th=&quot;Value&quot;&gt;&lt;code&gt;metadata&lt;/code&gt;&lt;/td&gt;
      &lt;td data-th=&quot;Description&quot;&gt;Metadata (duration, dimensions, text tracks) should be
      preloaded, but with minimal video.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td data-th=&quot;Value&quot;&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
      &lt;td data-th=&quot;Description&quot;&gt;Downloading the entire video right away is considered
      desirable. An empty string produces the same result.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;preload&lt;/code&gt; attribute has different effects on different platforms.
For example, Chrome buffers 25 seconds of video on desktop but none on iOS or
Android. This means that on mobile, there may be playback startup delays
that don&#39;t happen on desktop. See &lt;a href=&quot;https://web.dev/fast-playback-with-preload/&quot;&gt;Fast playback with audio and video preload&lt;/a&gt; or
&lt;a href=&quot;https://www.stevesouders.com/blog/2013/04/12/html5-video-preload/&quot; rel=&quot;noopener&quot;&gt;Steve Souders&#39; blog&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Now that you know how to add media to your web page it&#39;s time to learn about
&lt;a href=&quot;https://web.dev/media-accessibility/&quot;&gt;Media accessibility&lt;/a&gt; where you will add captions to your video for hearing
impaired, or when playing the audio is not a viable option.&lt;/p&gt;
</content>
    <author>
      <name>Sam Dutton</name>
    </author><author>
      <name>Joe Medley</name>
    </author><author>
      <name>Derek Herman</name>
    </author>
  </entry>
  
  <entry>
    <title>Content security policy</title>
    <link href="https://web.dev/csp/"/>
    <updated>2012-06-15T00:00:00Z</updated>
    <id>https://web.dev/csp/</id>
    <content type="html" mode="escaped">&lt;p&gt;The web&#39;s security model is rooted in the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Same-origin_policy&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;same-origin policy&lt;/em&gt;&lt;/a&gt;. Code
from &lt;code&gt;https://mybank.com&lt;/code&gt; should only have access to &lt;code&gt;https://mybank.com&lt;/code&gt;&#39;s
data, and &lt;code&gt;https://evil.example.com&lt;/code&gt; should certainly never be allowed access.
Each origin is kept isolated from the rest of the web, giving developers a safe
sandbox in which to build and play. In theory, this is perfectly brilliant. In
practice, attackers have found clever ways to subvert the system.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cross-site_scripting&quot; rel=&quot;noopener&quot;&gt;Cross-site scripting (XSS)&lt;/a&gt;
attacks, for example, bypass the same origin policy by tricking a site into
delivering malicious code along with the intended content. This is a huge
problem, as browsers trust all of the code that shows up on a page as being
legitimately part of that page&#39;s security origin. The
&lt;a href=&quot;https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet&quot; rel=&quot;noopener&quot;&gt;XSS Cheat Sheet&lt;/a&gt;
is an old but representative cross-section of the methods an attacker might use
to violate this trust by injecting malicious code. If an attacker successfully
injects &lt;em&gt;any&lt;/em&gt; code at all, it&#39;s pretty much game over: user session data is
compromised and information that should be kept secret is exfiltrated to The Bad
Guys. We&#39;d obviously like to prevent that if possible.&lt;/p&gt;
&lt;p&gt;This overview highlights a defense that can significantly reduce the risk and
impact of XSS attacks in modern browsers: Content Security Policy (CSP).&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use allowlists to tell the client what&#39;s allowed and what isn&#39;t.&lt;/li&gt;
&lt;li&gt;Learn what directives are available.&lt;/li&gt;
&lt;li&gt;Learn the keywords they take.&lt;/li&gt;
&lt;li&gt;Inline code and &lt;code&gt;eval()&lt;/code&gt; are considered harmful.&lt;/li&gt;
&lt;li&gt;Report policy violations to your server before enforcing them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;source-allowlists&quot;&gt;Source allowlists &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#source-allowlists&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The issue exploited by XSS attacks is the browser&#39;s inability to distinguish
between script that&#39;s part of your application and script that&#39;s been
maliciously injected by a third-party. For example, the Google +1 button at the
bottom of this page loads and executes code from
&lt;code&gt;https://apis.google.com/js/plusone.js&lt;/code&gt; in the context of this page&#39;s origin. We
trust that code, but we can&#39;t expect the browser to figure out on its own that code
from &lt;code&gt;apis.google.com&lt;/code&gt; is awesome, while code from &lt;code&gt;apis.evil.example.com&lt;/code&gt;
probably isn&#39;t. The browser happily downloads and executes any code a page
requests, regardless of source.&lt;/p&gt;
&lt;p&gt;Instead of blindly trusting &lt;em&gt;everything&lt;/em&gt; that a server delivers, CSP defines the
&lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header, which allows you to create an allowlist of
sources of trusted content, and instructs the browser to only execute or render
resources from those sources. Even if an attacker can find a hole through which
to inject script, the script won&#39;t match the allowlist, and therefore won&#39;t be
executed.&lt;/p&gt;
&lt;p&gt;Since we trust &lt;code&gt;apis.google.com&lt;/code&gt; to deliver valid code, and we trust ourselves
to do the same, let&#39;s define a policy that only allows script to execute when it
comes from one of those two sources:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;script-src &#39;self&#39; https://apis.google.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Simple, right? As you probably guessed, &lt;code&gt;script-src&lt;/code&gt; is a directive that
controls a set of script-related privileges for a specific page. We&#39;ve specified
&lt;code&gt;&#39;self&#39;&lt;/code&gt; as one valid source of script, and &lt;code&gt;https://apis.google.com&lt;/code&gt; as
another. The browser dutifully downloads and executes JavaScript from
&lt;code&gt;apis.google.com&lt;/code&gt; over HTTPS, as well as from the current page&#39;s origin.&lt;/p&gt;
&lt;div class=&quot;attempt-right&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Console error: Refused to load the script &amp;#x27;http://evil.example.com/evil.js&amp;#x27; because it violates the following Content Security Policy directive: script-src &amp;#x27;self&amp;#x27; https://apis.google.com&quot; decoding=&quot;async&quot; height=&quot;173&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 709px) 709px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/RSnKHpls9RiDnnSGxMlE.png?auto=format&amp;w=1418 1418w&quot; width=&quot;709&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;With this policy defined, the browser simply throws an error instead of
loading script from any other source. When a clever attacker manages to
inject code into your site, they&#39;ll run headlong into an error message rather
than the success they were expecting.&lt;/p&gt;
&lt;h3 id=&quot;policy-applies-to-a-wide-variety-of-resources&quot;&gt;Policy applies to a wide variety of resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#policy-applies-to-a-wide-variety-of-resources&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While script resources are the most obvious security risks, CSP provides a rich
set of policy directives that enable fairly granular control over the resources
that a page is allowed to load. You&#39;ve already seen &lt;code&gt;script-src&lt;/code&gt;, so the concept
should be clear.&lt;/p&gt;
&lt;p&gt;Let&#39;s quickly walk through the rest of the resource directives. The list below
represents the state of the directives as of level 2. A &lt;a href=&quot;https://www.w3.org/TR/CSP3/&quot; rel=&quot;noopener&quot;&gt;level 3
spec&lt;/a&gt; has been published, but is &lt;a href=&quot;https://www.chromestatus.com/features#csp3&quot; rel=&quot;noopener&quot;&gt;largely
unimplemented&lt;/a&gt; in the major
browsers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;base-uri&lt;/code&gt;&lt;/strong&gt; restricts the URLs that can appear in a page&#39;s &lt;code&gt;&amp;lt;base&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;child-src&lt;/code&gt;&lt;/strong&gt; lists the URLs for workers and embedded frame contents. For
example: &lt;code&gt;child-src https://youtube.com&lt;/code&gt; would enable embedding videos from
YouTube but not from other origins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;connect-src&lt;/code&gt;&lt;/strong&gt; limits the origins that you can connect to (via XHR,
WebSockets, and EventSource).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;font-src&lt;/code&gt;&lt;/strong&gt; specifies the origins that can serve web fonts. Google&#39;s web
fonts could be enabled via &lt;code&gt;font-src https://themes.googleusercontent.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;form-action&lt;/code&gt;&lt;/strong&gt; lists valid endpoints for submission from &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frame-ancestors&lt;/code&gt;&lt;/strong&gt;  specifies the sources that can embed the current page.
This directive applies to &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;applet&amp;gt;&lt;/code&gt; tags.
This directive can&#39;t be used in &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags and applies only to non-HTML
resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frame-src&lt;/code&gt;&lt;/strong&gt; was deprecated in level 2, but is restored in level 3. If not
present it still falls back to  &lt;code&gt;child-src&lt;/code&gt; as before.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;img-src&lt;/code&gt;&lt;/strong&gt; defines the origins from which images can be loaded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;media-src&lt;/code&gt;&lt;/strong&gt; restricts the origins allowed to deliver video and audio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;object-src&lt;/code&gt;&lt;/strong&gt; allows control over Flash and other plugins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;plugin-types&lt;/code&gt;&lt;/strong&gt; limits the kinds of plugins a page may invoke.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;report-uri&lt;/code&gt;&lt;/strong&gt; specifies a URL where a browser will send reports when a
content security policy is violated. This directive can&#39;t be used in &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt;
tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;style-src&lt;/code&gt;&lt;/strong&gt; is &lt;code&gt;script-src&lt;/code&gt;&#39;s counterpart for stylesheets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;upgrade-insecure-requests&lt;/code&gt;&lt;/strong&gt; instructs user agents to rewrite URL schemes,
changing HTTP to HTTPS. This directive is for websites with large numbers of
old    URL&#39;s that need to be rewritten.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;worker-src&lt;/code&gt;&lt;/strong&gt; is a CSP Level 3 directive that restricts the URLs that may
be loaded as a worker, shared worker, or service worker. As of July 2017, this
directive has
&lt;a href=&quot;https://www.chromestatus.com/features/5922594955984896&quot; rel=&quot;noopener&quot;&gt;limited implementations&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, directives are wide open. If you don&#39;t set a specific policy for a
directive, let&#39;s say &lt;code&gt;font-src&lt;/code&gt;, then that directive behaves by default as
though you&#39;d specified &lt;code&gt;*&lt;/code&gt; as the valid source (for example, you could load fonts from
anywhere, without restriction).&lt;/p&gt;
&lt;p&gt;You can override this default behavior by specifying a &lt;strong&gt;&lt;code&gt;default-src&lt;/code&gt;&lt;/strong&gt;
directive. This directive defines the defaults for most
directives that you leave unspecified. Generally, this applies to any directive that
ends with &lt;code&gt;-src&lt;/code&gt;. If &lt;code&gt;default-src&lt;/code&gt; is set to &lt;code&gt;https://example.com&lt;/code&gt;, and you fail
to specify a &lt;code&gt;font-src&lt;/code&gt; directive, then you can load fonts from
&lt;code&gt;https://example.com&lt;/code&gt;, and nowhere else. We specified only &lt;code&gt;script-src&lt;/code&gt; in our
earlier examples, which means that images, fonts, and so on can be loaded from
any origin.&lt;/p&gt;
&lt;p&gt;The following directives don&#39;t use &lt;code&gt;default-src&lt;/code&gt; as a fallback. Remember that
failing to set them is the same as allowing anything.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base-uri&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form-action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frame-ancestors&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;plugin-types&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;report-uri&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sandbox&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use as many or as few of these directives as makes sense for your
specific application, simply listing each in the HTTP header, separating
directives with semicolons. Make sure that you list &lt;em&gt;all&lt;/em&gt;
required resources of a specific type in a &lt;em&gt;single&lt;/em&gt; directive. If you wrote
something like &lt;code&gt;script-src https://host1.com; script-src https://host2.com&lt;/code&gt; the
second directive would simply be ignored. Something like the following would
correctly specify both origins as valid:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;script-src https://host1.com https://host2.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If, for example, you have an application that loads all of its resources from a
content delivery network (say, &lt;code&gt;https://cdn.example.net&lt;/code&gt;), and know that you
don&#39;t need any framed content or plugins, then your policy might look something
like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;default-src https://cdn.example.net; child-src &#39;none&#39;; object-src &#39;none&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;implementation-details&quot;&gt;Implementation details &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#implementation-details&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You will see &lt;code&gt;X-WebKit-CSP&lt;/code&gt; and &lt;code&gt;X-Content-Security-Policy&lt;/code&gt; headers in various
tutorials on the web. Going forward, you should ignore these prefixed
headers. Modern browsers (with the exception of IE) support the unprefixed
&lt;code&gt;Content-Security-Policy&lt;/code&gt; header. That&#39;s the header you should use.&lt;/p&gt;
&lt;p&gt;Regardless of the header you use, policy is defined on a page-by-page basis:
you&#39;ll need to send the HTTP header along with every response that you&#39;d like to
ensure is protected. This provides a lot of flexibility, as you can fine-tune
the policy for specific pages based on their specific needs. Perhaps one set of
pages in your site has a +1 button, while others don&#39;t: you could allow the
button code to be loaded only when necessary.&lt;/p&gt;
&lt;p&gt;The source list in each directive is flexible. You can specify sources by
scheme (&lt;code&gt;data:&lt;/code&gt;, &lt;code&gt;https:&lt;/code&gt;), or ranging in specificity from hostname-only
(&lt;code&gt;example.com&lt;/code&gt;, which matches any origin on that host: any scheme, any port) to
a fully qualified URI (&lt;code&gt;https://example.com:443&lt;/code&gt;, which matches only HTTPS, only
&lt;code&gt;example.com&lt;/code&gt;, and only port 443). Wildcards are accepted, but only as a scheme,
a port, or in the leftmost position of the hostname: &lt;code&gt;*://*.example.com:*&lt;/code&gt; would
match all subdomains of &lt;code&gt;example.com&lt;/code&gt; (but &lt;em&gt;not&lt;/em&gt; &lt;code&gt;example.com&lt;/code&gt; itself), using
any scheme, on any port.&lt;/p&gt;
&lt;p&gt;The source list also accepts four keywords:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&#39;none&#39;&lt;/code&gt;&lt;/strong&gt;, as you might expect, matches nothing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&#39;self&#39;&lt;/code&gt;&lt;/strong&gt; matches the current origin, but not its subdomains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&#39;unsafe-inline&#39;&lt;/code&gt;&lt;/strong&gt; allows inline JavaScript and CSS. (We&#39;ll touch on this in
more detail in a bit.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&#39;unsafe-eval&#39;&lt;/code&gt;&lt;/strong&gt; allows text-to-JavaScript mechanisms like &lt;code&gt;eval&lt;/code&gt;. (We&#39;ll get
to this too.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These keywords require single-quotes. For example, &lt;code&gt;script-src &#39;self&#39;&lt;/code&gt; (with quotes)
authorizes the execution of JavaScript from the current host; &lt;code&gt;script-src self&lt;/code&gt;
(no quotes) allows JavaScript from a server named &amp;quot;&lt;code&gt;self&lt;/code&gt;&amp;quot; (and &lt;em&gt;not&lt;/em&gt; from the
current host), which probably isn&#39;t what you meant.&lt;/p&gt;
&lt;h3 id=&quot;sandboxing&quot;&gt;Sandboxing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#sandboxing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&#39;s one more directive worth talking about: &lt;code&gt;sandbox&lt;/code&gt;. It&#39;s a bit
different from the others we&#39;ve looked at, as it places restrictions on actions that
the page can take rather than on resources that the page can load. If the
&lt;code&gt;sandbox&lt;/code&gt; directive is present, the page is treated as though it was loaded
inside of an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; with a &lt;code&gt;sandbox&lt;/code&gt; attribute. This can have a wide range of
effects on the page: forcing the page into a unique origin, and preventing form
submission, among others. It&#39;s a bit beyond the scope of this article, but you
can find full details on valid sandboxing attributes in the
&lt;a href=&quot;https://html.spec.whatwg.org/dev/origin.html#sandboxing&quot; rel=&quot;noopener&quot;&gt;&amp;quot;Sandboxing&amp;quot; section of the HTML5 spec&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-meta-tag&quot;&gt;The meta tag &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#the-meta-tag&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CSPs preferred delivery mechanism is an HTTP header. It can be useful, however,
to set a policy on a page directly in the markup. Do that using a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag with
an &lt;code&gt;http-equiv&lt;/code&gt; attribute:&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;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;http-equiv&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;Content-Security-Policy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&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;default-src https://cdn.example.net; child-src &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;none&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;; object-src &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;none&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This can&#39;t be used for &lt;code&gt;frame-ancestors&lt;/code&gt;, &lt;code&gt;report-uri&lt;/code&gt;, or &lt;code&gt;sandbox&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;inline-code-is-considered-harmful&quot;&gt;Inline code is considered harmful &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#inline-code-is-considered-harmful&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It should be clear that CSP is based on allowlist origins, as that&#39;s an
unambiguous way of instructing the browser to treat specific sets of resources
as acceptable and to reject the rest. Origin-based allowlists don&#39;t,
however, solve the biggest threat posed by XSS attacks: inline script injection.
If an attacker can inject a script tag that directly contains some malicious
payload (&lt;code&gt;&amp;lt;script&amp;gt;sendMyDataToEvilDotCom()&amp;lt;/script&amp;gt;&lt;/code&gt;),
the browser has no mechanism by which to distinguish it from a legitimate
inline script tag. CSP solves this problem by banning inline script entirely:
it&#39;s the only way to be sure.&lt;/p&gt;
&lt;p&gt;This ban includes not only scripts embedded directly in &lt;code&gt;script&lt;/code&gt; tags, but also
inline event handlers and &lt;code&gt;javascript:&lt;/code&gt; URLs. You&#39;ll need to move the content of
&lt;code&gt;script&lt;/code&gt; tags into an external file, and replace &lt;code&gt;javascript:&lt;/code&gt; URLs and &lt;code&gt;&amp;lt;a ... onclick=&amp;quot;[JAVASCRIPT]&amp;quot;&amp;gt;&lt;/code&gt; with appropriate &lt;code&gt;addEventListener()&lt;/code&gt; calls. For example,
you might rewrite the following from:&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;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doAmazingThings&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 function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;YOU AM AMAZING!&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&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;script&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;button&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onclick&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;&#39;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;doAmazingThings&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&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Am I amazing?&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;button&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;p&gt;to something more like:&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 comment&quot;&gt;&amp;lt;!-- amazing.html --&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;script&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;&#39;&lt;/span&gt;amazing.js&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&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;script&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;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&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;&#39;&lt;/span&gt;amazing&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Am I amazing?&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;button&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;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// amazing.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doAmazingThings&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 function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;YOU AM AMAZING!&#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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;document&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;DOMContentLoaded&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;amazing&#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 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;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; doAmazingThings&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The rewritten code has a number of advantages above and beyond working well with
CSP; it&#39;s already best practice, regardless of your use of CSP. Inline
JavaScript mixes structure and behavior in exactly the way you shouldn&#39;t.
External resources are easier for browsers to cache, more understandable for
developers, and conducive to compilation and minification. You&#39;ll write better
code if you do the work to move code into external resources.&lt;/p&gt;
&lt;p&gt;Inline style is treated in the same way: both the &lt;code&gt;style&lt;/code&gt; attribute and &lt;code&gt;style&lt;/code&gt;
tags should be consolidated into external stylesheets to protect against a
variety of &lt;a href=&quot;https://scarybeastsecurity.blogspot.com/2009/12/generic-cross-browser-cross-domain.html&quot; rel=&quot;noopener&quot;&gt;surprisingly
clever&lt;/a&gt;
data exfiltration methods that CSS enables.&lt;/p&gt;
&lt;p&gt;If you must have inline script and style, you can enable it
by adding &lt;code&gt;&#39;unsafe-inline&#39;&lt;/code&gt; as an allowed source in a &lt;code&gt;script-src&lt;/code&gt; or &lt;code&gt;style-src&lt;/code&gt;
directive. You can also use a nonce or a hash (see below), but you really shouldn&#39;t.
Banning inline script is the biggest security win CSP provides, and
banning inline style likewise hardens your application. It&#39;s a little bit of
effort up front to ensure that things work correctly after moving all the code
out-of-line, but that&#39;s a tradeoff that&#39;s well worth making.&lt;/p&gt;
&lt;h3 id=&quot;if-you-absolutely-must-use-it&quot;&gt;If you absolutely must use it &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#if-you-absolutely-must-use-it&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CSP Level 2 offers backward compatibility for inline scripts by allowing you to
add specific inline scripts to the allowlist using either a cryptographic nonce (number
used once) or a hash. Although this may be cumbersome, it is useful
in a pinch.&lt;/p&gt;
&lt;p&gt;To use a nonce, give your script tag a nonce attribute. Its value must match one
in the list of trusted sources. For example:&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;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;nonce&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;EDNnf03nceIOfn39fn3e9h3sdfa&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;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Some inline code I can&#39;t remove yet, but need to asap.&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&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;script&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;p&gt;Now, add the nonce to your &lt;code&gt;script-src&lt;/code&gt; directive appended to the &lt;code&gt;nonce-&lt;/code&gt; keyword.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;script-src &#39;nonce-EDNnf03nceIOfn39fn3e9h3sdfa&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Remember that nonces must be regenerated for every page request and they must be
unguessable.&lt;/p&gt;
&lt;p&gt;Hashes work in much the same way. Instead of adding code to the script tag,
create a SHA hash of the script itself and add it to the &lt;code&gt;script-src&lt;/code&gt; directive.
For example, let&#39;s say your page contained this:&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;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello, world.&#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;/span&gt;&lt;/span&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;script&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;p&gt;Your policy would contain this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;script-src &#39;sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;There are a few things to note here. The &lt;code&gt;sha*-&lt;/code&gt; prefix specifies the algorithm
that generates the hash. In the example above, &lt;code&gt;sha256-&lt;/code&gt; is used. CSP also
supports &lt;code&gt;sha384-&lt;/code&gt; and &lt;code&gt;sha512-&lt;/code&gt;. When generating the hash, do not include the
&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags. Also capitalization and whitespace matter, including leading or
trailing whitespace.&lt;/p&gt;
&lt;p&gt;A Google search on generating SHA hashes will lead you to solutions in any
number of languages. Using Chrome 40 or later, you can open DevTools and then
reload your page. The Console tab will contain error messages with the correct
sha256 hash for each of your inline scripts.&lt;/p&gt;
&lt;h2 id=&quot;eval-too&quot;&gt;Eval too &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#eval-too&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even when an attacker can&#39;t inject script directly, they might be able to trick
your application into converting otherwise inert text into executable JavaScript
and executing it on their behalf. &lt;code&gt;eval()&lt;/code&gt;, &lt;code&gt;new Function()&lt;/code&gt;, &lt;code&gt;setTimeout([string], …)&lt;/code&gt;, and
&lt;code&gt;setInterval([string], ...)&lt;/code&gt; are all vectors through which injected
text might end up executing something unexpectedly malicious. CSP&#39;s default
response to this risk is to completely block all of these vectors.&lt;/p&gt;
&lt;p&gt;This has more than a few impacts on the way you build applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You must parse JSON via the built-in &lt;code&gt;JSON.parse&lt;/code&gt;, rather than relying on
&lt;code&gt;eval&lt;/code&gt;. Native JSON operations are available in
&lt;a href=&quot;https://caniuse.com/#feat=json&quot; rel=&quot;noopener&quot;&gt;every browser since IE8&lt;/a&gt;, and they&#39;re
completely safe.&lt;/li&gt;
&lt;li&gt;Rewrite any &lt;code&gt;setTimeout&lt;/code&gt; or &lt;code&gt;setInterval&lt;/code&gt; calls you&#39;re currently making
with inline functions rather than strings. For example:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;document.querySelector(&#39;a&#39;).style.display = &#39;none&#39;;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;would be better written as:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setTimeout&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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;a&#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;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#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 number&quot;&gt;10&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Avoid inline templating at runtime: Many templating libraries use &lt;code&gt;new Function()&lt;/code&gt; liberally to speed up template generation at runtime. It&#39;s a
nifty application of dynamic programming, but comes at the risk of
evaluating malicious text. Some frameworks support CSP out of the box,
falling back to a robust parser in the absence of &lt;code&gt;eval&lt;/code&gt;.
&lt;a href=&quot;https://docs.angularjs.org/api/ng/directive/ngCsp&quot; rel=&quot;noopener&quot;&gt;AngularJS&#39;s ng-csp directive&lt;/a&gt;
is a good example of this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, a better choice would be a templating language that offers
precompilation (&lt;a href=&quot;https://handlebarsjs.com/installation/precompilation.html&quot; rel=&quot;noopener&quot;&gt;Handlebars does&lt;/a&gt;,
for instance). Precompiling your templates can make the user experience even
faster than the fastest runtime implementation, and it&#39;s safer too.  If eval and
its text-to-JavaScript brethren are essential to your application, you can
enable them by adding &lt;code&gt;&#39;unsafe-eval&#39;&lt;/code&gt; as an allowed source in a &lt;code&gt;script-src&lt;/code&gt;
directive, but we strongly discourage this. Banning the ability to execute
strings makes it much more difficult for an attacker to execute unauthorized
code on your site.&lt;/p&gt;
&lt;h2 id=&quot;reporting&quot;&gt;Reporting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#reporting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CSP&#39;s ability to block untrusted resources client-side is a huge win for your
users, but it would be quite helpful to have some sort of notification
sent back to the server so that you can identify and squash any bugs that allow
malicious injection in the first place. To this end, you can instruct the
browser to &lt;code&gt;POST&lt;/code&gt; JSON-formatted violation reports to a location
specified in a &lt;code&gt;report-uri&lt;/code&gt; directive.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;default-src &#39;self&#39;; ...; report-uri /my_amazing_csp_report_parser;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Those reports will look something like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;csp-report&quot;&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 property&quot;&gt;&quot;document-uri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://example.org/page.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;referrer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://evil.example.com/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;blocked-uri&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://evil.example.com/evil.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;violated-directive&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;script-src &#39;self&#39; https://apis.google.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;original-policy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;script-src &#39;self&#39; https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser&quot;&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This contains a good chunk of information that will help you track down the
specific cause of the violation, including the page on which the violation
occurred (&lt;code&gt;document-uri&lt;/code&gt;), that page&#39;s referrer (note that unlike the HTTP
header field, the key is &lt;em&gt;not&lt;/em&gt; misspelled), the resource that violated the
page&#39;s policy (&lt;code&gt;blocked-uri&lt;/code&gt;), the specific directive it violated
(&lt;code&gt;violated-directive&lt;/code&gt;), and the page&#39;s complete policy (&lt;code&gt;original-policy&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;report-only&quot;&gt;Report-Only &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#report-only&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re just starting out with CSP, it makes sense to evaluate the current
state of your application before rolling out a draconian policy to your users.
As a stepping stone to a complete deployment, you can ask the browser to monitor
a policy, reporting violations but not enforcing the restrictions. Instead of
sending a &lt;code&gt;Content-Security-Policy&lt;/code&gt; header, send a
&lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; header.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy-Report-Only&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;default-src &#39;self&#39;; ...; report-uri /my_amazing_csp_report_parser;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The policy specified in report-only mode won&#39;t block restricted resources, but
it will send violation reports to the location you specify. You can even send
&lt;em&gt;both&lt;/em&gt; headers, enforcing one policy while monitoring another. This is a great
way to evaluate the effect of changes to your application&#39;s CSP: turn on
reporting for a new policy, monitor the violation reports and fix any bugs that
turn up; when you&#39;re satisfied with its effect, start enforcing the new policy.&lt;/p&gt;
&lt;h2 id=&quot;real-world-usage&quot;&gt;Real World Usage &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#real-world-usage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CSP 1 is quite usable in Chrome, Safari, and Firefox, but has very limited
support in IE 10. You can &lt;a href=&quot;https://caniuse.com/#feat=contentsecuritypolicy&quot; rel=&quot;noopener&quot;&gt;view specifics at caniuse.com&lt;/a&gt;. CSP Level 2 has been available in Chrome since
version 40. Massive sites like Twitter and Facebook have deployed the header
(&lt;a href=&quot;https://blog.twitter.com/engineering/en_us/a/2011/improving-browser-security-with-csp.html&quot; rel=&quot;noopener&quot;&gt;Twitter&#39;s
case study&lt;/a&gt; is worth a read), and the standard is very much ready
for you to start deploying on your own sites.&lt;/p&gt;
&lt;p&gt;The first step towards crafting a policy for your application is to evaluate the
resources you&#39;re actually loading. Once you think you have a handle on how
things are put together in your app, set up a policy based on those
requirements. Let&#39;s walk through a few common use cases and determine how we&#39;d
best be able to support them within the protective confines of CSP.&lt;/p&gt;
&lt;h3 id=&quot;use-case-#1-social-media-widgets&quot;&gt;Use case #1: social media widgets &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#use-case-#1-social-media-widgets&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Facebook&#39;s &lt;a href=&quot;https://developers.facebook.com/docs/plugins/like-button&quot; class=&quot;external&quot; rel=&quot;noopener&quot;&gt;Like button&lt;/a&gt;
has a number of implementation options. We recommend sticking with the
&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; version as it&#39;s safely sandboxed from the rest of your site. It
requires a &lt;code&gt;child-src https://facebook.com&lt;/code&gt; directive to function properly. Note
that, by default, the &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; code that Facebook provides loads a relative
URL, &lt;code&gt;//facebook.com&lt;/code&gt;. Change that to explicitly specify HTTPS:
&lt;code&gt;https://facebook.com&lt;/code&gt;. There&#39;s no reason to use HTTP if you don&#39;t have to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Twitter&#39;s &lt;a href=&quot;https://publish.twitter.com/#&quot; rel=&quot;noopener&quot;&gt;Tweet button&lt;/a&gt;
relies on access to a script and a frame, both hosted at
&lt;code&gt;https://platform.twitter.com&lt;/code&gt;. (Twitter likewise provides a relative URL by
default; edit the code to specify HTTPS when copy/pasting it locally.)
You&#39;ll be all set with &lt;code&gt;script-src https://platform.twitter.com; child-src https://platform.twitter.com&lt;/code&gt;, as long as you move the JavaScript snippet
that Twitter provides out into an external JavaScript file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Other platforms have similar requirements, and can be addressed similarly.
We suggest just setting a &lt;code&gt;default-src&lt;/code&gt; of &lt;code&gt;&#39;none&#39;&lt;/code&gt;, and watching your console to
determine which resources you&#39;ll need to enable to make the widgets work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Including multiple widgets is straightforward: simply combine the policy
directives, remembering to merge all resources of a single type into a single
directive. If you wanted all three social media widgets, the policy would look
like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;use-case-#2-lockdown&quot;&gt;Use case #2: lockdown &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#use-case-#2-lockdown&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Assume for a moment that you run a banking site and want to make sure that
only those resources you&#39;ve written yourself can be loaded. In this scenario,
start with a default policy that blocks absolutely everything (&lt;code&gt;default-src &#39;none&#39;&lt;/code&gt;), and build up from there.&lt;/p&gt;
&lt;p&gt;Let&#39;s say the bank loads all images, style, and script from a CDN at
&lt;code&gt;https://cdn.mybank.net&lt;/code&gt;, and connects via XHR to &lt;code&gt;https://api.mybank.com/&lt;/code&gt; to
pull various bits of data down. Frames are used, but only for pages local to the
site (no third-party origins). There&#39;s no Flash on the site, no fonts, no
extras. The most restrictive CSP header that we could send is this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;default-src &#39;none&#39;; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src &#39;self&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;use-case-#3-ssl-only&quot;&gt;Use case #3: SSL only &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#use-case-#3-ssl-only&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A wedding-ring discussion forum admin wants to ensure that all resources are
only loaded via secure channels, but doesn&#39;t really write much code; rewriting
large chunks of the third-party forum software that&#39;s filled to the brim with
inline script and style is beyond his abilities. The following policy would be
effective:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Content-Security-Policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value csp languages-csp&quot;&gt;default-src https:; script-src https: &#39;unsafe-inline&#39;; style-src https: &#39;unsafe-inline&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Even though &lt;code&gt;https:&lt;/code&gt; is specified in &lt;code&gt;default-src&lt;/code&gt;, the script and style
directives don&#39;t automatically inherit that source. Each directive completely
overwrites the default for that specific type of resource.&lt;/p&gt;
&lt;h2 id=&quot;the-future&quot;&gt;The future &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/csp/#the-future&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Content Security Policy Level 2 is a
&lt;a href=&quot;https://www.w3.org/TR/CSP2/&quot; rel=&quot;noopener&quot;&gt;Candidate Recommendation&lt;/a&gt;. The W3C&#39;s Web Application Security Working Group
has already begun work on the specification&#39;s next iteration,
&lt;a href=&quot;https://www.w3.org/TR/CSP3/&quot; rel=&quot;noopener&quot;&gt;Content Security Policy Level 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re interested in the discussion around these upcoming features,
&lt;a href=&quot;http://lists.w3.org/Archives/Public/public-webappsec/&quot; rel=&quot;noopener&quot;&gt;skim the public-webappsec@ mailing list archives&lt;/a&gt;,
or join in yourself.&lt;/p&gt;
</content>
    <author>
      <name>Mike West</name>
    </author><author>
      <name>Joe Medley</name>
    </author>
  </entry>
</feed>
