<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin Stark</title>
    <description>The latest articles on DEV Community by Martin Stark (@martinstark).</description>
    <link>https://dev.to/martinstark</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F251163%2Fcad1be70-048e-4c47-87e5-aae83c0630e5.png</url>
      <title>DEV Community: Martin Stark</title>
      <link>https://dev.to/martinstark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martinstark"/>
    <language>en</language>
    <item>
      <title>nvoc: linux overclocking gains multi GPU support, scripting, and is improved for ai use</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Fri, 26 Jun 2026 20:53:08 +0000</pubDate>
      <link>https://dev.to/martinstark/nvoc-linux-overclocking-gains-multi-gpu-support-scripting-and-is-improved-for-ai-use-33o</link>
      <guid>https://dev.to/martinstark/nvoc-linux-overclocking-gains-multi-gpu-support-scripting-and-is-improved-for-ai-use-33o</guid>
      <description>&lt;p&gt;The latest version has focused on overclocking for running local LLMs (AI). Primarily better memory OC support, and improved support for targeting mixed GPU models in a single system. Read on.&lt;/p&gt;

&lt;p&gt;Back in September I posted about &lt;a href="https://github.com/martinstark/nvoc/" rel="noopener noreferrer"&gt;NVOC - NVIDIA GPU Overclocking for Linux&lt;/a&gt;, an ergonomic cli tool for overclocking and undervolting RTX 50-series cards on Linux.&lt;/p&gt;

&lt;p&gt;The tool is at v0.3.0 now. It picked up multi-GPU support, machine-readable output, a few more cards, and a clean way to apply settings at boot.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than one GPU
&lt;/h2&gt;

&lt;p&gt;The first release assumed you had one card and operated on device 0. There was no way to point it at a specific GPU in a multi-card system.&lt;/p&gt;

&lt;p&gt;There is now a &lt;code&gt;-d/--device&lt;/code&gt; flag. The simplest form takes an index, or a comma separated list of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# one card&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; 1 &lt;span class="nt"&gt;-o&lt;/span&gt; 856

&lt;span class="c"&gt;# two specific cards&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; 0,1 &lt;span class="nt"&gt;-c&lt;/span&gt; 200,2820 &lt;span class="nt"&gt;-o&lt;/span&gt; 856 &lt;span class="nt"&gt;-m&lt;/span&gt; 2000 &lt;span class="nt"&gt;-p&lt;/span&gt; 105

&lt;span class="c"&gt;# everything in the box&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; all &lt;span class="nt"&gt;-o&lt;/span&gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indices are convenient until you reboot and they shuffle. NVML does not promise they stay put, so for anything you want to survive a restart, select by something stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selecting a card without counting
&lt;/h2&gt;

&lt;p&gt;You can target a card by UUID, by a substring of its name, or by a regex against the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# stable across reboots&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; GPU-1234... &lt;span class="nt"&gt;-o&lt;/span&gt; 856

&lt;span class="c"&gt;# case-insensitive substring on the device name&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; name:5090 &lt;span class="nt"&gt;-o&lt;/span&gt; 856
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"n:5060 ti"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; 100

&lt;span class="c"&gt;# regex, for a mix of models&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"regex:RTX 50[89]0"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; 856
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"r:5060( Ti)?"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a box with a 5090 and a couple of 5060 Ti, &lt;code&gt;name:5090&lt;/code&gt; is easier than working out which index the driver handed each card this boot.&lt;/p&gt;

&lt;h2&gt;
  
  
  nvoc list
&lt;/h2&gt;

&lt;p&gt;To select a card you first need to know what is in the machine. &lt;code&gt;nvoc list&lt;/code&gt; prints the index, name, and UUID of every GPU it can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nvoc list
&lt;span class="go"&gt;0 - NVIDIA GeForce RTX 5090 - GPU-1234...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It skips devices it cannot read instead of falling over on them, so one flaky card does not take the whole listing down with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output you can pipe
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;info&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt; both take &lt;code&gt;--json&lt;/code&gt; now, which is what you need to drive nvoc from a script rather than by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvoc info &lt;span class="nt"&gt;--json&lt;/span&gt;
nvoc list &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also &lt;code&gt;nvoc list --uuid&lt;/code&gt;, which prints just the UUIDs, one per line. Put the two together and you can apply the same settings to every card by UUID without hardcoding anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;uuid &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nvoc list &lt;span class="nt"&gt;--uuid&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$uuid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; 856 &lt;span class="nt"&gt;-m&lt;/span&gt; 2000 &lt;span class="nt"&gt;-p&lt;/span&gt; 105
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;-d all&lt;/code&gt; does the same in one shot. The loop earns its keep when you want different values per card.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Not only RTX 50-series
&lt;/h2&gt;

&lt;p&gt;The original gated on the card being an RTX 50-series part by matching its name. That was lazy, and it locked out the RTX PRO Blackwell cards, which are the same architecture wearing a different name.&lt;/p&gt;

&lt;p&gt;nvoc now asks NVML for the architecture directly and accepts anything Blackwell, so the workstation cards work too. It is still Blackwell only, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  A call for help
&lt;/h2&gt;

&lt;p&gt;There is experimental support for Ada Lovelace (RTX 4060, 4070, 4080, 4090) and Ampere (RTX 3050, 3060, 3070, 3080, 3090) already, but it needs testing on hardware I do not have. If you own one of those cards, trying the relevant branch and reporting your results would be appreciated.&lt;/p&gt;

&lt;p&gt;Draft PRs: &lt;a href="https://github.com/martinstark/nvoc/pull/12" rel="noopener noreferrer"&gt;Ada Lovelace (40-series)&lt;/a&gt; and &lt;a href="https://github.com/martinstark/nvoc/pull/13" rel="noopener noreferrer"&gt;Ampere (30-series)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying settings at boot
&lt;/h2&gt;

&lt;p&gt;Overclock settings do not persist. Reboot and you are back at stock. A systemd oneshot service reapplies them automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/gpu-oc.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;GPU overclock settings&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/nvoc -c 200,2820 -o 856 -m 2000 -p 105&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; gpu-oc.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a multi-GPU box, pin by UUID or model rather than index, for the reasons above. The README has the full walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing
&lt;/h2&gt;

&lt;p&gt;If you are on Arch, it is in the AUR now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;paru &lt;span class="nt"&gt;-S&lt;/span&gt; nvoc-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building from source still works the same way with &lt;code&gt;cargo build --release&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller things
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reset&lt;/code&gt; puts clocks back through NVML directly, instead of the slightly hacky idle-clock trick the first version leaned on, and it attempts every reset before reporting what failed rather than bailing on the first error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--dry-run&lt;/code&gt; no longer needs root, since previewing a change should not require privileges to do nothing.&lt;/li&gt;
&lt;li&gt;Device names stopped truncating, after a switch to the v2 NVML name buffer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info&lt;/code&gt; shows the memory clock offset next to the graphics one now.&lt;/li&gt;
&lt;li&gt;An unknown device error points you at &lt;code&gt;nvoc list&lt;/code&gt; instead of leaving you to guess.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ nvoc info&lt;/span&gt;
&lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;595.71.05&lt;/span&gt;
&lt;span class="na"&gt;gpu 0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NVIDIA GeForce RTX &lt;/span&gt;&lt;span class="m"&gt;5090&lt;/span&gt;
&lt;span class="na"&gt;gpu clock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1080MHz&lt;/span&gt;
&lt;span class="na"&gt;gpu offset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;856MHz&lt;/span&gt;
&lt;span class="na"&gt;mem clock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;810MHz&lt;/span&gt;
&lt;span class="na"&gt;mem offset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2000MHz&lt;/span&gt;
&lt;span class="na"&gt;temp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;52°C&lt;/span&gt;
&lt;span class="na"&gt;power&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;28W&lt;/span&gt;
&lt;span class="na"&gt;power limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;600W (104%)&lt;/span&gt;
&lt;span class="na"&gt;power range&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;400W-575W (600W hard limit)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo and issues: &lt;a href="https://github.com/martinstark/nvoc/" rel="noopener noreferrer"&gt;https://github.com/martinstark/nvoc/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>ai</category>
      <category>linux</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A Free Screenshot Editor That Never Uploads Your Image</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Fri, 26 Jun 2026 11:34:04 +0000</pubDate>
      <link>https://dev.to/martinstark/a-free-screenshot-editor-that-never-uploads-your-image-npb</link>
      <guid>https://dev.to/martinstark/a-free-screenshot-editor-that-never-uploads-your-image-npb</guid>
      <description>&lt;p&gt;A few times a week I take a screenshot, circle something on it, sometimes cover up an email or a token, and send it on. I wanted to do that without the image ever leaving my machine, especially when it holds something private.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://edit-image.eu" rel="noopener noreferrer"&gt;edit-image.eu&lt;/a&gt;. It runs entirely in the browser. Paste a screenshot with Ctrl+V, drop a file, or pick one from your device, then mark it up with text, arrows, shapes, and freehand drawing. Crop it, then copy it back to the clipboard or download it as PNG, JPEG, or WebP. Nothing is sent anywhere, because there's no server behind it.&lt;/p&gt;

&lt;p&gt;There's an undo stack and a history panel that removes any single edit you made earlier and leaves the rest. That panel shaped the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping edits editable
&lt;/h2&gt;

&lt;p&gt;The easy way to build a canvas editor is to draw each shape straight onto the bitmap. That works until someone wants to remove one arrow they drew five arrows back. Once the pixels are merged, it's gone.&lt;/p&gt;

&lt;p&gt;So the picture isn't a bitmap with drawings baked in. It's a base image plus an ordered list of annotation objects, each one knowing its own position and style. Rendering replays that list over the base whenever something changes.&lt;/p&gt;

&lt;p&gt;Undo and redo become snapshots of the list. The history panel drops object number three and redraws the rest as if it was never there. Moving a label nudges one object's coordinates. Crop swaps the base image and shifts everything else, so it undoes like any other edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Big photos on phones
&lt;/h2&gt;

&lt;p&gt;Loading photos from a phone takes some care. The obvious way to turn that file into something drawable is &lt;code&gt;createImageBitmap(blob)&lt;/code&gt;, which is fine on a laptop. On a phone, a 12 megapixel photo decodes to a full resolution bitmap in memory, enough on some devices to kill the tab before anything reaches the screen.&lt;/p&gt;

&lt;p&gt;So large images decode through a plain &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and draw into a canvas capped at 4096 pixels on the longest edge. &lt;code&gt;createImageBitmap&lt;/code&gt; only sees the small result, and the huge intermediate never exists. Files already under the cap keep their original bytes, so nothing is re-encoded needlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  No framework
&lt;/h2&gt;

&lt;p&gt;There's no React or Vue here. The interesting state lives in the canvas, not the DOM, so a framework would mostly stand around watching. The visible UI is a couple of toolbars and a few popovers.&lt;/p&gt;

&lt;p&gt;So it's plain TypeScript with a small pub/sub store, Canvas2D, rsbuild for bundling, and Biome for lint and formatting. The shipped JavaScript is about 48 KB, or 16 gzipped. No analytics or backend calls, so the network tab stays quiet after the page loads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's free, at &lt;a href="https://edit-image.eu" rel="noopener noreferrer"&gt;edit-image.eu&lt;/a&gt;. Paste a screenshot and start marking it up. It's still a work in progress, so if something breaks, especially on a browser or device I haven't tried, I'd like to hear about it.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>editor</category>
      <category>canvas</category>
      <category>images</category>
    </item>
    <item>
      <title>nvoc - linux OC/UV cli utility for RTX-50 series</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Fri, 26 Sep 2025 08:22:26 +0000</pubDate>
      <link>https://dev.to/martinstark/nvoc-linux-ocuv-cli-utility-for-rtx-50-series-3fgd</link>
      <guid>https://dev.to/martinstark/nvoc-linux-ocuv-cli-utility-for-rtx-50-series-3fgd</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/martinstark/nvoc/" rel="noopener noreferrer"&gt;https://github.com/martinstark/nvoc/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I got tired of having to create multi-line scripts for applying a semblance of overclocking/undervolting on Linux, so I wrote a cli app that does it in a single line, example for 5090:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo nvoc -c 200,2820 -o 900 -m 3000 -p 105&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;-c = min,max core clocks&lt;br&gt;
-o = graphics offset&lt;br&gt;
-m = memory offset&lt;br&gt;
-p = power limit&lt;/p&gt;

&lt;p&gt;Since it's only possible to set a full curve offset on Linux (there is no per-voltage offset like in MSI Afterburner), and I don't want my idle clocks to be ~1100mhz, I wanted a utility to quickly apply and reset the OC when I need it.&lt;/p&gt;

&lt;p&gt;NVIDIA kindly removed the ability to query GPU voltage from their Linux APIs. Due to that, I think the only way to simulate MSI Afterburner-like behaviour, where you can have default idle clocks (200mhz) and have an offset for higher load scenarios, would be to set up an elevated service that applies the OC as soon as the GPU goes above ~2000mhz, and resets the OC if the clocks drop down below 2000mhz for a number of seconds.&lt;/p&gt;

&lt;p&gt;Functionality&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show GPU information&lt;/span&gt;
nvoc info

&lt;span class="c"&gt;# OC&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc &lt;span class="nt"&gt;-c&lt;/span&gt; MIN,MAX &lt;span class="nt"&gt;-o&lt;/span&gt; OFFSET &lt;span class="nt"&gt;-m&lt;/span&gt; MEM_OFFSET &lt;span class="nt"&gt;-p&lt;/span&gt; POWER_LIMIT

&lt;span class="c"&gt;# Reset&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nvoc reset

&lt;span class="c"&gt;# Dry Run&lt;/span&gt;
nvoc &lt;span class="nt"&gt;-c&lt;/span&gt; 200,2800 &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;info output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nvoc info
Driver 580.82.09
0: NVIDIA GeForce RTX 5090
Blackwell v16777240
GPU: 1177MHz
GPU Offset: 960MHz
Mem: 15501MHz
Temp: 56°C
Power: 45W
Power Limit: 600W &lt;span class="o"&gt;(&lt;/span&gt;104% of default&lt;span class="o"&gt;)&lt;/span&gt;
Power Range: 400W-575W &lt;span class="o"&gt;(&lt;/span&gt;hard limit: 600W&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tooling</category>
      <category>performance</category>
      <category>cli</category>
      <category>linux</category>
    </item>
    <item>
      <title>Client Side Ad Insertion Crash Course</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Wed, 29 May 2024 12:43:28 +0000</pubDate>
      <link>https://dev.to/video/client-side-ad-insertion-crash-course-4fdm</link>
      <guid>https://dev.to/video/client-side-ad-insertion-crash-course-4fdm</guid>
      <description>&lt;p&gt;This guide acts as a quick overview of concepts relevant to CSAI, it is not meant to act as a complete tutorial. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is CSAI?
&lt;/h2&gt;

&lt;p&gt;Client Side Ad Insertion means that the app on the user's device is responsible for fetching and displays advertisement as part of the streaming experience. This is in contrast to Server Side Ad Insertion, where the ads are stitched into the video on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  VMAP
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.iab.com/guidelines/vmap/" rel="noopener noreferrer"&gt;VMAP&lt;/a&gt; is fetched or polled by the client, it is parsed to check when an ad break should occur.  &lt;/p&gt;

&lt;h2&gt;
  
  
  VAST
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.iab.com/guidelines/vast/" rel="noopener noreferrer"&gt;VAST&lt;/a&gt; document is fetched and parsed by the client, it represents the content of an ad break.&lt;/p&gt;

&lt;p&gt;The VAST document can contain several types of ads. For example: video, images, and companion links.&lt;/p&gt;

&lt;p&gt;The VAST specifies how an ad should be tracked. It says which events need to be sent to a tracking server in order to verify if and how much of the ad was watched and/or interacted with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Displaying the Ads
&lt;/h2&gt;

&lt;p&gt;When it is time to show the ad to the user, it is usually handled by pausing the content stream, creating a new video element, overlaying it on the content stream, and playing the ad(s). When the ad break is finished, remove the ad specific video element and resume the content stream.&lt;/p&gt;

</description>
      <category>csai</category>
      <category>ads</category>
      <category>video</category>
      <category>streaming</category>
    </item>
    <item>
      <title>Interpreting HTML5 Video Events</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Mon, 13 Mar 2023 12:05:55 +0000</pubDate>
      <link>https://dev.to/video/interpreting-html5-video-events-2j54</link>
      <guid>https://dev.to/video/interpreting-html5-video-events-2j54</guid>
      <description>&lt;p&gt;When building a HTML5 web video player, it tends to need a user interface, quality of service tracking, user interaction tracking, and sometimes ad tracking. To achieve this, the &lt;a href="https://html.spec.whatwg.org/multipage/media.html#mediaevents" rel="noopener noreferrer"&gt;HTML5 Media Event&lt;/a&gt; standard needs to be interpreted.&lt;/p&gt;

&lt;p&gt;After building multiple web video players, and having worked on more than one iteration of an event interpreter, something didn't feel right. Things weren't kept &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When repetition strikes, it's usually a good idea to do something about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Media Event Filter
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://www.npmjs.com/package/@eyevinn/media-event-filter" rel="noopener noreferrer"&gt;@eyevinn/media-event-filter&lt;/a&gt;, a filter that interprets HTML5 media events in a way that makes sense.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This filter aims to provide a single source of truth that can be used across player engines and native browser playback.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether using one or multiple player engines, like &lt;a href="https://github.com/shaka-project/shaka-player/" rel="noopener noreferrer"&gt;Shaka Player&lt;/a&gt;, &lt;a href="https://github.com/video-dev/hls.js/" rel="noopener noreferrer"&gt;HLS.js&lt;/a&gt;, or &lt;a href="https://github.com/Dash-Industry-Forum/dash.js/" rel="noopener noreferrer"&gt;dash.js&lt;/a&gt;, the filter will output a standardised event sequence. It does so without relying on events output by the player engine, giving applications a single, player agnostic, source of truth for video playback state updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getMediaEventFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FilteredMediaEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@eyevinn/media-event-filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaEventFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getMediaEventFilter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;videoElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FilteredMediaEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FilteredMediaEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LOADED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// handle loaded&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FilteredMediaEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BUFFERING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// handle buffering&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FilteredMediaEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BUFFERED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// handle buffered&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Create a player, using your engine of choice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Load a manifest and play&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Call when done&lt;/span&gt;
&lt;span class="nx"&gt;mediaEventFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;teardown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Live Example
&lt;/h2&gt;

&lt;p&gt;See a barebones &lt;a href="https://codepen.io/atlimar/pen/wvEmpXM" rel="noopener noreferrer"&gt;React + Shaka + Media Event Filter&lt;/a&gt; example over at codepen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Readiness
&lt;/h2&gt;

&lt;p&gt;While making the filter open source is recent, it has been battle tested for years. With millions of playback sessions, multiple third party tracking systems have been validated using the filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event Sequence
&lt;/h2&gt;

&lt;p&gt;The sequence follows the &lt;a href="https://github.com/Eyevinn/player-analytics-specification" rel="noopener noreferrer"&gt;Eyevinn Player Analytics Specification&lt;/a&gt;, which maps directly to many popular tracking service provider SDKs.&lt;/p&gt;

&lt;p&gt;Sample event sequence comparison, using &lt;code&gt;&amp;lt;video autoplay&amp;gt;&lt;/code&gt; with Shaka Player in Firefox, excluding &lt;code&gt;timeupdate&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native&lt;/th&gt;
&lt;th&gt;Filtered&lt;/th&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Shaka sets playback rate to 0 to control buffering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;durationchange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resize&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loadedmetadata&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loadeddata&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplay&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;play&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;loaded&lt;/td&gt;
&lt;td&gt;video is ready to start playing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;video is playing, play event missing due to autoplay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;manual pause&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking while paused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Shaka sets playback rate to 0 to control buffering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;play&lt;/td&gt;
&lt;td&gt;play&lt;/td&gt;
&lt;td&gt;Play requested during a seek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;waiting&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplay&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;video element thinks it can start playing, but playback rate is 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;shaka returns playback rate to normal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;video is playing again after previous pause&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;play&lt;/td&gt;
&lt;td&gt;play&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking while playing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;waiting&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplay&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;seek finished, video is rolling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;buffering&lt;/td&gt;
&lt;td&gt;buffer empty, shaka sets playbackrate to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;video element thinks it can start playing, but playback rate is 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;buffered&lt;/td&gt;
&lt;td&gt;shaka returns playback rate to normal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;waiting&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplay&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratechange&lt;/td&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;durationchange&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progress&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking&lt;/td&gt;
&lt;td&gt;seeking within buffer, no ratechange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;waiting&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;seeked&lt;/td&gt;
&lt;td&gt;seek finished&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplay&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playing&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;canplaythrough&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;pause&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ended&lt;/td&gt;
&lt;td&gt;ended&lt;/td&gt;
&lt;td&gt;end of stream reached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emptied&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Download it at &lt;a href="https://www.npmjs.com/package/@eyevinn/media-event-filter" rel="noopener noreferrer"&gt;npm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Contribute at &lt;a href="https://github.com/Eyevinn/media-event-filter" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>streaming</category>
      <category>typescript</category>
      <category>html5video</category>
    </item>
    <item>
      <title>HTML5 subtitles with dynamic width on OSX</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Thu, 17 Mar 2022 15:28:46 +0000</pubDate>
      <link>https://dev.to/video/html5-subtitles-with-dynamic-width-on-osx-1da1</link>
      <guid>https://dev.to/video/html5-subtitles-with-dynamic-width-on-osx-1da1</guid>
      <description>&lt;p&gt;If you've run into subtitles taking up 100% width of the HTML5 video player window, which is common in Chrome or Safari on OSX, here is a neat styling trick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;::-webkit-media-text-track-display&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;prevent&lt;/span&gt; &lt;span class="err"&gt;subtitles&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;taking&lt;/span&gt; &lt;span class="err"&gt;up&lt;/span&gt; &lt;span class="err"&gt;100%&lt;/span&gt; &lt;span class="err"&gt;width&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;while&lt;/span&gt; &lt;span class="err"&gt;remaining&lt;/span&gt; &lt;span class="err"&gt;centered&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;prevent&lt;/span&gt; &lt;span class="err"&gt;unexpected&lt;/span&gt; &lt;span class="err"&gt;line&lt;/span&gt; &lt;span class="err"&gt;breaks,&lt;/span&gt; &lt;span class="err"&gt;since&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt;  
  &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;above&lt;/span&gt; &lt;span class="err"&gt;styling&lt;/span&gt; &lt;span class="err"&gt;will&lt;/span&gt; &lt;span class="err"&gt;otherwise&lt;/span&gt; &lt;span class="err"&gt;force&lt;/span&gt; &lt;span class="err"&gt;50%&lt;/span&gt; &lt;span class="err"&gt;width&lt;/span&gt;
  &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;on&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;subtitle&lt;/span&gt; &lt;span class="err"&gt;element&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will center the subtitle element, make sure it keeps a dynamic width, and prevent unexpected line-breaks.&lt;/p&gt;

&lt;p&gt;Since the fix maintains native subtitle rendering, it allows for operating system level accessibility options to be applied.&lt;/p&gt;

</description>
      <category>html5video</category>
      <category>subtitles</category>
      <category>css</category>
      <category>ttml</category>
    </item>
    <item>
      <title>The equivalent of componentWillMount using React hooks</title>
      <dc:creator>Martin Stark</dc:creator>
      <pubDate>Thu, 29 Apr 2021 12:29:48 +0000</pubDate>
      <link>https://dev.to/video/the-equivalent-of-componentwillmount-using-react-hooks-11em</link>
      <guid>https://dev.to/video/the-equivalent-of-componentwillmount-using-react-hooks-11em</guid>
      <description>&lt;h2&gt;
  
  
  Given that
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;the goal is to execute some code once, before the ui is updated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;componentWillMount&lt;/code&gt; is deprecated (&lt;a href="https://reactjs.org/docs/react-component.html#unsafe_componentwillmount" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://stackoverflow.com/questions/49206280/componentwillmount-is-deprecated-and-will-be-removed-in-the-next-major-version-0/49213753#49213753" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html" rel="noopener noreferrer"&gt;3&lt;/a&gt;), and that the suggested replacement is executing code in the &lt;code&gt;constructor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;code executed before the return statement of a functional component is implicitly run before rendering it&lt;/li&gt;
&lt;li&gt;the rough equivalent of mounting a class component is the initial call of a functional component&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The solution would be
&lt;/h2&gt;

&lt;p&gt;Calling a function in the body of the functional component once. This can potentially be achieved with &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, or &lt;code&gt;useEffect&lt;/code&gt;, depending on the timing required for the use case.&lt;/p&gt;

&lt;p&gt;Since the code needs to run before the initial render is committed to the screen, this disqualifies &lt;code&gt;useEffect&lt;/code&gt;, as “The function passed to useEffect will run after the render is committed to the screen.” &lt;a href="https://reactjs.org/docs/hooks-reference.html#useeffect" rel="noopener noreferrer"&gt;4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since we want to guarantee that the code will only run once, this disqualifies &lt;code&gt;useMemo&lt;/code&gt;, as "In the future, React may choose to “forget” some previously memoized values and recalculate them on next render" &lt;a href="https://reactjs.org/docs/hooks-reference.html#usememo" rel="noopener noreferrer"&gt;5&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; supports &lt;a href="https://reactjs.org/docs/hooks-reference.html#lazy-initial-state" rel="noopener noreferrer"&gt;lazy initial state&lt;/a&gt; calculations that are guaranteed to only run once during the initial render, which seems like a good candidate for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example with useState:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runOnceBeforeRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runOnceBeforeRender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  As a custom hook:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runOnceBeforeRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useBeforeInitialRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useBeforeInitialRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runOnceBeforeRender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;runOnceBeforeRender&lt;/code&gt; function can optionally return a state that will be available immediately upon the first render of the function, triggering no re-renders.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://www.npmjs.com/package/@martinstark/use-once" rel="noopener noreferrer"&gt;use-once&lt;/a&gt; on npm.&lt;/p&gt;

</description>
      <category>react</category>
      <category>hooks</category>
      <category>componentwillmount</category>
      <category>functional</category>
    </item>
  </channel>
</rss>
