# Droppy - Development Notes ## Overview Droppy is a self-hosted web-based content casting system - like AirPlay/Chromecast but browser-based. Drop files or paste URLs to display content fullscreen on a connected display. ## Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Upload Page │ ───► │ Local Server │ ───► │ Display Page │ │ (browser tab) │ │ (localhost) │ │ (fullscreen) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` **Stack:** - **Frontend:** SvelteKit with adapter-static - **Backend:** Node.js + Express - **Real-time:** Socket.IO for content sync - **Media extraction:** yt-dlp + Cobalt API - **Deployment:** Docker Compose ## Running the App ```bash docker compose up -d --build ``` Access at `http://localhost:3001` ## Key Components ### Upload Page (`/src/routes/+page.svelte`) - Drag-drop file upload - URL paste for YouTube, Spotify, SoundCloud - Recent items grid (click to recast) - Status indicator showing display connection ### DropZone Component (`/src/lib/components/DropZone.svelte`) - File upload via drag-drop or click - URL input with platform detection - Loading states with spinner - Error toast notifications ### ContentDisplay (`/src/lib/components/ContentDisplay.svelte`) - TV-friendly fullscreen display - Cinematic blur backdrop from album art - Large text readable from couch distance (uses `clamp()`) - Platform-specific branding (Spotify green, SoundCloud orange) - Embedded players for audio/video ## Design System **Fonts:** - Instrument Sans - UI text - JetBrains Mono - monospace elements (room IDs, URLs) **Colors:** - Background: `#0C0C0C` (near black) - Surface: `#161616` - Border: `#2A2A2A` - Text: `#FAFAFA` - Accent: `#FF6B35` (coral) - Success: `#4ADE80` (green) ## Docker Configuration Notes ### Build Fixes Applied 1. **No package-lock.json** - Changed Dockerfile to use `npm install` instead of `npm ci` 2. **Vite version conflict** - `@sveltejs/vite-plugin-svelte` requires vite@^5, so downgraded from ^6.0.0 to ^5.4.0 3. **youtube-dl-exec Python check** - Added `ENV YOUTUBE_DL_SKIP_PYTHON_CHECK=1` to skip Python requirement during install 4. **Missing favicon prerender error** - Added `prerender: { handleHttpError: 'warn' }` to svelte.config.js 5. **Port conflict** - Mapped container port 3000 to host port 3001 in docker-compose.yml ## File Structure ``` droppy/ ├── src/ │ ├── lib/ │ │ ├── components/ │ │ │ ├── DropZone.svelte # Upload interface │ │ │ ├── ContentDisplay.svelte # Fullscreen display │ │ │ └── MediaPlayer.svelte # Video/audio player │ │ ├── stores/ │ │ │ └── content.ts # State management │ │ └── socket.ts # Socket.IO client │ └── routes/ │ ├── +page.svelte # Upload page │ └── display/+page.svelte # Display page ├── server/ │ ├── index.ts # Express + Socket.IO │ └── services/ │ ├── contentExtractor.ts # URL routing + playlist detection │ ├── youtubeExtractor.ts # YouTube extraction + playlists │ ├── soundcloudExtractor.ts # SoundCloud extraction + playlists │ ├── spotifyExtractor.ts # Spotify metadata extraction │ ├── cobaltClient.ts # Cobalt API (social media) │ └── socketManager.ts # Socket.IO event handlers + queue ├── Dockerfile ├── docker-compose.yml ├── docs/ │ └── SOUNDCLOUD_PLAYLISTS.md # Playlist implementation details └── examples/ # Static HTML prototypes ``` ## URL Handling | URL Type | Handler | |----------|---------| | Direct media (.mp4, .jpg) | Display directly | | YouTube | Extract via yt-dlp (supports playlists) | | YouTube Playlist | Extract all videos, on-demand download | | SoundCloud | Extract via yt-dlp | | SoundCloud Playlist | Extract metadata, on-demand audio download | | Spotify | Album art + search audio elsewhere | | Twitter/TikTok/Instagram | Cobalt API | | Other | oEmbed or iframe fallback | ## Playlist Support Both YouTube and SoundCloud playlists are supported. See [docs/SOUNDCLOUD_PLAYLISTS.md](docs/SOUNDCLOUD_PLAYLISTS.md) for detailed implementation notes. **Key features:** - Paste playlist URL → all tracks queued with metadata - On-demand audio extraction (first play downloads, subsequent plays use cache) - Playlist grouping in UI (shows "Playlist Name • 3/15") - Remove entire playlist with one click - Rate-limited metadata fetching to avoid API blocks **Cache locations:** - YouTube: `/uploads/youtube-cache/` - SoundCloud: `/uploads/soundcloud-cache/`