<?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: Cao Wen</title>
    <description>The latest articles on DEV Community by Cao Wen (@durianlive).</description>
    <link>https://dev.to/durianlive</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%2F4242932%2F6da30ad1-2a84-40a1-ae12-c240a98c5fff.png</url>
      <title>DEV Community: Cao Wen</title>
      <link>https://dev.to/durianlive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/durianlive"/>
    <language>en</language>
    <item>
      <title>What I learned building a live-streaming widget with Go + SRS (and one iframe)</title>
      <dc:creator>Cao Wen</dc:creator>
      <pubDate>Sun, 15 Nov 2026 11:59:52 +0000</pubDate>
      <link>https://dev.to/durianlive/what-i-learned-building-a-live-streaming-widget-with-go-srs-and-one-iframe-49li</link>
      <guid>https://dev.to/durianlive/what-i-learned-building-a-live-streaming-widget-with-go-srs-and-one-iframe-49li</guid>
      <description>&lt;p&gt;I run a small live-selling widget for Shopify and WooCommerce stores. Not a startup — one person, one Go binary, SRS for media. A few decisions turned out to matter more than the feature list, so I'm writing them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Iframe instead of a script tag
&lt;/h2&gt;

&lt;p&gt;The first version was &lt;code&gt;&amp;lt;script src=".../shell.js" data-key="..."&amp;gt;&lt;/code&gt;. It injected a floating player into the merchant's page.&lt;/p&gt;

&lt;p&gt;It broke constantly. Theme CSS leaked in, my CSS leaked out, and every third-party script on the page fought with mine. Worse: the API key sat in the DOM of a public storefront page.&lt;/p&gt;

&lt;p&gt;What I ship now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/embed?shop_id=6"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"375"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"667"&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"position:fixed;right:20px;bottom:20px;border:0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same iframe, one public shop id, zero secrets in the markup. The merchant pastes four lines. If they break their theme, they don't break the player.&lt;/p&gt;

&lt;p&gt;Trade-off: iframe can't inherit the store's fonts. For a player that's fine — isolation is worth more than pixel-matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. SRT for the ingest path, not just RTMP
&lt;/h2&gt;

&lt;p&gt;RTMP is everywhere and everyone supports it. It's also TCP with fixed latency and handles packet loss badly — which is the normal condition for a merchant streaming from a phone on store wifi.&lt;/p&gt;

&lt;p&gt;SRT gives you loss recovery and configurable latency for maybe 200ms more delay. For live selling, 200ms of extra delay costs nothing; a frozen frame costs the sale.&lt;/p&gt;

&lt;p&gt;I still expose RTMP for compatibility, but the default config in my docs is SRT, and the bitrate defaults are deliberately conservative (1500–2000 kbps, 2s keyframe). Cross-border upload can't sustain 6 Mbps and the failure mode is a black player.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Enforce limits on the server, never in the widget
&lt;/h2&gt;

&lt;p&gt;Viewer caps, stream keys, tier upgrades — all of it lives in the Go backend. The widget only renders.&lt;/p&gt;

&lt;p&gt;Early on I had a cap check in the frontend so it would "respond faster". It took one person with devtools to watch a 200-viewer room on a 20-viewer plan. Anything that costs money has to be a server-side decision, even if the client shows a cached number.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The bug that taught me the most was caching
&lt;/h2&gt;

&lt;p&gt;I deploy static assets by scp'ing them into place and restarting nginx. Everything under &lt;code&gt;/static&lt;/code&gt; was served with &lt;code&gt;Cache-Control: max-age=604800, immutable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The day I added a feature that needed new i18n keys, every returning user got the new HTML with the old dictionary — and saw raw keys like &lt;code&gt;lc_title&lt;/code&gt; on screen instead.&lt;/p&gt;

&lt;p&gt;Now every asset reference carries a build version. &lt;code&gt;immutable&lt;/code&gt; is only safe if the URL changes when the content does. Obvious in hindsight; not obvious at 2am.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still hard
&lt;/h2&gt;

&lt;p&gt;Payments. If you're a solo developer outside the US/EU, merchant onboarding is the real boss fight — not the media pipeline. I've been through two providers' review processes and neither outcome was about code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;The whole thing is at &lt;a href="https://durianlive.com" rel="noopener noreferrer"&gt;https://durianlive.com&lt;/a&gt; — happy to hear what I got wrong, especially from anyone who has run live video at small scale.&lt;/p&gt;

</description>
      <category>go</category>
      <category>showdev</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>How do small stores handle live selling without commission?</title>
      <dc:creator>Cao Wen</dc:creator>
      <pubDate>Sat, 14 Nov 2026 10:44:37 +0000</pubDate>
      <link>https://dev.to/durianlive/how-do-small-stores-handle-live-selling-without-commission-2g11</link>
      <guid>https://dev.to/durianlive/how-do-small-stores-handle-live-selling-without-commission-2g11</guid>
      <description></description>
    </item>
    <item>
      <title>Live selling shouldn't cost you 10% of every order</title>
      <dc:creator>Cao Wen</dc:creator>
      <pubDate>Fri, 13 Nov 2026 12:54:17 +0000</pubDate>
      <link>https://dev.to/durianlive/live-selling-shouldnt-cost-you-10-of-every-order-10dc</link>
      <guid>https://dev.to/durianlive/live-selling-shouldnt-cost-you-10-of-every-order-10dc</guid>
      <description>&lt;p&gt;I'm the founder of DurianLive. This is the business problem we ran into,&lt;br&gt;
and what we changed — not a technical deep dive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Almost every live shopping tool asks the merchant to do the same thing:&lt;br&gt;
move the sale to the tool's platform. You get a spike in orders, then a&lt;br&gt;
bill for the commission, and the customer — their email, their purchase&lt;br&gt;
history, their attention — stays with the platform.&lt;/p&gt;

&lt;p&gt;The second problem is the catalog. You have to rebuild your products&lt;br&gt;
inside the tool: same photos, same descriptions, uploaded a second time.&lt;/p&gt;

&lt;p&gt;The third is the redirect. The shopper was on a product page, ready to&lt;br&gt;
buy. Now they're in another tab, and your checkout is three clicks away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;The show runs on the merchant's own website. A small player sits in the&lt;br&gt;
corner of the store. A shopper browsing a product page can open it, watch,&lt;br&gt;
ask a question, and click a product card — which takes them to the product&lt;br&gt;
page they already have, with the checkout they already use. They never leave.&lt;/p&gt;

&lt;p&gt;What that means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You keep the sale.&lt;/strong&gt; Payments run through the existing checkout —
Shopify, WooCommerce, whatever the store has. We never touch the money
and never take a percentage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You keep the customer.&lt;/strong&gt; Orders, emails, and browsing behaviour stay in
the merchant's dashboard and email tool, not ours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You keep your catalog.&lt;/strong&gt; Product cards link to the pages the merchant
already maintains. Nothing to re-upload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup is one line.&lt;/strong&gt; Paste it into the theme, save, open the store.
No plugin, no app install, no developer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The price is flat.&lt;/strong&gt; Merchants pay for how many people can watch at
once, not for how much they sell. Free up to 100 concurrent viewers;
paid plans start at $2 per day or $49 per month.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a show looks like
&lt;/h2&gt;

&lt;p&gt;Boring on purpose: open the player ten minutes early, show three to five&lt;br&gt;
products instead of the whole catalog, keep the product card pinned while&lt;br&gt;
talking about it, run 20–30 minutes, and finish with a discount code for&lt;br&gt;
people who stayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is not for
&lt;/h2&gt;

&lt;p&gt;If most of a store's reach comes from TikTok or Instagram, they want that&lt;br&gt;
algorithm and that audience; a widget on their own site won't manufacture&lt;br&gt;
demand. This is for stores that already have traffic — from search, ads,&lt;br&gt;
email, or a physical shop — and want to turn it into sales with a human on&lt;br&gt;
camera.&lt;/p&gt;

&lt;p&gt;The trade-off is honest: you give up the platform's built-in audience, and&lt;br&gt;
in exchange you keep the customer, the margin, and the data.&lt;/p&gt;




&lt;p&gt;If you want the step-by-step setup, it's here — Shopify and WooCommerce,&lt;br&gt;
about 15 minutes end to end: &lt;a href="https://durianlive.com/blog/shopify-live-selling" rel="noopener noreferrer"&gt;https://durianlive.com/blog/shopify-live-selling&lt;/a&gt;&lt;/p&gt;

</description>
      <category>woocommerce</category>
      <category>ecommerce</category>
      <category>saas</category>
      <category>shopify</category>
    </item>
  </channel>
</rss>
