This feature is still in our final beta round. If you’d like to get access to it as a beta tester, please send us an email at offlinemode@despia.com
Local CDN uses native background task APIs (NSURLSession on iOS, WorkManager on Android) with built-in retry mechanisms. Start a download, close the app, get notified when it’s ready. When users reopen your app,
window.contentServerChange(item) is called for each item that completed in the background.Why we built this
Local CDN was built by web developers who love the web platform. We wanted offline media that works the way web developers expect: set asrc attribute, use fetch(), work with standard URLs. No Base64 encoding. No Blob URL workarounds. No arbitrary storage limits. And critically: downloads that continue when users close the app - just like Netflix and Spotify.
So we collaborated with native iOS and Android engineers. We studied how Spotify, Netflix, and YouTube handle offline downloads in their native apps. Then we brought that same architecture to hybrid web apps while keeping everything familiar to web developers.
The result: cache gigabytes of media with two function calls, play it back with a standard URL, and maintain O(1) memory usage independent of file size. Background downloads with native retry mechanisms. Push notifications when content is ready. And window.contentServerChange(item) to update your UI the moment new content is cached.
Cross-platform by design
Local CDN is optimized for both iOS and Android through a unified JavaScript SDK. Write your code once. It works identically on both platforms. No iOS-specific workarounds. No Android edge cases. The API is fully standardized so you can focus on building your app instead of handling platform differences.Fully client-side. No cloud dependencies.
Local CDN runs entirely on the device. There is no cloud component, no proprietary backend, no special hosting requirements. Built on Despia Local Server. Despia Local Server provides the HTTP infrastructure - a proprietary, production-grade localhost server that runs entirely within your app. Local CDN extends this by adding file storage and content serving capabilities through the native file system. Together, they form a complete on-device content delivery system. Fully maintained, fully client-side. Both Despia Local Server and Local CDN are compiled into your app binary. No external services, no cloud dependencies, no opinionated backend architecture forced on you. Everything runs on the device. Use any hosting. Cache files from any URL - your own servers, AWS S3, Cloudflare, Google Cloud Storage, or any public CDN. Local CDN does not care where files come from. No cloud lock-in. No data lock-in. Your files are standard files on the device file system. Your URLs are standard HTTP URLs. Your backend, your database, your API - all remain yours. Despia never sits between you and your infrastructure. Runtime dependency only. Local CDN and Despia Local Server are runtime dependencies, similar to any other SDK you include in your app. The proprietary component is the on-device localhost server and native file system integration. Your data formats, hosting choices, and backend architecture remain portable. No per-user fees. Unlike cloud-based solutions that charge per monthly active user, Local CDN has zero runtime costs. Scale to millions of users without infrastructure costs scaling with them. Works offline completely. Once files are cached, the entire system operates without any network connectivity. No license checks, no heartbeats, no server dependencies.Despia Local Server: The complete picture
Local CDN is one part of the Despia Local Server ecosystem. The full Local Server provides:Instant Boot
Zero network latency. Your entire web app loads in milliseconds from localhost.
Complete Offline Support
Not “works offline sometimes” but actually works without any connectivity, forever.
OTA Updates
Push web app changes live without app store approval. Updates download in the background and apply on next launch.
Your Hosting, Your Rules
Keep using Netlify, Vercel, AWS, whatever you already have. No migration, no cloud lock-in, no MAU fees.
100% store compliant. Fully approved architecture for both Apple App Store and Google Play Store.
Learn More
Complete documentation on Despia Local Server including setup, OTA updates, and architecture details
Who this is for
Local CDN solves a specific problem for a specific type of app. It’s not for everyone.
- This is for you if
- Probably not for you if
- Your app caches large media files (videos, podcasts, audio courses, image libraries)
- You need true offline playback, not just “works offline sometimes”
- Memory constraints are a real problem - your app crashes or lags with current caching approaches
- You’re building for both iOS and Android and want one solution
- You’re comfortable with a runtime dependency in exchange for solving a hard problem
The tradeoff is clear: You get O(1) memory usage, unlimited storage, and cross-platform consistency. In exchange, you take a runtime dependency on Despia Local Server.
The 15-year problem
Since PhoneGap launched in 2009, hybrid web-native apps have struggled with offline media. The core issue: WebViews run in a sandboxed environment with no direct file system access. Every framework has attempted to solve this. None have done it well. Cordova required plugins and Blob URLs. Files had to be read into JavaScript memory before playback. Ionic inherited Cordova’s limitations. Developers worked around them with complex caching strategies that still hit browser storage quotas. Capacitor improved file system access but still requires Base64 encoding to pass data through the JavaScript bridge. A 500MB video requires approximately 1.5GB of memory during the read-decode-play cycle. React Native offers partial solutions, but media handling remains complex and platform-specific. The fundamental problem across all these frameworks: everything passes through JavaScript. Files must be encoded, transferred across the bridge, decoded, and converted to Blob URLs before playback. Memory usage scales with file size. Large files crash apps. Web developers have been forced to choose between streaming everything (no offline support), caching small files only (poor user experience), or abandoning web technologies entirely.How other frameworks handle it
Capacitor’s approach
- Base64 encoding adds 33% size overhead
- Entire file must load into JavaScript memory
- Blob URL creation duplicates the memory reference
- Large files cause out-of-memory crashes
- Platform-specific quirks require additional handling
Framework comparison
| Framework | File System | Direct Playback | Memory Efficient | Background Downloads | Offline Media |
|---|---|---|---|---|---|
| Cordova | Plugin required | No | No | No | Limited |
| Capacitor | Yes | No | No | No | Limited |
| React Native | Yes | Partial | Partial | Plugin required | Complex |
| Flutter | Yes | Yes | Yes | Plugin required | Not web-based |
| Despia Local CDN | Yes | Yes | Yes | Yes (native) | Full support |
Our approach
Local CDN bypasses JavaScript entirely for file operations. When you cache a file, Despia’s native layer (Swift on iOS, Kotlin on Android) downloads it directly to the device file system. The file never touches the JavaScript bridge. The result is returned to a variable matching your uniqueindex for promise-based success/failure handling. Optionally, trigger a push notification on completion.
Background caching: Downloads continue even when the user closes or backgrounds the app. Native background transfer APIs (NSURLSession on iOS, WorkManager on Android) handle this with built-in retry mechanisms. When users reopen your app - even hours later - window.contentServerChange(item) is called for each item that completed in the background. This is the Netflix/Spotify offline experience, now available for web apps.
When you play it back, Local CDN serves the file through Despia Local Server via http://localhost. Your web app uses a standard URL - the same way you would load any media from a remote CDN.
When you delete a file, it’s removed from device storage immediately, freeing up space.
item received in contentServerChange:
data.cdnItems (array of file objects):
window.deletedCdnItems (array of deleted items):
src attribute.
Built for web developers
We kept everything familiar to web developers: Standard URLs. Thelocal_cdn value is a regular HTTP URL. Use it anywhere you would use a CDN URL: src attributes, fetch() calls, CSS url() values.
No encoding. Files stay as files. No Base64. No ArrayBuffers. No Blobs.
No new APIs to learn. If you know how to set videoElement.src, you know how to use Local CDN.
Familiar patterns. Write is like uploading to a CDN. Read is like querying a database. The response is JSON.
Technical architecture
Write operation
When you calllocalcdn://write:
Remote file URL to download
Local path (folder/subfolder/filename)
Your unique ID
Set to
true to show push notification on completionNotification message wrapped in quotes
Read operation
When you calllocalcdn://read:
Delete operation
When you calllocalcdn://delete:
Background caching
This is a native capability that web apps cannot replicate. Service workers and the Fetch API do not support true background downloads that survive app termination.
- iOS: NSURLSession background transfer service
- Android: WorkManager with automatic retry on failure
- Both: Resume interrupted downloads, battery-aware scheduling, network-aware execution
The Lifecycle
When you calllocalcdn://write, here’s exactly what happens:
item:
How Background Replay Works
The native layer handles everything automatically: Just implementcontentServerChange and it will be called for both:
- Downloads that complete while the app is open
- Downloads that completed in the background (replayed on next launch)
Example: Building a Download List
Content Server HTTP API
Only available when your app is served via Despia Local Server (not from origin/remote server).
localcdn:// protocol.
Two approaches for different use cases:
| Approach | Best for |
|---|---|
localcdn://write | Remote URLs, background downloads |
HTTP POST to /api/upload | User file uploads from file picker |
localcdn://write>/localcdn/directory/api/upload>/files/directory
Why localhost
Despia Local Server provides the HTTP infrastructure. Local CDN stores files and serves them through this infrastructure viahttp://localhost. This architecture provides several advantages:
- Standard web APIs work without modification
- No CORS restrictions (same-origin)
- HTTP range requests enable seeking without loading entire files
- Localhost is treated as a secure context
- Hardware-accelerated decoders work with HTTP sources
Memory comparison
What O(1) means
O(1) means “constant” - the memory usage stays the same regardless of input size. Whether you play a 10MB file or a 10GB file, Local CDN uses the same amount of memory. This is the opposite of traditional approaches where memory scales with file size (O(n)). Double the file size, double the memory usage. That’s why large files crash apps.500MB video: Capacitor vs Despia
Let’s trace what happens when you play a 500MB video file. Capacitor (traditional approach): Despia Local CDN:| Metric | Capacitor | Despia Local CDN |
|---|---|---|
| Peak JS heap | ~1,666MB | ~100 bytes |
| System memory | ~1,666MB | ~5-8MB |
| Scales with file size | Yes | No |
| 2GB video | Crash | Works |
Where the ~2-8MB comes from
Local CDN doesn’t use zero memory - that would be physically impossible. Here’s what actually uses memory during playback: Kernel read buffer (~1-2MB): The operating system reads files in chunks, not byte-by-byte. This buffer exists for any file read operation on any platform. Media pipeline buffer (~2-4MB): iOS and Android media frameworks buffer a few seconds of decoded frames for smooth playback. This is handled entirely in native code. Hardware decoder buffer (~1-2MB): The video decoder chip has its own memory for processing frames. This is separate from app memory. These buffers exist in native apps too - Spotify, Netflix, and YouTube all have them. The key difference: none of this memory is in your JavaScript heap, and none of it scales with file size. A 100MB video and a 2GB video use the same ~5-8MB of buffer memory. That’s what O(1) means in practice.Use cases
Media-heavy Applications
Podcast apps can cache entire libraries. Video learning platforms can download courses for offline viewing. Music apps can store playlists locally. All without memory constraints. Users start downloads on WiFi, close the app, and get notified when content is ready.
Offline-first Experiences
Field service apps, travel apps, and educational tools that must work without connectivity can cache all required media assets during initial sync. Background downloads with retry mechanisms ensure reliable delivery even on unstable connections.
User-generated Content
Apps that let users upload photos, videos, or documents can store them locally via the Content Server HTTP API. Upload from file pickers, camera roll, or any File/Blob source. Files are security-scanned and stored with the same O(1) memory efficiency.
Performance-critical Applications
Localhost serving eliminates network latency entirely. Media plays instantly. Seeking is immediate. The experience matches native apps.
”Download before you go” workflows
Users queue downloads while on WiFi at home. They close the app. Downloads continue in the background with native retry mechanisms. Push notification when complete. When they open the app later - on a plane, in a subway, in the wilderness - everything is cached and ready.window.contentServerChange updates the UI immediately.
Frequently asked questions
How much storage can I use?
How much storage can I use?
The device’s available storage. No artificial quotas like IndexedDB. Cache gigabytes of content.
Does this work offline?
Does this work offline?
Yes. Files are stored on the device file system and served from an on-device HTTP server. No network required after initial download.
What file types are supported?
What file types are supported?
Any file type: video (mp4, webm, mov), audio (mp3, wav, aac), images (jpg, png, webp), documents (pdf, json), and more.
Is this App Store compliant?
Is this App Store compliant?
Yes. Downloading and caching media content is standard app behavior, identical to how Spotify, Netflix, and YouTube handle offline downloads.
What happens if storage is full?
What happens if storage is full?
The download will fail. Implement cache management logic to remove old files before downloading new ones using
localcdn://delete.How do I know if a download succeeded?
How do I know if a download succeeded?
Use the Note: Do not await the write call with a key - the JS bridge has a ~30s timeout that will resolve with
contentServerChange callback. When a download completes, the native runtime calls your callback with the file data:null for large files, even though the download continues.Optional: If you need more control, poll via localcdn://read to check status.Do downloads continue if the user closes the app?
Do downloads continue if the user closes the app?
Yes. Downloads continue in the background even when the app is closed or backgrounded. This uses native OS background transfer APIs (NSURLSession on iOS, WorkManager on Android). Add
push=true and pushmessage to notify users when the download completes - especially useful for large files.Can I upload files from a file picker?
Can I upload files from a file picker?
Yes, but only when your app is served via Despia Local Server (not from origin). POST to
http://localhost:7777/api/upload with FormData. Works with any File/Blob source - file inputs, camera, clipboard, etc. Uploaded files are stored in /files/ directory.Are uploaded files secure?
Are uploaded files secure?
Yes. All uploaded files are security-scanned before storage. Malicious or corrupted files are rejected. Only safe files are kept on the device.
How do I track which downloads completed in the background?
How do I track which downloads completed in the background?
You don’t need to track this manually. When the app reopens after background downloads, the native layer automatically calls
contentServerChange(item) for each item that completed while backgrounded. Just implement contentServerChange and it handles both foreground completions and background replays.How do I delete cached files?
How do I delete cached files?
Call
localcdn://delete with a JSON array of index IDs:Does it work the same on iOS and Android?
Does it work the same on iOS and Android?
Yes. The JavaScript API is identical on both platforms. Despia handles platform-specific implementation details internally.
Summary
O(1) Memory
Fixed memory usage regardless of file size
Device Storage
Use full device capacity, no artificial quotas
Complete Offline
Works without any network connectivity
Hardware Accelerated
Native playback performance
Unified SDK
Same API for iOS and Android
No Cloud Lock-in
Runs entirely on-device
| Capability | Traditional Web | Local CDN |
|---|---|---|
| Memory usage | Scales with file size | O(1) fixed |
| Storage limit | 50-100MB | Device capacity |
| Offline support | Unreliable | Complete |
| Playback performance | Software decoded | Hardware accelerated |
| Cross-platform | Platform quirks | Unified SDK |
| Cloud dependency | Often required | None |
| Large file support | Crashes | Works |
No cloud lock-in, no data lock-in, no per-user fees. The runtime dependency is Despia Local Server - a tradeoff that eliminates the 15-year hybrid app memory problem in exchange for a maintained, production-grade SDK.
Resources
NPM Package
Install the Despia SDK
Local CDN API Reference
Quick start guide
Despia Local Server
Complete web app offline hosting with OTA updates
Support
Contact our support team