{"id":50381646,"url":"https://github.com/px7nn/px7.fm","last_synced_at":"2026-05-30T12:03:17.287Z","repository":{"id":357206585,"uuid":"1234859765","full_name":"px7nn/px7.fm","owner":"px7nn","description":"Lightweight desktop music streaming app built with Flask + NeutralinoJS.","archived":false,"fork":false,"pushed_at":"2026-05-18T20:03:07.000Z","size":6538,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T21:54:01.252Z","etag":null,"topics":["audio-player","desktop-app","flask","music-player","neutralinojs","pyinstaller","python","streaming","webview","youtube","yt-dlp"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/px7nn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-10T18:26:29.000Z","updated_at":"2026-05-18T20:01:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/px7nn/px7.fm","commit_stats":null,"previous_names":["px7nn/px7.fm"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/px7nn/px7.fm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/px7nn%2Fpx7.fm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/px7nn%2Fpx7.fm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/px7nn%2Fpx7.fm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/px7nn%2Fpx7.fm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/px7nn","download_url":"https://codeload.github.com/px7nn/px7.fm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/px7nn%2Fpx7.fm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33691312,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-player","desktop-app","flask","music-player","neutralinojs","pyinstaller","python","streaming","webview","youtube","yt-dlp"],"created_at":"2026-05-30T12:03:15.618Z","updated_at":"2026-05-30T12:03:17.280Z","avatar_url":"https://github.com/px7nn.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"#\"\u003e\u003cimg src=\"neutralino/resources/icons/PX7.png\" alt=\"PX7 logo\" title=\"PX7.FM\" align=\"right\" height=\"60px\" /\u003e \u003c/a\u003e\n\n# PX7.FM\n\n\u003e A self-contained desktop music streaming app for Windows, macOS, and Linux — and an installable PWA for Android \u0026 iOS — powered by YouTube, built with Flask + NeutralinoJS.\n\n![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux%20%7C%20Android%20%7C%20iOS-blue)\n![Python](https://img.shields.io/badge/python-3.10%2B-yellow)\n![PWA](https://img.shields.io/badge/PWA-installable-brightgreen)\n![License](https://img.shields.io/badge/license-GPLv3-green)\n\n---\n\n## What is PX7.FM?\n\nPX7.FM is a **music streaming player** that streams audio directly from YouTube — no browser tabs, no ads, no account. It runs as a native desktop app and as an installable Progressive Web App on your phone.\n\nYou get:\n- **Full-text search** across YouTube's music catalogue\n- **Genre-filtered trending** home feed\n- **Queue management** with shuffle \u0026 repeat\n- **Liked songs** and **recently played** history (persisted in localStorage)\n- **A clean, responsive UI** that works at any window size — including a compact mobile-style layout\n- **OS-level Now Playing card** with artwork, hardware button support, and lock-screen controls (via Web Media Session API)\n- **Installable on Android \u0026 iOS** — appears on your home screen like a native app\n\nUnder the hood it's a Flask HTTP server wrapped in a NeutralinoJS window for desktop, and served as a PWA for mobile.\n\n\u003e **Disclaimer:** PX7.FM is an independent open-source project and is not affiliated with, endorsed by, or connected to YouTube or Google in any way. Use responsibly and in accordance with YouTube's Terms of Service.\n\n---\n\n## Screenshots\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg width=\"749\" height=\"486\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/7e559324-dd9f-4c07-8f79-b94e2e136509\" /\u003e\n\n\u003c/div\u003e\n\n---\n\n## Why Flask + Neutralino?\n\nMost desktop music apps built in Python reach for Electron — which ships a full Chromium runtime and adds 150–200MB to the binary. PX7 takes a leaner approach:\n\n- **Flask** is the natural fit because the core logic is already Python (`yt-dlp`). A thin local REST server is all that's needed to bridge it to a browser-side UI — no IPC layer, no native bindings.\n- **NeutralinoJS** wraps that server in a proper desktop window using the **system WebView** (Edge WebView2 on Windows, WebKit on macOS/Linux) rather than bundling its own browser engine. Unlike Electron apps, PX7 stays lightweight (~30MB) while still providing a native desktop experience.\n- **PyInstaller `--onedir`** bundles the Python runtime and all dependencies alongside the Neutralino binary into one portable folder. No installer, no registry entries — unzip and run.\n\nThe tradeoff: you're dependent on the system WebView being present (it is by default on Windows 10+ and most modern macOS/Linux systems), and the frontend can't use Node.js APIs. For a music player, neither matters.\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│                  PX7.exe                    │\n│  (PyInstaller bundle — launcher.py entry)   │\n└───────────────┬─────────────────────────────┘\n                │ spawns\n    ┌───────────▼──────────┐      ┌─────────────────────────┐\n    │   Flask backend      │◄────►│  NeutralinoJS window    │\n    │   localhost:5000     │ HTTP │  native system WebView  │\n    │                      │      │  loads http://127.0.0.1 │\n    └───────────┬──────────┘      └─────────────────────────┘\n                │\n    ┌───────────▼──────────┐\n    │   yt-dlp             │\n    │   (search / stream)  │\n    └──────────────────────┘\n```\n\n### Layer breakdown\n\n| Layer | Tech | Role |\n|---|---|---|\n| **Launcher** | `launcher.py` | Entry point. Detects the OS and selects the correct Neutralino binary. Starts Flask in a background thread, waits for it to be ready, then spawns the Neutralino window process. If Flask is already running (second launch), skips straight to the window. On non-Windows, sets the binary executable before spawning. |\n| **Backend** | Flask (`app.py`) | Thin REST API server. Also runnable standalone (`python app.py`) to serve the PWA on the local network for mobile access. |\n| **API module** | `api/` package | Wraps `yt-dlp` for search and stream URL extraction. Handles title cleaning, duration formatting, and yt-dlp option configs. |\n| **Frontend** | Vanilla JS (ES modules) + Jinja2 HTML | Single-page app served by Flask. Modular JS files handle state, player, queue, search, and views. |\n| **Media Session** | `mediasession.js` | Pushes track metadata and artwork to the OS Now Playing widget. Wires hardware prev/pause/next buttons and lock-screen controls. |\n| **Desktop shell** | NeutralinoJS `v6.7.0` | Lightweight native window using the system WebView. Points to `http://127.0.0.1:5000/`. No Electron bloat. |\n| **Build** | PyInstaller `--onedir` | Bundles Python runtime, all deps, and the platform-appropriate Neutralino binary into a portable `PX7/` directory. |\n\n---\n\n## Project Structure\n\n```\npx7.fm/\n├── app.py                       # Flask app — also runnable standalone for PWA/mobile\n├── launcher.py                  # Entry point — boots Flask + Neutralino\n├── requirements.txt             # Python dependencies\n├── CHANGELOG\n├── LICENSE\n│\n├── .github/\n│   └── workflows/\n│       └── build.yml            # CI — builds for Windows, macOS, Linux on push to main\n│\n├── api/\n│   ├── __init__.py              # yt-dlp options, shared helpers (clean_title, format_duration)\n│   ├── search.py                # YouTube search via yt-dlp\n│   ├── stream.py                # Stream URL extractor\n│   └── trending.py              # Curated \"trending\" feed (randomised seed queries)\n│\n├── templates/\n│   └── index.html               # Full SPA shell — layout, player bar, sidebar, views\n│\n├── static/\n│   ├── site.webmanifest         # PWA manifest — name, icons, display mode\n│   ├── css/\n│   │   └── styles.css           # All styling — dark theme, responsive breakpoints\n│   ├── js/\n│   │   ├── index.js             # App init, global __px7 bridge\n│   │   ├── state.js             # Single shared state object S + localStorage persistence\n│   │   ├── player.js            # Audio engine, playback controls, like/recent logic\n│   │   ├── queue.js             # Queue data + rendering\n│   │   ├── search.js            # Search fetch + result rendering\n│   │   ├── views.js             # View switching, home grids, genre chips, mobile drawer\n│   │   ├── utils.js             # fmt(), esc(), showToast(), cleanTitle()\n│   │   └── mediasession.js      # Web Media Session API — Now Playing card, hardware keys\n│   └── images/icons/\n│       ├── favicon.ico\n│       ├── favicon.svg\n│       ├── favicon-96x96.png\n│       ├── apple-touch-icon.png\n│       ├── web-app-manifest-192x192.png\n│       └── web-app-manifest-512x512.png\n│\n└── neutralino/\n    ├── neutralino.config.json   # Window config (1200×800, min 380×600, loads localhost:5000)\n    ├── bin/\n    │   ├── neutralino-win_x64.exe\n    │   ├── neutralino-linux_x64\n    │   └── neutralino-mac_universal\n    └── resources/\n        ├── js/neutralino.js     # Neutralino client runtime\n        └── icons/\n            ├── PX7.ico\n            └── PX7.png\n```\n\n---\n\n## API Endpoints\n\nAll endpoints are served by Flask on `http://127.0.0.1:5000`.\n\n### `GET /api/search?q=\u003cquery\u003e\u0026limit=\u003cn\u003e`\nSearch YouTube for tracks matching `query`.\n\n**Response**\n```json\n{\n  \"results\": [\n    {\n      \"id\": \"dQw4w9WgXcQ\",\n      \"title\": \"Never Gonna Give You Up\",\n      \"channel\": \"Rick Astley\",\n      \"thumb\": \"https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg\",\n      \"duration\": \"3:33\"\n    }\n  ]\n}\n```\n\n### `GET /api/trending`\nReturns a curated list of tracks from randomised seed queries (lo-fi, indie, jazz, etc.), filtered to tracks ≤ 4 minutes.\n\n**Response** — same shape as `/api/search`.\n\n### `GET /api/stream?id=\u003cyoutube_video_id\u003e`\nExtracts a direct audio stream URL for the given video ID using yt-dlp.\n\n**Response**\n```json\n{ \"stream_url\": \"https://...\" }\n```\nReturns `500` with `{ \"error\": \"Could not extract stream\" }` on failure.\n\n### `HEAD /api/health`\nLiveness check used internally by the launcher. Returns `200` when Flask is up.\n\n---\n\n## Frontend Modules\n\nThe frontend is pure vanilla JS using ES modules — no framework, no bundler.\n\n| Module | Responsibility |\n|---|---|\n| `state.js` | Single source of truth (`S` object). Tracks current track, queue, playback flags, liked/recent lists. Persists liked \u0026 recent to `localStorage`. |\n| `player.js` | Owns the `\u003caudio\u003e` element. Handles play/pause, seek, volume (drag + touch), mute, shuffle, repeat, prev/next, like toggle, loading spinner state, and Media Session sync. |\n| `queue.js` | Queue array CRUD and DOM rendering. Supports drag-and-drop reordering. |\n| `search.js` | Fetches `/api/search`, renders result rows, exposes `getLastResults()` for auto-queue. |\n| `views.js` | SPA view switching, home featured/recent grids, liked/recent list views, genre chip filter, mobile search drawer. |\n| `utils.js` | Tiny pure helpers: `fmt()` (seconds → m:ss), `esc()` (HTML escape), `showToast()`, `cleanTitle()`. |\n| `mediasession.js` | Web Media Session API. Updates the OS Now Playing card with track metadata and artwork. Wires hardware prev/pause/next action handlers and seek support. |\n| `index.js` | Bootstraps the app on `DOMContentLoaded`. Wires everything into `window.__px7` for inline `onclick` handlers. |\n\n---\n\n## Installation\n\n### Desktop — Pre-built binary\n\n**No Python or dependencies required** — just download and run.\n\n1. Go to the [Releases](https://github.com/px7nn/px7.fm/releases) page\n2. Download the ZIP for your platform (`PX7-windows`, `PX7-macos`, or `PX7-linux`) from the latest release\n3. Extract the ZIP anywhere on your machine\n4. Run the `PX7` executable inside the extracted folder\n\nThe app is fully portable. Nothing is written to the registry (Windows) and no installer is needed. To uninstall, delete the folder.\n\n\u003e **Windows:** Requires Windows 10 or later (Edge WebView2 is included by default).  \n\u003e **macOS / Linux:** Requires a system WebKit/WebView. Most modern desktop installations include this out of the box.\n\n---\n\n### Mobile — Android with Termux\n\nNo desktop needed — your Android phone runs both the server and the client.\n\n**Step 1 — Install Termux**\n\nInstall [Termux from F-Droid](https://f-droid.org/packages/com.termux/). The Play Store version is outdated and no longer receives updates.\n\n**Step 2 — Install dependencies**\n\n```bash\npkg update \u0026\u0026 pkg upgrade -y\npkg install python ffmpeg git -y\npip install flask yt-dlp\n```\n\n\u003e `ffmpeg` is required by yt-dlp for certain audio formats. Skipping it will cause silent stream failures on some tracks.\n\n**Step 3 — Clone the repo**\n\n```bash\ngit clone --depth 1 https://github.com/px7nn/px7.fm.git\ncd px7.fm\n```\n\nOr copy the source folder to your phone over USB and `cd` into it.\n\n**Step 4 — Start the server**\n\n```bash\npython app.py\n```\n\n**Step 5 — Open in Chrome**\n\nOpen Chrome on the same device and go to `http://127.0.0.1:5000`. Install to your home screen from the browser menu — the app will open full-screen with lock-screen playback controls.\n\n**Keeping the server alive when the screen is off**\n\nAndroid's battery optimiser kills background Termux sessions by default. Two things to do:\n\nGo to **Settings → Apps → Termux → Battery** and set to **Unrestricted**.\n\nThen run the server inside `tmux` so it survives Termux being backgrounded:\n\n```bash\npkg install tmux -y\ntmux new -s px7\npython app.py\n# Detach: Ctrl+B then D\n# Reattach later: tmux attach -t px7\n```\n\n---\n\n### Mobile — iOS\n\niOS does not support running a local Python server from any app in a way that's reliable enough to recommend. The best approach is to run the server on another device (desktop or Android/Termux) and access PX7 over Wi-Fi from Safari on your iPhone, then add it to your home screen.\n\n---\n\n## Building from Source\n\n### Prerequisites\n\n- Python 3.10+\n- `requirements.txt` covers runtime deps; add PyInstaller for the build:\n  ```bash\n  pip install -r requirements.txt pyinstaller\n  ```\n- The appropriate Neutralino binary in `neutralino/bin/` for your target platform\n\n### Build command — Windows\n\n```batch\npyinstaller ^\n  --onedir ^\n  --noconsole ^\n  --noupx ^\n  --name PX7 ^\n  --icon \"neutralino/resources/icons/PX7.ico\" ^\n  --add-data \"templates;templates\" ^\n  --add-data \"static;static\" ^\n  --add-binary \"neutralino/bin/neutralino-win_x64.exe;neutralino/bin\" ^\n  --add-data \"neutralino/neutralino.config.json;neutralino\" ^\n  --add-data \"neutralino/resources/icons/PX7.ico;neutralino/resources/icons\" ^\n  --collect-all yt_dlp ^\n  --collect-all flask ^\n  --collect-all requests ^\n  launcher.py\n```\n\n### Build command — macOS / Linux\n\n```bash\npyinstaller \\\n  --onedir \\\n  --noconsole \\\n  --noupx \\\n  --name PX7 \\\n  --icon \"neutralino/resources/icons/PX7.png\" \\\n  --add-data \"templates:templates\" \\\n  --add-data \"static:static\" \\\n  --add-binary \"neutralino/bin/neutralino-mac_universal:neutralino/bin\" \\\n  --add-data \"neutralino/neutralino.config.json:neutralino\" \\\n  --add-data \"neutralino/resources/icons/PX7.png:neutralino/resources/icons\" \\\n  --collect-all yt_dlp \\\n  --collect-all flask \\\n  --collect-all requests \\\n  launcher.py\n```\n\nReplace `neutralino-mac_universal` with `neutralino-linux_x64` on Linux.\n\nOutput will be in `dist/PX7/`. The entire folder is portable — copy it anywhere and run `PX7` (or `PX7.exe` on Windows).\n\n### CI builds\n\nThe GitHub Actions workflow (`.github/workflows/build.yml`) builds for all three platforms automatically on every push to `main`, using Python 3.11. Artifacts are uploaded as `PX7-windows`, `PX7-macos`, and `PX7-linux`.\n\n### Running in dev mode\n\n```bash\npython app.py          # server only — open http://127.0.0.1:5000 in any browser\npython launcher.py     # full desktop experience — Flask + Neutralino window\n```\n\n---\n\n## How Streaming Works\n\n1. User clicks a track → frontend calls `/api/stream?id=\u003cid\u003e`\n2. Flask passes the YouTube video URL to `yt-dlp` with `format: bestaudio/best` and `skip_download: True`\n3. yt-dlp resolves the best available audio-only stream URL (from YouTube's CDN)\n4. The URL is returned to the frontend and set as the `\u003caudio\u003e` element's `src`\n5. The browser's native audio engine streams directly from YouTube's CDN\n6. `mediasession.js` pushes track metadata and artwork to the OS Now Playing widget\n\nNo audio data ever passes through the Flask server — it only resolves the URL.\n\n---\n\n## Known Limitations \u0026 Notes\n\n- **Stream URLs expire.** YouTube's CDN URLs are time-limited. If a track fails after sitting paused for a long time, re-clicking it will re-fetch a fresh URL.\n- **Trending feed is seeded**, not live. The \"trending\" content is randomly picked from a small set of curated seed queries in `trending.py` — not a real trending API.\n- **No downloads.** PX7 is a streaming player only.\n- **yt-dlp currency.** YouTube frequently changes its extraction logic. If streams stop working, update yt-dlp: `pip install -U yt-dlp` and rebuild.\n- **PWA on iOS requires Safari.** Chrome and Firefox on iOS cannot install PWAs due to Apple's browser restrictions.\n- **Termux battery optimisation.** Android will kill the Termux session if battery optimisation is not disabled for Termux. See the Termux section above.\n\n---\n\n## Roadmap Ideas\n\n- [ ] Live trending via YouTube Music API or RSS\n- [ ] Playlist creation \u0026 persistence\n- [ ] System media key support (via Neutralino native API)\n- [ ] Album/artist page views\n- [ ] Mini-player / always-on-top compact mode\n\n---\n\n## License\n\nGPLv3 — you're free to use, modify, and distribute PX7.FM, but any derivative work must also be released under GPLv3 with source available. See [`LICENSE`](https://github.com/px7nn/px7.fm/blob/main/LICENSE) for the full terms.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpx7nn%2Fpx7.fm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpx7nn%2Fpx7.fm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpx7nn%2Fpx7.fm/lists"}