<?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: Rajan Prasaila Yadav</title>
    <description>The latest articles on DEV Community by Rajan Prasaila Yadav (@rajan_prasailayadav_7366).</description>
    <link>https://dev.to/rajan_prasailayadav_7366</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%2F4213963%2Ffb262e4f-84e1-4e52-ab89-04b55b83bfe7.jpg</url>
      <title>DEV Community: Rajan Prasaila Yadav</title>
      <link>https://dev.to/rajan_prasailayadav_7366</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rajan_prasailayadav_7366"/>
    <language>en</language>
    <item>
      <title>How I Spent 40 Hours Building a Serverless 8K Video Downloader on Android by Embedding CPython and FFmpeg</title>
      <dc:creator>Rajan Prasaila Yadav</dc:creator>
      <pubDate>Wed, 28 Oct 2026 23:06:38 +0000</pubDate>
      <link>https://dev.to/rajan_prasailayadav_7366/how-i-spent-40-hours-building-a-serverless-8k-video-downloader-on-android-by-embedding-cpython-and-219h</link>
      <guid>https://dev.to/rajan_prasailayadav_7366/how-i-spent-40-hours-building-a-serverless-8k-video-downloader-on-android-by-embedding-cpython-and-219h</guid>
      <description>&lt;p&gt;Building a mobile YouTube downloader seems straightforward at first glance—until you actually try to build one that runs &lt;strong&gt;100% on-device&lt;/strong&gt;, downloads in &lt;strong&gt;4K and 8K&lt;/strong&gt;, requires &lt;strong&gt;zero servers&lt;/strong&gt;, and costs &lt;strong&gt;₹0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Over the last 40 hours of continuous research, day-and-night testing, and hundreds of failed experiments, I went down the deepest rabbit hole of mobile systems engineering I’ve ever experienced. &lt;/p&gt;

&lt;p&gt;Here is the story of how I bypassed the infamous "360p ceiling," why free cloud hosting failed, and how I ended up embedding a full &lt;strong&gt;CPython 3.11 runtime&lt;/strong&gt; and an &lt;strong&gt;ARM64 FFmpeg binary&lt;/strong&gt; directly inside a modern &lt;strong&gt;Kotlin + Jetpack Compose&lt;/strong&gt; Android app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act I: The 360p Ceiling and the DASH Problem
&lt;/h2&gt;

&lt;p&gt;My original goal was simple: write a lightweight client using cross-platform tooling (Flutter + Dart) to download videos directly to mobile storage.&lt;/p&gt;

&lt;p&gt;I quickly hit three massive walls:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The 360p Lock-In
&lt;/h3&gt;

&lt;p&gt;YouTube stopped serving combined video-and-audio files for high resolutions years ago. If you want 720p, 1080p, 1440p, 4K, or 8K, YouTube serves them via &lt;strong&gt;DASH (Dynamic Adaptive Streaming over HTTP)&lt;/strong&gt;—meaning the video track and the audio track are two completely separate network streams. &lt;/p&gt;

&lt;p&gt;To get a playable &lt;code&gt;.mp4&lt;/code&gt;, you &lt;strong&gt;must&lt;/strong&gt; have a multiplexer (&lt;code&gt;muxer&lt;/code&gt;) like FFmpeg running locally to combine them. Without an on-device muxer, pure mobile scrapers are permanently locked to legacy &lt;strong&gt;360p&lt;/strong&gt; (&lt;code&gt;itag 18&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The 4–6 MB Stream Cutoff
&lt;/h3&gt;

&lt;p&gt;When downloading direct HTTP stream chunks, downloads would start at full speed, hit around 5 MB, and suddenly choke to 0 KB/s before failing. YouTube's CDN actively rate-limits and drops TCP sockets if the client does not continuously solve and send valid player tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bot Challenges
&lt;/h3&gt;

&lt;p&gt;Direct scraping triggered YouTube’s bot detection systems: &lt;code&gt;HTTP 403 Forbidden&lt;/code&gt; and &lt;code&gt;"Sign in to confirm you're not a bot"&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act II: The Free Cloud Hosting Dead-End
&lt;/h2&gt;

&lt;p&gt;Thinking the mobile device’s network or CPU was the issue, I moved the extraction engine to free cloud hosting platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hugging Face Spaces&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Railway&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fly.io&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel &amp;amp; Netlify&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Render&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Result: Total IP Blockage
&lt;/h3&gt;

&lt;p&gt;Every single cloud host failed. Why? &lt;strong&gt;Datacenter IP reputation&lt;/strong&gt;. &lt;br&gt;
YouTube maintains real-time blacklists of known cloud provider IP CIDR blocks (AWS, Google Cloud, Fastly, Cloudflare). The moment a request comes from a cloud datacenter IP, YouTube demands a CAPTCHA or blocks the connection outright. &lt;/p&gt;

&lt;p&gt;A remote cloud server was not going to work. The only way to guarantee longevity was to make the app &lt;strong&gt;run entirely locally on the user's phone IP&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Act III: The Desktop Breakthrough
&lt;/h2&gt;

&lt;p&gt;I stepped back and built a standalone Python desktop script using &lt;code&gt;Tkinter&lt;/code&gt;, &lt;code&gt;yt-dlp&lt;/code&gt;, and native &lt;code&gt;ffmpeg&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;On my PC, it worked flawlessly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It parsed complete metadata (title, tags, thumbnail, duration).&lt;/li&gt;
&lt;li&gt;It filtered out junk streams and identified the true highest available resolutions—from 144p all the way to &lt;strong&gt;8K (4320p)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;FFmpeg merged high-bitrate video with lossless audio streams locally with zero rate limits and zero cloud hosting fees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led to the ultimate question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"If a local CPython engine and FFmpeg can download 8K videos without any server, why can't we bundle that entire engine inside an Android APK?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Act IV: Packing an Entire Desktop Engine into Android
&lt;/h2&gt;

&lt;p&gt;I started a clean native Android project built with &lt;strong&gt;Kotlin 2.0&lt;/strong&gt;, &lt;strong&gt;Jetpack Compose&lt;/strong&gt;, and &lt;strong&gt;Media3 ExoPlayer&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Here are the key technical challenges I had to overcome to embed CPython on Android:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SELinux &amp;amp; Native Execution (Error 13: Permission Denied)
&lt;/h3&gt;

&lt;p&gt;Modern Android (API 29+) forbids executing binaries from external storage or normal cache partitions (&lt;code&gt;noexec&lt;/code&gt; mount flag).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt;: On first launch, the app decompresses a standalone Linux ARM64 CPython 3.11 bundle and static FFmpeg binary from &lt;code&gt;assets/&lt;/code&gt; into the app's private internal storage (&lt;code&gt;/data/user/0/&amp;lt;package&amp;gt;/files/runtime/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Then, low-level POSIX execution permissions are granted programmatically:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binaryFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolutePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;493&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 0755 rwxr-xr-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Environment variables (&lt;code&gt;PYTHONHOME&lt;/code&gt;, &lt;code&gt;PYTHONPATH&lt;/code&gt;, and &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;) are mapped dynamically so Python can load shared cryptographic libraries (&lt;code&gt;libcrypto.so&lt;/code&gt;, &lt;code&gt;libssl.so&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Solving JavaScript Cipher Challenges with QuickJS
&lt;/h3&gt;

&lt;p&gt;YouTube uses dynamic JavaScript challenges (&lt;code&gt;n-sig&lt;/code&gt; transformations) to throttle download speeds. Rather than bundling a bulky Node.js binary, I embedded &lt;strong&gt;QuickJS&lt;/strong&gt; (&lt;code&gt;libqjs.so&lt;/code&gt;), a tiny, lightweight C-based JavaScript engine that allows &lt;code&gt;yt-dlp&lt;/code&gt; to evaluate cipher challenges in milliseconds directly on mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Android Doze Mode &amp;amp; Foreground Services
&lt;/h3&gt;

&lt;p&gt;High-resolution 4K/8K multiplexing consumes CPU cycles. When the user turned off the screen, Android's battery manager would kill the child process mid-stream.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt;: Implemented an Android &lt;strong&gt;Foreground Service&lt;/strong&gt; with &lt;code&gt;FOREGROUND_SERVICE_TYPE_DATA_SYNC&lt;/code&gt; and a &lt;code&gt;PARTIAL_WAKE_LOCK&lt;/code&gt;. Real-time &lt;code&gt;stdout&lt;/code&gt; from the child process is parsed into an ongoing notification progress bar with download speed (MB/s) and ETA.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Scoped Storage &amp;amp; MediaStore Publishing
&lt;/h3&gt;

&lt;p&gt;Under Android 11–15 Scoped Storage, child processes cannot write directly to &lt;code&gt;/storage/emulated/0/Download/&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solution&lt;/strong&gt;: Downloads stage inside the private cache (&lt;code&gt;context.cacheDir/staging/&lt;/code&gt;). Once FFmpeg finishes merging, a native Kotlin worker commits the file directly into &lt;code&gt;MediaStore.Video&lt;/code&gt; or &lt;code&gt;MediaStore.Audio&lt;/code&gt; using Android's &lt;code&gt;ContentResolver&lt;/code&gt;. The files immediately appear in the user's Gallery, VLC, and MX Player.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Act V: Building the Native Media Player
&lt;/h2&gt;

&lt;p&gt;Downloading high-quality media is only half the experience; playing it is the other half. I built a native player powered by &lt;strong&gt;Media3 ExoPlayer&lt;/strong&gt; featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MX Player-Style Aspect Ratio Modes&lt;/strong&gt;: Cycle through &lt;code&gt;FIT TO SCREEN&lt;/code&gt;, &lt;code&gt;STRETCH&lt;/code&gt;, &lt;code&gt;CROP&lt;/code&gt;, and &lt;code&gt;100%&lt;/code&gt; (unscaled 1:1 pixel rendering with borders all around) complete with centered bold HUD overlays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-Zone Gestures&lt;/strong&gt;: Screen-left vertical swipe adjusts brightness; screen-right vertical swipe adjusts system volume; horizontal swipe scrubs the timeline with a &lt;code&gt;+Xs / -Xs (MM:SS)&lt;/code&gt; HUD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Compose "Pointer Starvation" Fix&lt;/strong&gt;: 
When implementing a &lt;em&gt;"Touch &amp;amp; Hold for 2 seconds → 2X speed boost"&lt;/em&gt;, standard Compose gesture loops (&lt;code&gt;awaitPointerEvent()&lt;/code&gt;) only fire when the finger &lt;em&gt;moves&lt;/em&gt;. Holding still produced zero touch events, meaning timer checks never ran! 
I solved this by decoupling the timer into an independent coroutine job:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;holdJob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1800L&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="n"&gt;hasDragged&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isMultiTouch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setPlaybackSpeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0f&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Speed Engine&lt;/strong&gt;: Users can select speeds from &lt;code&gt;0.25x&lt;/code&gt; to &lt;code&gt;2.0x&lt;/code&gt; that persist across all video and audio tracks globally.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Result: ₹0 Lifetime Cost &amp;amp; Total Privacy
&lt;/h2&gt;

&lt;p&gt;After 40 hours of focused development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: Exactly &lt;strong&gt;₹0&lt;/strong&gt;. No monthly cloud bills, no VPS hosting, no paid API tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: 100% direct connection between the phone and the media source. No proxy servers intercepting traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Direct on-device multiplexing up to &lt;strong&gt;8K (4320p)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Open Source &amp;amp; Download
&lt;/h2&gt;

&lt;p&gt;The project is completely open source under the MIT License. You can check out the source code, architecture diagrams, and download the pre-compiled APK directly:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/Rajan7013/YtMax" rel="noopener noreferrer"&gt;https://github.com/Rajan7013/YtMax&lt;/a&gt;&lt;br&gt;&lt;br&gt;
📦 &lt;strong&gt;Direct v1.0.0 APK&lt;/strong&gt;: &lt;a href="https://github.com/Rajan7013/YtMax/releases/tag/v1.0.0" rel="noopener noreferrer"&gt;Download from GitHub Releases&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>opensource</category>
      <category>python</category>
    </item>
  </channel>
</rss>
