<?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: Leonard Matasel</title>
    <description>The latest articles on DEV Community by Leonard Matasel (@leonard_matasel).</description>
    <link>https://dev.to/leonard_matasel</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%2F4131609%2Fdeffddd5-05bd-4df8-b8c6-25013d6b6b5c.jpg</url>
      <title>DEV Community: Leonard Matasel</title>
      <link>https://dev.to/leonard_matasel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leonard_matasel"/>
    <language>en</language>
    <item>
      <title>Playing YouTube in Flutter: the case for a WebView fallback</title>
      <dc:creator>Leonard Matasel</dc:creator>
      <pubDate>Fri, 18 Sep 2026 13:26:30 +0000</pubDate>
      <link>https://dev.to/leonard_matasel/playing-youtube-in-flutter-the-case-for-a-webview-fallback-25dn</link>
      <guid>https://dev.to/leonard_matasel/playing-youtube-in-flutter-the-case-for-a-webview-fallback-25dn</guid>
      <description>&lt;p&gt;Every Flutter app that plays YouTube picks one of two strategies, and both break.                                                                                        &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct extraction&lt;/strong&gt; resolves the stream URLs and hands them to &lt;code&gt;video_player&lt;/code&gt;. You get native playback, real quality switching and a UI you control. You also get a dependency on an interface nobody promised you: when YouTube changes something upstream, extraction returns nothing and your users get a black screen.                                                                                        &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A WebView&lt;/strong&gt; is the strategy that doesn't break, because it's the player YouTube itself maintains. You pay for it: the chrome isn't yours, quality selection isn't yours, and integrating it with the rest of your UI is work.                                                                                              &lt;/p&gt;

&lt;p&gt;The useful answer is not to choose. Try extraction, and when it fails, fall back to the WebView for that video — not for the whole app, not permanently.                                                                                                 &lt;/p&gt;

&lt;h2&gt;
  
  
  What "fails" means
&lt;/h2&gt;

&lt;p&gt;Failure here isn't an exception you catch around the player widget. It's earlier: the extraction step returns no playable stream, or returns one that the platform refuses. The fallback belongs at the point where you still know which video you were resolving and haven't shown anything to the user yet. Deciding after the first black frame is too late — by then you're swapping a widget the user is already looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this costs you
&lt;/h2&gt;

&lt;p&gt;The fallback path is not feature-equivalent, and pretending otherwise is how you get bug reports. In a WebView you lose the native quality selector and your own control overlay. So the fallback needs to be a degraded-but-working path you have actually tested, not a branch you wrote once and never exercised.                                                                                                        &lt;/p&gt;

&lt;h2&gt;
  
  
  Where this ended up
&lt;/h2&gt;

&lt;p&gt;I wrapped all of it in a package, &lt;code&gt;omni_video_player&lt;/code&gt;: YouTube with the automatic fallback described here, plus Vimeo, HLS with quality switching, network files and assets — one widget, one controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  OmniVideoPlayer(                                                                                                                                                         
    sourceConfiguration: VideoSourceConfiguration.youtube(                                                                                                                 
      videoUrl: Uri.parse('https://www.youtube.com/watch?v=...'),                                                                                                          
      preferredQualities: [OmniVideoQuality.high720],                                                                                                                      
    ),                                                                                                                                                                     
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two limits worth knowing before you adopt it: YouTube quality selection works on Android only, because on iOS the API exposes a single muxed 360p stream and there is nothing to switch between; and WebM seeking is off on iOS, because WebKit stalls the decoder on a seek and can't recover.                                                                                                                          &lt;/p&gt;

&lt;p&gt;Android, iOS, Web. BSD-3.&lt;br&gt;&lt;br&gt;
  &lt;a href="https://pub.dev/packages/omni_video_player" rel="noopener noreferrer"&gt;https://pub.dev/packages/omni_video_player&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>programming</category>
      <category>software</category>
    </item>
  </channel>
</rss>
