<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matt McClure</title>
    <description>The latest articles on DEV Community by Matt McClure (@mmcc).</description>
    <link>https://dev.to/mmcc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F233913%2F9ffd7050-b123-489f-849d-f998f65d2b69.png</url>
      <title>DEV Community: Matt McClure</title>
      <link>https://dev.to/mmcc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mmcc"/>
    <language>en</language>
    <item>
      <title>A React component that shares video with others.</title>
      <dc:creator>Matt McClure</dc:creator>
      <pubDate>Fri, 07 Aug 2020 16:28:53 +0000</pubDate>
      <link>https://dev.to/mmcc/a-react-component-that-shares-video-with-others-3i4o</link>
      <guid>https://dev.to/mmcc/a-react-component-that-shares-video-with-others-3i4o</guid>
      <description>&lt;p&gt;As part of our &lt;a href="https://mux.com"&gt;Mux.com&lt;/a&gt; refresh, we wanted to demo our API experience via a React-based animation. At the end of it we wanted to show a video playing across multiple devices, which starts to get into weirder territory than you might expect.&lt;/p&gt;

&lt;p&gt;It'd be easy to jump to using multiple video elements across the devices. On top of loading the same video multiple times (and the bandwidth that entails), synchronizing the playback gets problematic. Starting them all at the same time is a good start, but what if any of the players is slow to start or rebuffers at any point?&lt;/p&gt;

&lt;p&gt;Instead, we decided to &lt;a href="https://mux.com/blog/canvas-adding-filters-and-more-to-video-using-just-a-browser/"&gt;keep playing with &lt;code&gt;canvas&lt;/code&gt;&lt;/a&gt;. We made a React component that plays video in a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag...but actually never displays that video. Instead, it distributes that video content to the array of canvas &lt;a href="https://reactjs.org/docs/hooks-reference.html#useref"&gt;&lt;code&gt;refs&lt;/code&gt;&lt;/a&gt; passed to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;CanvasPlayer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateCanvases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If the player is empty, we probably reset!&lt;/span&gt;
    &lt;span class="c1"&gt;// In that case, let's clear out the canvases&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;canvases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// I don't know how we'd get to this point without&lt;/span&gt;
    &lt;span class="c1"&gt;// player being defined, but... yeah. Here we check&lt;/span&gt;
    &lt;span class="c1"&gt;// to see if the video is actually playing before&lt;/span&gt;
    &lt;span class="c1"&gt;// continuing to paint to the canvases&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paused&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Paint! Map over each canvas and draw what's currently&lt;/span&gt;
    &lt;span class="c1"&gt;// in the video element.&lt;/span&gt;
    &lt;span class="nx"&gt;canvases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Loop that thing.&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateCanvases&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Fired whenever the video element starts playing&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;updateCanvases&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We're using HLS, so this is just to make sure the player&lt;/span&gt;
    &lt;span class="c1"&gt;// can support it. This isn't necessary if you're just using&lt;/span&gt;
    &lt;span class="c1"&gt;// an mp4 or something.&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canPlayType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.apple.mpegurl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loadedmetadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Hls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MANIFEST_PARSED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;hls&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="cm"&gt;/* eslint-disable jsx-a11y/media-has-caption */&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onPlay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPlay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All the magic is in the &lt;code&gt;updateCanvases&lt;/code&gt; function. While the video is playing it maps over each canvas ref and draws whatever is in the video tag to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it ends up looking
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;FunComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasOne&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasTwo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SomeComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasOne&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SomeComponent&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OtherComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasTwo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/OtherComponent&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CanvasPlayer&lt;/span&gt;
        &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://stream.mux.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;playbackID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.m3u8`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;muted&lt;/span&gt;
        &lt;span class="nx"&gt;canvases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nx"&gt;canvasOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvasTwo&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="nx"&gt;loop&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CanvasPlayer&lt;/code&gt; won't actually play anything itself, but it'll distribute the video image around to each of the refs passed to it. This means you could sprinkle a video all around a page if you want but only have to download it once!&lt;/p&gt;

</description>
      <category>react</category>
      <category>video</category>
    </item>
    <item>
      <title>The state of going live from a browser</title>
      <dc:creator>Matt McClure</dc:creator>
      <pubDate>Mon, 20 Apr 2020 16:30:35 +0000</pubDate>
      <link>https://dev.to/mux/the-state-of-going-live-from-a-browser-13m8</link>
      <guid>https://dev.to/mux/the-state-of-going-live-from-a-browser-13m8</guid>
      <description>&lt;p&gt;Publishing a live stream directly from a browser feels like it &lt;em&gt;must&lt;/em&gt; be one of those solved problems. Watching live video in a browser is so common these days it's hard to imagine a time when it required proprietary plugins to even have a chance of working. Even video &lt;em&gt;communication&lt;/em&gt; feels trivial now thanks to modern browser features like WebRTC. The "trivial" part is only really true if you're using two browser windows on the same machine, but still, it's you on video! Twice!&lt;/p&gt;

&lt;p&gt;So as a web developer looking at all this video successfully being sent and played back by the browser, it's totally reasonable to think that publishing a live broadcast directly from a browser would be easy. All the building blocks are here, there's surely an npm package that ties it all together for publishing to sources like Mux, Facebook, YouTube Live, Twitch, etc...&lt;/p&gt;

&lt;h2&gt;
  
  
  That's gonna be a no from browsers, dawg.
&lt;/h2&gt;

&lt;p&gt;Unfortunately that's simply not the case. There's no reasonable way to publish a live broadcast directly from a browser. It's possible to capture the video and eventually get it there, but you're almost always going to need to get a server involved.&lt;/p&gt;

&lt;p&gt;One of the big reasons for this is that the industry standard for publishing live streams is &lt;a href="https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol"&gt;RTMP&lt;/a&gt;, which is a protocol browsers simply aren't able to natively speak. We've written about the options out there for &lt;a href="https://mux.com/blog/guide-to-rtmp-broadcast-apps-for-ios/"&gt;native mobile applications&lt;/a&gt;, and the desktop has fantastic, open tools like the &lt;a href="https://obsproject.com/"&gt;OBS project&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why go live from the browser?
&lt;/h1&gt;

&lt;p&gt;One of the most common reasons is simply due to friction. If you're building a live streaming solution and you want your customers to be able to go live as easily as possible, asking them to leave your service to go figure out some other piece of desktop software is a big ask.&lt;/p&gt;

&lt;p&gt;On top of that, the tools out there for live streaming are complex in their own right. OBS Studio, for example, is an &lt;em&gt;incredibly&lt;/em&gt; powerful and flexible tool, but that comes with the cost of being a daunting piece of software for the unfamiliar. Even with guides and tools out there to help users get set up, you're now supporting not only your service, but whatever tools your streamers end up using.&lt;/p&gt;

&lt;p&gt;If you're already building a web app there's a good chance your team is good at...well building web apps. Building your go-live dashboard directly into your browser application would allow you to continue to utilize the expertise of your team, giving end-users a low-friction, branded experience that doesn't require them to learn anything but &lt;em&gt;your&lt;/em&gt; application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we go on...
&lt;/h2&gt;

&lt;p&gt;Yes, for all of the reasons just mentioned, it's easy to see why it's so tempting, but going live directly from the browser is almost certainly going to be a worse experience for everyone involved. The quality will be worse, the stream less reliable, and the tooling more limited. Your streamers and your viewers are all probably better off if the broadcast is done from a native application.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ok cool, now let's talk about our options.
&lt;/h1&gt;

&lt;p&gt;We're going to talk about 3 high-level approaches to going live from the browser. By "going live," what we're specifically referring to is getting video from a streamer's browser to a broadcast endpoint via RTMP. Spoiler alert: all three of the approaches we're going to discuss are related, and two of them are essentially the same workflow with a twist. There are probably other options out there, but these are the closest to production ready you'll find.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebRTC rebroadcasting
&lt;/h2&gt;

&lt;p&gt;Most commonly, WebRTC is known as the technology that lets web developers to build live video chat into the browser. That's true, but it actually goes much further than that. WebRTC is made up of standards that allow for peer-to-peer Web applications that can transmit audio, video, or even just arbitrary data without the need for plug-ins or technically even servers[1].&lt;/p&gt;

&lt;p&gt;A quick aside, a fellow Muxologist, Nick Chadwick, &lt;a href="https://www.youtube.com/watch?v=ZlQfWs_XTvc"&gt;gave a talk&lt;/a&gt; on WebRTC → RTMP at AllThingsRTC in 2019. He goes much deeper into the underlying protocols in that talk than we are here, so if you're interested in the nitty gritty details, that one's highly recommended.&lt;/p&gt;

&lt;p&gt;Given the well-documented path to video teleconferencing that WebRTC provides, the most common solution that people immediately gravitate towards is what's called "rebroadcasting." A server implements the WebRTC API to become a peer, then takes the video feed and publishes it via RTMP. &lt;/p&gt;

&lt;p&gt;This approach is, to put it simply, difficult. The good news is, that path has gotten a little easier in recent months, with projects like &lt;a href="https://pion.ly/"&gt;Pion&lt;/a&gt; maturing and higher level tools like &lt;code&gt;node-webrtc&lt;/code&gt; adding support for &lt;a href="https://github.com/node-webrtc/node-webrtc-examples/tree/master/examples/record-audio-video-stream"&gt;accessing actual video frames&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broadcasting headless Chrome
&lt;/h2&gt;

&lt;p&gt;Nick also mentions this approach in his talk (and &lt;a href="https://github.com/muxinc/chromium_broadcast_demo"&gt;built an example&lt;/a&gt;), but another approach is to simply bypass server-side implementations altogether and use the one that's arguably the most battle-tested and has a wide selection of open-source tooling: Chrome. Yes, that one, the browser.&lt;/p&gt;

&lt;p&gt;Thanks to projects like &lt;a href="https://github.com/puppeteer/puppeteer"&gt;Puppeteer&lt;/a&gt;, the process of programmatically interacting with a headless Chrome instance is pretty straightforward. From there you can build a normal WebRTC experience and use &lt;code&gt;ffmpeg&lt;/code&gt; to broadcast whatever's in your headless Chrome instance via RTMP.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;huge&lt;/em&gt; benefit of this approach is that it allows the developer to effectively build any experience in the user interface. Stream overlays, multiple speakers on a call, video effects, whatever you could build with canvas or the DOM would Just Work™ since it's...well, it's a browser. It's also not &lt;em&gt;that&lt;/em&gt; much additional work on top of building out normal, peer-to-peer chat for that reason.&lt;/p&gt;

&lt;p&gt;The downside of this approach is that you need to have a Chrome instance for every streamer. If you're just looking to stream yourself this isn't a huge issue, but if you're looking to support an arbitrary number of streamers this could become problematic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video over WebSockets
&lt;/h2&gt;

&lt;p&gt;This one is the simplest and, in my opinion, the most fun to hack around on. Yes, as promised, this solution also uses at least one piece of the WebRTC toolchain, &lt;code&gt;getUserMedia()&lt;/code&gt; (the way you request access to the browser's mic and camera). However, once you have the media, instead of delivering the media via WebRTC's protocols, you use the &lt;code&gt;MediaRecorder&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;This allows for similar flexibility to the headless Chrome example: you can render the user's camera to a canvas element and manipulate the video however you'd like there. The &lt;code&gt;MediaRecorder&lt;/code&gt; will fire an event every time it has a "chunk" of video data ready, at which point you send it to the server via the websocket as a binary blob. The server then listens for these data chunks and pipes them into a running &lt;code&gt;ffmpeg&lt;/code&gt; command as they're received.&lt;/p&gt;

&lt;p&gt;The benefit to this approach is that it's much closer to "traditional" applications in terms of running and scaling. You need a persistent WebSocket connection with each streamer, yes, but the requirements of each stream are actually pretty low since we've got &lt;code&gt;ffmpeg&lt;/code&gt; doing as little as possible before publishing the RTMP stream. In fact, this &lt;a href="https://github.com/mmcc/next-streamr"&gt;example application using Next.js&lt;/a&gt; runs just fine on a &lt;a href="https://mmcc-next-streamr.glitch.me/"&gt;Glitch server&lt;/a&gt;. Let's talk about how it works.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/mmcc-next-streamr?path=index.html" alt="mmcc-next-streamr on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  The Client
&lt;/h3&gt;

&lt;p&gt;For the example we used a &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; framework called &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; with a custom Node.js server. &lt;/p&gt;

&lt;p&gt;Before the client can do anything, it needs to request access to the user's camera and microphone by calling &lt;code&gt;getUserMedia&lt;/code&gt; with the requested constraints. Calling this function will prompt the browser to ask the end-user if they'd like to share the requested resources.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This would just ask for access to audio and video, but you can also specify
// what resolution you want from the video if you'd like.
const cameraStream = await navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The call to &lt;code&gt;getUserMedia&lt;/code&gt; returns a promise. which (if the user agrees) will resolve and return the camera stream. That camera stream can then be set as the &lt;code&gt;srcObject&lt;/code&gt; of a video tag, at which point you've got the webcam playing back in the browser window!&lt;/p&gt;

&lt;p&gt;From here, what we're doing in the demo is rendering that video stream to a canvas element using a very similar technique to what we described in our &lt;a href="https://mux.com/blog/canvas-adding-filters-and-more-to-video-using-just-a-browser/"&gt;blog post on manipulating video via the canvas element&lt;/a&gt;. Once we're copying the video over to the canvas element, we can capture that stream, and initialize a new &lt;code&gt;MediaRecorder&lt;/code&gt; instance.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mediaStream = canvasEl.captureStream(30); // 30 frames per second
const mediaRecorder = new MediaRecorder(mediaStream, {
  mimeType: 'video/webm',
  videoBitsPerSecond: 3000000
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The new MediaRecorder object will fire an event every time a blob is ready (&lt;code&gt;ondataavailable&lt;/code&gt;). We can listen for that event, and when we receive it send the data blob right down an open WebSocket connection.&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Listen for the dataavailable event on our mediaRecorder instance&lt;br&gt;
mediaRecorder.addEventListener('dataavailable', e =&amp;gt; {&lt;br&gt;
  ws.send(e.data); // Then send the binary data down the website!&lt;br&gt;
}); &lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The Server&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;The server listens for incoming WebSocket connections, and when a new one is created it initializes a new &lt;code&gt;ffmpeg&lt;/code&gt; process that's streaming to the specified RTMP endpoint. Whenever a new chunk of video comes in via a message, the server pipes that received data to the &lt;code&gt;ffmpeg&lt;/code&gt; process, which in turn broadcasts it via RTMP.&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;webSocketServer.on('connection', (ws) =&amp;gt; {;&lt;br&gt;
  // When a new connection comes in, spawn a new &lt;code&gt;ffmpeg&lt;/code&gt; process&lt;br&gt;
  const ffmpeg = child_process.spawn('ffmpeg', [&lt;br&gt;
    // ... ffmpeg settings ...
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// final argument should be the output, 
// which in this case is our RTMP endpoint
`rtmps://global-live.mux.com/app/${STREAM_KEY}`,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;]);&lt;/p&gt;

&lt;p&gt;// If our ffmpeg process goes away, end the WebSocket connection&lt;br&gt;
  ffmpeg.on('close', (code, signal) =&amp;gt; {&lt;br&gt;
    ws.terminate();&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;ws.on('message', (msg) =&amp;gt; {&lt;br&gt;
    // If we're using this WebSocket for other messages, check &lt;br&gt;
    // and make sure before piping it to our ffmpeg process&lt;br&gt;
    if (Buffer.isBuffer(msg)) {&lt;br&gt;
      ffmpeg.stdin.write(msg);&lt;br&gt;
    }&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;// If the WebSocket connection goes away, clean up the ffmpeg process&lt;br&gt;
  ws.on('close', (e) =&amp;gt; {&lt;br&gt;
    ffmpeg.kill('SIGINT');&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Profit! Kinda.&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;It works! It's fun and fairly simple, with both code and client coming in at &amp;lt; 300 lines of code. It's got the advantage of being easy to interact with the outgoing stream, and it's quick and easy to hack on. You can give it a try now, just go remix the Glitch, specify your own Mux stream key, and try it out.&lt;/p&gt;

&lt;p&gt;However, there are huge drawbacks to the Javascript side of things. For example, modern browsers will de-prioritize the timers on a tab that isn't front-and-center, meaning if the streamer switches to a different tab, the streaming page won't send chunks of video fast enough and eventually the stream will stall. There are ways to ensure that doesn't happen, but most of them will require at least some participation from your streamer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let us help your users go live!
&lt;/h1&gt;

&lt;p&gt;Unless you have a lot of resources to devote for building out an application around going live from the browser we suggest providing your users other tried and true native options or pointing them towards one of the fantastic paid browser options. That being said, we're here to help! If you want help figuring out the best way to let users go live in your application, please reach out.&lt;/p&gt;

&lt;p&gt;[1]: Yes, in practice most applications would want a server for connection negotiation and more, but &lt;em&gt;technically&lt;/em&gt; a simple application could allow users to share the required details via another method.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webrtc</category>
      <category>video</category>
    </item>
  </channel>
</rss>
