<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ video - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ video - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 14 May 2026 22:43:50 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/video/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ The WebCodecs Handbook: Native Video Processing in the Browser  ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever tried to process video in the browser, like for a video editing or streaming app, your options were either to process video on a server (expensive) or to use ffmpeg.js (clunky). With th ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-webcodecs-handbook-native-video-processing-in-the-browser/</link>
                <guid isPermaLink="false">69d6bc21707c1ce7688010d3</guid>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #WebCodecs  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sam Bhattacharyya ]]>
                </dc:creator>
                <pubDate>Wed, 08 Apr 2026 20:35:45 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9b0978d4-7d8c-464c-ade0-07d007f56d92.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever tried to process video in the browser, like for a video editing or streaming app, your options were either to process video on a server (expensive) or to use ffmpeg.js (clunky). With the WebCodecs API, there's now a better way to do this.</p>
<p>WebCodecs is a relatively new API that allows browser applications to process video efficiently with very low-level control.</p>
<p>In the past, if you wanted to build, say, a video-editing app or live-streaming studio or anything that required 'heavy lifting', you needed to build a native desktop application. Many SaaS tools like Canva got around this with server-side video processing, which provided a much better UX, but which is much more complex and expensive.</p>
<p>With WebCodecs, it's now possible to build these apps entirely in the browser, without requiring users to download and install software, and without expensive, complex server infrastructure.</p>
<p>This isn't theoretical. Video Editing tools like Capcut saw an 83% boost in traffic after switching to WebCodecs + WebAssembly [<a href="https://web.dev/case-studies/capcut?hl=en">1</a>]. Utility apps like <a href="https://www.remotion.dev/convert">Remotion Convert</a> and <a href="https://free.upscaler.video/">Free AI Video Upscaler</a> (both open source) process thousands of videos a day with zero server costs and no installation required [<a href="https://web.dev/case-studies/ai-video-upscaler-case-study">2</a>].</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/73f19be2-b746-421c-888f-7431962520d7.png" alt="Remotion Convert" style="display:block;margin:0 auto" width="630" height="566" loading="lazy">

<p>WebCodecs is even being used for entirely new use cases, like generating videos programatically [<a href="https://github.com/remotion-dev/remotion">3</a>].</p>
<p>If you're building any kind of video app, it's worthwhile to at least know about WebCodecs as an option for working with video in the browser.</p>
<p>In this guide, we will:</p>
<ol>
<li><p>Review the basics of Video Processing</p>
</li>
<li><p>Introduce the WebCodecs API</p>
</li>
<li><p>Discuss Muxing + Demuxing to read and write video files</p>
</li>
<li><p>Build our own video conversion utility to convert videos between webm + mp4, and apply basic transformations</p>
</li>
<li><p>Cover some production-level concerns</p>
</li>
<li><p>Discuss additional resources</p>
</li>
</ol>
<p>The goal of this article is to be a practical entry point and introduction to the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API">WebCodecs API</a> for frontend developers. It'll teach you how the API works and what you can do with it. I'll assume you know the basics of Javascript but you don't need to be a senior developer or a video engineer to follow along.</p>
<p>At the end, I'll mention additional learning resources and references. In future tutorials, I'll go more in-depth on specific topics like building a video editor, or doing live-streaming with WebCodecs. But this handbook should provide a solid starting point for what WebCodecs is, what it can do, and how to build a basic application with it.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-primer-on-video-processing">Primer on Video Processing</a></p>
<ul>
<li><p><a href="#heading-video-frames">Video Frames</a></p>
</li>
<li><p><a href="#heading-codecs">Codecs</a></p>
</li>
<li><p><a href="#heading-encoding-amp-decoding">Encoding &amp; Decoding</a></p>
</li>
<li><p><a href="#heading-containers">Containers</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-what-is-webcodecs">What is WebCodecs?</a></p>
<ul>
<li><p><a href="#heading-before-webcodecs">Before WebCodecs</a></p>
</li>
<li><p><a href="#heading-core-api">Core API</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-muxing-and-demuxing">Muxing and Demuxing</a></p>
<ul>
<li><p><a href="#heading-demuxing">Demuxing</a></p>
</li>
<li><p><a href="#heading-muxing">Muxing</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-building-a-video-converter-utility">Building a Video Converter Utility</a></p>
<ul>
<li><p><a href="#heading-transcoding">Transcoding</a></p>
</li>
<li><p><a href="#heading-transformations">Transformations</a></p>
</li>
<li><p><a href="#heading-transform-pipeline">Transform Pipeline</a></p>
</li>
<li><p><a href="#heading-complete-demo">Complete Demo</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-production-concerns">Production Concerns</a></p>
<ul>
<li><p><a href="#heading-codecs-1">Codecs</a></p>
</li>
<li><p><a href="#heading-bit-rate">Bit rate</a></p>
</li>
<li><p><a href="#heading-gpu-vs-cpu">GPU vs CPU</a></p>
</li>
<li><p><a href="#heading-memory">Memory</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-further-resources">Further Resources</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You don't need to be a video engineer to follow along, but you should be comfortable with:</p>
<ul>
<li><p>Core JavaScript, including async/await and callbacks</p>
</li>
<li><p>Basic browser APIs like fetch and the DOM</p>
</li>
<li><p>What a File object is and how file inputs work in HTML</p>
</li>
<li><p>A general sense of what HTML5 is (we'll use it briefly, but won't go deep)</p>
</li>
</ul>
<p>No prior knowledge of video processing, codecs, or media APIs is required — that's what the first half of this handbook covers.</p>
<h2 id="heading-primer-on-video-processing">Primer on Video Processing</h2>
<p>Hold your bunnies, because before getting into WebCodecs, I want to make sure you're aware of what codecs are before we even consider putting codecs on the web.</p>
<h3 id="heading-video-frames">Video Frames</h3>
<p>I presume you know what a video is. Ironically the 'video' below is actually a gif, but you get the idea.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/0e95c1f7-d384-4065-bba9-4dade19ce6f8.gif" alt="Big Buck Bunny, an opensource video" style="display:block;margin:0 auto" width="320" height="180" loading="lazy">

<p>Videos are just a series of images, shown one after the other, in quick succession. Each image is called a <em>Video Frame</em>, and each frame is associated with a timestamp. When a video player plays back the video, it displays each video frame at the time indicated by the timestamp.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/26f534e8-f828-41ec-9bcd-de059916f528.png" alt="Video Frames" style="display:block;margin:0 auto" width="540" height="360" loading="lazy">

<p>Every frame in the video is made of pixels, with a 4K video frame containing approximately 8 million pixels (3840*2160 = 8294400).</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/2c73397d-2d9f-4091-8b32-fa7a5dac115f.png" alt="VideoFrames have pixels" style="display:block;margin:0 auto" width="960" height="540" loading="lazy">

<p>Each pixel itself is actually made of 3 components: a Red, Green, and Blue value (also called RGB value).</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/2fc2b555-265b-4d6a-8670-981e197c6566.svg" alt="RGB Channels" style="display:block;margin:0 auto" width="1152" height="288" loading="lazy">

<p>Each of of the R, G and B color values is stored as an 8-bit integer, ranging from 0 to 255, with the number indicating the intensity of the red, green, or blue color component.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/d9d7ae0b-e67b-4cbb-b21a-867108cd3303.png" alt="uint8 color channel" style="display:block;margin:0 auto" width="960" height="384" loading="lazy">

<p>Combining the intensity of each of the R, G, and B components lets you represent any arbitrary color on the color spectrum:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/adbeb63b-20b8-4ddb-9b7f-83c88f86e09d.png" alt="RGB Color value examples" style="display:block;margin:0 auto" width="1152" height="1152" loading="lazy">

<p>So for each pixel, we need 3 bytes of data: 1 byte for each of the R, G, and B color values (1 byte = 8 bits). A 4K video frame therefore would contain ~25 Megabytes of data.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/2fc2b555-265b-4d6a-8670-981e197c6566.svg" alt="RGB Channnels" style="display:block;margin:0 auto" width="1152" height="288" loading="lazy">

<p>At 30 frames per second (a typical frame rate), a 1 hour, 4K video would be around <strong>746 Gigabytes of data</strong>. If you've ever downloaded a large video or recorded HD video with your phone camera, you'll know that video files can be large, but they're never <em>that</em> large.</p>
<p>In reality, actual video files you might watch on YouTube, record on your phone camera, or download from the internet are ~100x smaller than that. The reason actual video files are much smaller is because of <em>video compression</em>, a family of very sophisticated algorithms that help reduce the data by ~100x.</p>
<p>Without this video compression, you wouldn't be able to record more than 10 minutes of video on the latest high-end smartphones, and you wouldn't be able to stream anything HD on a high-end home internet connection.</p>
<p>As sophisticated as our modern devices and internet connections are, without aggressive video compression, we wouldn't be able to watch, record, or stream anything in HD.</p>
<h3 id="heading-codecs">Codecs</h3>
<p>A <em>codec</em> is a fancy word for a video compression algorithm. There are a few established codecs / compression algorithms, such as:</p>
<ul>
<li><p><code>h264</code>: The most common codec. If you see an mp4 file, it most likely uses the h264 codec.</p>
</li>
<li><p><code>vp9</code>: An open source codec used commonly by YouTube and in video conferencing, often found in webm files.</p>
</li>
<li><p><code>av1</code>: A new open source codec, increasingly being used by platforms like YouTube and Netflix.</p>
</li>
</ul>
<p>How these algorithms work is too complex and out of scope for this handbook. But at a very high level, here are some major ways these algorithms compress video:</p>
<h4 id="heading-removing-detail">Removing detail</h4>
<p>All these algorithms use a technique called the Discrete Cosine Transform to "remove details". As you remove "detail" from the video frame, the frame starts looking "blockier". This technique is so effective, though, that you can compress a video frame by ~10x before the differences start becoming visible to the human eye.</p>
<p>For the curious, you can see <a href="https://www.youtube.com/watch?v=n_uNPbdenRs">this video</a> by Computerphile on how the DCT algorithm works.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/f2b3eac4-c36a-4943-a176-4f2af72cfbac.png" alt="DCT algorithm removing details" style="display:block;margin:0 auto" width="1920" height="480" loading="lazy">

<h4 id="heading-encoding-frame-differences">Encoding frame differences</h4>
<p>When you actually look at a sequence of video frames, you'll notice that visually they're quite similar, with only small portions of the video changing, depending on how much movement there is.</p>
<p>These codecs/compression algorithms use sophisticated math and computer vision techniques to encode just the differences between frames,.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/40d309dd-84fc-4018-8da4-1438bbfb8df6.png" alt="Frame Differences" style="display:block;margin:0 auto" width="960" height="288" loading="lazy">

<p>You therefore only need to send the first frame (a <em>Key Frame</em>) – then for subsequent frames you can send the "frame differences", also called <em>Delta Frames</em>, to reconstruct the each full frame.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/66bbe03f-c394-477d-a3c2-64b571c39348.png" alt="Key Frames vs Delta frames" style="display:block;margin:0 auto" width="960" height="288" loading="lazy">

<p>In practice, for an hour long video, we don't just encode the first frame and store millions of delta frames. Instead, algorithms encode every 60th frame or so as a Key Frame, and then the next 59 frames are delta frames.</p>
<p>This technique is also highly effective, reducing data used by another ~10x. The distinction between <em>Key Frames</em> and <em>Delta Frames</em> is one of the few bits of "how these algorithms work" that you actually need to be aware of.</p>
<p>There's a number of other details and compression techniques that go into these compression algorithms that are out of scope for an intro article.</p>
<h3 id="heading-encoding-amp-decoding">Encoding &amp; Decoding</h3>
<p>For video compression to work, we need to be able to both compress video (turn raw video into compressed binary data) and then decompress video (turn the compressed binary data back into raw video frames).</p>
<p>Turning raw video frames into compressed binary data is called <em>encoding</em>, and turning compressed binary data back into raw video frames is called <em>decoding.</em> The word <em>codec</em> is just an abbreviation for "encode decode".</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/843e75e0-4453-4e3e-91d5-a441f1a8e2ff.png" alt="VideoEncoder and VideoDecoder" style="display:block;margin:0 auto" width="960" height="384" loading="lazy">

<p>From a practical, developer perspective, you don't need to know how these codecs work, but you do need to know that:</p>
<ol>
<li><p>There are different video codecs, like <code>h264</code>, <code>vp9</code>, and <code>av1</code></p>
</li>
<li><p>When you encode a video with a codec (like <code>h264</code>), you need a video player that can support the same codec to play back the video.</p>
</li>
<li><p>Encoding video takes a lot more computation than decoding video, so playing 4K video on a low-end phone is fine, but encoding 4K video on it would be super slow.</p>
</li>
<li><p>Most consumer devices (phones, laptops) have specialized chips designed specifically for encoding and decoding video, making encoding/decoding much faster than if run on the CPU like a normal software program. This is called <em>hardware acceleration.</em></p>
</li>
</ol>
<p>In practice, there are only a handful of video codecs, because the entire world needs to agree on standards, so that video recorded on an iPhone can be played back on a windows device.</p>
<h3 id="heading-containers">Containers</h3>
<p>Most people haven't heard of <code>h264</code> or <code>vp9</code>. When you think of video files, you typically think of file formats like MP4 or MKV. These are also relevant, but they're a separate thing called containers.</p>
<p>A video file typically has encoded audio, encoded video, and metadata about the video file. A file format like MP4 describes a specific format for storing the encoded audio and video data, as well as the metadata.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/961e7177-e331-4aa2-acdc-9d6732f18c56.png" alt="Video Container" style="display:block;margin:0 auto" width="624" height="720" loading="lazy">

<p>Video compression software stores the encoded audio/video and metadata into a file according to the file format / specs. This is called <em>muxing.</em></p>
<p>Likewise, video players follow the file format specs to read the metadata and find the encoded audio/video. This is called <em>demuxing</em>.</p>
<p>When compressing a video file, you need to both <em>encode</em> it and <em>mux</em> it (in that order). These are two separate stages of the process. Likewise, when playing a video file, you need to both <em>demux</em> it and then <em>decode</em> it (in that order).</p>
<p>When a video player opens, say, an mp4 file, the logic flow is as follows:</p>
<ul>
<li><p>Ok, the file ends in .mp4, so it must be an mp4 file. Let me load the library for parsing mp4 files, and parse then parse file.</p>
</li>
<li><p>Great, I've parsed the mp4 file, I now have the metadata and know where in the byte offsets are to fetch the encoded audio and video.</p>
</li>
<li><p>I'll start fetching the first encoded video frames, decode them, and start displaying the decoded video frame to the user.</p>
</li>
</ul>
<p>If you ever see a "video file is corrupt" message from a video player, it's likely that the video file doesn't follow the file format spec and there was an error while trying the parse / demux the video.</p>
<h2 id="heading-what-is-webcodecs">What is WebCodecs?</h2>
<p>Now that we've covered codecs, let's put them on the Web.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/3fc9d220-f397-479a-9517-6b8dc94aaa6b.png" alt="WebCodecs = Web + Codecs" style="display:block;margin:0 auto" width="960" height="288" loading="lazy">

<p>WebCodecs is an API that allows frontend developers to encode and decode video in the browser efficiently (using hardware acceleration), and with very low level control (encode/decode on a frame by frame basis).</p>
<p>The hardware acceleration bit is important, as you can't just poly fill or re-implement the API yourself. WebCodecs gives direct access to specialized hardware for encoding/decoding, making it as performant as a desktop video app.</p>
<h3 id="heading-before-webcodecs">Before WebCodecs</h3>
<p>It's worth taking a moment to understand why WebCodecs exists. Before the WebCodecs API existed, there were several alternatives you could use for video operations in the browser.</p>
<ul>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement">HTMLVideoElement</a>: You can still create a element and use it for decoding a video. It's easy to use, but you lack frame level control. Your only control is setting the 'video.currrentTime' property and waiting for it to seek, often leading to dropped/missing frames.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder">Media Recorder API</a>: Essentially allows you to 'screen record' any canvas element or video stream. While it works, it's functionally equivalent to screen recording Adobe Premeire pro instead of clicking render. For editing scenarios, you lose frame level control and can only process video at real-time speed.</p>
</li>
<li><p><a href="https://github.com/Kagami/ffmpeg.js/">FFMPEG.js</a>: A port of the popular video processing tool ffmpeg, which runs ffmpeg in the browser. Many tools used this in the past, but it lacks hardware acceleration, making it much slower than WebCodecs. It also has file size restrictions stemming from the fact that it runs in WebAssembly, making it difficult to work with videos that are larger than 100 MB.</p>
</li>
</ul>
<p>WebCodecs was built and released in 2021 to enable low-level, hardware accelerated video decoding and encoding. It's great for high-performance streaming and video editing, which were use cases not well-served by the existing APIs.</p>
<h3 id="heading-core-api">Core API</h3>
<p>The core API for WebCodecs consists of two new "data types", the <em>VideoFrame</em> and <em>EncodedVideoChunk</em>, as well as the <em>VideoEncoder</em> and <em>VideoDecoder</em> interfaces.</p>
<h4 id="heading-videoframe">VideoFrame</h4>
<p>The Javascript <em>VideoFrame</em> object conceptually contains both pixel data and metadata about the video frame.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/bd341249-fab6-4c76-85b4-dd21d8de10f6.svg" alt="VideoFrame object" style="display:block;margin:0 auto" width="1920" height="816" loading="lazy">

<p>You can actually create a new <em>VideoFrame</em> object from any image source, as long as you include the metadata:</p>
<pre><code class="language-javascript">const bitmapFrame = new VideoFrame(imgBitmap, {timestamp: 0});

const imageFrame = new VideoFrame(htmlImageEl, {timestamp: 0});

const videoFrame = new VideoFrame(htmlVideoEl, {timestamp: 0});

const canvasFrame = new VideoFrame(canvasEl, {timestamp: 0});
</code></pre>
<p>For a video editing app, for example, you would typically perform image editing operations on each frame on a canvas, and then you would grab each <em>VideoFrame</em> from the canvas.</p>
<p>You can also draw a <em>VideoFrame</em> to a canvas using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">Canvas 2D rendering context</a>:</p>
<pre><code class="language-typescript">ctx.drawImage(frame, 0, 0);
</code></pre>
<p>You would typically do this when rendering / playing back a video in the browser.</p>
<h4 id="heading-encodedvideochunk">EncodedVideoChunk</h4>
<p>An <em>EncodedVideoChunk</em> is just the compressed version of a <em>VideoFrame,</em> containing the binary data as well as the same metadata as the frame.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/d20b9306-03ae-48a8-b10a-f462f4a620e2.svg" alt="EncodedVideoChunk" style="display:block;margin:0 auto" width="1728" height="816" loading="lazy">

<p>You would typically get <em>EncodedVideoChunks</em> from a library which extracts them from a <em>File</em> object.</p>
<pre><code class="language-typescript">import { getVideoChunks } from 'webcodecs-utils'

const chunks = &lt;EncodedVideoChunk[]&gt; await getVideoChunks(&lt;File&gt; file);
</code></pre>
<p>Alternatively, it's the output you get from a <em>VideoEncoder</em> object.</p>
<p>There's not much useful stuff you can do with <em>EncodedVideoChunks</em> – it's just the binary data that you read from files, write to files, or stream over the internet.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/c5778116-44ec-4fb7-8f35-6ebd2f2a8d15.gif" alt="Video streaming with encode and decode" style="display:block;margin:0 auto" width="800" height="560" loading="lazy">

<p>The value in <em>EncodedVideoChunk</em> is that it's ~100x smaller than raw video data, which is why you'd send <em>EncodedVideoChunks</em> instead of raw video when streaming (and writing to a file).</p>
<h4 id="heading-videoencoder">VideoEncoder</h4>
<p>A <em>VideoEncoder</em> turns <em>VideoFrame</em> objects into <em>EncodedVideoChunk</em> objects.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/612a6f6a-04c6-4f24-aebd-074f232cd06e.svg" alt="VideoEncoder" style="display:block;margin:0 auto" width="960" height="384" loading="lazy">

<p>The core API looks something like this, where you define the callback where the <em>VideoEncoder</em> returns <em>EncodedVideoChunk</em> objects.</p>
<pre><code class="language-typescript">const encoder = new VideoEncoder({
    output: function(chunk: EncodedVideoChunk, meta: any){
        // Do something with the chunk
    },
    error: function(e: any)=&gt; console.warn(e);
});
</code></pre>
<p>Keep in mind that this is an async process, and not even a typical async process. You can't just treat this as a per-frame operation.</p>
<pre><code class="language-javascript">// Does not work like this
const frame  = await encoder.encode(chunk);
</code></pre>
<p>This is because of how video encoding actually works under the hood. So you have to accept that the outputs are returned via callback, and you get the outputs when you get them.</p>
<p>Once you define your encoder, you can then configure the <em>VideoEncoder</em> with your choice of codec (we'll get to this), as well as other parameters like width, height, framerate and bitrate.</p>
<pre><code class="language-typescript">encoder.configure({
    'codec': 'vp9.00.10.08.00', // We'll get to this
     width: 1280,
     height: 720,
     bitrate: 1000000 //1 MBPS,
     framerate: 25
});
</code></pre>
<p>You can then start encoding frames. Here we assume we already have <em>VideoFrame</em> objects, and we make every 60th frame a <em>Key Frame</em>.</p>
<pre><code class="language-typescript">for (let i=0; i &lt; frames.length; frames++){
    encoder.encode(frames[i], {keyFrame: i%60 ==0})
}
</code></pre>
<h4 id="heading-videodecoder">VideoDecoder</h4>
<p>The Video Decoder does the reverse, turning <em>EncodedVideoChunk</em> objects into <em>VideoFrame</em> objects.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/47258dd5-0a7b-479a-8729-6f384ff8bc4b.svg" alt="VideoDecoder" style="display:block;margin:0 auto" width="960" height="384" loading="lazy">

<p>Here's a simplified example of how to set up the <em>VideoDecoder.</em> First, extract the <em>EncodedVideoChunk</em> objects and the decoder config from the video file. Here, we don't choose the config&nbsp;– the config was chosen by whoever encoded the file. When decoding, we extract the config from the file.</p>
<pre><code class="language-typescript">import { demuxVideo } from 'webcodecs-utils';

const {chunks, config} = await demuxVideo(&lt;File&gt; file);
</code></pre>
<p>Next, we set up the <em>VideoDecoder</em> by specifying the callback when <em>VideoFrame</em> objects are generated, and we configure it with the config.</p>
<pre><code class="language-typescript">const decoder = new VideoDecoder({
    output: function(frame: VideoFrame){
        //do something with the VideoFrame
    },
    error: function(e: any)=&gt; console.warn(e);
});

decoder.configure(config)
</code></pre>
<p>Again, like with <em>VideoEncoder</em>, it returns frames in a callback. Finally we can start decoding chunks.</p>
<pre><code class="language-typescript">for (const chunk of chunks){
    decoder.decode(chunk);
}
</code></pre>
<h4 id="heading-putting-it-all-together">Putting it all together</h4>
<p>At its core, the WebCodecs API is just the two data types (<em>EncodedVideoChunk, VideoFrame)</em> and the <em>VideoEncoder</em> and <em>VideoDecoder</em> interfaces which convert between the two data types.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/444553ad-58af-4b0e-ba46-d8910d3d548b.svg" alt="The core of WebCodecs" style="display:block;margin:0 auto" width="960" height="384" loading="lazy">

<p>Keep in mind that the WebCodecs API doesn't actually work with video files. It only applies the encoding and decoding, and <em>EncodedVideoChunk</em> objects just represent binary data.</p>
<p>Reading video files and writing video files are their own, separate thing called muxing/demuxing.</p>
<h2 id="heading-muxing-and-demuxing">Muxing and Demuxing</h2>
<p>To write to a video file, you'll also need to <em>mux</em> the video. And to play a video file, you need to <em>demux</em> the video. This involves following the file format of the video container, parsing the video file (in the case of demuxing), or placing encoded video data in the right place in the file you are writing to (muxing).</p>
<p>Muxing and Demuxing are not included in the WebCodecs API, so you'll need to use a separate library to handle muxing and demuxing.</p>
<h3 id="heading-demuxing">Demuxing</h3>
<p>To play a video back in the browser, we need to both <em>demux</em> the video and <em>decode</em> the video, in that order.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/6e0459e9-f960-43dd-a1e3-a214091c439e.png" alt="Demuxing and decoding" style="display:block;margin:0 auto" width="960" height="720" loading="lazy">

<p>There are several libraries you can use to demux videos, including <a href="http://mediabunny.dev/">MediaBunny</a> or <a href="https://github.com/bilibili/web-demuxer">web-demuxer</a>. For the purposes of this tutorial, I put a very simplified wrapper around these libraries and exposed it in the <a href="https://www.npmjs.com/package/webcodecs-utils">webcodecs-utils</a> package, so that demuxing is a very simple 2-liner:</p>
<pre><code class="language-typescript">import { demuxVideo } from 'webcodecs-utils'
const {chunks, config} = await demuxVideo(file);
</code></pre>
<p>This reads the entire video into memory, so don't do this in practice. But it's helpful in making a simple, readable hello world for WebCodecs.</p>
<p>The following snippet will take in a video file (<em>File</em> object), decode it, and paint the result to a canvas. Here, we get the frames from the output callback, and run the draw calls directly from the callback.</p>
<pre><code class="language-typescript">import { demuxVideo } from 'webcodecs-utils'

async function playFile(file: File){

    const {chunks, config} = await demuxVideo(file);
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const decoder = new VideoDecoder({
        output(frame: VideoFrame) {
            ctx.drawImage(frame, 0, 0);
            frame.close()
        },
        error(e) {}
    });


    decoder.configure(config);

    for (const chunk of chunks){
        decoder.decode(chunk)
    }

}
</code></pre>
<p>Here's our super barebones demo for playing back an actual video:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/OPRErmj" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>For a more 'correct' demuxing example, here is what demuxing looks like with MediaBunny, where you can extract chunks in an iterative fashion.</p>
<pre><code class="language-typescript">import { EncodedPacketSink, Input, ALL_FORMATS, BlobSource } from 'mediabunny';

const input = new Input({
  formats: ALL_FORMATS,
  source: new BlobSource(&lt;File&gt; file),
});

const videoTrack = await input.getPrimaryVideoTrack();
const sink = new EncodedPacketSink(videoTrack);

for await (const packet of sink.packets()) {
  const chunk = &lt;EncodedVideoChunk&gt; packet.toEncodedVideoChunk();
}
</code></pre>
<h3 id="heading-muxing">Muxing</h3>
<p>To write a video file, you not only need to encode it (with the <em>VideoEncoder</em>) you also need to <em>mux</em> it. This involves taking the encoded chunks and placing them in the right place in the output binary file that you're writing to.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/176ef792-e761-46d5-badf-58fd08d78552.png" alt="Muxing and Encoding" style="display:block;margin:0 auto" width="960" height="624" loading="lazy">

<p>Again, you need a library to mux videos ( <a href="http://mediabunny.dev/">MediaBunny</a>), but for demo purposes I created a super simple wrapper. Here we define a super basic ExampleMuxer.</p>
<pre><code class="language-typescript">import { ExampleMuxer } from 'webcodecs-utils'

const muxer = new ExampleMuxer('video');

for (const chunk of encodedChunks){
    muxer.addChunk(chunk);
}

const outputBlob = await muxer.finish();
</code></pre>
<p>As a full encoding + muxing demo, we'll create an encoder, and we'll set it to mux the output encoded chunks as soon as they are returned.</p>
<pre><code class="language-typescript">const encoder = new VideoEncoder({
    output: function(chunk, meta){
        muxer.addChunk(chunk, meta);
    },
    error: function(e){}
})

encoder.configure({
    'codec': 'avc1.4d0034', // We'll get to this
     width: 1280,
     height: 720,
     bitrate: 1000000 //1 MBPS,
     framerate: 25
});
</code></pre>
<p>We'll then define a canvas animation, which will draw the current frame number to the screen, just to prove it's working.</p>
<pre><code class="language-typescript">const canvas = new OffscreenCanvas(640, 360);
const ctx = canvas.getContext('2d');
const TOTAL_FRAMES=300;
let frameNumber = 0;
let chunksMuxed = 0;
const fps = 30;


function renderFrame(){
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'white';
    ctx.font = `bold ${Math.min(canvas.width / 10, 72)}px Arial`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(`Frame ${frameNumber}`, canvas.width / 2, canvas.height / 2);
}
</code></pre>
<p>Finally we'll create the encode loop, which will draw the current frame, and then encode it.</p>
<pre><code class="language-typescript">
let flushed = false;

async function encodeLoop(){

    renderFrame();

    const frame = new VideoFrame(canvas, {timestamp: frameNumber/fps*1e6});
    encoder.encode(frame, {keyFrame: frameNumber %60 ===0});
    frame.close();

    frameNumber++;

    if(frameNumber === TOTAL_FRAMES) {
        if (!flushed) encoder.flush();
    }
    else return requestAnimationFrame(encodeLoop);
}
</code></pre>
<p>Putting it all together, you can encode the canvas animation to a video file with frame-level accuracy.</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/KwgebEJ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>You can download the video and use any video inspection tool to verify that every single frame number is included.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/776d6db5-f67c-4538-8e7d-f61e35e698ce.png" alt="Videos with frame level accuracy" style="display:block;margin:0 auto" width="915" height="630" loading="lazy">

<p>This is one of the critical distinctions that separates this from other web APIs like <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder">MediaRecorder</a> which can also encode video, but has no frame-level accuracy. WebCodecs makes sure that you can control and guarantee the consistency of each frame.</p>
<p>Finally, a proper full, muxing example using MediaBunny would look like this:</p>
<pre><code class="language-typescript">import {
  EncodedPacket,
  EncodedVideoPacketSource,
  BufferTarget,
  Mp4OutputFormat,
  Output
} from 'mediabunny';

async function muxChunks(chunks: EncodedVideoChunk[]): Promise &lt;Blob&gt;{

    const output = new Output({
        format: new Mp4OutputFormat(),
        target: new BufferTarget(),
    });

    const source = new EncodedVideoPacketSource('avc');
    output.addVideoTrack(source);

    await output.start();

    for (const chunk of chunks){
        source.add(EncodedPacket.fromEncodedChunk(chunk))
    }

    await output.finalize();
    const buffer = &lt;ArrayBuffer&gt; output.target.buffer;
    return new Blob([buffer], { type: 'video/mp4' });

});
</code></pre>
<h2 id="heading-building-a-video-converter-utility">Building a Video Converter Utility</h2>
<p>Now that we've covered the basics of WebCodecs as well as Muxing, we'll move towards actually building an MVP of something useful: a video converter utility. We'll be able to use it to convert between mp4 and webm, and do some basic operations like resizing and flipping the video.</p>
<h3 id="heading-transcoding">Transcoding</h3>
<p>Before we do resizing and flipping, let's first handle a basic conversion decoding a video, and encoding the video to a new format. This is called transcoding.</p>
<p>To transcode video, we need to set up a pipeline with the following processes:</p>
<ul>
<li><p><strong>Demuxing</strong>: Read <em>EncodedVideoChunks</em> from a video file</p>
</li>
<li><p><strong>Decoding</strong>: Convert <em>EncodedVideoChunks</em> to <em>VideoFrames</em></p>
</li>
<li><p><strong>Encoding</strong>: Convert <em>VideoFrames</em> to new <em>EncodedVideoChunks</em></p>
</li>
<li><p><strong>Muxing</strong>: Write the <em>EncodedVideoChunks</em> to a new video file</p>
</li>
</ul>
<p>Our pipeline looks something like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/f06e8516-5498-44bd-9e68-e048afb303e9.png" alt="Transcoding pipeline" style="display:block;margin:0 auto" width="960" height="576" loading="lazy">

<p>Using everything we've covered in this article up until now, we could build a full working demo with just <em>VideoEncoder</em> and <em>VideoDecoder</em> as discussed. But then state management and tracking frames becomes complicated and error prone.</p>
<p>We're going to add one more abstraction, using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams API</a>, which will make our pipeline look like the below. It ties directly to our mental model of our pipeline and simplifies a ton of details like state management.</p>
<pre><code class="language-javascript">const transcodePipeline = demuxerReader
    .pipeThrough(new VideoDecoderStream(videoDecoderConfig))
    .pipeThrough(new VideoEncoderStream(videoEncoderConfig))
    .pipeTo(createMuxerWriter(muxer));

await transcodePipeline;
</code></pre>
<p>To do this, we'll create a TransformStream for the <em>VideoDecoder</em> and <em>VideoEncoder.</em></p>
<pre><code class="language-typescript">class VideoDecoderStream extends TransformStream&lt;{ chunk: EncodedVideoChunk; index: number }, { frame: VideoFrame; index: number }&gt; {
  constructor(config: VideoDecoderConfig) {
    let pendingIndices: number[] = [];
    super(
      {
        start(controller) {
          decoder = new VideoDecoder({
            output: (frame) =&gt; {
              const index = pendingIndices.shift()!;
              controller.enqueue({ frame, index });
            },
            error: (e) =&gt; controller.error(e),
          });

          decoder.configure(config);
        },

        async transform(item, controller) {
          pendingIndices.push(item.index);
          decoder.decode(item.chunk);
        },

        async flush(controller) {
          await decoder.flush();
          if decoder.state !== 'closed' decoder.close();
        },
      }
    );
  }
}
</code></pre>
<p>I won't bore you with the full code, but I've packaged these utilities in the webcodecs-utils package, which can be used as such:</p>
<pre><code class="language-typescript">import {
  SimpleDemuxer,
  VideoDecodeStream,
  VideoEncodeStream,
  SimpleMuxer,
} from "webcodecs-utils";
</code></pre>
<p>Our code for transcoding a file then becomes this:</p>
<pre><code class="language-typescript">const demuxer = new SimpleDemuxer(videoFile);
await demuxer.load();
const decoderConfig = await demuxer.getVideoDecoderConfig();

const encoderConfig = {/*Whatever we decide*/};

// Set up muxer
const muxer = new SimpleMuxer({ video: "avc" });

// Build the upscaling pipeline
await demuxer.videoStream()
  .pipeThrough(new VideoDecodeStream(decoderConfig))
  .pipeThrough(new VideoEncodeStream(encoderConfig))
  .pipeTo(muxer.videoSink());

// Get output
const blob = await muxer.finalize();
</code></pre>
<p>For this intermediate demo, just to actually get transcoding to work, we'll download a <a href="https://katana.video/files/hero-small.mp4">pre-built file</a>, and we'll introduce a toggle to output an mp4 file (using <code>h264)</code> or a webm file (using <code>vp9</code>).</p>
<p>We'll use <code>avc1.4d0034</code> for h264 (most widely supported h264 codec string) and <code>vp09.00.40.08.00</code> for vp9 (most widely supported vp9 string).</p>
<p>Here's a basic transcoding demo on CodePen:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/YPGvBgO" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<h3 id="heading-transformations">Transformations</h3>
<p>If we want to do any kind of transformations to the video, like flips, crops, rotations, resizing, and so on, we can't just work with pure <em>VideoFrame</em> objects.</p>
<p>The simplest way to accomplish this would be to introduce a Canvas element, where we'll use a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">2d Canvas Context</a> to manipulate our source frame and draw that to a canvas.</p>
<pre><code class="language-typescript">const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext('2d');

// Very easy to do transformations
ctx.drawImage(sourceFrame, 0, 0);
</code></pre>
<p>We'll then use the Canvas as a source image for our output video frame.</p>
<pre><code class="language-typescript">const outFrame = new VideoFrame(canvas, {timestamp: sourceFrame.timestamp});
</code></pre>
<p>To apply a resize operation, we'll first set the canvas dimensions to our output height and width.</p>
<pre><code class="language-typescript">const canvas = new OffscreenCanvas(outputWidth, outputHeight);
const ctx = canvas.getContext('2d');

// Resize sourceFrame to fit output dimensions
ctx.drawImage(sourceFrame, 0, 0, outputWidth, outputHeight);
</code></pre>
<p>To apply a horizontal flip operation with canvas2d, we can do the following:</p>
<pre><code class="language-typescript">ctx.scale(-1, 1);
ctx.translate(-outputWidth, 0);
ctx.drawImage(sourceFrame, 0, 0, outputWidth, outputHeight);
</code></pre>
<p>You can create a full render function that applies these transformations which looks like this:</p>
<pre><code class="language-typescript">function render(videoFrame, outW, outH, flipped) {

  canvas.width  = outW;
  canvas.height = outH;

  if (flipped) {
    ctx.scale(-1, 1);
    ctx.translate(-outW, 0);
  }
  ctx.drawImage(videoFrame, 0, 0, outW, outH);

}
</code></pre>
<p>Here's an interactive demo of what these transformations look like:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/WbGymNQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<h3 id="heading-transform-pipeline">Transform Pipeline</h3>
<p>With these transformations, we need to adjust our pipeline to include a transformation step. It will take in a <em>VideoFrame</em>, apply the transforms, and return a transformed frame.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/0111f887-1f27-4436-a68e-ec91b2dd9959.svg" alt="Transcoding pipeline with transforms" style="display:block;margin:0 auto" width="1344" height="576" loading="lazy">

<p>In the webcodecs-utils package, there is a VideoProcessStream object for this purpose, which takes in an async function which takes in a <em>VideoFrame</em> and returns a <em>VideoFrame:</em></p>
<pre><code class="language-typescript">import { VideoProcessStream} from "webcodecs-utils";
 
new VideoProcessStream(async (frame) =&gt; {
      // Apply transformations
      return procesedFrame;
    }),
</code></pre>
<p>So to apply our transformations, we can set it up as so:</p>
<pre><code class="language-typescript">import { VideoProcessStream} from "webcodecs-utils";
 

const canvas = new OffscreenCanvas(outW, outH);
const ctx = canvas.getContext('2d');

const processStream = new VideoProcessStream(async (frame) =&gt; {
  
  if (flipped) {
    ctx.scale(-1, 1);
    ctx.translate(-outW, 0);
  }
  ctx.drawImage(frame, 0, 0, outW, outH);

  return new VideoFrame(canvas, {timestamp: frame.timestamp});

});
</code></pre>
<p>And then our full pipeline looks like this:</p>
<pre><code class="language-typescript">const demuxer = new SimpleDemuxer(videoFile);
await demuxer.load();
const decoderConfig = await demuxer.getVideoDecoderConfig();

const encoderConfig = {/*Whatever we decide*/};

// Set up muxer
const muxer = new SimpleMuxer({ video: "avc" });

// Build the upscaling pipeline
await demuxer.videoStream()
  .pipeThrough(new VideoDecodeStream(decoderConfig))
  .pipeThrough(processStream) // Just defined this
  .pipeThrough(new VideoEncodeStream(encoderConfig))
  .pipeTo(muxer.videoSink());

// Get output
const blob = await muxer.finalize();
</code></pre>
<p>Here's a full working demo with the process pipeline:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/PwGaLPM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<h3 id="heading-complete-demo">Complete Demo</h3>
<p>Now, for the complete tool, we'll make some key changes:</p>
<ul>
<li><p>You can upload your own video</p>
</li>
<li><p>We'll preview the transformations by extracting a frame</p>
</li>
<li><p>We'll add progress measurement</p>
</li>
</ul>
<p>For the input, that's trivial:</p>
<pre><code class="language-html">&lt;input type="file" onchange="handler(event)" /&gt;
</code></pre>
<p>For frame previews, we could use WebCodecs to generate a preview, but because the preview doesn't need frame-level accuracy or high performance, it's easier to just use the HTML5 VideoElement to grab a video frame from the source file.</p>
<pre><code class="language-javascript">async function getFirstFrame(file) {
  const video = document.createElement("video");
  video.src = URL.createObjectURL(file);
  video.muted = true;

  await new Promise((resolve) =&gt; video.addEventListener("loadeddata", resolve, { once: true }));
  video.currentTime = 0;
  await new Promise((resolve) =&gt; video.addEventListener("seeked", resolve, { once: true }));

  return new VideoFrame(video, {timestamp: 0});
}
</code></pre>
<p>Finally, we can calculate progress in the process function by using the frame timestamp / the video duration.</p>
<pre><code class="language-typescript">const {duration} = await demuxer.getMediaInfo();


const processStream = new VideoProcessStream(async (frame) =&gt; {
  
  if (flipped) {
    ctx.scale(-1, 1);
    ctx.translate(-outW, 0);
  }
  ctx.drawImage(frame, 0, 0, outW, outH);

   // Frame timestamps are in microseconds, duration in seconds
  const progress = frame.timestamp/(duration*1e6); 

  return new VideoFrame(canvas, {timestamp: frame.timestamp});

});
</code></pre>
<p>Putting this all together, we can finally put together a full working video converter utility:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/Sam-Bhattacharyya/embed/WbGymaj" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>And that's it! We've built an MVP of something actually useful with WebCodecs 🎉, with Demuxing, Decoding, Canvas Transforms, Encoding, and Muxing.</p>
<p>The only difference between this and a full-fledged browser editing suite like Capcut is the scale and scope of transformations. But the video processing logic would be nearly identical.</p>
<h2 id="heading-production-concerns">Production Concerns</h2>
<p>It's great that we've been able to create something useful, but before we wrap up, it's important to cover some production-level concerns.</p>
<h3 id="heading-codecs">Codecs</h3>
<p>You might have noticed strings like <code>vp09.00.10.08</code> in the demos, but I glossed over the details. We'll cover that now:</p>
<p>First, WebCodecs works with specific codec strings like <code>vp09.00.10.08</code>, not just '<code>vp9</code>'. The following won't work:</p>
<pre><code class="language-plaintext">const codec = VideoEncoder({
    codec: 'vp9', //This won't work!
    //...
})
</code></pre>
<p>As discussed previously, when decoding video, you don't really get a choice of codec. The video is already encoded, and so you need to get the codec from the video, as shown in the previous demos.</p>
<p>The demuxing libraries mentioned will identify the correct codec string, so you don't need to worry about that.</p>
<pre><code class="language-typescript">const decoderConfig = await demuxer.getVideoDecoderConfig();
//decoderConfig.codec = exact codec string for the video
</code></pre>
<p>When encoding a video, you can can choose your codec. Some people care a lot about codec choice, but from a very practical, pragmatic perspective, these rules of thumb should work for most developers:</p>
<ul>
<li><p>If the videos your app generates will be downloaded by users and/or you want to output mp4 files, use <code>h264</code>.</p>
</li>
<li><p>If the videos generated are for internal use or you control video playback, and you don't care about format, use <code>vp9</code> with webm (open source, better compression, most widely supported codec).</p>
</li>
<li><p>For most apps, these two options will cover you — deeper codec selection is a rabbit hole you don't need to go down yet.</p>
</li>
</ul>
<p>Once you have a codec family chosen, you need to choose a specific codec string such as <code>avc1.42001f</code>.</p>
<p>The other numbers in the string specify certain codec parameters which are not as important from a developer perspective. If your goal is maximum compatibility, here's your cheat sheet for what codec strings to use</p>
<h5 id="heading-h264-for-mp4-files"><strong>h264</strong> (for mp4 files)</h5>
<ul>
<li><p><code>avc1.42001f</code> - base profile, most compatible, supports up to 720p (<a href="https://webcodecsfundamentals.org/codecs/avc1.42001f.html">99.6% support</a>)</p>
</li>
<li><p><code>avc1.4d0034</code> - main profile, level 5.2 (supports up to 4K) (<a href="https://webcodecsfundamentals.org/codecs/avc1.4d0034.html">98.9% support</a>)</p>
</li>
<li><p><code>avc1.42003e</code> - base profile, level 6.2 (supports up to 8k) (<a href="https://webcodecsfundamentals.org/codecs/avc1.42003e.html">86.8% support</a>)</p>
</li>
<li><p><code>avc1.64003e</code> - high profile - level 6.2 (supports up to 8k) (<a href="https://webcodecsfundamentals.org/codecs/avc1.64003e.html">85.9% support</a>)</p>
</li>
</ul>
<h5 id="heading-vp9-for-webm-files"><strong>vp9</strong> (for webm files)</h5>
<ul>
<li><p><code>vp09.00.10.08.00</code> - basic, most compatible, level 1 (<a href="https://webcodecsfundamentals.org/codecs/vp09.00.10.08.00.html">99.98% support</a>)</p>
</li>
<li><p><code>vp09.00.40.08.00</code> - level 4 (<a href="https://webcodecsfundamentals.org/codecs/vp09.00.40.08.00.html">99.96% support</a>)</p>
</li>
<li><p><code>vp09.00.50.08.00</code> - level 5 (<a href="https://webcodecsfundamentals.org/codecs/vp09.00.50.08.00.html">99.97% support</a>)</p>
</li>
<li><p><code>vp09.00.61.08.00</code> - level 6 (<a href="https://webcodecsfundamentals.org/codecs/vp09.00.61.08.00.html">99.97% support</a>)</p>
</li>
</ul>
<p>You can also use the <em>getCodecString</em> function from the <a href="https://www.npmjs.com/package/webcodecs-utils">webcodecs-utils</a> package:</p>
<pre><code class="language-typescript">import { getCodecString } from 'webcodecs-utils'

const codec_string = getCodecString('vp9', width, height, bitrate)
</code></pre>
<p>You can find a comprehensive list of what codecs and codec strings you can use in WebCodecs <a href="https://webcodecsfundamentals.org/datasets/codec-support-table/">here</a>.</p>
<h3 id="heading-bit-rate">Bit rate</h3>
<p>On top of height and width (which you presumably know from your content) and a codec string (which we just discussed), you also need to specify a bit rate when encoding video.</p>
<p>Video Compression algorithms have a trade-off between quality and file size. You can have high quality video with big file sizes, or lower quality video with lower file sizes.</p>
<p>Here's a quick visualization of what different quality levels look like for a 1080p video encoded at different bit rates:</p>
<p><strong>300 kbps</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/43d4af76-9951-47f8-9833-c64ea8034ded.png" alt="300kbps frame" style="display:block;margin:0 auto" width="256" height="256" loading="lazy">

<p><strong>1 Mbps</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/2872effb-d8ae-4001-a82a-00338bf69168.png" alt="1Mbps frame" style="display:block;margin:0 auto" width="256" height="256" loading="lazy">

<p><strong>3 Mbps</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/e25723ca-2896-4c85-be91-56fcaf4a426b.png" alt="3 Mbps frame" style="display:block;margin:0 auto" width="256" height="256" loading="lazy">

<p><strong>10 Mbps</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/6984c5b1feda93761574fcb1/cdb983e9-d55d-49f5-9852-4e85f053f6ba.png" alt="10 Mbps frame" style="display:block;margin:0 auto" width="256" height="256" loading="lazy">

<p>Here's a quick lookup table for bitrate guidance:</p>
<table>
<thead>
<tr>
<th><strong>Resolution</strong></th>
<th><strong>Bitrate (30fps)</strong></th>
<th><strong>Bitrate (60fps)</strong></th>
</tr>
</thead>
<tbody><tr>
<td>4K</td>
<td>13-20 Mbps</td>
<td>20-30 Mbps</td>
</tr>
<tr>
<td>1080p</td>
<td>4.5-6 Mbps</td>
<td>6-9 Mbps</td>
</tr>
<tr>
<td>720p</td>
<td>2-4 Mbps</td>
<td>3-6 Mbps</td>
</tr>
<tr>
<td>480p</td>
<td>1.5-2 Mbps</td>
<td>2-3 Mbps</td>
</tr>
<tr>
<td>360p</td>
<td>0.5-1 Mbps</td>
<td>1-1.5 Mbps</td>
</tr>
<tr>
<td>240p</td>
<td>300-500 kbps</td>
<td>500-800 kbps</td>
</tr>
</tbody></table>
<p>You can also use this utility function in your own app as a quick approximation:</p>
<pre><code class="language-typescript">function getBitrate(width, height, fps, quality = 'good') {
    const pixels = width * height;

    const qualityFactors = {
      'low': 0.05,
      'good': 0.08,
      'high': 0.10,
      'very-high': 0.15
    };

    const factor = qualityFactors[quality] || qualityFactors['good'];

    // Returns bitrate in bits per second
    return pixels * fps * factor;
  }
</code></pre>
<p>The same function is also available in the webcodecs-utils package:</p>
<pre><code class="language-typescript">import { getBitrate } from 'webcodecs-utils'
</code></pre>
<h3 id="heading-gpu-vs-cpu">GPU vs CPU</h3>
<p>Most user devices have some type of graphics card (typically called integrated graphics). These are specialized chips with specific silicon architectures optimized for encoding and decoding video, as well as for basic graphics.</p>
<p>You might hear "GPU" and think AI data centers and gamers. But as far as web applications are concerned, almost everyone has a GPU.</p>
<p>This is important because while most frontend-development almost exclusively deals with the CPU, WebCodecs and video processing work primarily on the GPU.</p>
<p>Here's a quick guide for what kind of data is stored where:</p>
<table>
<thead>
<tr>
<th><strong>Data Type</strong></th>
<th><strong>Location</strong></th>
</tr>
</thead>
<tbody><tr>
<td>VideoFrame</td>
<td>GPU</td>
</tr>
<tr>
<td>EncodedVideoChunk</td>
<td>CPU</td>
</tr>
<tr>
<td>ImageBitmap</td>
<td>GPU</td>
</tr>
<tr>
<td>ArrayBuffer</td>
<td>CPU</td>
</tr>
<tr>
<td>File</td>
<td>CPU + Disk</td>
</tr>
</tbody></table>
<p>There's a performance cost to moving data around, and this also becomes important for managing memory.</p>
<h3 id="heading-memory">Memory</h3>
<p>VideoFrame objects can be quite large&nbsp;–&nbsp;30MB for a 4K video. A user's graphics card typically reserves some portion of RAM for "Video Memory" or "VRAM" which is where <em>VideoFrame</em> objects would be stored.</p>
<p>So if a user has 8GB of RAM, they would typically have 2GB of VRAM (how much is decided by the operating system).</p>
<p>If the amount of video data exceeds VRAM, your application will crash. This means that for a typical user, if you have more than 67 4K frames in memory (~2 seconds of video) the program will crash.</p>
<h4 id="heading-when-videoframes-are-generated">When VideoFrames are generated</h4>
<p>VideoFrame objects are generated whenever you create a <code>new VideoFrame(source)</code> but also from the <code>VideoDecoder</code>, specifically the output callback. Every time a frame is generated, memory usage goes up.</p>
<h4 id="heading-how-to-remove-videoframes">How to remove VideoFrames</h4>
<p>You can't rely on standard garbage collection for VideoFrame objects. You have to explicitly call close() on a frame when you're done:</p>
<pre><code class="language-typescript">frame.close()
</code></pre>
<p>In the Streams/Pipeline code and demo showed earlier, frames are actually being <a href="https://github.com/sb2702/webcodecs-utils/blob/main/src/streams/video-encode-stream.ts">closed</a> as soon as they are encoded in the <em>VideoProcessStream</em> and <em>VideoEncodeStream</em> interfaces.</p>
<p>The other reason Streams are helpful for WebCodecs is the <code>highWaterMark</code> property, which defaults to 10. What this means is that when you run:</p>
<pre><code class="language-typescript">await demuxer.videoStream()
  .pipeThrough(new VideoDecodeStream(decoderConfig))
  .pipeThrough(processStream) 
  .pipeThrough(new VideoEncodeStream(encoderConfig))
  .pipeTo(muxer.videoSink());
</code></pre>
<p>You ensure that no more than 10 video frames are in memory at any given time. The Streams API allows you to specify that limit while the browser itself deals with the logic of how to make that happen.</p>
<p>If you don't use the Streams API, you'll need to make sure you manage keeping track of memory limits and number of open video frames yourself.</p>
<h2 id="heading-further-resources">Further Resources</h2>
<p>Through this article we've gone over the basics of video processing, introduced the core concepts of the WebCodecs API, and built an MVP of a video converter utility. This is one of the simplest possible demos which actually touches all parts of the API. We also covered some basic production concerns.</p>
<p>This is just an introduction, and only scratches the surface of WebCodecs. For how simple the API looks, building a proper, production-ready WebCodecs application requires moving beyond hello-world demos.</p>
<p>To learn more about WebCodecs, you can check out <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API">MDN</a> and the <a href="https://webcodecsfundamentals.org/">WebCodecsFundamentals</a>, a comprehensive online textbook going much more in depth on WebCodecs.</p>
<p>You can also examine the source code of existing, production tested apps like <a href="https://www.remotion.dev/convert">Remotion Convert</a> (<a href="https://github.com/remotion-dev/remotion/tree/main/packages/convert">source code</a>) which is most similar to the demo app we covered, and <a href="http://free.upscaler.video/">Free AI Video Upscaler</a> (<a href="https://github.com/sb2702/free-ai-video-upscaler">source code</a>, <a href="https://github.com/sb2702/free-ai-video-upscaler/blob/main/src/processors/pipeline-processor.ts">processing pipeline</a>) which is the inspiration for the design patterns presented here and implemented in <a href="https://www.npmjs.com/package/webcodecs-utils">webcodecs-utils</a>.</p>
<p>Finally, while WebCodecs is harder than it looks, you can make your life a lot easier by using a library like <a href="https://mediabunny.dev/">MediaBunny</a>, which simplifies a lot of the details of things like memory management, file I/O, and other details. I use it in my own production WebCodecs applications.</p>
<p>Whether or not you actually build a full, production grade WebCodecs application, you now at least know that it's an option&nbsp;– one that's relatively new, provides better UX with lower server costs, and which is increasingly being adopted by prominent video applications like Capcut and Descript for its benefits.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Convert Video Files to a Gif in Python ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I was able to convert some video files to a gif as I needed them in gif format for some of my articles. I decided to show you how I did it in 3 lines of code, so you can save yourself the extra effort of looking up a ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-convert-video-files-to-gif-in-python/</link>
                <guid isPermaLink="false">66adf10b2d0eb5bfdd6b0c24</guid>
                
                    <category>
                        <![CDATA[ gif ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Thu, 31 Mar 2022 16:06:08 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/convert.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I was able to convert some video files to a gif as I needed them in gif format for some of my articles.</p>
<p>I decided to show you how I did it in 3 lines of code, so you can save yourself the extra effort of looking up a Saas to do it for you.</p>
<h2 id="heading-how-to-convert-video-to-a-gif-in-python">How to Convert Video to a Gif in Python</h2>
<p>To convert video to gif in Python, you need to install a package called <code>moviepy</code> with pip by opening your terminal and running <code>pip install moviepy</code>.</p>
<p>This module has several methods with which you can edit and enhance videos.
<img src="https://www.freecodecamp.org/news/content/images/2022/03/ss1-3.png" alt="ss1-3" width="600" height="400" loading="lazy"></p>
<p>After successfully installing <code>moviepy</code>, you need to import a method called <code>VideoFileClip</code> from it. This is the method with which you will be able to specify the name of the video file and its relative path.</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> moviepy.editor <span class="hljs-keyword">import</span> VideoFileClip
</code></pre>
<p>The next thing you need to do is to specify the relative path of the video you want to convert to a gif inside the VideoFileClip method. Then you need to assign it to a variable. </p>
<p>In the code snippet below, I call that variable <code>videoClip</code>:</p>
<pre><code class="lang-py">videoClip = VideoFileClip(<span class="hljs-string">"my-life.mp4"</span>)
</code></pre>
<p>To finally convert the video to gif, you need to bring in the <code>videoClip</code> variable and use the <code>write_gif()</code> method on it, then specify the name you want to give to the gif inside it.</p>
<pre><code class="lang-py">videoClip.write_gif(<span class="hljs-string">"my-life.gif"</span>)
</code></pre>
<p>Open the terminal and run the file:
<img src="https://www.freecodecamp.org/news/content/images/2022/03/ss2-1.png" alt="ss2-1" width="600" height="400" loading="lazy"></p>
<p>Check the folder inside which the video file is located and you should see the gif file. If you’re using VS Code, open the sidebar by pressing <code>CTRL + B</code> and you should see the gif file.
<img src="https://www.freecodecamp.org/news/content/images/2022/03/ss3-1.png" alt="ss3-1" width="600" height="400" loading="lazy"></p>
<p>You can open the gif with VS Code too.</p>
<p>The whole code that did the conversion looks like this:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> moviepy.editor <span class="hljs-keyword">import</span> VideoFileClip

videoClip = VideoFileClip(<span class="hljs-string">"my-life.mp4"</span>)

videoClip.write_gif(<span class="hljs-string">"my-life.gif"</span>)
</code></pre>
<p>You can learn more about the <code>moviepy</code> module on <a target="_blank" href="https://zulko.github.io/moviepy/">their official website</a>.</p>
<p>If you have any questions, feel free to contact me on <a target="_blank" href="https://twitter.com/Ksound22">Twitter</a>.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ HTML Video – How to Embed a Video Player with the HTML 5 Video Tag ]]>
                </title>
                <description>
                    <![CDATA[ Before the advent of HTML 5, web developers had to embed video on a web page with a plugin like Adobe flash player. Today, you can easily embed videos in an HTML document with the <video> tag. In this article, we'll see how the <video> tag works in H... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/html-video-how-to-embed-a-video-player-with-the-html-5-video-tag/</link>
                <guid isPermaLink="false">66adf17fd93fd5c2bc7d8f33</guid>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Tue, 08 Feb 2022 16:59:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/pexels-donald-tong-66134.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Before the advent of HTML 5, web developers had to embed video on a web page with a plugin like Adobe flash player.</p>
<p>Today, you can easily embed videos in an HTML document with the <code>&lt;video&gt;</code> tag.</p>
<p>In this article, we'll see how the <code>&lt;video&gt;</code> tag works in HTML.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-basic-syntax">Basic Syntax</a></li>
<li><a class="post-section-overview" href="#heading-attributes-of-the-tag">Attributes of the <code>&lt;video&gt;</code> Tag</a></li>
<li><a class="post-section-overview" href="#heading-the-src-attribute">The <code>src</code> Attribute</a></li>
<li><a class="post-section-overview" href="#heading-the-poster-attribute">The <code>poster</code> Attribute</a></li>
<li><a class="post-section-overview" href="#heading-the-controls-attribute">The <code>controls</code> Attribute</a></li>
<li><a class="post-section-overview" href="#heading-the-loop-attribute">The <code>loop</code> Attribute</a></li>
<li><a class="post-section-overview" href="#heading-the-autoplay-attribute">The <code>autoplay</code> Attribute</a></li>
<li><a target="_blank" href="$thewidthandheightattributes">The <code>width</code> and <code>height</code> Attributes</a></li>
<li><a class="post-section-overview" href="#heading-the-muted-attribute">The <code>muted</code> Attribute</a></li>
<li><a class="post-section-overview" href="#heading-the-preload-attribute">The <code>preload</code> Attribute</a></li>
<li>C<a class="post-section-overview" href="#heading-conclusion">onclusion</a></li>
</ul>
<h2 id="heading-basic-syntax">Basic Syntax</h2>
<p>Just like the <code>&lt;img&gt;</code> tag, <code>&lt;video&gt;</code> takes an <code>src</code> attribute with which you need to specify the source of the video. </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p>By default, it is displayed like an image in the browser:
<img src="https://www.freecodecamp.org/news/content/images/2022/02/ss-1-2.png" alt="ss-1-2" width="600" height="400" loading="lazy"></p>
<p>This CSS centers everything in the web page and change the background color:</p>
<pre><code class="lang-css"> <span class="hljs-selector-tag">body</span> {
      <span class="hljs-attribute">display</span>: flex;
      <span class="hljs-attribute">align-items</span>: center;
      <span class="hljs-attribute">justify-content</span>: center;
      <span class="hljs-attribute">min-height</span>: <span class="hljs-number">100vh</span>;
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#d3d3d3</span>;
    }
</code></pre>
<p>In addition, you can specify multiple video sources for the <code>&lt;video&gt;</code> with the <code>&lt;source&gt;</code> tag. This <code>&lt;source&gt;</code> tag has to carry its own <code>src</code> attribute too.</p>
<p>You can use multiple <code>&lt;source&gt;</code> tags to make different formats of the same video available. The browser will then play the format it supports.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">controls</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.ogg"</span> /&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend .webm"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<h2 id="heading-attributes-of-the-tag">Attributes of the <code>&lt;video&gt;</code> Tag</h2>
<p>The <code>&lt;video&gt;</code> tag supports global attributes such as <code>id</code>, <code>class</code>, <code>style</code>, and so on. </p>
<p>If you're wondering what global attributes are, they are attributes supported by all HTML tags.</p>
<p>The specific attributes supported by the <code>&lt;video&gt;</code> tag include <code>src</code>, <code>poster</code>, <code>controls</code>, <code>loop</code>, <code>autoplay</code>, <code>width</code>, <code>height</code>, <code>muted</code>, <code>preload</code>, and others.</p>
<h3 id="heading-the-src-attribute">The <code>src</code> Attribute</h3>
<p>The src attribute is used to specify the source of the video. It could be a relative path to the video on your local machine or a live video link from the internet.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p>It’s optional because you can use the <code>&lt;source&gt;</code> tag instead of it.</p>
<h3 id="heading-the-poster-attribute">The <code>poster</code> Attribute</h3>
<p>With the poster attribute, you can incorporate an image to show before the video starts playing or while it is downloading.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span> <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p>Instead of the image of the first scene of the video, the browser will show this image:
<img src="https://www.freecodecamp.org/news/content/images/2022/02/ss-2-2.png" alt="ss-2-2" width="600" height="400" loading="lazy"></p>
<h3 id="heading-the-controls-attribute">The <code>controls</code> Attribute</h3>
<p>When you use  control, it lets the browser show playback controllers such as play and pause, volume, seek, and so on.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/ss-3-1.png" alt="ss-3-1" width="600" height="400" loading="lazy"></p>
<h3 id="heading-the-loop-attribute">The <code>loop</code> Attribute</h3>
<p>With the loop attribute, you can make the video repeat automatically. That is, make it start playing again every time it stops playing.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">loop</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<h3 id="heading-the-autoplay-attribute">The <code>autoplay</code> Attribute</h3>
<p>The <code>autoplay</code> attribute lets you make the video start playing automatically immediately after the page loads.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">loop</span>
      <span class="hljs-attr">autoplay</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<h3 id="heading-the-width-and-height-attributes">The <code>width</code> and <code>height</code> Attributes</h3>
<p>You can use the width and height attributes to specify a width and height for the video in pixels. It accepts only absolute values, for example, pixels.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">loop</span>
      <span class="hljs-attr">autoplay</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"350px"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"250px"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/ss-4-1.png" alt="ss-4-1" width="600" height="400" loading="lazy"></p>
<h3 id="heading-the-muted-attribute">The <code>muted</code> Attribute</h3>
<p>You can use the muted attribute to tell the browser not to play any sound associated with the video when it starts playing. </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">loop</span>
      <span class="hljs-attr">autoplay</span>
      <span class="hljs-attr">muted</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"350px"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"250px"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/ss-5-1.png" alt="ss-5-1" width="600" height="400" loading="lazy"></p>
<p>If the <code>controls</code> attribute is specified, the user can click the volume button to unmute.</p>
<h3 id="heading-the-preload-attribute">The <code>preload</code> Attribute</h3>
<p>With the preload attribute, you can provide a hint to the browser on whether to download the video or not when the page loads.</p>
<p>This attribute is crucial for user experience.</p>
<p>You can use 3 values with the preload attribute:</p>
<ul>
<li><p>none: specifies that the video won't load until the user presses play</p>
</li>
<li><p>auto: specifies that the video should be downloaded even when the user doesn't press play</p>
</li>
<li><p>metadata: specifies that the browser should collect metadata such as length, size, duration, and so on.</p>
</li>
</ul>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">controls</span>
      <span class="hljs-attr">loop</span>
      <span class="hljs-attr">autoplay</span>
      <span class="hljs-attr">muted</span>=<span class="hljs-string">"true"</span>
      <span class="hljs-attr">preload</span>=<span class="hljs-string">"metadata"</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"weekend.mp4"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"350px"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"250px"</span>
      <span class="hljs-attr">poster</span>=<span class="hljs-string">"benefits-of-coding.jpg"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned about the HTML5 <code>&lt;video&gt;</code> tag and its attributes, so you can use it in your projects the right way.</p>
<p>Since audio is an important part of a complete video, you can also use the <code>&lt;video&gt;</code> tag to put an audio file on a web page. But in most cases, you should use the <code>&lt;audio&gt;</code> tag for this purpose for the appropriate user experience.</p>
<p>If you find this article helpful, share it with your friends and family so it can reach more people who might need it.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Android Camera2 – How to Use the Camera2 API to Take Photos and Videos ]]>
                </title>
                <description>
                    <![CDATA[ We all use the camera on our phones and we use it a l-o-t. There are even some applications that have integrated the camera as a feature.  On one end, there is a standard way of interacting with the camera. On the other, there is a way to customize ]]>
                </description>
                <link>https://www.freecodecamp.org/news/android-camera2-api-take-photos-and-videos/</link>
                <guid isPermaLink="false">66ba4fd343a51af2a76f7563</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Photography ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Thu, 29 Jul 2021 22:36:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/07/0_0d3MvPnozsSTafWk.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We all use the camera on our phones and we use it a l-o-t. There are even some applications that have integrated the camera as a feature. </p>
<p>On one end, there is a standard way of interacting with the camera. On the other, there is a way to customize your interaction with the camera. This distinction is an important one to make. And that’s where Camera2 comes in.</p>
<h2 id="heading-what-is-camera2">What is Camera2?</h2>
<p>While it has been available since API level 21, the Camera2 API has got to be one of the more complex pieces of architecture developers have to deal with. </p>
<p>This API and its predecessor were put in place so developers could harness the power of interacting with the camera inside of their applications. </p>
<p>Similar to how there is a way to interact with the microphone or the volume of the device, the Camera2 API gives you the tools to interact with the device's camera. </p>
<p>In general, if you want to user the Camera2 API, it would probably be for more than just taking a picture or recording a video. This is because the API lets you have in depth control of the camera by exposing various classes that will need to be configured per specific device.</p>
<p>Even if you've dealt with the camera previously, it is such a drastic change from the former camera API, that you might as well forget all that you know. </p>
<p>There are a ton of resources out there that try to showcase how to use this API directly, but some of them may be outdated and some don’t present the whole picture. </p>
<p>So, instead of trying to fill in the missing pieces by yourself, this article will (hopefully) be your one stop shop for interacting with the Camera2 API.</p>
<h2 id="heading-camera2-use-cases">Camera2 Use Cases</h2>
<p>Before we dive into anything, it is important to understand that if you only want to use the camera to take a picture or to record a video, you do not need to bother yourself with the Camera2 API. </p>
<p>The primary reason to use the Camera2 API is if your application requires some custom interaction with the camera or its functionality. </p>
<p>If you are interested in doing the former instead of the latter, I'll suggest that you visit the following documentation from Google:</p>
<ol>
<li><a target="_blank" href="https://developer.android.com/training/camera/photobasics">Take Photos</a></li>
<li><a target="_blank" href="https://developer.android.com/training/camera/videobasics">Capture Video</a></li>
</ol>
<p>There you will find all the necessary steps you need to take to capture great photos and videos with your camera. But in this article, the main focus will be on how to use Camera2.</p>
<p>Now, there are some things we need to add to our manifest file:</p>
<p>Camera permissions:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.CAMERA"</span> /&gt;</span>
</code></pre>
<p>Camera feature:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-feature</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.hardware.camera"</span> /&gt;</span>
</code></pre>
<p>You will have to deal with checking if the camera permission has been granted or not, but since this topic has been covered widely, we won’t be dealing with that in this article.</p>
<h2 id="heading-how-to-set-up-the-camera2-api-components">How to Set up the Camera2 API Components</h2>
<p>The Camera2 API introduces several new interfaces and classes. Let’s break down each of them so we can better understand how to use them.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/1_nPqyLhTqxaqRytV8lV41VA.png" alt="Image" width="600" height="400" loading="lazy">
<em>Look at all those components</em></p>
<p>First off, we’ll start with the <a target="_blank" href="https://developer.android.com/reference/android/view/TextureView">TextureView</a>.</p>
<h3 id="heading-camera2-textureview-component">Camera2 TextureView Component</h3>
<p>A TextureView is a UI component that you use to display a content stream (think video). We need to use a TextureView to display the feed from the camera, whether it's a preview or before taking the picture/video. </p>
<p>Two properties that are important to use regarding the TextureView are:</p>
<ul>
<li>The SurfaceTexture field</li>
<li>The SurfaceTextureListener interface</li>
</ul>
<p>The first is where the content will get displayed, and the second has four callbacks:</p>
<ol>
<li><a target="_blank" href="https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureAvailable%28android.graphics.SurfaceTexture,%20int,%20int%29">onSurfaceTextureAvailable</a></li>
<li><a target="_blank" href="https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged%28android.graphics.SurfaceTexture,%20int,%20int%29">onSurfaceTextureSizeChanged</a></li>
<li><a target="_blank" href="https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureUpdated%28android.graphics.SurfaceTexture%29">onSurfaceTextureUpdated</a></li>
<li><a target="_blank" href="https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed%28android.graphics.SurfaceTexture%29">onSurfaceTextureDestroyed</a></li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> surfaceTextureListener = <span class="hljs-keyword">object</span> : TextureView.SurfaceTextureListener {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onSurfaceTextureAvailable</span><span class="hljs-params">(texture: <span class="hljs-type">SurfaceTexture</span>, width: <span class="hljs-type">Int</span>, height: <span class="hljs-type">Int</span>)</span></span> {

        }
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onSurfaceTextureSizeChanged</span><span class="hljs-params">(texture: <span class="hljs-type">SurfaceTexture</span>, width: <span class="hljs-type">Int</span>, height: <span class="hljs-type">Int</span>)</span></span> {

        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onSurfaceTextureDestroyed</span><span class="hljs-params">(texture: <span class="hljs-type">SurfaceTexture</span>)</span></span> {

        }
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onSurfaceTextureUpdated</span><span class="hljs-params">(texture: <span class="hljs-type">SurfaceTexture</span>)</span></span> {

        }
}
</code></pre>
<p>The first callback is crucial when using the camera. This is because we want to be notified when the SurfaceTexture is available so we can start displaying the feed on it. </p>
<p>Be aware that only once the TextureView is attached to a window does it become available.</p>
<p>Interacting with the camera has changed since the previous API. Now, we have the <a target="_blank" href="https://developer.android.com/reference/android/hardware/camera2/CameraManager">CameraManager</a>. This is a system service that allows us to interact with <a target="_blank" href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice">CameraDevice</a> objects. </p>
<p>The methods you want to pay close attention to are:</p>
<ul>
<li><a target="_blank" href="https://developer.android.com/reference/android/hardware/camera2/CameraManager#openCamera%28java.lang.String,%20android.hardware.camera2.CameraDevice.StateCallback,%20android.os.Handler%29">openCamera</a></li>
<li><a target="_blank" href="https://developer.android.com/reference/android/hardware/camera2/CameraManager#getCameraCharacteristics%28java.lang.String%29">getCameraCharacteristics</a></li>
<li><a target="_blank" href="https://developer.android.com/reference/android/hardware/camera2/CameraManager#getCameraIdList%28%29">getCameraIdList</a></li>
</ul>
<p>After we know that the TextureView is available and ready, we need to call openCamera to open a connection to the camera. This method takes in three arguments:</p>
<ol>
<li>CameraId - String</li>
<li>CameraDevice.StateCallback</li>
<li>A Handler</li>
</ol>
<p>The CameraId argument signifies which camera we want to connect to. On your phone, there are mainly two cameras, the front and the back. Each has its own unique id. Usually, it is either a zero or a one. </p>
<p>How do we get the camera id? We use the CameraManager’s getCamerasIdList method. It will return an array of string type of all the camera ids identified from the device.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) <span class="hljs-keyword">as</span> CameraManager
<span class="hljs-keyword">val</span> cameraIds: Array&lt;String&gt; = cameraManager.cameraIdList
<span class="hljs-keyword">var</span> cameraId: String = <span class="hljs-string">""</span>
<span class="hljs-keyword">for</span> (id <span class="hljs-keyword">in</span> cameraIds) {
    <span class="hljs-keyword">val</span> cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    <span class="hljs-comment">//If we want to choose the rear facing camera instead of the front facing one</span>
    <span class="hljs-keyword">if</span> (cameraCharacteristics.<span class="hljs-keyword">get</span>(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      <span class="hljs-keyword">continue</span>
    }

    <span class="hljs-keyword">val</span> previewSize = cameraCharacteristics.<span class="hljs-keyword">get</span>(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    <span class="hljs-keyword">val</span> imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, <span class="hljs-number">1</span>)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}
</code></pre>
<p>The next arguments are callbacks to the camera state after we try to open it. If you think about it, there can only be several outcomes for this action:</p>
<ul>
<li>The camera manages to open successfully</li>
<li>The camera disconnects</li>
<li>Some error occurs</li>
</ul>
<p>And that’s what you will find inside the CameraDevice.StateCallback:</p>
<pre><code class="lang-kotlin"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> cameraStateCallback = <span class="hljs-keyword">object</span> : CameraDevice.StateCallback() {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onOpened</span><span class="hljs-params">(camera: <span class="hljs-type">CameraDevice</span>)</span></span> {

        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onDisconnected</span><span class="hljs-params">(cameraDevice: <span class="hljs-type">CameraDevice</span>)</span></span> {

        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onError</span><span class="hljs-params">(cameraDevice: <span class="hljs-type">CameraDevice</span>, error: <span class="hljs-type">Int</span>)</span></span> {
            <span class="hljs-keyword">val</span> errorMsg = <span class="hljs-keyword">when</span>(error) {
                ERROR_CAMERA_DEVICE -&gt; <span class="hljs-string">"Fatal (device)"</span>
                ERROR_CAMERA_DISABLED -&gt; <span class="hljs-string">"Device policy"</span>
                ERROR_CAMERA_IN_USE -&gt; <span class="hljs-string">"Camera in use"</span>
                ERROR_CAMERA_SERVICE -&gt; <span class="hljs-string">"Fatal (service)"</span>
                ERROR_MAX_CAMERAS_IN_USE -&gt; <span class="hljs-string">"Maximum cameras in use"</span>
                <span class="hljs-keyword">else</span> -&gt; <span class="hljs-string">"Unknown"</span>
            }
            Log.e(TAG, <span class="hljs-string">"Error when trying to connect camera <span class="hljs-variable">$errorMsg</span>"</span>)
        }
    }
</code></pre>
<p>The third argument deals with where this work will happen. Since we don’t want to occupy the main thread, it is better to do this work in the background. </p>
<p>That’s why we need to pass a Handler to it. It would be wise to have this handler instance instantiated with a thread of our choosing so we can delegate work to it.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> backgroundHandlerThread: HandlerThread
<span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> backgroundHandler: Handler

 <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">startBackgroundThread</span><span class="hljs-params">()</span></span> {
    backgroundHandlerThread = HandlerThread(<span class="hljs-string">"CameraVideoThread"</span>)
    backgroundHandlerThread.start()
    backgroundHandler = Handler(
        backgroundHandlerThread.looper)
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">stopBackgroundThread</span><span class="hljs-params">()</span></span> {
    backgroundHandlerThread.quitSafely()
    backgroundHandlerThread.join()
}
</code></pre>
<p>With everything that we have done, we can now call openCamera:</p>
<pre><code class="lang-kotlin">cameraManager.openCamera(cameraId, cameraStateCallback,backgroundHandler)
</code></pre>
<p>Then in the <strong>onOpened</strong> callback, we can start to deal with the logic on how to present the camera feed to the user via the TextureView.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/0_hW39WzgV8lm87Ql0.jpg" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral" rel="photo-creator noopener"&gt;Markus Spiske on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm<em>medium=referral" rel="photo-source noopener)</em></p>
<h3 id="heading-how-to-show-a-preview-of-the-feed">How to Show a Preview of the Feed</h3>
<p>We've got our camera (cameraDevice) and our TextureView to show the feed. But we need to connect them to each other so we can show a preview of the feed. </p>
<p>To do that, we will be using the SurfaceTexture property of TextureView and we will be building a CaptureRequest.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> surfaceTexture : SurfaceTexture? = textureView.surfaceTexture <span class="hljs-comment">// 1</span>

<span class="hljs-keyword">val</span> cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) <span class="hljs-comment">//2</span>
<span class="hljs-keyword">val</span> previewSize = cameraCharacteristics.<span class="hljs-keyword">get</span>(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
  .getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!

surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height) <span class="hljs-comment">//3</span>

<span class="hljs-keyword">val</span> previewSurface: Surface = Surface(surfaceTexture)

captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) <span class="hljs-comment">//4</span>
captureRequestBuilder.addTarget(previewSurface) <span class="hljs-comment">//5</span>

cameraDevice.createCaptureSession(listOf(previewSurface, imageReader.surface), captureStateCallback, <span class="hljs-literal">null</span>) <span class="hljs-comment">//6</span>
</code></pre>
<p>In the code above, first we get the surfaceTexture from our TextureView. Then we use the cameraCharacteristics object to get the list of all output sizes. To get the desired size, we set it for the surfaceTexture.</p>
<p>Next, we create a captureRequest where we pass in <strong>TEMPLATE_PREVIEW</strong>. We add our input surface to the captureRequest.</p>
<p>Finally, we start a captureSession with our input and output surfaces, captureStateCallback, and pass in null for the handler</p>
<p>So what is this captureStateCallback? If you remember the diagram from the beginning of this article, it is part of the CameraCaptureSession which we are starting. This object tracks the progress of the captureRequest with the following callbacks:</p>
<ul>
<li>onConfigured</li>
<li>onConfigureFailed</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> captureStateCallback = <span class="hljs-keyword">object</span> : CameraCaptureSession.StateCallback() {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onConfigureFailed</span><span class="hljs-params">(session: <span class="hljs-type">CameraCaptureSession</span>)</span></span> {

        }
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onConfigured</span><span class="hljs-params">(session: <span class="hljs-type">CameraCaptureSession</span>)</span></span> {

        }
}
</code></pre>
<p>When the <strong>cameraCaptureSession</strong> is configured successfully, we set a repeating request for the session to allow us to show the preview continuously. </p>
<p>To do that, we use the session object we get in the callback:</p>
<pre><code class="lang-kotlin"> session.setRepeatingRequest(captureRequestBuilder.build(), <span class="hljs-literal">null</span>, backgroundHandler)
</code></pre>
<p>You will recognize our captureRequestBuilder object that we created earlier as the first argument for this method. We enact the build method so the final parameter passed in is a CaptureRequest. </p>
<p>The second argument is a CameraCaptureSession.captureCallback listener, but since we don’t want to do anything with the captured images (since this is a preview), we pass in null. </p>
<p>The third argument is a handler, and here we use our own backgroundHandler. This is also why we passed in null in the previous section, since the repeating request will run on the background thread.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/dicky-jiang-ovUgpiDrbrc-unsplash.jpg" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@dicky_juwono?utm_source=medium&amp;utm_medium=referral" rel="photo-creator noopener"&gt;Dicky Jiang on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm<em>medium=referral" rel="photo-source noopener)</em></p>
<h2 id="heading-how-to-take-a-picture">How to Take a Picture</h2>
<p>Having a live preview of the camera is awesome, but most users will probably want to do something with it. Some of the logic that we will write to take a picture will be similar to what we did in the previous section.</p>
<ol>
<li>We will create a captureRequest</li>
<li>We will use an ImageReader and its listener to gather the photo taken</li>
<li>Using our cameraCaptureSession, we will invoke the capture method</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> orientations : SparseIntArray = SparseIntArray(<span class="hljs-number">4</span>).apply {
    append(Surface.ROTATION_0, <span class="hljs-number">0</span>)
    append(Surface.ROTATION_90, <span class="hljs-number">90</span>)
    append(Surface.ROTATION_180, <span class="hljs-number">180</span>)
    append(Surface.ROTATION_270, <span class="hljs-number">270</span>)
}

<span class="hljs-keyword">val</span> captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureRequestBuilder.addTarget(imageReader.surface)

<span class="hljs-keyword">val</span> rotation = windowManager.defaultDisplay.rotation
captureRequestBuilder.<span class="hljs-keyword">set</span>(CaptureRequest.JPEG_ORIENTATION, orientations.<span class="hljs-keyword">get</span>(rotation))
cameraCaptureSession.capture(captureRequestBuilder.build(), captureCallback, <span class="hljs-literal">null</span>)
</code></pre>
<p>But what is this <a target="_blank" href="https://developer.android.com/reference/android/media/ImageReader">ImageReader</a>? Well, an ImageReader provides access to image data that is rendered onto a surface. In our case, it is the surface of the TextureView. </p>
<p>If you look at the code snippet from the previous section, you will notice we have already defined an ImageReader there.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) <span class="hljs-keyword">as</span> CameraManager
<span class="hljs-keyword">val</span> cameraIds: Array&lt;String&gt; = cameraManager.cameraIdList
<span class="hljs-keyword">var</span> cameraId: String = <span class="hljs-string">""</span>
<span class="hljs-keyword">for</span> (id <span class="hljs-keyword">in</span> cameraIds) {
    <span class="hljs-keyword">val</span> cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    <span class="hljs-comment">//If we want to choose the rear facing camera instead of the front facing one</span>
    <span class="hljs-keyword">if</span> (cameraCharacteristics.<span class="hljs-keyword">get</span>(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      <span class="hljs-keyword">continue</span>
    }

    <span class="hljs-keyword">val</span> previewSize = cameraCharacteristics.<span class="hljs-keyword">get</span>(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    <span class="hljs-keyword">val</span> imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, <span class="hljs-number">1</span>)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}
</code></pre>
<p>As you can see above, we instantiate an ImageReader by passing in a width and height, the image format we would like our image to be in and the number of images that it can capture.</p>
<p>A property the ImageReader class has is a listener called onImageAvailableListener. This listener will get triggered once a photo is taken (since we passed in its surface as the output source for our capture request).</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> onImageAvailableListener = <span class="hljs-keyword">object</span>: ImageReader.OnImageAvailableListener{
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onImageAvailable</span><span class="hljs-params">(reader: <span class="hljs-type">ImageReader</span>)</span></span> {
            <span class="hljs-keyword">val</span> image: Image = reader.acquireLatestImage()
        }
    }
</code></pre>
<p>⚠️ <strong>Make sure to close the image after processing it or else you will not be able to take another photo.</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/jakob-owens-CiUR8zISX60-unsplash.jpg" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@jakobowens1?utm_source=medium&amp;utm_medium=referral" rel="photo-creator noopener"&gt;Jakob Owens on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm<em>medium=referral" rel="photo-source noopener)</em></p>
<h2 id="heading-how-to-record-a-video">How to Record a Video</h2>
<p>To record a video, we need to interact with a new object called <a target="_blank" href="https://developer.android.com/reference/android/media/MediaRecorder">MediaRecorder</a>. The media recorder object is in charge of recording audio and video and we will be using it do just that.</p>
<p>Before we do anything, we need to setup the media recorder. There are various configurations to deal with and <strong>they must be in the correct order or else exceptions will be thrown</strong>. </p>
<p>Below is an example of a selection of configurations that will allow us to capture video (without audio).</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setupMediaRecorder</span><span class="hljs-params">(width: <span class="hljs-type">Int</span>, height: <span class="hljs-type">Int</span>)</span></span> {
  <span class="hljs-keyword">val</span> mediaRecorder: MediaRecorder = MediaRecorder()
  mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
  mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
  mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
  mediaRecorder.setVideoSize(videoSize.width, videoSize.height)
  mediaRecorder.setVideoFrameRate(<span class="hljs-number">30</span>)
  mediaRecorder.setOutputFile(PATH_TO_FILE)
  mediaRecorder.setVideoEncodingBitRate(<span class="hljs-number">10_000_000</span>)
  mediaRecorder.prepare()
}
</code></pre>
<p>Pay attention to the <strong>setOutputFile</strong> method as it expects a path to the file which will store our video. At the end of setting all these configurations we need to call prepare.</p>
<p>Note that the mediaRecorder also has a start method and we must call prepare before calling it.</p>
<p>After setting up our mediaRecoder, we need to create a capture request and a capture session.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">startRecording</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> surfaceTexture : SurfaceTexture? = textureView.surfaceTexture
        surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height)
        <span class="hljs-keyword">val</span> previewSurface: Surface = Surface(surfaceTexture)
        <span class="hljs-keyword">val</span> recordingSurface = mediaRecorder.surface
        captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
        captureRequestBuilder.addTarget(previewSurface)
        captureRequestBuilder.addTarget(recordingSurface)

        cameraDevice.createCaptureSession(listOf(previewSurface, recordingSurface), captureStateVideoCallback, backgroundHandler)
    }
</code></pre>
<p>Similar to setting up the preview or taking a photograph, we have to define our input and output surfaces. </p>
<p>Here we are creating a Surface object from the surfaceTexture of the TextureView and also taking the surface from the media recorder. We are passing in the <strong>TEMPLATE_RECORD</strong> value when creating a capture request. </p>
<p>Our captureStateVideoCallback is of the same type we used for the still photo, but inside the onConfigured callback we call media recorder’s start method.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> captureStateVideoCallback = <span class="hljs-keyword">object</span> : CameraCaptureSession.StateCallback() {
      <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onConfigureFailed</span><span class="hljs-params">(session: <span class="hljs-type">CameraCaptureSession</span>)</span></span> {

      }

      <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onConfigured</span><span class="hljs-params">(session: <span class="hljs-type">CameraCaptureSession</span>)</span></span> {
          session.setRepeatingRequest(captureRequestBuilder.build(), <span class="hljs-literal">null</span>, backgroundHandler)
          mediaRecorder.start()
      }
  }
</code></pre>
<p>Now we are recording a video, but how do we stop recording? For that, we will be using the stop and reset methods on the mediaRecorder object:</p>
<pre><code class="lang-kotlin">mediaRecorder.stop()
mediaRecorder.reset()
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That was a lot to process. So if you made it here, congratulations! There is no way around it – only by getting your hands dirty with the code will you start to understand how everything connects together. </p>
<p>You are more than encouraged to look at all the code featured in this article below :</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles/tree/master/Camrea2API">https://github.com/TomerPacific/MediumArticles/tree/master/Camrea2API</a></div>
<p>Bear in mind that this is just the tip of the iceberg when it comes to the Camera2 API. There are a lot of other things you can do, like capturing a slow motion video, switching between the front and back cameras, controlling the focus, and much more.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Setup Instagram-like Video Stories in Your App ]]>
                </title>
                <description>
                    <![CDATA[ By Agam Mahajan The article will teach you how you can show multiple videos in one view, like we see in Instagram Stories. We'll also learn how to cache the videos in the user's device to help save that user's data and network calls and smooth out th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/video-stories-and-caching-ios/</link>
                <guid isPermaLink="false">66d45d5cbd438296f45cd377</guid>
                
                    <category>
                        <![CDATA[ app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ios app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 22 Sep 2020 22:01:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/09/1_gYkQNP0BaohLJ8hDKL1C6w-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Agam Mahajan</p>
<p>The article will teach you how you can show multiple videos in one view, like we see in Instagram Stories.</p>
<p>We'll also learn how to cache the videos in the user's device to help save that user's data and network calls and smooth out their experience.</p>
<p>A quick note: this implementation is for iOS, but the same logic can be applied in other codebases as well.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/09/ezgif.com-video-to-gif--5-.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In general, whenever we want to play a video, we get the video URL and simply present <code>**AVPlayerViewController**</code> with that URL.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> videoURL = <span class="hljs-type">URL</span>(string: <span class="hljs-string">"Sample-Video-Url"</span>)
<span class="hljs-keyword">let</span> player = <span class="hljs-type">AVPlayer</span>(url: videoURL!)
<span class="hljs-keyword">let</span> playerViewController = <span class="hljs-type">AVPlayerViewController</span>()
playerViewController.player = player
<span class="hljs-keyword">self</span>.present(playerViewController, animated: <span class="hljs-literal">true</span>) {
    playerViewController.player.play()
}
</code></pre>
<p>Pretty straightforward, right?</p>
<p>But the drawback of this implementation is that you <strong>can’t</strong> <strong>customiz</strong>e it. Which, if you are working for a good product company, will be an everyday ask. :D</p>
<p>Alternatively, we can use <code>**AVPlayerLayer**</code> which will do a similar job – but it allows us to customize the view and other elements.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> videoURL = <span class="hljs-type">URL</span>(string: <span class="hljs-string">"Sample-Video-Url"</span>)
<span class="hljs-keyword">let</span> player = <span class="hljs-type">AVPlayer</span>(url: videoURL!)
<span class="hljs-keyword">let</span> playerLayer = <span class="hljs-type">AVPlayerLayer</span>(player: player)
playerLayer.frame = <span class="hljs-keyword">self</span>.view.bounds
<span class="hljs-keyword">self</span>.view.layer.addSublayer(playerLayer)
player.play()
</code></pre>
<p>But what if you want to combine multiple videos, similar to <strong>Instagram stories</strong>? Then we probably have to dive in a bit deeper.</p>
<h2 id="heading-coming-back-to-the-problem-statement">Coming Back to the Problem Statement</h2>
<p>Now, let me tell you about my use case.</p>
<p>In my company, Swiggy, we want to be able to show multiple videos, where each video should be shown x number of times.</p>
<p>On top of that, it should have an Instagram-like stories feature.</p>
<ul>
<li>Video-2 should seamlessly autoplay after video-1, and so on</li>
<li>It should jump to corresponding videos whenever the user taps left or right.</li>
</ul>
<p>If you think caching could be the answer, don't worry – I’ll get to that in a bit.</p>
<h3 id="heading-multiple-layers-in-one-view">Multiple layers in one view</h3>
<p>First things first, we need to figure out how to add multiple videos in one view.</p>
<p>What we can do is create one <code>**AVPlayerLayer**</code> and assign the first video to it. When the first video is finished, then we assign the next video to the same <code>**AVPlayerLayer**</code> .</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addPlayer</span><span class="hljs-params">(player: AVPlayer)</span></span> {
    player.currentItem?.seek(to: <span class="hljs-type">CMTime</span>.zero, completionHandler: <span class="hljs-literal">nil</span>)
    playerViewModel?.player = player
    playerView.playerLayer.player = player
}
</code></pre>
<p>To jump to the previous or next video, we can do the following:</p>
<ul>
<li>Add a tap gesture on the view</li>
<li>If the touch location ‘x’ is less than half of the screen, then assign the previous video, else assign the next video</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-meta">@objc</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSnap</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: UITapGestureRecognizer)</span></span> {
   <span class="hljs-keyword">let</span> touchLocation = sender.location(ofTouch: <span class="hljs-number">0</span>, <span class="hljs-keyword">in</span>: view)
   <span class="hljs-keyword">if</span> touchLocation.x &lt; view.frame.width/<span class="hljs-number">2</span> {
     changePlayer(forward: <span class="hljs-literal">false</span>)
     } 
   <span class="hljs-keyword">else</span> {
     fillupLastPlayedSnap()
     changePlayer(forward: <span class="hljs-literal">true</span>)
    }
}
</code></pre>
<p>There we go. We now have our own Insta-like Stories video feature.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/13ZwNq4FnbM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>But our task is not done yet!</p>
<h2 id="heading-now-back-to-caching">Now Back to Caching</h2>
<p>We don't want it to be the case that every time a user navigates from one video to another, it starts to download the video from the beginning.</p>
<p>Also, if the video is shown again in the next session, we don't need to do another server call. </p>
<p>If we can cache the video, then the user’s internet will be saved. The load on the server will also be reduced.</p>
<p>Finally, the UX will improve as the user won't have to wait a long time to load the video.</p>
<p><strong>As a good developer, reducing</strong> a <strong>user’s internet usage should be our priority.</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/less-data-usage-happy-customer.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Less data usage, happy customer</em></p>
<h3 id="heading-load-videos-asynchronously">Load Videos <strong>Asynchronously</strong></h3>
<p>The first thing we can use to load videos is <strong>loadValuesAsynchronously</strong>.</p>
<p>According to <a target="_blank" href="https://developer.apple.com/documentation/avfoundation/avasynchronouskeyvalueloading/1387321-loadvaluesasynchronously">the Apple documentation</a>, <strong>loadValuesAsynchronously:</strong></p>
<blockquote>
<p><em>Tells the asset to load the values of all of the specified keys (property names) that are not already loaded.</em></p>
</blockquote>
<p>The advantage here is that it saves the video until it is rendered. So it will not download the video from the start whenever the user navigates to a previous video. It will only download the part which was not rendered earlier.</p>
<p><strong>Let's look at an e</strong>xample<em>**</em>: say we have Video_1 that is 15 seconds long, and the user saw 10 seconds of that video before jumping to Video_2. </p>
<p>Now if the user comes back to Video_1 again by tapping to the left, <strong>loadValuesAsynchronously</strong> will have that 10 seconds of video saved and will only download the remaining (unwatched) 5 seconds.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">asynchronouslyLoadURLAssets</span><span class="hljs-params">(<span class="hljs-number">_</span> newAsset: AVURLAsset)</span></span> {
    <span class="hljs-type">DispatchQueue</span>.main.async {
            newAsset.loadValuesAsynchronously(forKeys: <span class="hljs-keyword">self</span>.assetKeysRequiredToPlay) {
                <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.assetKeysRequiredToPlay {
                    <span class="hljs-keyword">var</span> error: <span class="hljs-type">NSError?</span>
                    <span class="hljs-keyword">if</span> newAsset.statusOfValue(forKey: key, error: &amp;error) == .failed {
                        <span class="hljs-keyword">self</span>.delegate?.playerDidFailToPlay(message: <span class="hljs-string">"Can't use this AVAsset because one of it's keys failed to load"</span>)
                        <span class="hljs-keyword">return</span>
                    }
                }

                <span class="hljs-keyword">if</span> !newAsset.isPlayable || newAsset.hasProtectedContent {
                    <span class="hljs-keyword">self</span>.delegate?.playerDidFailToPlay(message: <span class="hljs-string">"Can't use this AVAsset because it isn't playable or has protected content"</span>)
                    <span class="hljs-keyword">return</span>
                }
                <span class="hljs-keyword">let</span> currentItem = <span class="hljs-type">AVPlayerItem</span>(asset: newAsset)
                <span class="hljs-keyword">let</span> currentPlayer = <span class="hljs-type">AVPlayer</span>(playerItem: currentItem)
                <span class="hljs-keyword">self</span>.delegate?.playerDidSuccesToPlay(playerDetail: currentPlayer)
            }

        }
</code></pre>
<p>You can find more details on <strong>loadValuesAsynchronously</strong> at this <a target="_blank" href="https://developer.apple.com/documentation/avfoundation/avasynchronouskeyvalueloading/1387321-loadvaluesasynchronously">link</a>.</p>
<p>The caveat here is it persists video data for that session only. If the user closes and comes back to the app, the video has to be downloaded again.</p>
<p>So what other options do we have?</p>
<h3 id="heading-saving-videos-in-device">Saving Videos in Device</h3>
<p>Now comes <strong>Video Caching</strong>!</p>
<p>When the video is rendered completely, we can export the video and save it to the user’s device. When the video comes up again in their next session, we can pick the video from the device and simply load it.</p>
<p><strong>AVAssetExportSession</strong><br>According to <a target="_blank" href="https://developer.apple.com/documentation/avfoundation/avassetexportsession">Apple's documentation</a>:</p>
<blockquote>
<p><em>An object that transcodes the contents of an asset source object to create an output of the form described by a specified export preset.</em></p>
</blockquote>
<p>This means that AVAssetExportSession acts as an exporter, through which we can save the file to the user’s device. We have to give the output URL and the output file type.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> exporter = <span class="hljs-type">AVAssetExportSession</span>(asset: avUrlAsset, presetName: <span class="hljs-type">AVAssetExportPresetHighestQuality</span>)
exporter?.outputURL = outputURL
exporter?.outputFileType = <span class="hljs-type">AVFileType</span>.mp4

exporter?.exportAsynchronously(completionHandler: {
    <span class="hljs-built_in">print</span>(exporter?.status.rawValue)
    <span class="hljs-built_in">print</span>(exporter?.error)
})
</code></pre>
<p>You can find more details on <strong>AVAssetExportSession</strong> at this <a target="_blank" href="https://developer.apple.com/documentation/avfoundation/avassetexportsession">link</a>.</p>
<p>Now the only thing left is to fetch the data from the cache and load the video.</p>
<p>Before loading, check if the video is present in the cache. Then fetch that local URL and give it to <strong>loadValuesAsynchronously.</strong></p>
<pre><code class="lang-swift"><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> cacheUrl = <span class="hljs-type">FindCachedVideoURL</span>(forVideoId: videoId) {
    <span class="hljs-keyword">let</span> cacheAsset = <span class="hljs-type">AVURLAsset</span>(url: cacheUrl)
    asynchronouslyLoadURLAssets(cacheAsset)
}
<span class="hljs-keyword">else</span> {
  asynchronouslyLoadURLAssets(newAsset)
}
</code></pre>
<p>Caching will help reduce a lot of user data usage as well as server load (sometimes up to TBs of data).</p>
<h2 id="heading-other-use-cases-for-caching">Other use cases for caching</h2>
<p>What other use cases we can handle with caching? The following are examples of ways you could use caching here:</p>
<h3 id="heading-ensure-optimum-storage">Ensure Optimum Storage</h3>
<p>Before saving the video on the device, you should check whether enough storage is present on the device to do so.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">isStorageAvailable</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Bool</span> {
   <span class="hljs-keyword">let</span> fileURL = <span class="hljs-type">URL</span>(fileURLWithPath: <span class="hljs-type">NSHomeDirectory</span>() <span class="hljs-keyword">as</span> <span class="hljs-type">String</span>)
   <span class="hljs-keyword">do</span> {
      <span class="hljs-keyword">let</span> values = <span class="hljs-keyword">try</span> fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeTotalCapacityKey])
      <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> totalSpace = values.volumeTotalCapacity,
      <span class="hljs-keyword">let</span> freeSpace = values.volumeAvailableCapacityForImportantUsage <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
      }
      <span class="hljs-keyword">if</span> freeSpace &gt; minimumSpaceRequired {
         <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
      } <span class="hljs-keyword">else</span> {
          <span class="hljs-comment">// Capacity is unavailable</span>
          <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
      }  
    <span class="hljs-keyword">catch</span> {}
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
}
</code></pre>
<h3 id="heading-remove-deprecated-videos">Remove Deprecated Videos</h3>
<p>You can have a timestamp for each video so that you can clean up old videos from device memory after a certain number of days.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">cleanExpiredVideos</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> currentTimeStamp = <span class="hljs-type">Date</span>().timeIntervalSince1970
        <span class="hljs-keyword">var</span> expiredKeys: [<span class="hljs-type">String</span>] = []
        <span class="hljs-keyword">for</span> videoData <span class="hljs-keyword">in</span> videosDict <span class="hljs-keyword">where</span> currentTimeStamp - videoData.value.timeStamp &gt;= expiryTime {
            <span class="hljs-comment">// video is expired. delete</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-number">_</span> = popupVideosDict[videoData.key] {
                expiredKeys.append(videoData.key)
            }
        }
        <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> expiredKeys {
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-number">_</span> = popupVideosDict[key] {
                popupVideosDict.removeValue(forKey: key)
                deleteVideo(<span class="hljs-type">ForVideoId</span>: key)
            }
        }
    }
</code></pre>
<h3 id="heading-maintain-a-limited-number-of-videos">Maintain a limited number of videos</h3>
<p>You can make sure only a limited number of videos are saved in the file at a time. Let's say 10. </p>
<p>Then when the 11th video comes, you can have it delete the least-viewed video and replace it with the new one. This will also help you not consume too much of the user’s device memory.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeVideoIfMaxNumberOfVideosReached</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> popupVideosDict.<span class="hljs-built_in">count</span> &gt;= maxVideosAllowed {
            <span class="hljs-comment">// remove the least recently used video</span>
            <span class="hljs-keyword">let</span> sortedDict = popupVideosDict.keysSortedByValue { (v1, v2) -&gt; <span class="hljs-type">Bool</span> <span class="hljs-keyword">in</span>
                v1.timeStamp &lt; v2.timeStamp
            }
            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> videoId = sortedDict.first <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span>
            }
            popupVideosDict.removeValue(forKey: videoId)
            deleteVideo(<span class="hljs-type">ForVideoId</span>: videoId)
        }
    }
</code></pre>
<h3 id="heading-measure-impact">Measure Impact</h3>
<p>Don’t forget to add logs, so that you can measure the impact of your feature. I have used a custom New Relic Log Event to do so:</p>
<pre><code class="lang-swift"> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">findCachedVideoURL</span><span class="hljs-params">(forVideoId id: String)</span></span> -&gt; <span class="hljs-type">URL?</span> {
        <span class="hljs-keyword">let</span> nsDocumentDirectory = <span class="hljs-type">FileManager</span>.<span class="hljs-type">SearchPathDirectory</span>.documentDirectory
        <span class="hljs-keyword">let</span> nsUserDomainMask = <span class="hljs-type">FileManager</span>.<span class="hljs-type">SearchPathDomainMask</span>.userDomainMask
        <span class="hljs-keyword">let</span> paths = <span class="hljs-type">NSSearchPathForDirectoriesInDomains</span>(nsDocumentDirectory, nsUserDomainMask, <span class="hljs-literal">true</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> dirPath = paths.first {
            <span class="hljs-keyword">let</span> fileURL = <span class="hljs-type">URL</span>(fileURLWithPath: dirPath).appendingPathComponent(folderPath).appendingPathComponent(id + <span class="hljs-string">".mp4"</span>)
            <span class="hljs-keyword">let</span> filePath = fileURL.path
            <span class="hljs-keyword">let</span> fileManager = <span class="hljs-type">FileManager</span>.<span class="hljs-keyword">default</span>
            <span class="hljs-keyword">if</span> fileManager.fileExists(atPath: filePath) {
                <span class="hljs-type">NewRelicService</span>.sendCustomEvent(with: <span class="hljs-type">NewRelicEventType</span>.statusCodes,
                                                                   eventName: <span class="hljs-type">NewRelicEventName</span>.videoCacheHit,
                                                                   attributes: [<span class="hljs-type">NewRelicAttributeKey</span>.videoSize: fileURL.fileSizeString])
                <span class="hljs-keyword">return</span> fileURL
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
            }
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
</code></pre>
<p>To convert the file size to a readable format, I fetch the file size and convert it to Mbs.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">URL</span> </span>{
    <span class="hljs-keyword">var</span> attributes: [<span class="hljs-type">FileAttributeKey</span> : <span class="hljs-type">Any</span>]? {
        <span class="hljs-keyword">do</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">try</span> <span class="hljs-type">FileManager</span>.<span class="hljs-keyword">default</span>.attributesOfItem(atPath: path)
        } <span class="hljs-keyword">catch</span> <span class="hljs-keyword">let</span> error <span class="hljs-keyword">as</span> <span class="hljs-type">NSError</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"FileAttribute error: \(error)"</span>)
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }

    <span class="hljs-keyword">var</span> fileSize: <span class="hljs-type">UInt64</span> {
        <span class="hljs-keyword">return</span> attributes?[.size] <span class="hljs-keyword">as</span>? <span class="hljs-type">UInt64</span> ?? <span class="hljs-type">UInt64</span>(<span class="hljs-number">0</span>)
    }

    <span class="hljs-keyword">var</span> fileSizeString: <span class="hljs-type">String</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-type">ByteCountFormatter</span>.string(fromByteCount: <span class="hljs-type">Int64</span>(fileSize), countStyle: .file)
    }
}
</code></pre>
<p>This is how you can measure your impact:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/09/Screenshot-2020-09-16-at-11.34.24-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>Total data saved = n</strong>umber <strong>of request</strong>s <strong><em> video_size = 2.4MB</em>20.3K ~= 49GB</strong></p>
<p>This is just two weeks of data. You do the math for the whole year. ? And this will keep on increasing exponentially over time.</p>
<p>That’s it! You have now built your own caching mechanism.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/09/yay.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h1 id="heading-wrapping-up">Wrapping up</h1>
<p>In this article, we saw how easily we can integrate multiple videos in one view, giving an Instagram-like story feature.</p>
<p>We also learned why and how caching plays an important role here. We saw how it helps the user save a lot of data and have a smooth user experience.</p>
<p>Do let me know if I missed something, or if you can think of any more use cases.<br>Thanks for your time. :)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ HTML5 Video: How to Embed Video in Your HTML ]]>
                </title>
                <description>
                    <![CDATA[ Before HTML5, in order to have a video play on a webpage, you would need to use a plugin like Adobe Flash Player. With the introduction of HTML5, you can now place videos directly into the page itself. This makes it possible to have videos play on pa... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/html5-video/</link>
                <guid isPermaLink="false">66c356fa1efc805979ae9f74</guid>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 27 Jan 2020 00:43:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9d71740569d1a4ca37cc.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Before HTML5, in order to have a video play on a webpage, you would need to use a plugin like Adobe Flash Player. With the introduction of HTML5, you can now place videos directly into the page itself.</p>
<p>This makes it possible to have videos play on pages that are designed for mobile devices, as plugins like Adobe Flash Player don't work on Android or iOS.</p>
<p>The HTML <code>&lt;video&gt;</code> element is used to embed video in web documents. It may contain one or more video sources, represented using the <code>src</code> attribute or the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source">source</a> element.</p>
<p>To embed a video file, just add this code snippet and change the <code>src</code> to the path of your video file:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">controls</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"tutorial.ogg"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video /ogg"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"tutorial.mp4"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video /mpeg"</span>&gt;</span>
  Your browser does not support the video element. Kindly update it to latest version.
<span class="hljs-tag">&lt;/<span class="hljs-name">video</span> &gt;</span>
</code></pre>
<p>The <code>&lt;video&gt;</code> element is supported by all modern browsers. However, not all browsers support the same video file format.  MP4 files are the most widely accepted format, and other formats like WebM and Ogg are supported in Chrome, Firefox, and Opera.</p>
<p>To ensure your video plays in most browsers, it's best practice to encode them into both Ogg and MP4 formats, and include both in the <code>&lt;video&gt;</code> element like in the example above. Browsers will use the first recognized format.</p>
<p>If for some reason the browser doesn't recognize any of the formats, the text "Your browser does not support the video element. Kindly update it to latest version" will be displayed instead.</p>
<p>You also might have noticed <code>controls</code> in the <code>&lt;video&gt;</code> tag. This element includes a lot of useful attributes to customize the playback experience.</p>
<h2 id="heading-attributes"><code>&lt;video&gt;</code> attributes</h2>
<h3 id="heading-controls"><code>controls</code></h3>
<p>The <code>controls</code> attribute handles whether controls such as the play/pause button or volume slider appear. </p>
<p>This is a boolean attribute, meaning it can be set to either true or false. To set it to true, simply add it to the <code>&lt;video&gt;</code> tag. If it's not present in the tag then it will be set to false and the controls won't appear.</p>
<h4 id="heading-autoplay"><code>autoplay</code></h4>
<p>"autoplay" can be set to either true or false. You set it to true by adding it into the tag, if it is not present in the tag it is set to false. If set to true, the video will begin playing as soon as enough of the video has buffered for it to be able to play. Many people find autoplaying videos as disruptive or annoying. So use this feature sparingly. Also note, that some mobile browsers, such as Safari for iOS, ignore this attribute.</p>
<p>This is another boolean attribute. By including <code>autoplay</code> in the <code>&lt;video&gt;</code> tag, the embedded video will begin playing as soon as enough of it has buffered.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">autoplay</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"video.mp4"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video/mp4"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<p>Keep in mind that many people find autoplaying videos disruptive or annoying, so use this feature sparingly. Also note that some mobile browsers like Safari for iOS ignore this attribute entirely.</p>
<h4 id="heading-poster"><code>poster</code></h4>
<p>The <code>poster</code> attribute is the image that shows on the video until the user clicks to play it.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">poster</span>=<span class="hljs-string">"poster.png"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"video.mp4"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video/mp4"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
</code></pre>
<h3 id="heading-videos-can-be-expensive">Videos can be expensive</h3>
<p>While it's easier than ever to include videos on your page, it's often better to upload your videos to a service like YouTube, Vimeo, or Wistia and embed their code instead. This is because serving videos can be expensive, both for you in terms of server costs and for your viewers if they have limited data plans.</p>
<p>Hosting your own video files can also lead to problems with bandwith, which could mean stuttering of slow loading videos. On top of that, browsers tend to vary in quality when it comes to video playback, so it's hard to control exactly what your viewers will see. It's also very easy to download videos embedded with the <code>&lt;video&gt;</code> tag, so if you're concerned with piracy you might want to look into other options.</p>
<p>And with that, go forth and embed videos to your heart's content. Or not – it's your call.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ HLS Video Streaming: What it is, and When to Use it ]]>
                </title>
                <description>
                    <![CDATA[ By Anton Garcia Diaz In this short article I will focus on HLS, the most extended adaptive bitrate protocol for video delivery. I'll answer some of the main questions that anyone considering HLS for the first time will likely ask: what it is, when to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-hls-and-when-to-use-it/</link>
                <guid isPermaLink="false">66d45da33dce891ac3a967b0</guid>
                
                    <category>
                        <![CDATA[ Adaptive Bitrate ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hls ]]>
                    </category>
                
                    <category>
                        <![CDATA[ media ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Video.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ VideoJS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 18 Dec 2019 22:59:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/12/HLS-video.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Anton Garcia Diaz</p>
<p>In this short article I will focus on HLS, the most extended adaptive bitrate protocol for video delivery. I'll answer some of the main questions that anyone considering HLS for the first time will likely ask: what it is, when to use it, and how to use it. </p>
<p>To help along the way, I will show some examples using <a target="_blank" href="https://abraia.me/video/">an online video publishing tool</a> that you can freely use to test out the performance of HLS on your own.</p>
<h2 id="heading-what-is-hls-and-how-does-it-work">What is HLS and how does it work?</h2>
<p>HLS is a protocol defined by Apple to implement an adaptive bitrate streaming format that can be supported on their devices and software. Over the time, it has gained widespread support. </p>
<p>The most important feature of HLS is its ability to adapt the bitrate of the video to the actual speed of the connection. This optimizes the quality of the experience. </p>
<p>HLS videos are encoded in different renditions at different resolutions and bitrates. This is usually referred to as the bitrate ladder. When a connection gets slower, the protocol automatically adjusts the requested bitrate to the bandwidth available. </p>
<p>Compared to progressive videos, HLS avoids re-buffering and stalling effects as well as bloating the client connection. We can see it at work in this video.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/HLS-video/HLS_video-at-work/index.html">https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/HLS-video/HLS_video-at-work/index.html</a></div>
<p>In essence, HLS provides a much better user experience when we use video content in our apps or sites.</p>
<p>It has native support in iOS and Android. It is also supported by Safari, and by using some JavaScript it is supported in all the main browsers (Chrome, Firefox, Edge). While using HLS requires some effort, it's not a big deal. </p>
<p>Let's see when we should use it and how.</p>
<h2 id="heading-when-should-we-use-hls">When should we use HLS?</h2>
<p>There are cases where videos are not that heavy. For instance, you could have a sequence of images encoded as a 1-2 seconds video, with a weight of less than 1 MB. In this case, a progressive video – that can be consumed, like an image, using plain HTML5 – is for sure the best option. HLS does not offer any advantage here.</p>
<p>But, HLS does make sense when we want to deliver high resolution videos (HD or over) with a weight over 3MB. This type of content may kill our web UX when viewed on an average mobile connection. </p>
<p>It's worth noting that this is the case in an increasing amount of media content, including many short videos of less than 20 seconds used in ecommerce and marketing contexts. In the example at the beginning of the post, we have a full HD video of only 9 seconds that weights in at over 6MB.</p>
<h2 id="heading-how-can-we-use-hls-in-our-sites">How can we use HLS in our sites?</h2>
<p>To use HLS we have to address a number of aspects. I'll focus on two important points:  </p>
<ul>
<li>the need to encode the video, and, </li>
<li>the need to embed it in our page. </li>
</ul>
<p>For a more comprehensive view on what a general video publishing pipeline entails, you may check out <a target="_blank" href="https://www.freecodecamp.org/news/short-videos-in-web-and-ecommerce-workflows/">this post</a>.</p>
<h3 id="heading-hls-encoding">HLS encoding</h3>
<p>We can encode videos in HLS in-house or by using a third party service. To build an in-house encoder, the best option is to use FFMPEG, a powerful open source library for video processing and encoding. In this case, we should analyse the content we are going to encode and set a number of parameters. </p>
<p>In HLS we should define a bitrate ladder (the bitrates and resolutions of each step) and the length of chunks. When we encode a video, we end with a set of playlists and chunks. Typically, we end the former with .m3u8 and the latter with .ts extensions. We can see an example in the next image.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/imaxe.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can see one master playlist, one additional playlist per rendition, and all the chunks of each rendition. The master playlist specifies the bitrate ladder and the relative path to each rendition.</p>
<p>Apple makes a generic recommendation specifying the bitrate ladder and a chunk duration of 10 seconds.  However, this is not very useful for many types of content, like the short videos common in ecommerce and marketing. </p>
<p>In fact, the best approach is to tune the bitrate ladder specifically to the content of the video. In this case, if you want to make the most of HLS and you're not expert in encoding, a third party service providing per-title encoding (with HLS) is likely the right choice.</p>
<h2 id="heading-hls-players">HLS players</h2>
<p>Here, we find two main options. We can stick to the HTML5 player or we can use one implemented in JavaScript.</p>
<h3 id="heading-html5-player">HTML5 player</h3>
<p>Recent Safari versions support HLS. In this case, you may use HLS playlists in the same manner as progressive videos. With other browsers, you may use a tiny JavaScript library to implement the HLS protocol and again use the HTML5 player for progressive videos. </p>
<p>This can be done with HLS.js. This library just implements the negotiation of renditions, based on the available bandwidth. Support is almost universal, only conditional on the support of the media element's API.</p>
<h3 id="heading-javascript-player">JavaScript Player</h3>
<p>In case we need to customise the video experience – which is pretty common in marketing and stories pages – then we need to use something other than the default HTML5 player. </p>
<p>While there are many commercial options out there, Video.js is a good choice. It's an open source player that supports a high degree of customization, including different skins and controls. </p>
<p>A player like Video.js also supports the tracking of video-related events (like play or pause actions) so we can include them in our own analytics. In fact, including these data in our Google Analytics is really easy.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/imaxe-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>GA data for events tracked in a video viewed with a Video.js player</em></p>
<h2 id="heading-summary">Summary</h2>
<p>I've tackled the first questions about HLS that most potential users will have: what it is, and when we should use it.</p>
<p>While a video publishing pipeline reliant on HLS can be implemented and deployed in-house with open source tools like FFMPEG and video.js, it may be a good idea to use a <a target="_blank" href="https://abraia.me/video/">video publishing service</a> if you're not an expert in the tech. They bring advanced features like per-title encoding, take care of all the hard work, and let us focus on our customization needs.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to deploy a complete video publishing pipeline for web and ecommerce ]]>
                </title>
                <description>
                    <![CDATA[ By Anton Garcia Diaz From ffmpeg and cloud video transcoding to HLS, delivery, players, Video.js, and analytics. After the conquest of social networks, video is spreading through web businesses. As a media consultant working for several of the larges... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/short-videos-in-web-and-ecommerce-workflows/</link>
                <guid isPermaLink="false">66d45d9f3a8352b6c5a2aa03</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hls ]]>
                    </category>
                
                    <category>
                        <![CDATA[ publishing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[  #Transcoding  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Video.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ VideoJS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 13 Nov 2019 08:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/11/Video-Publishing-Demo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Anton Garcia Diaz</p>
<p><em>From ffmpeg and cloud video transcoding to HLS, delivery, players, Video.js, and analytics.</em></p>
<p>After the conquest of social networks, video is spreading through web businesses. As a media consultant working for several of the <a target="_blank" href="https://www.similarweb.com/top-websites/category/lifestyle/fashion-and-apparel">largest fashion ecommerce sites</a> in the world, I feel safe saying the video-everywhere trend is all but unstoppable.  </p>
<p>In this post, I review the main aspects to consider when publishing short-format videos in a web workflow. I comment about open source resources that make an in-house solution possible for each step, like ffmpeg or Video.js. Besides, I use an example with <a target="_blank" href="https://abraia.me/video/">Abraia's video optimization and publishing demo</a> - specially tailored to short videos for fashion ecommerce. </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Short-Video-Publishing-Demo/Workflow/index.html">https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Short-Video-Publishing-Demo/Workflow/index.html</a></div>
<p>It gives full access to the resources created: chunks, playlists, and html code for the video player. This brings quick insights on the inner workings of a complete pipeline. </p>
<p>The content should be helpful either to pursue an in-house processing and publishing pipeline or to sort out the best combination of services. </p>
<h2 id="heading-quality-of-experience-qoe-and-other-business-related-concerns">Quality of experience (QoE) and other business related concerns.</h2>
<p>There are two main concerns that are closely linked. The fear of bloating the bandwidth of users, which damages UX and engagement, and the fear of delivering poor visual quality, which potentially damages brand image. </p>
<p>The balance between these two antagonising factors is what determines the QoE. Keeping a <strong>high QoE</strong> requires delivering nearly the <strong>best possible quality, without rebuffering or stalling</strong> effects or noticeable drops of page speed.  </p>
<p>Of course, there are other issues that matter</p>
<ul>
<li>the customisation of the viewing experience to match the branding of the business</li>
<li>the cost increase of delivering higher bandwidth content </li>
<li>and the additional burden in terms of devops</li>
</ul>
<p>...just to name a few.</p>
<h2 id="heading-a-first-choice-progressive-vs-adaptive-bitrate-abr">A first choice: progressive vs adaptive bitrate (ABR).</h2>
<p>Regarding <a target="_blank" href="https://www.freecodecamp.org/news/video-formats-for-the-web/">video format selection</a>, there are two main options with important implications: progressive video and ABR.</p>
<p>Progressive videos may be delivered and consumed like images, using plain HTML5 code. Moreover, progressive mp4 videos with H264 encoding have universal support across browsers and systems. So, they're the straightforward approach.</p>
<p>However, in the likely event that QoE is a main concern we should go for ABR. More specifically for HLS -again with H264 encoding – which is a broadly supported option. </p>
<p>With <strong>HLS</strong> we'll be able, in most cases, to keep the <strong>bits per second - the bitrate - of the video within the connection capacity limits</strong>. This avoids rebuffering, stalling, or blocking other content. In HLS, the video is available at different bitrates and is split in pieces. This allows the client to request the best quality affordable, based on the network speed at any time. The only caveat is that we'll need to use a player in our front-end (basically a piece of JavaScript). In apps, it's easier because both iOS and Android feature native support for the protocol. </p>
<h2 id="heading-the-pipeline-and-the-workflow">The pipeline and the workflow</h2>
<p>That said, let's see what a video optimization and delivery pipeline for web entails. The pipeline is supposed to process a master or pristine video with a high quality and make it suited to the web. It's also supposed to meet brand requirements on visualization, and to integrate the video events in the analytics of the site. </p>
<p>In sum, our pipeline should address the following problems:</p>
<ul>
<li>Content management</li>
<li>Transcoding and optimization</li>
<li>Delivery</li>
<li>Visualization</li>
<li>Analytics</li>
</ul>
<p>In the end, the pipeline should allow a workflow similar to that of social video platforms - where you upload a video and get a <a target="_blank" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Short-Video-Publishing-Demo/Video-Freecodecamp/index.html">link like this</a> to embed or share elsewhere - but under all the custom requirements of our business.</p>
<p>To keep this post short and focused, I'll skip the content management issue, which is basically the way we handle all the resources, including the collaborative media editing and approval workflows. Next, I go through the main optimization and delivery ingredients found in a video publishing pipeline.</p>
<h2 id="heading-transcoding-and-optimization">Transcoding and optimization</h2>
<p>For progressive videos to be responsive, we can create versions with different resolution and quality to be consumed based on breakpoints, similar to images. </p>
<p>In an in-house scheme this operation can be <a target="_blank" href="https://medium.com/abraia/video-transcoding-and-optimization-for-web-with-ffmpeg-made-easy-511635214df0">easily accomplished with ffmpeg</a>. It's an open source tool that performs resizing, compression, and many other operations very efficiently. For instance, to scale a 4K video to fullHD with good visual quality you may simply use:</p>
<pre><code>ffmpeg -y -i input.mp4 -vf scale=<span class="hljs-number">1920</span>:<span class="hljs-number">-2</span> -c:v libx264 -crf <span class="hljs-number">22</span> -profile:v high -pix_fmt yuv420p -color_primaries <span class="hljs-number">1</span> -color_trc <span class="hljs-number">1</span> -colorspace <span class="hljs-number">1</span> -movflags +faststart -an output.mp4
</code></pre><p>Alternatively, with a cloud platform the operation should be a no brainer, although in many cases we loose effective control of the quality settings and possible breakpoints.</p>
<p>Encoding for <strong>HLS</strong> is a bit trickier. First, <strong>we have to define a coding ladder</strong>. Each step of the ladder will feature a different bitrate, from a maximum to a minimum. They set respectively the maximum and minimum quality. </p>
<p>For each bitrate in the ladder, we also have to set the resolution, again from maximum to minimum. Ideally, we should use bitrates specifically tuned to the video content to optimize the use of bandwidth. When done automatically, <strong>on a per video basis</strong>, this is called <strong>per title encoding</strong>. </p>
<p>We have to code the video with the resolutions and bitrates defined and then cut each rendition in chunks. We also have to decide the duration of the chunk. That is, how frequently is HLS renegotiating the quality to request, based on the current network speed. We can do all of the encoding with ffmpeg or with a cloud service.</p>
<p>Let's see the files generated for our example. We have a folder containing all the chunks (.ts extension), and the playlists ( .m3u8 extension). </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/11/imaxe-7.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The playlists contain all the information about the renditions available. Next, we can see the content of the master playlist: the ladder - bitrates and resolutions - and the relative route to the renditions.</p>
<pre><code>#EXTM3U
#EXT-X-VERSION:<span class="hljs-number">3</span>
#EXT-X-STREAM-INF:BANDWIDTH=<span class="hljs-number">3374012</span>,RESOLUTION=<span class="hljs-number">1920</span>x1080
<span class="hljs-number">1080</span>p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=<span class="hljs-number">1836580</span>,RESOLUTION=<span class="hljs-number">1280</span>x720
<span class="hljs-number">720</span>p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=<span class="hljs-number">1002050</span>,RESOLUTION=<span class="hljs-number">856</span>x480
<span class="hljs-number">480</span>p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=<span class="hljs-number">649329</span>,RESOLUTION=<span class="hljs-number">640</span>x360
<span class="hljs-number">360</span>p.m3u8
</code></pre><p>That is, for each rendition we have an additional playlist containing the information about the duration and route to the corresponding chunks. We also need a poster to use as thumbnail and get covered in the event of a very slow connection or HLS compatibility issues. In our example, all the resources are in the same folder so the route to each resource is simply the name.</p>
<h2 id="heading-delivery">Delivery</h2>
<p><strong>Videos should be delivered through a CDN</strong>. If you make a poor transcoding, many users may suffer slow page loads. But at least if you use a CDN you won't take your site down because the server is unable to handle the load. I've seen big sites that more than doubled their peak traffic the day they decided to use videos in their home page. So videos, whether progressive or HLS, should be delivered as static files cached and delivered by a CDN.</p>
<p>If you are using a cloud platform for video publishing, you should be covered. Any decent one offers video delivery through at least one CDN. If you need coverage in some countries like China, you need to look into each specific platform and the CDN used, since some of them do not work there.</p>
<h2 id="heading-visualization">Visualization</h2>
<p>While for progressive videos HTML5 is enough to ensure visualization, in the case of HLS we need a <strong>JavaScript player with HLS support</strong>. </p>
<p>There are many commercial options, but there are also open source alternatives with very high quality. A good example is <strong>Video.js</strong>. It has a wide support among browsers, only limited by the dependency on the <a target="_blank" href="https://caniuse.com/#search=media%20source">Media Source Extensions API</a>. It brings a high degree of customization using skins and a flexible configuration, for instance allowing you to use autoplay or different video controls.</p>
<p>The player may be inserted in the page code, or it can be in an html static that is embedded as an iframe. </p>
<p>Going back to our example, when we publish the video we create an <a target="_blank" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Tests/PexelsVideos2795392/index.html">html resource</a> that has a Video.js player with default settings. The content url should point to the master playlist and the thumbnail to the poster image extracted from the video.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/11/imaxe-3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this case, the html resource also adds <strong>oembed compatibility</strong>. Besides directly access in the browser this html - or a different one in which we copy/paste the player's code - to play <a target="_blank" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Tests/PexelsVideos2795392/index.html">the video</a>, we can embed it in a content management system (CMS). For instance, when writing this post for freeCodeCamp.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Short-Video-Publishing-Demo/Embedding/index.html">https://store.abraia.me/05bf471cbb3f9fa9ed785718e6f60e28/Short-Video-Publishing-Demo/Embedding/index.html</a></div>
<h2 id="heading-analytics">Analytics</h2>
<p>In short videos, typical analytics of interest are the <strong>ratio of users that play the video, the ratio of those who view it in full, or the ratio of playback failures</strong>. </p>
<p>Again, there are many commercial options available. However in many cases a widespread free option like Google Analytics (GA) may be enough. If we are using Video.js, we only have to instrument the html resource with GA, like for any other web page. Going back to our example, we can see it in the editable HTML created.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/11/imaxe-5.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To track the video use in GA, we just have to track the video events in the player. For instance:</p>
<pre><code>    player.analytics({
      <span class="hljs-attr">defaultVideoCategory</span>: <span class="hljs-string">'Video'</span>,
      <span class="hljs-attr">events</span>: [{
        <span class="hljs-attr">name</span>: <span class="hljs-string">'play'</span>,
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Video-Freecodecamp'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'play'</span>,
      }, {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'pause'</span>,
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Video-Freecodecamp'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'pause'</span>,
      }, {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'ended'</span>,
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Video-Freecodecamp'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'ended'</span>,
      }, {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'error'</span>,
        <span class="hljs-attr">label</span>: <span class="hljs-string">'Video-Freecodecamp'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'error'</span>,
      }]
    });
</code></pre><p>Then, in GA we can see the events taking place. This screenshot shows my own real-time activity - with two devices and browsers - on the video example created for this post.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/11/imaxe-4.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-summary">Summary</h2>
<p>I have reviewed the main aspects involved in a video publishing pipeline, from transcoding, to delivery, visualization, and analytics. I have made reference to potential use of different resources, including two prominent open source initiatives like ffmpeg and Video.js.</p>
<p>I have supported the explanation with a simple example using our <a target="_blank" href="https://abraia.me/video/">video publishing demo</a>. It gives full access to the resources created. You'll be able to download, modify, and use the resources in your tests. You can freely use it to repeat the process with a short video of your choice. </p>
<p>Remember to start with a high quality video. The example here is based on a 9 seconds 4k video from <a target="_blank" href="https://www.pexels.com/@cottonbro">@cottonbro</a>. Overall, I expect the post to bring a bird's eye view of what a custom deployment for video publishing entails.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Video formats for the web, a short guide  to help you choose ]]>
                </title>
                <description>
                    <![CDATA[ By Anton Garcia Diaz Video in the web will be on the rise for long. While embedding Instagram and Youtube videos is simple, there are more and more situations -like many ecommerce use cases- demanding a custom approach to video delivery. When it come... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/video-formats-for-the-web/</link>
                <guid isPermaLink="false">66d45da19f2bec37e2da0604</guid>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ video ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 08:45:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/photo-1440404653325-ab127d49abc1-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Anton Garcia Diaz</p>
<p>Video in the web will be on the rise for long. While embedding Instagram and Youtube videos is simple, there are more and more situations -like many ecommerce use cases- demanding a custom approach to video delivery.</p>
<p>When it comes to setting a video processing and delivery pipeline, the first decision to take is about the video formats to serve. Aspects like UX, support (browsers and systems), compression efficiency, or coding speed are likely to be relevant to this choice.</p>
<p>Based on my <a target="_blank" href="https://abraia.me/">experience on media optimization for web businesses</a>, I try to highlight here the main aspects to consider. If you are looking for a simple transcoding and optimization option using ffmpeg you may also check <a target="_blank" href="https://medium.com/abraia/video-transcoding-and-optimization-for-web-with-ffmpeg-made-easy-511635214df0">this article</a>.</p>
<h2 id="heading-containers-and-codecs">Containers and codecs</h2>
<p>In contrast to usual <a target="_blank" href="https://www.freecodecamp.org/news/best-image-format-for-web-in-2019-jpeg-webp-heic-avif-41ba0c1b2789/">image formats</a>, it is really important to be aware of the difference between container and coding standard. The file extension tells us which container, but not which codec is being used. And the standard followed to encode the clip will determine if it is supported by the browser or the system.</p>
<p>For instance, while the universally supported video format for web uses a mp4 container and the H264 standard for encoding, not every mp4 file is universally supported since it may be coded under a different standard, like H265.</p>
<p>It even gets a bit more complex with adaptive bit rate (ABR), which brings the best way to be responsive to the network and device capabilities of the user.</p>
<p>Let’s see the main combinations of containers  and coding and delivering standards and the differences between them in terms of support, compression efficiency, encoding speed, and user experience.</p>
<h2 id="heading-progressive-video">Progressive video</h2>
<h3 id="heading-h264avc">H264/AVC</h3>
<p>The king format for video features a mp4 container with H264/AVC encoding. Sometimes you’ll find it in a m4v container (default format in Handbrake), an mp4 derivative developed by Apple for H264 videos with DRM protection.</p>
<p>Every browsers and systems -also native applications in both iOS and Android- support this format. It is the safe choice to avoid compatibility issues.</p>
<p>Moreover, almost any device from desktop to mobile, features support for hardware acceleration for H264. It’s fast to encode and decode.</p>
<p>In sum, encoding and delivering this format is really easy. Like for images, you can simply insert the link to the video using HTML5 and it will work with any browser out there.</p>
<p>The problems may appear for resolutions over VGA, good visual quality -bitrates about 2000 kbps and over- and a duration over several seconds. When viewed through a mobile network -in many regions also in home connections during peak hours- it may suffer stalls and rebufering. The alternative of reducing the quality will produce artifacts like blur, mosquito, or blockiness.</p>
<h3 id="heading-h265hevc">H265/HEVC</h3>
<p>Using the same container and H265/HEVC encoding we find a powerful video format that yield much higher compression efficiency (<a target="_blank" href="https://www.bbc.co.uk/rd/blog/2016-01-h-dot-265-slash-hevc-vs-h-dot-264-slash-avc-50-percent-bit-rate-savings-verified">about 50% lighter</a>) and much less risk of artifacts other than blur. The problem of this format is that support is limited to Apple devices, which include the hefty royalties in their price. Almost only Safari and iOS apps will be able to use it. If you have many iPhone or Mac users you can include it with a fallback to H264. The experience for them will be better.</p>
<p>Even with hardware acceleration -available almost only in Apple devices- the higher complexity of this format means that encoding is significantly slower, so producing the variants for delivery takes more computing and more time.</p>
<h3 id="heading-vp9">VP9</h3>
<p>This is the open source royalty free reply from Google. Instead of mp4 it uses webm containers, basically a mkv container but setting the coding standard to VP8 or VP9. In <a target="_blank" href="https://medium.com/netflix-techblog/a-large-scale-comparison-of-x264-x265-and-libvpx-a-sneak-peek-2e81e88f8b0f">brings similar benefits to H265</a>, perhaps a bit less efficient but still much more compared to H264. Again, it allows to reduce the weight with much less risk of artifacts other than blur. The encoding speed is similar to H265, which is slow. The encoding speed may be something to bear in mind, specially in an inhouse transcoding pipeline.</p>
<p>Notice that while a previous version (VP8) exists with same support, we don't recommend it at all, since it does not add any benefits to H264, which is already universally supported. The use of webm is only justified with VP9 encoding.</p>
<p>Of course, support for webm is limited to Google world. That means Chrome and Android. Again, we’ll need a fallback to H264.</p>
<h3 id="heading-av1">AV1</h3>
<p>A first stable version of this standard was released in march 2018, with mappings for both MP4 and MKV containers. It delivers similar or slightly higher gains in compression efficiency compared to H265, while being license free. The <a target="_blank" href="https://www.bbc.co.uk/rd/blog/2019-05-av1-codec-streaming-processing-hevc-vvc">last implementations have also improved the decoding speed compared to H265</a>, making AV1 videos a compelling alternative for web delivery.</p>
<p>The partners involved in the Alliance for Open Media that created the format make the case for widespread support in the near future. It promises sweeping all the other formats out there.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/07/imaxe.png" alt="Image" width="600" height="400" loading="lazy">
<em>Partners of the Alliance for Open Media behind AV1</em></p>
<p>However, the implementation currently available should be still considered experimental and its bottleneck is still encoding speed. The lack of hardware acceleration for this operation is clearly an issue, with the first solutions expected for the end of the year.</p>
<h3 id="heading-vvc">VVC</h3>
<p>The comittee responsible for H264 AVC and H265 HEVC has fast tracked a new standard, with a release expected for 2020. Preliminary tests on the approaches currently considered have shown remarkable gains compared to H265 and AV1.  I include it here as a futuristic notice, just to show that the video coding race seems far from over.</p>
<h2 id="heading-adaptive-bitrate-abr">Adaptive bitrate (ABR)</h2>
<p>This is a very interesting alternative to any progressive format. It builds upon a HTTP-based media streaming communications protocol. In this approach videos are delivered as a master playlist. The playlist offers a representation or ladder, with different options of resolution and bitrate that cater to different viewport sizes, network bandwidths, and device capabilities.</p>
<p>Moreover, videos are split into pieces or <em>chunks</em> so that the client may jump from one quality level to other. It is able to adapt to the conditions of the user, namely to the network speed but also to the viewport size -like switching to full screen-.</p>
<p>ABR brings a big advantage to optimize UX for mobile devices, avoiding stalls or re-buffering events under mobile networks. If you seek a true responsive behavior, this is clearly the approach to take. There are two main standards, HLS and MPEG-DASH.</p>
<p>Although there is an extended belief that ABR only makes sense for quite long videos, in my experience many situations with fairly short clips can also benefit from this approach.</p>
<h3 id="heading-hls">HLS</h3>
<p>Developed by Apple, this ABR protocol relies on different renditions split in chunks in mp4 format. Originally with H264, it also supports H265 now. However, as a compromise we would recommend to stick to the H264 encoding with HLS since it brings much better compatibility across a variety of client cases.</p>
<p>A big point of this standard is support in recent Apple devices. For clients other than Safari or native iOS applications, you’ll need a viewer. But this is not a big problem since good open source options like videojs are available out there. Or course, you’ll need some effort to customise it and put it to work in your front-end. There are also great transcoding and delivery services doing all this work for you.</p>
<p>Since each rendition should be encoded at constant bitrate, I recommend combining HLS with per title encoding. That is, selecting the rendition bitrate based on the content of the video.</p>
<h3 id="heading-mpeg-dash">MPEG-DASH</h3>
<p>This is a codec-agnostic protocol for ABR, so it is capable to also work with VP9 encoding besides H264 and H265, or even new alternatives like AV1. The downside is its relative youth, which means enjoying much less support than HLS. This is why we don’t recommend it yet for most web businesses -even large ecommerce stores-.</p>
<h2 id="heading-summary">Summary</h2>
<p>After years of predominance of H264 AVC compression, new approaches are animating the scene. The race on display sizes and resolutions is fueling the development of new formats capable to deliver greater content within the same bandwidth.</p>
<p>VP9 in webm provides a significant gain in compression eficiency (about 30%), is royalty free and is supported by Google solutions (Chrome, Android). Going much further, H265/HEVC has achieved a comparable or better subjective quality at half the bit rate compared to H264. Since none of them features universal support, H264 will be still needed, at least as a fallback.</p>
<p>Adaptive bit rate is a compelling alternative, providing unbeatable user experience. In this regard,  HLS enjoys a wide support with the help of open source viewers.  It is possibly the best option for a medium sized web. The complexity added by the need of a viewer is fairly mitigated by the availability of open source initiatives like videojs for inhouse solutions, but also of third party services to do the job at competitive prices. If you go through this last route, make sure to ask for <a target="_blank" href="https://abraia.me/docs/video-optimization/#per-title-encoding">per-title encoding</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
