<?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: Truman</title>
    <description>The latest articles on DEV Community by Truman (@truman-17nas).</description>
    <link>https://dev.to/truman-17nas</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%2F4170060%2Fc854860f-5235-4d2f-988f-51341c8fa116.png</url>
      <title>DEV Community: Truman</title>
      <link>https://dev.to/truman-17nas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/truman-17nas"/>
    <language>en</language>
    <item>
      <title>Your browser can detect your NAT type in 30 lines of JavaScript</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 22 Oct 2026 15:54:57 +0000</pubDate>
      <link>https://dev.to/truman-17nas/your-browser-can-detect-your-nat-type-in-30-lines-of-javascript-1hah</link>
      <guid>https://dev.to/truman-17nas/your-browser-can-detect-your-nat-type-in-30-lines-of-javascript-1hah</guid>
      <description>&lt;h1&gt;
  
  
  Your browser can detect your NAT type in 30 lines of JavaScript
&lt;/h1&gt;

&lt;p&gt;Ever had a video call that connects fine on one network but refuses to establish peer-to-peer on another? Or a game where voice chat works for everyone except that one friend? Nine times out of ten, the culprit is the NAT sitting between you and the internet — and your browser already has everything needed to interrogate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-line version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stunUrl&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;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stunUrl&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;probe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// kick off ICE gathering&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;srflx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onicecandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;srflx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// null = gathering done&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;srflx&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stun:stun.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stun:stun1.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;portOf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;portOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cone NAT (predictable mapping)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Symmetric NAT (per-destination mapping)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No extensions, no native code, no permissions prompt. Let me explain why this works.&lt;/p&gt;

&lt;h2&gt;
  
  
  ICE candidates are a free STUN client
&lt;/h2&gt;

&lt;p&gt;When you create an &lt;code&gt;RTCPeerConnection&lt;/code&gt; with STUN servers configured, the browser's ICE agent automatically sends STUN Binding Requests to each server. The responses come back to your JavaScript as &lt;strong&gt;ICE candidates&lt;/strong&gt; — strings like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;candidate:8421630493 1 udp 1685987071 203.0.113.45 54321 typ srflx raddr 192.168.1.5 rport 54321
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields that matter: &lt;code&gt;203.0.113.45 54321&lt;/code&gt; is your &lt;strong&gt;server-reflexive&lt;/strong&gt; address — how the STUN server saw you &lt;em&gt;after&lt;/em&gt; your NAT translated the packet. The &lt;code&gt;raddr&lt;/code&gt;/&lt;code&gt;rport&lt;/code&gt; pair is your local address behind the NAT. The &lt;code&gt;typ srflx&lt;/code&gt; marker tells you this candidate came from STUN rather than local interface enumeration (&lt;code&gt;host&lt;/code&gt;) or a TURN relay (&lt;code&gt;relay&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So the browser is effectively a STUN client already. We're just reading its output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core trick: compare mappings across servers
&lt;/h2&gt;

&lt;p&gt;A NAT has to decide what external port to assign your traffic. There are two philosophies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cone NAT&lt;/strong&gt;: one internal address:port always maps to the same external address:port, regardless of destination. Ask two different STUN servers and both report the same external port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symmetric NAT&lt;/strong&gt;: each &lt;em&gt;destination&lt;/em&gt; gets its own mapping. Ask two different STUN servers and you get two different external ports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly what the snippet compares. Same port from both servers → cone family. Different ports → symmetric. This single distinction predicts most real-world connectivity pain: with symmetric NAT, the other peer can never guess which port to send to, so direct P2P hole punching fails and everything falls back to TURN relay (higher latency, server bandwidth).&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can't tell from the browser alone
&lt;/h2&gt;

&lt;p&gt;Honesty corner: this trick separates cone from symmetric, but it can't split the cone family further. Telling Full Cone apart from Address-Restricted or Port-Restricted Cone requires &lt;em&gt;probing&lt;/em&gt; — having a second server send packets at you from an address or port you never contacted, and seeing if they get through. Browsers can't receive arbitrary unsolicited UDP, so that test needs infrastructure outside the page. Most browser-based NAT tests (including the one linked below) report the cone/symmetric verdict and treat the cone subtypes as one bucket. For diagnosing "why won't my P2P connect," that bucket is usually enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters beyond curiosity
&lt;/h2&gt;

&lt;p&gt;Hole punching — the technique WebRTC, game netcode, and tools like Tailscale rely on — only works when at least one side has a predictable mapping. If both sides sit behind symmetric NATs, direct connection is essentially impossible and you &lt;em&gt;must&lt;/em&gt; relay. Knowing your NAT type up front tells you whether to even attempt P2P, what TURN capacity to provision, and why that one user keeps filing "call won't connect" tickets from their mobile hotspot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole technique is: configure STUN servers → harvest &lt;code&gt;srflx&lt;/code&gt; candidates → compare external ports across servers. The browser does the hard protocol work; you just read the results.&lt;/p&gt;

&lt;p&gt;If you'd rather not wire up the comparison logic yourself, &lt;a href="https://17nas.com/nat-test.php" rel="noopener noreferrer"&gt;17NAS NAT Test&lt;/a&gt; runs exactly this probe in your browser — it queries multiple STUN servers, compares the mappings, and reports your NAT type with the raw candidate details. Free, no sign-up, everything happens client-side.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webrtc</category>
      <category>networking</category>
      <category>webdev</category>
    </item>
    <item>
      <title>7 Best Free IPv6 Test Tools (2026)</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Wed, 21 Oct 2026 14:21:53 +0000</pubDate>
      <link>https://dev.to/truman-17nas/7-best-free-ipv6-test-tools-2026-5nd</link>
      <guid>https://dev.to/truman-17nas/7-best-free-ipv6-test-tools-2026-5nd</guid>
      <description>&lt;p&gt;IPv6 adoption keeps climbing, but most people still have no idea whether their own connection actually uses it. Your ISP may advertise "IPv6 ready" while your router silently falls back to IPv4 for everything. The only way to know is to test.&lt;/p&gt;

&lt;p&gt;I rounded up the best free online tools for checking IPv6 connectivity, plus a few bonus network diagnostics worth bookmarking. All of them run in the browser — no installs, no sign-ups.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 17NAS IPv6 Test — Best Overall
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://17nas.com/ipv6-test.php" rel="noopener noreferrer"&gt;https://17nas.com/ipv6-test.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most complete free option I found. It checks your IPv4 and IPv6 addresses side by side, measures latency on both protocols so you can see which is actually faster on your connection, identifies your ISP and ASN, and tells you whether you're on dual-stack, IPv4-only, or IPv6-only. The connection quality score is a nice touch — one number summarizing how healthy your IPv6 path is.&lt;/p&gt;

&lt;p&gt;What sets it apart: there's also a free no-auth API (&lt;code&gt;ip.17nas.com&lt;/code&gt;) returning JSON, which is handy for scripts and server monitoring. No registration required for anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Test-IPv6.com — The Classic
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://test-ipv6.com" rel="noopener noreferrer"&gt;https://test-ipv6.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The long-standing reference test, run by Jason Fesler. It gives you a 0–10 score for IPv6 readiness and detects common transition mechanisms (Teredo, 6to4). The interface is dated but the test itself is solid and widely trusted. Good second opinion after any other test.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. IPv6-test.com — Best for Detail Lovers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://ipv6-test.com" rel="noopener noreferrer"&gt;https://ipv6-test.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validates your IPv6 address, checks for common misconfigurations, and shows detailed protocol information. If you want to dig into the technical details of your connection rather than just getting a pass/fail, this is the one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cloudflare's IPv6 Test — Quickest Check
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://www.cloudflare.com/ssl/encrypted-sni/" rel="noopener noreferrer"&gt;https://www.cloudflare.com/ssl/encrypted-sni/&lt;/a&gt; (see also their connectivity diagnostics)&lt;/p&gt;

&lt;p&gt;Cloudflare runs several quick connectivity checks. Fast, no frills, and you can trust the infrastructure behind it. Best when you just want a yes/no answer in five seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 17NAS NAT Test — Best Companion Test
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://17nas.com/nat-test.php" rel="noopener noreferrer"&gt;https://17nas.com/nat-test.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IPv6 is only half the story for gamers and VoIP users — NAT type matters just as much. This free test detects your NAT type and shows your public IP, which determines whether peer-to-peer connections (gaming, video calls, file sharing) will work smoothly or need relaying. Pairs naturally with the IPv6 test above.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 17NAS Port Check — For the Self-Hosters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://17nas.com/port-check.php" rel="noopener noreferrer"&gt;https://17nas.com/port-check.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running a home server or NAS? This checks whether a TCP port is actually reachable from the internet — the fastest way to confirm your port forwarding works. Free, no signup.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. MXToolbox — Best All-Rounder for DNS/Network Pros
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://mxtoolbox.com" rel="noopener noreferrer"&gt;https://mxtoolbox.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not strictly an IPv6 tester, but the Swiss Army knife of network diagnostics: DNS lookups, MX records, blacklist checks, and more. If you do any server administration, you already know it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Read Your Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dual-stack with IPv6 preferred:&lt;/strong&gt; ideal. You're getting the full modern internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPv4-only:&lt;/strong&gt; common, especially on older routers or ISPs that haven't enabled IPv6 on your line. Check your router settings — many support IPv6 but ship with it off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPv6-only:&lt;/strong&gt; rare for residential connections; more common in mobile networks and some enterprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teredo/6to4 detected:&lt;/strong&gt; transition tunnels. They work, but native IPv6 is better — consider it a hint to configure things properly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Start with the &lt;a href="https://17nas.com/ipv6-test.php" rel="noopener noreferrer"&gt;17NAS IPv6 Test&lt;/a&gt; for the full picture, cross-check with Test-IPv6.com, and keep the NAT and port tools bookmarked if you game or self-host. All free, all in the browser, no accounts needed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? The author builds free network tools at &lt;a href="https://17nas.com" rel="noopener noreferrer"&gt;17NAS&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ipv6</category>
      <category>networking</category>
      <category>webdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to Choose the Right RAID Level for Your NAS (and Calculate Usable Capacity)</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:09:02 +0000</pubDate>
      <link>https://dev.to/truman-17nas/how-to-choose-the-right-raid-level-for-your-nas-and-calculate-usable-capacity-ckd</link>
      <guid>https://dev.to/truman-17nas/how-to-choose-the-right-raid-level-for-your-nas-and-calculate-usable-capacity-ckd</guid>
      <description>&lt;h1&gt;
  
  
  How to Choose the Right RAID Level for Your NAS (and Calculate Usable Capacity)
&lt;/h1&gt;

&lt;p&gt;Picking a RAID level for your home NAS or server is one of those decisions that feels simple until you actually sit down to do it. RAID 5 sounds like the safe default. RAID 6 sounds safer. RAID 10 sounds fast. But how much usable space do you actually get? And what happens when a drive fails during a rebuild?&lt;/p&gt;

&lt;p&gt;This guide walks through the RAID levels that matter for NAS setups in 2026, the capacity math behind each one, and how to pick the right level for your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAID Levels at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Min Drives&lt;/th&gt;
&lt;th&gt;Usable Capacity&lt;/th&gt;
&lt;th&gt;Fault Tolerance&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAID 0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;N × drive size&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Cache, temp data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;N-1 drives&lt;/td&gt;
&lt;td&gt;System disk, critical data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;(N-1) × drive size&lt;/td&gt;
&lt;td&gt;1 drive&lt;/td&gt;
&lt;td&gt;File server, home NAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;(N-2) × drive size&lt;/td&gt;
&lt;td&gt;2 drives&lt;/td&gt;
&lt;td&gt;Large storage, business&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID 10&lt;/td&gt;
&lt;td&gt;4 (even)&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;1 per mirror pair&lt;/td&gt;
&lt;td&gt;Databases, high-performance apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID-Z1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;(N-1) × drive size&lt;/td&gt;
&lt;td&gt;1 drive&lt;/td&gt;
&lt;td&gt;ZFS / TrueNAS home&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID-Z2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;(N-2) × drive size&lt;/td&gt;
&lt;td&gt;2 drives&lt;/td&gt;
&lt;td&gt;ZFS enterprise storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAID-Z3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;(N-3) × drive size&lt;/td&gt;
&lt;td&gt;3 drives&lt;/td&gt;
&lt;td&gt;Very large capacity, data centers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Capacity Math
&lt;/h2&gt;

&lt;p&gt;The formulas are straightforward once you know them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAID 0&lt;/strong&gt;: All drives count. 4 × 4TB = 16TB usable. But one failure = all data lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID 1&lt;/strong&gt;: Mirror. Usable = smallest drive size. Adding more drives increases redundancy, not capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID 5&lt;/strong&gt;: Usable = (N-1) × smallest drive. 4 × 4TB = 12TB usable, 1 drive of parity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID 6&lt;/strong&gt;: Usable = (N-2) × smallest drive. 4 × 4TB = 8TB usable, 2 drives of parity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID 10&lt;/strong&gt;: Usable = 50% of total. 4 × 4TB = 8TB usable. Fast reads and writes, but you lose half your raw capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAID-Z1/Z2/Z3&lt;/strong&gt;: Same math as RAID 5/6, but with ZFS checksums and copy-on-write.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch: traditional RAID uses the &lt;strong&gt;smallest drive&lt;/strong&gt; as the baseline. If you mix a 2TB drive with three 4TB drives in RAID 5, you get (4-1) × 2TB = 6TB usable, and the extra 2TB on each 4TB drive sits unused. Synology's SHR (Hybrid RAID) handles mixed drives better by creating multiple virtual arrays, which is why SHR is popular for home NAS setups where you upgrade drives gradually.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Safety first&lt;/strong&gt;: RAID 1, RAID 6, RAID 10, or RAID-Z2/Z3. If your data matters and you can't afford downtime, go with dual parity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance first&lt;/strong&gt;: RAID 0 or RAID 10. RAID 0 is fastest but has zero fault tolerance — only use it for data you can afford to lose. RAID 10 gives you both speed and redundancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Balanced&lt;/strong&gt;: RAID 5 or RAID-Z1. Good capacity efficiency with single-drive protection. Best for home NAS and small file servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise / high availability&lt;/strong&gt;: RAID 6, RAID 10, or RAID-Z2/Z3. When you have 8+ drives or drives larger than 8TB, dual parity becomes important because rebuild times get long, and the chance of a second drive failing during rebuild increases.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAID 5 vs RAID 6: The Rebuild Problem
&lt;/h2&gt;

&lt;p&gt;This is the decision most people get wrong. RAID 5 feels like enough — one drive can fail, you replace it, done. But with modern large drives (8TB, 12TB, 20TB), a RAID 5 rebuild can take days. During that rebuild, your array is running with zero redundancy. If a second drive has a latent bad sector (URE — Unrecoverable Read Error), the rebuild fails and you lose data.&lt;/p&gt;

&lt;p&gt;Rule of thumb: if your drives are 8TB or larger, or you have more than 6 drives, use RAID 6 or RAID-Z2. The extra parity drive is cheap insurance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. "RAID is backup"&lt;/strong&gt; — No, it's not. RAID protects against drive failure. It doesn't protect against accidental deletion, ransomware, fire, theft, or a second drive failing during rebuild. Always have a real backup (3-2-1 rule: 3 copies, 2 different media, 1 offsite).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. "My 4TB drives give me 16TB in RAID 5"&lt;/strong&gt; — No. 4 × 4TB in RAID 5 = (4-1) × 4TB = 12TB usable. One drive's worth goes to parity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. "The OS shows less space than I expected"&lt;/strong&gt; — Drive manufacturers use decimal TB (1TB = 1,000GB). Operating systems use binary TiB (1TiB = 1,024GiB). A "4TB" drive shows as ~3.64TiB in your OS. Add filesystem overhead and system reserved space, and usable capacity is always less than the label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. "Adding drives to RAID 1 increases capacity"&lt;/strong&gt; — No. RAID 1 is mirroring. Adding a third drive to RAID 1 gives you a third copy, not more space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. "Mixed drive sizes are fine in traditional RAID"&lt;/strong&gt; — They work, but you waste space. Traditional RAID uses the smallest drive as the baseline. SHR or ZFS handle mixed sizes more efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning Your Array
&lt;/h2&gt;

&lt;p&gt;Before buying drives, calculate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;How much usable space do you need?&lt;/strong&gt; Add 20-30% headroom for growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many drives can your NAS hold?&lt;/strong&gt; A 4-bay NAS limits your options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's your budget?&lt;/strong&gt; RAID 10 costs more per usable TB than RAID 5/6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How critical is the data?&lt;/strong&gt; If downtime is unacceptable, go dual parity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are you mixing drive sizes?&lt;/strong&gt; If yes, consider SHR or plan to buy same-size drives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A quick way to compare options: plug your drive sizes into a RAID calculator and see usable capacity, protection, and unused space for each level side by side. For example, &lt;a href="https://17nas.com/raid-calculator.php" rel="noopener noreferrer"&gt;this free RAID calculator&lt;/a&gt; supports up to 12 mixed-size drives, SHR, traditional RAID, and ZFS RAID-Z, with a visual breakdown of usable vs. protected vs. unused space. It's a handy way to sanity-check your plan before buying drives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;There's no single "best" RAID level — it depends on your drives, your budget, and how much you value capacity vs. safety vs. speed. For most home NAS users, RAID 5 or SHR hits the sweet spot. For anything business-critical or with large drives, RAID 6 or RAID-Z2 is the safer bet. And whatever you choose, remember: RAID is not backup.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Bass Tuning Online: A Complete Guide to Tuning Your Bass Guitar in Your Browser</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 13 Oct 2026 00:33:41 +0000</pubDate>
      <link>https://dev.to/truman-17nas/bass-tuning-online-a-complete-guide-to-tuning-your-bass-guitar-in-your-browser-602</link>
      <guid>https://dev.to/truman-17nas/bass-tuning-online-a-complete-guide-to-tuning-your-bass-guitar-in-your-browser-602</guid>
      <description>&lt;h1&gt;
  
  
  Bass Tuning Online: A Complete Guide to Tuning Your Bass Guitar in Your Browser
&lt;/h1&gt;

&lt;p&gt;If you searched for "bass tuning online," you're probably in one of two situations: you're a beginner who just picked up a bass and needs to get it in tune right now, or you're an experienced player who wants a reliable tuner without installing another app. Either way, this guide covers everything — from the standard E1–A1–D2–G2 tuning to 5-string setups, alternate tunings, and how to troubleshoot when your bass just won't stay in tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard Bass Tuning: E1 A1 D2 G2
&lt;/h2&gt;

&lt;p&gt;The standard tuning for a 4-string bass guitar, from the lowest (thickest) string to the highest (thinnest), is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;String&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;th&gt;Frequency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4th (thickest)&lt;/td&gt;
&lt;td&gt;E1&lt;/td&gt;
&lt;td&gt;41.20 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3rd&lt;/td&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;55.00 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;D2&lt;/td&gt;
&lt;td&gt;73.42 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1st (thinnest)&lt;/td&gt;
&lt;td&gt;G2&lt;/td&gt;
&lt;td&gt;98.00 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each string is tuned a perfect fourth apart — the same interval pattern as the lowest four strings of a guitar, just one octave lower. This is why bass feels familiar to guitarists but demands a different touch: those low frequencies are harder for tuners to lock onto, and small errors are more noticeable in a mix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5-string bass&lt;/strong&gt; adds a low B0 string (30.87 Hz) below the E, giving you B0–E1–A1–D2–G2. If you play metal, gospel, or modern worship music, that low B is essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use an Online Bass Tuner (Step by Step)
&lt;/h2&gt;

&lt;p&gt;A browser-based tuner like the &lt;a href="https://17nas.com/bass-tuner.php" rel="noopener noreferrer"&gt;free 17NAS bass tuner&lt;/a&gt; uses your device's microphone and the Web Audio API to detect pitch in real time — no app, no account, no audio uploaded anywhere. Here's how to get an accurate reading:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Allow microphone access.&lt;/strong&gt; Click "Allow" when your browser asks. (That's the browser requesting hardware access, not the site recording you. If you previously blocked it: click the lock icon in the address bar → Microphone → Allow → refresh the page.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select your instrument preset.&lt;/strong&gt; Choose 4-string or 5-string bass. The tuner will listen for E1, A1, D2, G2 (and B0 for 5-string).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pluck one string at a time.&lt;/strong&gt; Use your thumb or a pick, pluck cleanly near the neck pickup, and let the note ring. Mute the other strings with your fretting hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the needle.&lt;/strong&gt; Centered (or green) means you're in tune. Left means flat — tighten the tuning peg. Right means sharp — loosen it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn slowly, then re-pluck.&lt;/strong&gt; Bass strings take a moment to settle. After each adjustment, pluck again and let the tuner re-read before turning further.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go through all strings twice.&lt;/strong&gt; Tuning one string changes neck tension slightly, which can pull the others out. A second pass catches this.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tip for low strings:&lt;/strong&gt; The low E and B strings produce frequencies near the bottom of what laptop microphones pick up reliably. If the tuner struggles, pluck closer to the bridge (brighter harmonics help pitch detection), turn up your volume slightly, or use the 12th-fret harmonic — it rings at double the frequency and is much easier for the tuner to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning by Ear vs. Using a Tuner
&lt;/h2&gt;

&lt;p&gt;Learning to tune by ear is a valuable skill, and every bassist should develop relative pitch. The classic method: tune your A string to a reference (a piano, a tuning fork, or a tuner), then fret the 5th fret of the E string — that should match the open A. Repeat up the strings.&lt;/p&gt;

&lt;p&gt;But for day-to-day playing, a tuner wins on accuracy. Human ears are good at hearing &lt;em&gt;relative&lt;/em&gt; pitch (whether two notes match) but poor at &lt;em&gt;absolute&lt;/em&gt; pitch (whether your E is exactly 41.20 Hz). In a band context, being even 10 cents off your guitarist's tuning creates an audible clash in the low end. Use the tuner for the baseline, use your ear for the fine musical judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Bass Tuning Problems (and Fixes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"My bass won't stay in tune."&lt;/strong&gt; The usual suspects, in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Old strings.&lt;/strong&gt; Dead strings lose their ability to hold pitch. If your strings are more than 3–4 months old (or you play daily), change them. New strings also need a break-in: stretch each one gently by pulling it away from the fretboard, retune, and repeat 3–4 times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The nut is binding.&lt;/strong&gt; If you hear a "ping" when tuning up, the string is catching in the nut slot. A tiny amount of graphite (pencil lead) in the slot fixes this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature changes.&lt;/strong&gt; Bass necks move with temperature and humidity. If you tune up in a cold room and play a hot stage, expect drift. Tune after your bass has acclimated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intonation is off.&lt;/strong&gt; If open strings are in tune but fretted notes (especially at the 12th fret) are sharp or flat, that's an intonation problem, not a tuning problem. Compare the 12th-fret harmonic to the 12th-fret fretted note — they should match. If not, the bridge saddle needs adjusting. This is a setup job; a tech can do it in 20 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"The tuner can't hear my low B/E string."&lt;/strong&gt; As mentioned above, use the 12th-fret harmonic. Alternatively, plug your bass directly into your computer via an audio interface — a direct signal is far cleaner than a room microphone for sub-50 Hz frequencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Should I adjust the truss rod?"&lt;/strong&gt; Only if you have a specific neck-relief problem (buzzing frets, or action that changed suddenly). Truss rod adjustments affect playability, not tuning stability directly. If you're unsure, take it to a tech — a quarter-turn in the wrong direction can cause real damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternate Bass Tunings
&lt;/h2&gt;

&lt;p&gt;Standard tuning covers most music, but these are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drop D (D–A–D–G):&lt;/strong&gt; Lower only the E string to D. One-finger power chords, heavier low end. Common in rock and metal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D Standard (D–G–C–F):&lt;/strong&gt; Every string down a whole step. Darker tone, slightly looser feel. Popular in doom and stoner metal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Half-step down (Eb–Ab–Db–Gb):&lt;/strong&gt; Every string down a semitone. Easier on the fingers, matches horn-friendly keys. A chromatic tuner handles this easily — just tune each string until the needle centers on the flat note name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any decent online tuner with a chromatic mode covers all of these. The &lt;a href="https://17nas.com/bass-tuner.php" rel="noopener noreferrer"&gt;17NAS bass tuner&lt;/a&gt; supports both 4-string and 5-string presets plus chromatic detection, so alternate tunings are just a matter of watching the note readout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference: Tuning Frequencies
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tuning&lt;/th&gt;
&lt;th&gt;String 5&lt;/th&gt;
&lt;th&gt;String 4&lt;/th&gt;
&lt;th&gt;String 3&lt;/th&gt;
&lt;th&gt;String 2&lt;/th&gt;
&lt;th&gt;String 1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard 4&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;E1 41.20&lt;/td&gt;
&lt;td&gt;A1 55.00&lt;/td&gt;
&lt;td&gt;D2 73.42&lt;/td&gt;
&lt;td&gt;G2 98.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard 5&lt;/td&gt;
&lt;td&gt;B0 30.87&lt;/td&gt;
&lt;td&gt;E1 41.20&lt;/td&gt;
&lt;td&gt;A1 55.00&lt;/td&gt;
&lt;td&gt;D2 73.42&lt;/td&gt;
&lt;td&gt;G2 98.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drop D&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;D1 36.71&lt;/td&gt;
&lt;td&gt;A1 55.00&lt;/td&gt;
&lt;td&gt;D2 73.42&lt;/td&gt;
&lt;td&gt;G2 98.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D Standard&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;D1 36.71&lt;/td&gt;
&lt;td&gt;G1 49.00&lt;/td&gt;
&lt;td&gt;C2 65.41&lt;/td&gt;
&lt;td&gt;F2 87.31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I tune my bass guitar online without downloading an app?&lt;/strong&gt;&lt;br&gt;
Yes. A browser-based tuner uses the Web Audio API to read your microphone input and detect pitch in real time. Everything runs on your device — no install, no account, no audio sent to a server. Just open the page, allow mic access, and pluck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the standard tuning for a bass guitar?&lt;/strong&gt;&lt;br&gt;
E1–A1–D2–G2 for 4-string bass (low to high), each string a perfect fourth apart. 5-string bass adds a low B0 below the E.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my bass go out of tune so quickly?&lt;/strong&gt;&lt;br&gt;
Most often: old strings, a binding nut, or temperature changes. Change strings every few months, add graphite to the nut slots if you hear pinging, and always tune after your bass has warmed up to room/stage temperature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it bad to tune a bass by ear only?&lt;/strong&gt;&lt;br&gt;
Not bad, but less precise. Ears judge relative pitch well but absolute pitch poorly. In a band, even small deviations clash in the low frequencies. Use a tuner for the baseline and your ear for musical fine-tuning.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Keep your bass in tune and your low end tight. If this guide helped, the &lt;a href="https://17nas.com/bass-tuner.php" rel="noopener noreferrer"&gt;free online bass tuner&lt;/a&gt; is there whenever you need a quick tune-up — no app required.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Tune Your Guitar Quickly Using a Guitar Tuner Online</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Tue, 13 Oct 2026 00:19:17 +0000</pubDate>
      <link>https://dev.to/truman-17nas/how-to-tune-your-guitar-quickly-using-a-guitar-tuner-online-7oa</link>
      <guid>https://dev.to/truman-17nas/how-to-tune-your-guitar-quickly-using-a-guitar-tuner-online-7oa</guid>
      <description>&lt;h1&gt;
  
  
  How to Tune Your Guitar Quickly Using a Guitar Tuner Online
&lt;/h1&gt;

&lt;p&gt;You pick up your guitar, strum one chord, and something sounds off. Not catastrophically wrong — just enough to be annoying. You don't want to dig through your bag for a clip-on, and downloading another app feels like overkill for a five-minute practice session. Your browser already has everything you need: a free online guitar tuner lets you open a tab, allow mic access, pluck a string, and read your pitch within seconds. No install, no account, no app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a browser guitar tuner actually does (and why it works)
&lt;/h2&gt;

&lt;p&gt;Modern browsers include the Web Audio API, which lets a website read your microphone input directly on your device. The browser captures your guitar's waveform, runs pitch detection math (autocorrelation or FFT analysis), and shows the result instantly. Well-built tuners do all of this client-side — the audio stays on your machine. (It's worth checking any tuner's privacy statement, but that's how the &lt;a href="https://17nas.com/guitar-tuner.php" rel="noopener noreferrer"&gt;free tuner at 17NAS&lt;/a&gt; is built.)&lt;/p&gt;

&lt;p&gt;Two flavors worth understanding: a &lt;strong&gt;chromatic tuner&lt;/strong&gt; listens for any note across the full 12-note scale — flexible for any tuning. A preset tuner only watches the six standard strings (E A D G B e). Chromatic is the smarter pick if you ever play alternate tunings.&lt;/p&gt;

&lt;p&gt;One thing to know before you start: the needle shows pitch error in &lt;strong&gt;cents&lt;/strong&gt; (1 cent = 1/100th of a semitone). Within ±5 cents is solid for practice and most recordings. Beyond ±10 cents, most listeners hear something off. Center the needle and you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step: tuning with a browser tuner
&lt;/h2&gt;

&lt;p&gt;The browser will ask for mic permission when you open the tuner. Click Allow — this is the browser requesting hardware access, not the website recording you. On mobile the prompt comes from iOS/Android. If you previously blocked access, click the lock icon in the address bar, set Microphone to Allow, and refresh.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the online guitar tuner (&lt;a href="https://17nas.com/guitar-tuner.php" rel="noopener noreferrer"&gt;17NAS's version&lt;/a&gt; needs no sign-in — worth bookmarking).&lt;/li&gt;
&lt;li&gt;Click Allow when the browser requests mic access.&lt;/li&gt;
&lt;li&gt;Select Standard Tuning or your alternate tuning.&lt;/li&gt;
&lt;li&gt;Pluck the low E string cleanly. Mute all other strings.&lt;/li&gt;
&lt;li&gt;Watch the needle settle. Centered/green = in tune. Left = flat, right = sharp.&lt;/li&gt;
&lt;li&gt;Adjust the peg slowly, let the note sustain, read again.&lt;/li&gt;
&lt;li&gt;Repeat for each string, then do a &lt;strong&gt;complete second pass&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why the second pass matters
&lt;/h3&gt;

&lt;p&gt;Tightening one string shifts tension across the neck, nudging the others slightly out. One pass gets you close; the second locks it in. On acoustics especially, skipping the second pass leaves you audibly off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard vs. alternate tunings
&lt;/h2&gt;

&lt;p&gt;Standard EADGBE covers the vast majority of rock, pop, classical, and folk. Most browser tuners default to it.&lt;/p&gt;

&lt;p&gt;A chromatic tuner handles Drop D, Drop C, Open G, Open D, DADGAD, half-step down, and dozens more — it detects any pitch, not just six targets. Many tools also offer named presets so you don't look up notes manually.&lt;/p&gt;

&lt;p&gt;For 12-string guitars: tune each string in every pair individually, muting its partner. Tune the lower string of each octave pair first, then match the higher one. Recheck everything afterward — tension shifts compound across 12 strings.&lt;/p&gt;

&lt;p&gt;Once in tune, 17NAS also offers a &lt;a href="https://17nas.com/metronome.php" rel="noopener noreferrer"&gt;free online metronome&lt;/a&gt; alongside the tuner — same no-install, no-account design. Two tabs, ten seconds to set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuning in noisy rooms and on mobile
&lt;/h2&gt;

&lt;p&gt;Background noise is the #1 reason a browser tuner jumps around. Close windows, mute speakers, pause fans, move away from other instruments. Pluck one string firmly, mute the rest immediately, and wait past the initial sharp attack — the stable sustaining part is what the algorithm locks onto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mic placement:&lt;/strong&gt; 8–16 inches from an acoustic's soundhole, aimed slightly off-center (not directly in — that overemphasizes bass). Point toward where the neck meets the body. For electrics through an amp: clean tone, effects off — distortion adds harmonics that confuse detection.&lt;/p&gt;

&lt;p&gt;A phone mic works fine in a quiet room. 17NAS's tuner runs on iOS Safari and Android Chrome with no extra setup. For electrics in noisier settings, a USB audio interface into your laptop gives a dramatically cleaner signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick fixes when the tuner stops responding
&lt;/h2&gt;

&lt;p&gt;Three usual causes: browser blocked access previously, OS disabled mic for the browser, or another app locked the mic exclusively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser block:&lt;/strong&gt; lock icon → Microphone → Allow → refresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS block:&lt;/strong&gt; Windows → Settings → Privacy and Security → Microphone. macOS → System Settings → Privacy and Security → Microphone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competing app:&lt;/strong&gt; close video calls, DAWs, streaming tools, then reload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate it:&lt;/strong&gt; try your device's voice recorder. If that fails too, it's hardware/OS. If the recorder works, try the tuner in Incognito or a different browser (Chrome has the broadest Web Audio support).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use HTTPS always&lt;/strong&gt; — browsers block mic access on plain HTTP pages. 17NAS is HTTPS-only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clip-on vs. browser: picking the right tool
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Browser tuner wins:&lt;/strong&gt; home practice, bedroom recording prep, quiet rehearsal. No batteries, no extra device, handles standard and alternate tunings on any screen. In good conditions: ±1–5 cents — more than adequate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clip-on wins:&lt;/strong&gt; loud stages, full-band rehearsals, outdoors. Reads headstock vibration directly — background noise irrelevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strobe tuner wins:&lt;/strong&gt; intonation setup, pro studio tracking — where 1–2 cents actually matters.&lt;/p&gt;

&lt;p&gt;The honest breakdown: browser for daily practice, clip-on for noise, strobe for precision work. &lt;a href="https://17nas.com/guitar-tuner.php" rel="noopener noreferrer"&gt;17NAS's free browser tuner&lt;/a&gt; handles standard and alternate tunings on any device — no account, no audio uploaded. Bookmark it, do your second pass, save the clip-on for the stage.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Best Free Online Guitar Tuners for Beginners and Pros</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Mon, 12 Oct 2026 23:48:47 +0000</pubDate>
      <link>https://dev.to/truman-17nas/best-free-online-guitar-tuners-for-beginners-and-pros-ice</link>
      <guid>https://dev.to/truman-17nas/best-free-online-guitar-tuners-for-beginners-and-pros-ice</guid>
      <description>&lt;h1&gt;
  
  
  Best Free Online Guitar Tuners for Beginners and Pros
&lt;/h1&gt;

&lt;p&gt;You're about to play, your guitar is out of tune, and the clip-on tuner is nowhere in sight. You don't want to download an app or wait for anything to install. Opening a browser and using a free online guitar tuner is genuinely the fastest fix — and modern browser-based tools are accurate enough that "free" doesn't mean "approximate." Many run entirely in your browser, processing audio locally on your device: no account, no upload. Whether you're a beginner tuning up for the first time or a working guitarist who needs a quick reference between sets, you just need to know which tuner to pick and how to use it correctly.&lt;/p&gt;

&lt;p&gt;Not all browser tuners perform equally well. The difference between a good one and a mediocre one comes down to the pitch detection algorithm, how well it handles microphone noise, and whether it lets you adjust the calibration reference. This guide covers all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an online guitar tuner detects pitch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The algorithm doing the heavy lifting
&lt;/h3&gt;

&lt;p&gt;Browser tuners analyze audio from your microphone in real time using pitch detection algorithms — most commonly autocorrelation or the McLeod Pitch Method (MPM). The tuner captures incoming sound waves, compares them against known frequency patterns, and identifies the fundamental pitch your string is producing. Most free online tuners do this computation entirely within the browser tab without sending audio to a server (check each tuner's privacy page to confirm).&lt;/p&gt;

&lt;p&gt;MPM applies parabolic interpolation to improve pitch resolution and includes noise filtering to reduce false readings. In quiet conditions, MPM-based tuners can resolve pitch to within roughly 1 to 3 cents. Autocorrelation-based tuners perform similarly well in clean environments, though both get less reliable as background noise increases. Algorithmic resolution is a ceiling, not a guarantee — microphone quality and room conditions determine how close you get to it in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chromatic mode vs. guitar-specific mode
&lt;/h3&gt;

&lt;p&gt;Chromatic mode detects any pitch across the full 12-note spectrum and shows the nearest note plus how far off you are in cents. It's the right choice for alternate tunings, checking individual notes on the fretboard, or tuning other instruments. Guitar-specific mode locks the display to the six standard strings (E A D G B E) and guesses which string you're playing. It's faster for standard tuning; chromatic is more flexible for drop, open, or extended tunings.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to tune with an online guitar tuner
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting a clean read on acoustic guitar
&lt;/h3&gt;

&lt;p&gt;Position your microphone close to the sound hole but slightly off-center — dead center picks up too much resonance and confuses pitch detection. Pluck one string at a time with a clean, firm attack, then let the note ring and stabilize before reading the display. Never strum multiple strings at once; overlapping harmonics are more than most tuners can separate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tuning an electric guitar without an amp
&lt;/h3&gt;

&lt;p&gt;An unplugged electric is much quieter than an acoustic. If you have a USB audio interface, plug in directly and select it as your browser's mic input in system sound settings — this bypasses room acoustics entirely. Without an interface, position the mic close to the body where string vibration is most audible and test a few spots. You can also use the tuner's reference tones and tune by ear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting accurate results in a noisy room
&lt;/h3&gt;

&lt;p&gt;Silence competing noise first: fans off, windows closed, other tabs muted. Browser tuners perform well in quiet home environments but degrade noticeably with background noise — readings get jumpy and unreliable. In a rehearsal space with other instruments running, a physical clip-on is the practical choice. Save the browser tuner for home use where you control the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calibration settings every player should understand
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why A440 is the standard
&lt;/h3&gt;

&lt;p&gt;Concert pitch A440 means the A above middle C vibrates at exactly 440 Hz. Nearly every commercial recording, backing track, and fixed-pitch instrument of the last several decades uses this reference. If you're playing with others or along to recordings, A440 is correct — deviate and you'll sound flat or sharp against everything else. Most online tuners show an adjustable A4 value in settings. Leave it at 440 unless you have a specific reason to change it.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to try 432 Hz
&lt;/h3&gt;

&lt;p&gt;At 432 Hz, every pitch drops by roughly 31.8 cents — about a third of a semitone. Players who prefer it cite a slightly softer, warmer character, and the marginally lower string tension can feel gentler. To switch, find the reference pitch control (labeled A4 or Hz) and change 440 to 432, then &lt;strong&gt;retune every string from scratch&lt;/strong&gt; since all targets shift together. For group playing, every instrument and backing track must share the same reference — confirm before changing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternate and custom tunings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Drop D, open G, DADGAD, and other common presets
&lt;/h3&gt;

&lt;p&gt;Many browser tuners include preset alternate tunings. Drop D lowers only the low E to D — most tuners offer it as a preset or let you target D in chromatic mode. Open G (D G D G B D) and Open D appear as presets in stronger tools. DADGAD, popular in folk and Celtic styles, is common enough to be a labeled preset on most full-featured tuners.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extended range guitars: 7- and 8-string support
&lt;/h3&gt;

&lt;p&gt;For 7- and 8-string guitars, chromatic mode is the most reliable approach since guitar-specific mode typically assumes six strings. Some tuners offer dedicated 7-, 8-, and 12-string presets. Without those, switch to chromatic mode and target each string by name. For unusual configurations, choose a tuner with per-string custom targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser mic vs. clip-on tuner: when each one wins
&lt;/h2&gt;

&lt;p&gt;In a quiet room, a good browser tuner using MPM can come within about 1 to 3 cents of a clip-on's practical accuracy — inaudible in solo practice. In noise, the gap widens fast: a clip-on's piezo sensor reads vibration directly from the headstock, bypassing room acoustics entirely, giving faster and more stable readings.&lt;/p&gt;

&lt;p&gt;For quiet home practice, a free online tuner is genuinely sufficient — and has one real advantage: zero extra gear. For live performance, crowded rehearsals, or sessions where you tune between takes, a clip-on wins on speed and reliability. &lt;strong&gt;Keep both in your workflow.&lt;/strong&gt; A bookmarked browser tuner handles home practice; a clip-on in your gig bag handles everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing microphone problems
&lt;/h2&gt;

&lt;p&gt;If the tuner shows a mic error or no response:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the lock icon by the address bar, set Microphone to Allow, reload.&lt;/li&gt;
&lt;li&gt;On Windows: Settings → Privacy and Security → Microphone — confirm global and browser access are on.&lt;/li&gt;
&lt;li&gt;Close apps holding exclusive mic access (video calls, DAWs, recording software).&lt;/li&gt;
&lt;li&gt;Confirm the correct input device in system sound settings, especially with USB interfaces.&lt;/li&gt;
&lt;li&gt;If previously blocked in Chrome: Settings → Privacy and Security → Site Settings → Microphone — remove the site from the block list, reload, grant access.&lt;/li&gt;
&lt;li&gt;Try an Incognito window to rule out extension interference.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;On mobile:&lt;/strong&gt; iPhone → Settings → Safari → Microphone → Allow. Android → Settings → Apps → Chrome → Permissions → Microphone. Reload the tuner page and tap start manually — mobile browsers need explicit interaction before activating audio. If another app also can't hear anything, it's device-level, not the tuner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Match the mode to your situation (chromatic for alternate tunings, guitar-specific for standard). Tune one string at a time in the quietest space available. Set A440 before playing with anyone else. A free online tuner handles home practice; a clip-on takes over for anything noisier.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://17nas.com/guitar-tuner.php" rel="noopener noreferrer"&gt;online guitar tuner at 17NAS&lt;/a&gt; is built for exactly that use case: no installation, no account, local audio processing so your audio stays on your device. The site also includes a free online metronome and pitch trainer — once you're in tune, move straight into practice without switching tabs. Getting in tune takes about thirty seconds with the right browser tool used correctly.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>music</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to Check If Your Network Really Supports IPv6 (And Why It Matters in 2026)</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Mon, 12 Oct 2026 21:13:15 +0000</pubDate>
      <link>https://dev.to/truman-17nas/how-to-check-if-your-network-really-supports-ipv6-and-why-it-matters-in-2026-1mni</link>
      <guid>https://dev.to/truman-17nas/how-to-check-if-your-network-really-supports-ipv6-and-why-it-matters-in-2026-1mni</guid>
      <description>&lt;h1&gt;
  
  
  How to Check If Your Network Really Supports IPv6 (And Why It Matters in 2026)
&lt;/h1&gt;

&lt;p&gt;You've probably seen "IPv6" in your router settings. But is it actually working? Here's how to check — and what the results mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why IPv6 Still Matters
&lt;/h2&gt;

&lt;p&gt;Despite years of "IPv6 is coming," global adoption crossed 45% in 2026 (Google stats). If your ISP supports it but your setup doesn't, you're leaving performance and future-proofing on the table. Some services — especially P2P, gaming, and self-hosted apps — work noticeably better over IPv6.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-Second Test
&lt;/h2&gt;

&lt;p&gt;The simplest way: open a browser-based test that checks both protocols in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://17nas.com/ipv6-test.php" rel="noopener noreferrer"&gt;17NAS IPv6 Test&lt;/a&gt;&lt;/strong&gt; is a free tool that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your IPv4 and IPv6 addresses side by side&lt;/li&gt;
&lt;li&gt;Latency for each protocol (is v6 actually faster for you?)&lt;/li&gt;
&lt;li&gt;Your ISP and connection details&lt;/li&gt;
&lt;li&gt;Dual-stack status — whether both protocols work simultaneously&lt;/li&gt;
&lt;li&gt;A connection quality score&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No signup, no install. It runs entirely in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You have IPv6&lt;/strong&gt; — Great. Check the latency comparison. In many cases IPv6 is slightly faster because it avoids NAT overhead and CGNAT bottlenecks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No IPv6&lt;/strong&gt; — Common causes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your ISP doesn't offer it (still common in some regions)&lt;/li&gt;
&lt;li&gt;Your router has IPv6 disabled (check WAN settings)&lt;/li&gt;
&lt;li&gt;Your OS has it turned off&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;IPv6 works but slow&lt;/strong&gt; — Could be a tunneling setup (6to4, Teredo) rather than native IPv6. Native is always better.&lt;/p&gt;

&lt;h2&gt;
  
  
  For Developers: Script It
&lt;/h2&gt;

&lt;p&gt;If you need to check IPv6 programmatically (CI, monitoring, fleet checks), the same tool offers a curl-friendly endpoint. Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifying server IPv6 readiness before DNS cutover&lt;/li&gt;
&lt;li&gt;Monitoring dual-stack health&lt;/li&gt;
&lt;li&gt;Debugging "works on IPv4 but not IPv6" issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;IPv6 adoption data is public — &lt;a href="https://17nas.com/ipv6-statistics.php" rel="noopener noreferrer"&gt;17NAS publishes IPv6 statistics&lt;/a&gt; (CC BY 4.0, free to cite) tracking real-world deployment trends.&lt;/p&gt;

&lt;p&gt;Bottom line: if you haven't checked your IPv6 status recently, take 30 seconds. You might be surprised.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your 144Hz monitor might be running at 60Hz — here's how to actually check</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Sun, 11 Oct 2026 08:20:38 +0000</pubDate>
      <link>https://dev.to/truman-17nas/your-144hz-monitor-might-be-running-at-60hz-heres-how-to-actually-check-4a5</link>
      <guid>https://dev.to/truman-17nas/your-144hz-monitor-might-be-running-at-60hz-heres-how-to-actually-check-4a5</guid>
      <description>&lt;h1&gt;
  
  
  Your 144Hz monitor might be running at 60Hz — here's how to actually check
&lt;/h1&gt;

&lt;p&gt;You bought the 144Hz monitor. You plugged it in. Everything feels... exactly the same as before? Before you blame the panel, consider this: your monitor can only show what your output chain actually delivers, and there are half a dozen places where 144Hz quietly becomes 60Hz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh rate ≠ frame rate
&lt;/h2&gt;

&lt;p&gt;Refresh rate is how many times per second your display redraws (Hz). Frame rate is how many new frames your GPU or app actually produces per second (FPS). Refresh rate is the ceiling; frame rate is the supply. A 144Hz screen fed only 60 FPS just redraws the same frame repeatedly — it won't look much smoother than 60Hz. Frames above the refresh rate get thrown away. This distinction alone clears up most "why doesn't it feel faster" confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the number in your browser can lie
&lt;/h2&gt;

&lt;p&gt;Online refresh-rate testers measure &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks, which go through the browser's rendering pipeline — not the raw panel. Common reasons the reading comes in low:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The OS display setting is still on 60Hz (Windows: Settings → System → Display → Advanced display; macOS: System Settings → Displays).&lt;/li&gt;
&lt;li&gt;Laptop on battery — power saving downclocks the panel.&lt;/li&gt;
&lt;li&gt;Browser hardware acceleration is off, capping callbacks around 60Hz.&lt;/li&gt;
&lt;li&gt;HDMI cable, dock, or port version can't carry 144Hz at your resolution.&lt;/li&gt;
&lt;li&gt;The tab is in the background or windowed — the compositor throttles it. Go fullscreen and try again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frame skipping: the sneaky one
&lt;/h2&gt;

&lt;p&gt;An overclocked monitor (or a flaky cable) can &lt;em&gt;report&lt;/em&gt; 144Hz while silently dropping frames. The classic way to catch it: a grid where each frame lights the next cell, photographed with a camera at ~1/10s shutter. A continuous lit segment means every frame arrived; gaps mean dropped frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side-by-side test
&lt;/h2&gt;

&lt;p&gt;The most intuitive check needs no camera: play the same animation at 30, 60, 120, 144 and 240 FPS side by side and watch which columns you can still tell apart. On a true 60Hz output, the 120/144/240 columns blur together — the browser only gets one paint per refresh, so anything above the ceiling collapses into the same frame. If 60 and 120 clearly separate, you're above 60Hz. If 144 and 240 are indistinguishable on a 144Hz-rated panel, that's normal; if your panel claims 240Hz and you can't separate them, check whether the OS is capping output at 144Hz.&lt;/p&gt;

&lt;p&gt;I used 17NAS's free frame-rate comparison for this — five columns at 30/60/120/144/240 FPS running the same pixel character, with keyboard shortcuts (F for fullscreen, space to pause) and the settings encoded in the URL so you can share a specific setup: &lt;a href="https://17nas.com/tools/display-test/framerate/?lang=en-US" rel="noopener noreferrer"&gt;https://17nas.com/tools/display-test/framerate/?lang=en-US&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No install, no sign-up. If the high columns all look identical, don't blame the animation — your screen just ran out of paints per second.&lt;/p&gt;

</description>
      <category>monitors</category>
      <category>hardware</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I built an open-source voice input for macOS that never uploads your voice</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:14:50 +0000</pubDate>
      <link>https://dev.to/truman-17nas/i-built-an-open-source-voice-input-for-macos-that-never-uploads-your-voice-1hna</link>
      <guid>https://dev.to/truman-17nas/i-built-an-open-source-voice-input-for-macos-that-never-uploads-your-voice-1hna</guid>
      <description>&lt;h1&gt;
  
  
  I built an open-source voice input for macOS that never uploads your voice
&lt;/h1&gt;

&lt;p&gt;Most voice input tools upload every recording to somebody's server. I wanted something simpler: hold a hotkey, talk, release — and the text appears at my cursor, with my voice never leaving the Mac.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Cadenza&lt;/strong&gt; (随言). MIT open source, macOS 14+, Apple Silicon and Intel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/DragonKingIO/Cadenza-voice" rel="noopener noreferrer"&gt;https://github.com/DragonKingIO/Cadenza-voice&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site &amp;amp; docs:&lt;/strong&gt; &lt;a href="https://dragonkingio.github.io/cadenza-site/" rel="noopener noreferrer"&gt;https://dragonkingio.github.io/cadenza-site/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why local-first
&lt;/h2&gt;

&lt;p&gt;This category has a trust problem. We've watched popular dictation apps regress in accuracy after updates, get caught in screenshot scandals, and jack up lifetime prices from $249 to $849. Every one of them routes your voice through their servers.&lt;/p&gt;

&lt;p&gt;Cadenza's answer is architectural: recognition happens on your machine, period. There's no server to breach, no recording to subpoena, no subscription to hike.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Hold to talk.&lt;/strong&gt; Press and hold a shortcut (configurable; tap-to-toggle also works). A small recording bar shows what's happening — recording, recognizing, done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa63hjtq0tavmge8ebgim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa63hjtq0tavmge8ebgim.png" width="800" height="700"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvsr74hqn2gyh6o7wb0pq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvsr74hqn2gyh6o7wb0pq.png" width="288" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Recognize on-device.&lt;/strong&gt; Five models to choose from — SenseVoice (my recommendation), FireRedASR2, Paraformer, Parakeet, Qwen3-ASR — running locally via sherpa-onnx. Models download inside the app with checksum verification, and inference needs no network connection at all. The list shows each model's speed, memory footprint, and punctuation support so you can pick your tradeoff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fskffqgexwsa2hwzlxukz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fskffqgexwsa2hwzlxukz.png" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Text lands at your cursor.&lt;/strong&gt; Release the key and the transcription is typed wherever your cursor is. Filler words ("uh", "um") and repeated characters are cleaned up by default; an optional AI polish step fixes punctuation and obvious errors — text only, and it can run against a local Ollama instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbh8edxvskywyterreim7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbh8edxvskywyterreim7.png" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy is a design, not a promise
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No account, no telemetry, no analytics.&lt;/li&gt;
&lt;li&gt;Recordings and transcripts are never written to disk and never logged.&lt;/li&gt;
&lt;li&gt;API keys live in the macOS Keychain, not in a config file.&lt;/li&gt;
&lt;li&gt;There's even a "never go online" kill switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famlhpszhqa7pz3bxm4yw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Famlhpszhqa7pz3bxm4yw.png" width="800" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is MIT open source, so don't take my word for it — you can audit exactly what leaves your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud, strictly on your terms
&lt;/h2&gt;

&lt;p&gt;If you do want a cloud provider, you bring your own API key, and each provider receives audio only after you authorize it individually. Supported: OpenAI, Groq, Google, Azure, AssemblyAI, ElevenLabs, plus any OpenAI-compatible endpoint. These also power the optional voice-translation hotkey (separate shortcut, types the translation directly) and the AI polish step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujqls3stxnlb4o87kcpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujqls3stxnlb4o87kcpn.png" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One honest disclosure: I haven't tested the cloud providers against real paid accounts yet. If you try one, I'd genuinely like to hear how it went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Made for real vocabularies
&lt;/h2&gt;

&lt;p&gt;There's a vocabulary manager for your proper nouns, project names, and jargon — plus shared packs you can import. This is the unglamorous feature that actually determines whether dictation is usable for work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pgc5vbt53vljmbzwqsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3pgc5vbt53vljmbzwqsg.png" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also in testing: screenshot OCR (select a region, read text with on-device Apple Vision or PP-OCR).&lt;/p&gt;

&lt;h2&gt;
  
  
  Status: early preview, daily-driven
&lt;/h2&gt;

&lt;p&gt;We use it every day, but it hasn't been tested in many environments yet. Two practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not notarized by Apple yet, so first launch needs System Settings → Privacy &amp;amp; Security → "Open Anyway", plus microphone, accessibility, and input monitoring permissions.&lt;/li&gt;
&lt;li&gt;Requirements: macOS 14+, Apple Silicon or Intel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What would help most right now: recognition feedback across accents, microphones, and real rooms; real-world cloud provider results; UI translations and docs in other languages.&lt;/p&gt;

&lt;p&gt;If it's useful, a star is appreciated — and if not, that's completely fine too. Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>opensource</category>
      <category>ai</category>
      <category>speechrecognition</category>
    </item>
    <item>
      <title>Your browser can detect your NAT type in 30 lines of JavaScript</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 08 Oct 2026 17:50:46 +0000</pubDate>
      <link>https://dev.to/truman-17nas/your-browser-can-detect-your-nat-type-in-30-lines-of-javascript-43cm</link>
      <guid>https://dev.to/truman-17nas/your-browser-can-detect-your-nat-type-in-30-lines-of-javascript-43cm</guid>
      <description>&lt;h1&gt;
  
  
  Your browser can detect your NAT type in 30 lines of JavaScript
&lt;/h1&gt;

&lt;p&gt;Ever had a video call that connects fine on one network but refuses to establish peer-to-peer on another? Or a game where voice chat works for everyone except that one friend? Nine times out of ten, the culprit is the NAT sitting between you and the internet — and your browser already has everything needed to interrogate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-line version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stunUrl&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;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stunUrl&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;probe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// kick off ICE gathering&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;srflx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onicecandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;srflx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// null = gathering done&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;srflx&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stun:stun.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;probeNat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stun:stun1.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;portOf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;portOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cone NAT (predictable mapping)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Symmetric NAT (per-destination mapping)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No extensions, no native code, no permissions prompt. Let me explain why this works.&lt;/p&gt;

&lt;h2&gt;
  
  
  ICE candidates are a free STUN client
&lt;/h2&gt;

&lt;p&gt;When you create an &lt;code&gt;RTCPeerConnection&lt;/code&gt; with STUN servers configured, the browser's ICE agent automatically sends STUN Binding Requests to each server. The responses come back to your JavaScript as &lt;strong&gt;ICE candidates&lt;/strong&gt; — strings like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;candidate:8421630493 1 udp 1685987071 203.0.113.45 54321 typ srflx raddr 192.168.1.5 rport 54321
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields that matter: &lt;code&gt;203.0.113.45 54321&lt;/code&gt; is your &lt;strong&gt;server-reflexive&lt;/strong&gt; address — how the STUN server saw you &lt;em&gt;after&lt;/em&gt; your NAT translated the packet. The &lt;code&gt;raddr&lt;/code&gt;/&lt;code&gt;rport&lt;/code&gt; pair is your local address behind the NAT. The &lt;code&gt;typ srflx&lt;/code&gt; marker tells you this candidate came from STUN rather than local interface enumeration (&lt;code&gt;host&lt;/code&gt;) or a TURN relay (&lt;code&gt;relay&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So the browser is effectively a STUN client already. We're just reading its output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core trick: compare mappings across servers
&lt;/h2&gt;

&lt;p&gt;A NAT has to decide what external port to assign your traffic. There are two philosophies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cone NAT&lt;/strong&gt;: one internal address:port always maps to the same external address:port, regardless of destination. Ask two different STUN servers and both report the same external port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symmetric NAT&lt;/strong&gt;: each &lt;em&gt;destination&lt;/em&gt; gets its own mapping. Ask two different STUN servers and you get two different external ports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly what the snippet compares. Same port from both servers → cone family. Different ports → symmetric. This single distinction predicts most real-world connectivity pain: with symmetric NAT, the other peer can never guess which port to send to, so direct P2P hole punching fails and everything falls back to TURN relay (higher latency, server bandwidth).&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can't tell from the browser alone
&lt;/h2&gt;

&lt;p&gt;Honesty corner: this trick separates cone from symmetric, but it can't split the cone family further. Telling Full Cone apart from Address-Restricted or Port-Restricted Cone requires &lt;em&gt;probing&lt;/em&gt; — having a second server send packets at you from an address or port you never contacted, and seeing if they get through. Browsers can't receive arbitrary unsolicited UDP, so that test needs infrastructure outside the page. Most browser-based NAT tests (including the one linked below) report the cone/symmetric verdict and treat the cone subtypes as one bucket. For diagnosing "why won't my P2P connect," that bucket is usually enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters beyond curiosity
&lt;/h2&gt;

&lt;p&gt;Hole punching — the technique WebRTC, game netcode, and tools like Tailscale rely on — only works when at least one side has a predictable mapping. If both sides sit behind symmetric NATs, direct connection is essentially impossible and you &lt;em&gt;must&lt;/em&gt; relay. Knowing your NAT type up front tells you whether to even attempt P2P, what TURN capacity to provision, and why that one user keeps filing "call won't connect" tickets from their mobile hotspot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole technique is: configure STUN servers → harvest &lt;code&gt;srflx&lt;/code&gt; candidates → compare external ports across servers. The browser does the hard protocol work; you just read the results.&lt;/p&gt;

&lt;p&gt;If you'd rather not wire up the comparison logic yourself, &lt;a href="https://17nas.com/nat-test.php" rel="noopener noreferrer"&gt;17NAS NAT Test&lt;/a&gt; runs exactly this probe in your browser — it queries multiple STUN servers, compares the mappings, and reports your NAT type with the raw candidate details. Free, no sign-up, everything happens client-side.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>YouTube thumbnails are just predictable URLs: how maxresdefault really works</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 08 Oct 2026 05:43:05 +0000</pubDate>
      <link>https://dev.to/truman-17nas/youtube-thumbnails-are-just-predictable-urls-how-maxresdefault-really-works-3pne</link>
      <guid>https://dev.to/truman-17nas/youtube-thumbnails-are-just-predictable-urls-how-maxresdefault-really-works-3pne</guid>
      <description>&lt;h1&gt;
  
  
  YouTube thumbnails are just predictable URLs: how maxresdefault really works
&lt;/h1&gt;

&lt;p&gt;Every YouTube video has a set of thumbnail images sitting on a public CDN, no API key required. If you've ever built a tool that shows link previews, embeds videos, or lets users download covers, you've probably used them. This post explains the URL scheme, the size ladder, and the one gotcha that breaks naive implementations: &lt;code&gt;maxresdefault&lt;/code&gt; doesn't always exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The URL scheme
&lt;/h2&gt;

&lt;p&gt;All thumbnails live under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://i.ytimg.com/vi/{VIDEO_ID}/{SIZE}.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{VIDEO_ID}&lt;/code&gt; is the 11-character ID from the watch URL. &lt;code&gt;{SIZE}&lt;/code&gt; is one of:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;120×90&lt;/td&gt;
&lt;td&gt;Always exists, 4:3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mqdefault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;320×180&lt;/td&gt;
&lt;td&gt;Always exists, 16:9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hqdefault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;480×360&lt;/td&gt;
&lt;td&gt;Always exists, 4:3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sddefault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;640×480&lt;/td&gt;
&lt;td&gt;Usually exists, 4:3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxresdefault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1280×720&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Only if the video has an HD source&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is simple enough to construct by hand. The catch is the last row: YouTube only generates &lt;code&gt;maxresdefault&lt;/code&gt; when the uploaded video was HD. For older or low-resolution uploads, requesting it returns a 404 — or worse, a placeholder image with an HTTP 200, depending on the era of the upload. A robust fetcher must probe from largest to smallest and take the first that actually resolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting the video ID
&lt;/h2&gt;

&lt;p&gt;Users paste all kinds of URLs. Before you can build the thumbnail URL, you need the ID out of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://www.youtube.com/watch?v=dQw4w9WgXcQ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://youtu.be/dQw4w9WgXcQ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://www.youtube.com/shorts/dQw4w9WgXcQ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://www.youtube.com/embed/dQw4w9WgXcQ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://www.youtube.com/live/dQw4w9WgXcQ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://m.youtube.com/watch?v=dQw4w9WgXcQ&amp;amp;feature=shared&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;just &lt;code&gt;dQw4w9WgXcQ&lt;/code&gt; (11 chars)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single regex handles most of these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractVideoId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_-&lt;/span&gt;&lt;span class="se"&gt;]{11}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// bare ID&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;youtube&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com&lt;/span&gt;&lt;span class="se"&gt;\/(?:&lt;/span&gt;&lt;span class="sr"&gt;watch&lt;/span&gt;&lt;span class="se"&gt;\?[^&lt;/span&gt;&lt;span class="sr"&gt;#&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*v=|shorts&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;|embed&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;|live&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;|v&lt;/span&gt;&lt;span class="se"&gt;\/)&lt;/span&gt;&lt;span class="sr"&gt;|youtu&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;be&lt;/span&gt;&lt;span class="se"&gt;\/)([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_-&lt;/span&gt;&lt;span class="se"&gt;]{11})&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the character class: YouTube IDs are base64url, so &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; are valid. A regex limited to &lt;code&gt;[a-zA-Z0-9]&lt;/code&gt; will silently fail on roughly a quarter of videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the best available size
&lt;/h2&gt;

&lt;p&gt;The reliable approach is a fallback chain. In the browser, the cheapest probe is an &lt;code&gt;Image&lt;/code&gt; load with &lt;code&gt;onerror&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SIZES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maxresdefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sddefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hqdefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mqdefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bestThumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;tryNext&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;SIZES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SIZES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://i.ytimg.com/vi/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.jpg`&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;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Guard against the legacy 120x90 placeholder served with 200&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maxresdefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;tryNext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&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="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalHeight&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tryNext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;tryNext&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details worth noting. First, &lt;code&gt;sddefault&lt;/code&gt; (640×480) is 4:3 while the rest of the HD ladder is 16:9 — if your UI expects 16:9, you may want to skip it or crop. Second, the placeholder guard: very old videos serve a tiny 120×90 "no thumbnail" image at the &lt;code&gt;maxresdefault&lt;/code&gt; path with HTTP 200, so checking &lt;code&gt;naturalWidth&lt;/code&gt; is more reliable than status codes alone.&lt;/p&gt;

&lt;p&gt;Server-side, the same chain works with HEAD requests, but be polite: these are cheap CDN hits, not API calls, and there's no quota — but hammering them in a tight loop from one IP is still a good way to get throttled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why no API key is needed
&lt;/h2&gt;

&lt;p&gt;This surprises people, but the thumbnail CDN is intentionally public. The images are referenced by every embed player on the web; YouTube can't put them behind auth without breaking embeds. The official Data API (&lt;code&gt;thumbnails&lt;/code&gt; resource) returns the same URLs — you're just skipping the quota-limited middleman. What the API gives you that raw URLs don't is metadata (which sizes exist), but as shown above, probing is trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebP variants
&lt;/h2&gt;

&lt;p&gt;Append &lt;code&gt;.webp&lt;/code&gt; instead of &lt;code&gt;.jpg&lt;/code&gt; and you get WebP versions of the same images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://i.ytimg.com/vi/{VIDEO_ID}/maxresdefault.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful if you care about bytes on the wire. Availability mirrors the JPG ladder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole system is: extract 11-char ID → walk the size ladder → take the first real hit. No SDK, no key, no proxy server needed — the browser can do it all directly against &lt;code&gt;i.ytimg.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you'd rather not write the fallback chain yourself, &lt;a href="https://17nas.com/tools/youtube-thumbnail-downloader/" rel="noopener noreferrer"&gt;17NAS YouTube Thumbnail Downloader&lt;/a&gt; implements exactly this: paste any YouTube link (watch, youtu.be, Shorts, live, embed, mobile) or bare ID, it probes all sizes, auto-picks the best available, and gives you one-click JPG download or WebP in a new tab. Free, no sign-up, no watermark — everything is fetched by your browser straight from YouTube's image servers.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Web Audio APIだけで音声編集アプリを作る：ブラウザ内処理の仕組みを実装レベルで解説</title>
      <dc:creator>Truman</dc:creator>
      <pubDate>Thu, 08 Oct 2026 02:47:39 +0000</pubDate>
      <link>https://dev.to/truman-17nas/web-audio-apidakedeyin-sheng-bian-ji-apuriwozuo-ruburauzanei-chu-li-noshi-zu-miwoshi-zhuang-reberudejie-shuo-16jb</link>
      <guid>https://dev.to/truman-17nas/web-audio-apidakedeyin-sheng-bian-ji-apuriwozuo-ruburauzanei-chu-li-noshi-zu-miwoshi-zhuang-reberudejie-shuo-16jb</guid>
      <description>&lt;h1&gt;
  
  
  Web Audio APIだけで音声編集アプリを作る：ブラウザ内処理の仕組みを実装レベルで解説
&lt;/h1&gt;

&lt;p&gt;音声ファイルをサーバにアップロードせず、ブラウザだけでカット・変換・音量調整ができるWebアプリがあります。これを支えているのが &lt;strong&gt;Web Audio API&lt;/strong&gt; です。今回は「ブラウザ内で音声編集が完結する仕組み」を、実装レベルのコードとともに解説します。&lt;/p&gt;

&lt;h2&gt;
  
  
  1. decodeAudioData：ファイルを AudioBuffer（32bit float PCM）に変換する
&lt;/h2&gt;

&lt;p&gt;Web Audio API の入口は &lt;code&gt;AudioContext&lt;/code&gt; と &lt;code&gt;decodeAudioData&lt;/code&gt; です。MP3 や WAV といったファイルを、編集可能な PCM データ（&lt;code&gt;AudioBuffer&lt;/code&gt;）にデコードします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AudioContext&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;voice.mp3&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;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeAudioData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// audioBuffer.sampleRate        → 44100 など&lt;/span&gt;
&lt;span class="c1"&gt;// audioBuffer.numberOfChannels  → 1（モノラル）or 2（ステレオ）&lt;/span&gt;
&lt;span class="c1"&gt;// audioBuffer.getChannelData(0) → Float32Array（32bit float PCM、-1.0〜1.0）&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AudioBuffer&lt;/code&gt; の中身は、チャンネルごとの &lt;code&gt;Float32Array&lt;/code&gt; です。「編集する」とは、この配列を直接いじることに他なりません。入力フォーマットの差（MP3 / WAV / M4A など）は &lt;code&gt;decodeAudioData&lt;/code&gt; が吸収してくれますが、対応コーデックはブラウザ依存です（例：Safari は OGG/Vorbis をデコードできません）。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 波形選択とトリミング：秒数をサンプル数に直すだけ
&lt;/h2&gt;

&lt;p&gt;波形上で範囲を選んで切り出す処理は、考え方はシンプルです。秒数をサンプル数に変換し、配列を切り出すだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startSec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endSec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sampleRate&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;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startSec&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sr&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;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;endSec&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sr&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;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&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;ミリ秒単位の精密な選択も同じ理屈です。波形の描画自体は &lt;code&gt;getChannelData&lt;/code&gt; の値を間引いて canvas にプロットすれば実現できます。&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 削除時の 4ms クロスフェード：つなぎ目の「プチッ」を消す
&lt;/h2&gt;

&lt;p&gt;区間を削除して前後をつなぐと、波形の段差で「プチッ」というクリックノイズが発生します。これを防ぐ定番の手法が、つなぎ目にごく短いクロスフェードをかけることです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;spliceWithCrossfade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sampleRate&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;fadeLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sampleRate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4ms&lt;/span&gt;
  &lt;span class="c1"&gt;// head の末尾 fadeLen サンプルを、tail の先頭とミックスする&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;++&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;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;fadeLen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&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;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;fadeLen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 0 → 1&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;fadeLen&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;k&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;// あとは head と tail.slice(fadeLen) を連結する&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4ms という短さなら音の違和感はほぼなく、段差だけがきれいに消えます。&lt;/p&gt;

&lt;h2&gt;
  
  
  4. ピーク正規化：ピーク検出 → ゲイン計算
&lt;/h2&gt;

&lt;p&gt;「全体の音量を、最大ピークが -1dBFS になるように揃える」処理です。手順は3ステップです。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;全チャンネル・全サンプルを走査し、絶対値の最大（ピーク）を求める&lt;/li&gt;
&lt;li&gt;目標値 ÷ ピーク でゲインを計算する（-1dBFS ≒ 0.891）&lt;/li&gt;
&lt;li&gt;全サンプルにゲインを掛ける
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;peakNormalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;++&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&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;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetDb&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;peak&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;++&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;gain&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意点として、すでに音割れしている（クリッピング済みの）素材はこの処理では直りません。正規化は「小さすぎる音を安全に持ち上げる」ための処理です。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. エンコード：WAV 16bit 出力と lamejs による MP3 化
&lt;/h2&gt;

&lt;p&gt;編集後の &lt;code&gt;AudioBuffer&lt;/code&gt; は float32 のままではファイルに保存できません。WAV なら 16bit PCM に変換してヘッダを付けるだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Float32（-1.0〜1.0）→ Int16 への変換&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;floatTo16BitPCM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInt16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x8000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x7FFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MP3 はブラウザ標準 API ではエンコードできないため、LAME の JavaScript 移植版である &lt;strong&gt;lamejs&lt;/strong&gt; を使います。ビットレートは 96〜320kbps の範囲で指定できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;Mp3Encoder&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="s1"&gt;lamejs&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;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Mp3Encoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;44100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ch, sampleRate, kbps&lt;/span&gt;
&lt;span class="c1"&gt;// Int16 化したサンプルをブロックごとに入力 → encoder.encodeBuffer()&lt;/span&gt;
&lt;span class="c1"&gt;// 最後に encoder.flush() で残りのデータを吐き出す&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;実測ではデスクトップ環境で約5倍速（1分の音声を約12秒でエンコード）程度の性能が出ます。すべてブラウザ内（メインスレッド〜Worker）で完結し、サーバ通信はゼロです。&lt;/p&gt;

&lt;h2&gt;
  
  
  6. ブラウザ差とメモリの現実
&lt;/h2&gt;

&lt;p&gt;実装上ぶつかる壁は主に2つです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;コーデック差&lt;/strong&gt;：&lt;code&gt;decodeAudioData&lt;/code&gt; の対応形式はブラウザ依存です。MP3 / WAV はほぼ共通ですが、OGG は Safari 非対応、Opus の扱いにも差があります。対応入力形式の表は「実際にデコードできるか」の実測で作るのが確実です。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;メモリ&lt;/strong&gt;：PCM は非圧縮なので重いのが現実です。44.1kHz ステレオで1時間分 = 44100 × 2ch × 4byte × 3600秒 ≒ &lt;strong&gt;1.3GB&lt;/strong&gt;。実用的な設計としては、400MB（約19分）を超えたあたりで警告を出すのが無難です。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. プライバシー設計：ローカル処理 = アップロードなし
&lt;/h2&gt;

&lt;p&gt;この方式の最大の利点は、音声データがユーザーの PC から一歩も出ないことです。サーバに送らないので通信の待ち時間がなく、サーバ側での保存・漏洩リスクも存在しません。「無料・登録不要」がビジネスとして成立するのも、サーバコストがほぼゼロだからです。&lt;/p&gt;




&lt;p&gt;この仕組みをそのまま使った無料ツール例として、&lt;a href="https://17nas.com/tools/audio-tools/" rel="noopener noreferrer"&gt;17NAS Online Audio Tools&lt;/a&gt; があります。登録不要・透かしなしで、ここで解説したトリミングやピーク正規化、MP3/WAV 変換などがブラウザ内だけで動きます。&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
