{"id":43263535,"url":"https://github.com/adrialemany/resonode","last_synced_at":"2026-02-01T15:01:51.704Z","repository":{"id":335778114,"uuid":"1146988039","full_name":"adrialemany/ResoNode","owner":"adrialemany","description":"Self-hosted music streaming platform (Python + Android) designed for dynamic IPs. Stream your library remotely without port forwarding using Cloudflare Tunnels. Includes offline mode and legacy support.","archived":false,"fork":false,"pushed_at":"2026-02-01T13:27:27.000Z","size":1921,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-01T13:54:33.216Z","etag":null,"topics":["android","cloudflare-tunnel","fastapi","java","music-streaming","offline-first","python","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adrialemany.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-02-01T02:11:12.000Z","updated_at":"2026-02-01T13:27:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/adrialemany/ResoNode","commit_stats":null,"previous_names":["adrialemany/spotifly"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/adrialemany/ResoNode","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrialemany%2FResoNode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrialemany%2FResoNode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrialemany%2FResoNode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrialemany%2FResoNode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adrialemany","download_url":"https://codeload.github.com/adrialemany/ResoNode/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrialemany%2FResoNode/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28980855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T13:38:33.235Z","status":"ssl_error","status_checked_at":"2026-02-01T13:38:32.912Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["android","cloudflare-tunnel","fastapi","java","music-streaming","offline-first","python","self-hosted"],"created_at":"2026-02-01T15:01:49.125Z","updated_at":"2026-02-01T15:01:51.697Z","avatar_url":"https://github.com/adrialemany.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"./assets/icon.png\" align=\"right\" width=\"70\" alt=\"Logo\"\u003e\n\n# ResoNode\n\n**Self-hosted music streaming without barriers.**\n\nResoNode is a self-hosted music streaming platform designed to provide full control over a personal music library. It serves as an alternative to commercial streaming services, allowing users to host their own server and access their music remotely without subscriptions, ads, or data tracking.\n\n## Overview\n\nUnlike other media servers like Plex or Jellyfin, ResoNode is built with a specific focus on resilience and connection simplicity for users with dynamic IPs.\n\n* **Data Sovereignty:** You host the MP3 files. The library is not subject to licensing agreements or removals.\n* **Connection Mechanism:** Removes the need for static IPs, dynamic DNS (DDNS), or complex VPN configurations (like WireGuard/Tailscale). It uses a discovery system based on Cloudflare Tunnels and Gmail to locate the server automatically.\n* **Hybrid Playback:**\n    * **Streaming:** Plays directly from the server when an internet connection is available.\n    * **Offline Mode:** Downloads playlists to local storage. The app manages the local database transparently, switching modes automatically when network connectivity is lost.\n* **The Vault:** An automated file system that organizes uploads by Artist, Album, and Track.\n* **Legacy Support:** Includes a custom TLS 1.2 socket factory implementation, allowing the client to run on older Android devices (Android 4.4+) as dedicated media players.\n\n## System Architecture\n\nThe project consists of two main components:\n\n1.  **Server (Python/FastAPI):** Runs on a host machine (Linux/Windows/Mac). Manages the file system, user authentication, and streaming logic.\n2.  **Client (Android):** A native application optimized for low-latency streaming and offline synchronization.\n\n### The Connection Logic\n\n![ResoNode Network Architecture](assets/architecture.png)\n\nTo bypass carrier-grade NAT (CGNAT) and dynamic IPs without user intervention:\n\n1.  **Tunneling:** Upon startup, `server_launcher.py` initiates a secure Cloudflare Tunnel (`cloudflared`).\n2.  **Assignment:** Cloudflare assigns a temporary, random public URL (e.g., `https://random-id.trycloudflare.com`).\n3.  **Broadcasting:** The server detects this URL and emails it to a dedicated Gmail account via SMTP.\n4.  **Discovery:** When the Android app launches, it checks the Gmail inbox via IMAP, retrieves the latest email from the server, and parses the new URL.\n5.  **Handshake:** The app updates its configuration and establishes the connection.\n\n## Server Installation\n\n### Prerequisites\n* Python 3.8 or higher.\n* **Cloudflared:** The Cloudflare tunnel daemon must be installed and available in the system PATH.\n* **Gmail Account:** A dedicated account is recommended for the handshake process. App Passwords must be enabled.\n\n### Setup Steps\n\n1.  **Install Dependencies:**\n    ```bash\n    pip install fastapi uvicorn python-multipart mutagen requests\n    ```\n\n2.  **Configuration:**\n    * Navigate to the `server/` directory.\n    * Locate `server_config_example.py`.\n    * Rename it to `server_config.py` (this file is git-ignored to prevent credential leakage).\n    * Edit the file with your credentials:\n        ```python\n        API_SECRET_KEY = \"YOUR_SECURE_KEY\" # Must match the key in the Android App\n        GMAIL_USER = \"your_email@gmail.com\"\n        GMAIL_PASS = \"your_app_password\"\n        DESTINATION_EMAIL = \"destination_email@gmail.com\"\n        PORT = 8000\n        ```\n\n3.  **Run the Server:**\n    Execute the launcher script from within the server directory. This will start the API, initialize the tunnel, and send the connection email.\n    ```bash\n    cd server\n    python server_launcher.py\n    ```\n\n## Client Installation (Android)\n\n1.  Open the project in **Android Studio**.\n2.  **Security Configuration:**\n    * Navigate to `app/src/main/java/com/example/resonode/`.\n    * Modify the file named `Config.java` using it as a template.\n    * Fill in the required fields:\n        * `API_SECRET_KEY`: Must match the server key exactly.\n        * `GMAIL_EMAIL` / `PASSWORD`: Credentials used to read the connection email.\n3.  **Build:** compile the APK and install it on the target device.\n\n## Uploading Music\n\nResoNode includes a user-friendly helper script to upload your local music library to your server without technical hassles.\n\n### Automated Method (Recommended)\n\n1.  Ensure your server is running.\n2.  Run the uploader script from the project root:\n    ```bash\n    python music_uploader.py\n    ```\n3.  **Follow the interactive wizard:**\n    * **Select Source:** Drag and drop your local music folder containing `.mp3` files into the terminal window.\n    * **Select Environment:** Choose whether you are at home (Local IP) or away (Cloudflare Tunnel).\n    * **Select Destination:** (Optional) Choose a specific playlist folder on the server to link the music to.\n\nThe script automatically filters for MP3s, creates a ZIP package, and handles the upload process for you.\n\n### Manual Method (Advanced)\n\nIf you prefer to build your own tools or integrations, the server exposes the following endpoint:\n\n* **URL:** `/upload_zip`\n* **Method:** `POST`\n* **Headers:** `x-secret-key: [YOUR_API_SECRET_KEY]`\n* **Body (Multipart/Form-Data):**\n    * `file`: A `.zip` file containing valid MP3s.\n    * `target_playlist`: (Optional) Name of a subfolder/playlist.\n\n## OTA Updates (Auto-Updater)\n\nResoNode includes a built-in Over-The-Air update mechanism. You don't need to manually copy APKs to your phone.\n\n### How it works\n1.  **Check:** Every time the App opens, it queries `/update/check`.\n2.  **Compare:** It compares the server's `version.json` with the installed app version.\n3.  **Install:** If a newer version is found, it downloads and prompts the user to install it.\n\n### Building a new version\nIf you ever want to experiment and modify this app, feel free to do it! A helper script `deploy_tool.py` is included to automate this process.\n\n1.  **Build:** Generate a Signed APK in Android Studio (`Build \u003e Generate Signed Bundle / APK \u003e APK \u003e Debug/Release`).\n2.  **Run:** Execute the tool from the project root:\n    ```bash\n    python deploy_tool.py\n    ```\n3.  **Follow the wizard:**\n    * It detects the latest APK automatically.\n    * Enter the new Version Code (must be higher than current).\n    * Enter the Changelog.\n    * **Select Mode:**\n        * `[1] SSH/SCP`: For local network deployment (requires SSH access).\n        * `[2] HTTP Upload`: Deploys via the Cloudflare Tunnel. Useful if you are coding away from home.\n\n## Disclaimer\n\nThis project is a proof of concept regarding media streaming and network traversal. Users are responsible for ensuring they have the legal rights to the media files hosted, streamed, and downloaded on their private servers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrialemany%2Fresonode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadrialemany%2Fresonode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrialemany%2Fresonode/lists"}