# 🎧 Noxely > Un serveur cherche, guette les radios et enregistre. Le tĂ©lĂ©phone ne fait que rĂ©cupĂ©rer les > fichiers prĂȘts — et garde sa batterie. [![Platform](https://img.shields.io/badge/Plateforme-Android%208.0%2B-3DDC84?logo=android&logoColor=white)](https://developer.android.com) [![Kotlin](https://img.shields.io/badge/Kotlin-2.1.0-7F52FF?logo=kotlin&logoColor=white)](https://kotlinlang.org) [![Compose](https://img.shields.io/badge/Jetpack%20Compose-Material%203-4285F4?logo=jetpackcompose&logoColor=white)](https://developer.android.com/jetpack/compose) [![Media3](https://img.shields.io/badge/Media3-1.11.1-FF6F00?logo=googleplay&logoColor=white)](https://developer.android.com/media/media3) [![Version](https://img.shields.io/badge/Version-2.1.0-6B4EFF)](VERSION) [![Licence](https://img.shields.io/badge/Licence-GPL%20v3-3FD8C0)](LICENSE) [![Hors ligne](https://img.shields.io/badge/Hors%20ligne-100%25-3FD8C0)](#-comment-ça-marche) Noxely est un lecteur audio et vidĂ©o pensĂ© pour les trajets, les tunnels, les avions et les forfaits Ă©puisĂ©s. On choisit ce qu'on emporte, l'application le tĂ©lĂ©charge, et tout reste lisible sans la moindre connexion. --- ## 📾 Aperçu | BibliothĂšque | Lecture | Hors ligne | Catalogues libres | |:---:|:---:|:---:|:---:| | ![BibliothĂšque](docs/screenshots/bibliotheque.png) | ![Lecture](docs/screenshots/lecture.png) | ![Hors ligne](docs/screenshots/hors-ligne.png) | ![Catalogue](docs/screenshots/catalogue.png) | --- ## đŸ—ïž Architecture ``` SERVEUR (Python / FastAPI, sur votre machine) ├── cherche dans les catalogues libres ├── guette les morceaux sur les radios ├── enregistre les directs, titrage ICY compris └── tient les fichiers prĂȘts │ HTTP + jeton â–Œ MOBILE (Android / Compose) └── demande ce qui est neuf, tĂ©lĂ©charge, lit Wi-Fi non facturĂ© · batterie suffisante · toutes les 6 h ``` Tout ce qui Ă©coutait en continu a migrĂ© sur le serveur. Le tĂ©lĂ©phone dort entre deux synchronisations : c'est lĂ  qu'est l'Ă©conomie. ## ✹ Ce que fait le serveur | | | |---|---| | 🔎 **Recherche** | Internet Archive, Underground (netlabels + Free Music Archive), LibriVox, Wikimedia Commons | | đŸ§Ÿ **Choix piste par piste** | Un album affiche ses pistes avec leur taille avant d'ĂȘtre mis en file | | 📡 **Guet multi-stations** | Nommez un morceau : les stations les plus Ă©coutĂ©es sont balayĂ©es jusqu'Ă  le trouver | | đŸ“» **Enregistrement** | Capture du direct, nommĂ©e d'aprĂšs le titre annoncĂ© par la station | | ⏳ **File de tĂ©lĂ©chargement** | Reprise aprĂšs Ă©chec, Ă©tat visible | | đŸ–„ïž **Interface web** | Tout se pilote depuis un navigateur, sans toucher au tĂ©lĂ©phone | ## đŸ“± Ce que fait le mobile | | | |---|---| | 🔄 **Synchronisation sobre** | Toutes les 6 h, en Wi-Fi non facturĂ© et batterie suffisante | | đŸ“± **Appairage** | Un bouton cĂŽtĂ© serveur, un bouton cĂŽtĂ© tĂ©lĂ©phone : aucun jeton Ă  recopier | | đŸŽ” **Lecture** | Audio et vidĂ©o, arriĂšre-plan, casque, Ă©cran verrouillĂ©, ±15 s, stop | | 📚 **BibliothĂšque** | MĂ©dias reçus du serveur et contenu dĂ©jĂ  prĂ©sent sur l'appareil | | 📋 **Playlists** | Listes nommĂ©es, appui long pour y ajouter un titre | | đŸ–Œïž **Pochettes** | CherchĂ©es automatiquement pour les titres qui n'en ont pas | | â„č **À propos et mises Ă  jour** | Version, garanties, journal, et suivi des releases du dĂ©pĂŽt | --- ## 📩 Sources interrogĂ©es par le serveur | Source | État | DĂ©tail | |---|:---:|---| | đŸ“± Appareil | ✅ | Musique et vidĂ©os dĂ©jĂ  sur le tĂ©lĂ©phone (cĂŽtĂ© mobile) | | 🔗 Lien direct | ✅ | URL `http(s)` vers un fichier audio ou vidĂ©o | | đŸ“» Podcast RSS | ✅ | Flux standard avec `` | | đŸ›ïž Internet Archive | ✅ | Domaine public / Creative Commons | | 📚 LibriVox | ✅ | Livres audio du domaine public, filtrables par langue | | đŸŽŒ Wikimedia Commons | ✅ | Enregistrements classiques et archives sonores libres | | đŸ“» Radio | ✅ | Annuaire Radio Browser, direct et enregistrement (flux HLS exclus) | | 🎧 Underground | ✅ | Netlabels et Free Music Archive : ~94 000 morceaux d'artistes indĂ©pendants | ### 🔎 Catalogues Ă©valuĂ©s puis Ă©cartĂ©s | Candidat | Pourquoi non | |---|---| | **Openverse** | A cessĂ© de servir l'accĂšs anonyme : **401 sur huit requĂȘtes sur huit**, et jusqu'Ă  21 s de latence quand il rĂ©pondait encore. Exige dĂ©sormais une inscription. | | **ccMixter** | La recherche fonctionne, mais les fichiers ne sont servis qu'avec un en-tĂȘte `Referer` — que Media3 ne permet pas de poser par tĂ©lĂ©chargement. | | **Radio Browser** | Flux radio continus : rien Ă  mettre en cache. | ### ⚖ Ce que Noxely ne fait pas — et pourquoi | Service | Pourquoi | |---|---| | đŸŽ” **Spotify** | Le SDK ne donne aucun accĂšs au flux audio. Le hors-ligne n'existe que dans l'application Spotify. Un podcast Ă©galement diffusĂ© en RSS public reste ajoutable. | | ▶ **YouTube** | Les conditions d'utilisation interdisent d'extraire ou de stocker les flux. Le hors-ligne passe par YouTube Premium. | | ☁ **Google Drive / Photos** | Techniquement faisable pour vos propres fichiers, mais cela exige un identifiant OAuth Google liĂ© Ă  votre compte dĂ©veloppeur, volontairement non embarquĂ© ici. | C'est un choix assumĂ© : l'application ne contourne rien, elle travaille avec les sources qui autorisent la copie locale. --- ## 🧭 Comment ça marche ``` Source ──â–ș BibliothĂšque ──â–ș [ Garder ] ──â–ș Cache Media3 ──â–ș Lecture hors ligne │ reprise auto (WorkManager) ``` Trois propriĂ©taires de donnĂ©es, aucun doublon : - **MediaStore** possĂšde le contenu local — on le relit, on ne le recopie pas ; - **`imported.json`** possĂšde les seules rĂ©fĂ©rences distantes ajoutĂ©es Ă  la main ; - **l'index Media3** possĂšde l'Ă©tat du cache — taille, progression, complĂ©tude. Le cache utilise un `NoOpCacheEvictor` : il contient **exactement** ce que vous avez demandĂ© Ă  garder, et rien n'en est effacĂ© sans votre geste. La lecture lit ce cache mais n'y Ă©crit jamais, pour qu'une Ă©coute en ligne ne le fasse pas gonfler Ă  votre insu. --- ## 🚀 DĂ©marrage ### PrĂ©requis - ☕ JDK 17 - đŸ€– Android SDK avec la plateforme **36** et les build-tools 36 - đŸ“± Un appareil ou un Ă©mulateur en **Android 8.0 (API 26)** ou plus ### Construire ```bash make build # APK de debug make install # installe sur l'appareil connectĂ© make run # installe et lance make lint # analyse Android Lint make release # APK de release (signĂ© si keystore.properties existe) make publish # release Gitea : tag + notes + APK signĂ© make publish-dry # simulation, sans rien envoyer make deploy # release puis publish ``` `make help` liste toutes les cibles, groupĂ©es par catĂ©gorie. Sans `make`, les commandes Gradle Ă©quivalentes fonctionnent : ```bash ./gradlew assembleDebug ./gradlew installDebug ``` L'APK se trouve dans `app/build/outputs/apk/debug/app-debug.apk`. ### Signer une version de publication CrĂ©ez un `keystore.properties` Ă  la racine — il est ignorĂ© par git : ```properties storeFile=cachette.jks storePassword=... keyAlias=cachette keyPassword=... ``` ### đŸ“€ Publier une version `make publish` crĂ©e le tag, la release Gitea et y attache l'APK **signĂ©**. La cible est idempotente : relancĂ©e sur une version dĂ©jĂ  publiĂ©e, elle rĂ©utilise la release et remplace l'asset. `make publish-dry` rĂ©pĂšte l'opĂ©ration Ă  blanc. La publication dĂ©lĂšgue Ă  `gitea_release.sh` de **BorisSkill**, qui dĂ©duit l'hĂŽte, le propriĂ©taire et le dĂ©pĂŽt du remote `origin` — il n'y a donc aucun nom de dĂ©pĂŽt Ă  configurer ici. Le chemin du script est surchargeable : `make publish GITEA_RELEASE=/chemin/gitea_release.sh`. PrĂ©requis : | ÉlĂ©ment | RĂŽle | |---|---| | `VERSION` | Source unique de la version — `versionCode` en est dĂ©duit (`1.0.0` → `10000`) | | `CHANGELOG.md` | La section `## [x.y.z]` devient le corps des notes de release | | `keystore.properties` | Signature — sans lui l'APK est `-unsigned` et refusĂ© Ă  la publication | | DĂ©pĂŽt git | Un `origin` configurĂ© : le tag `vx.y.z` y est poussĂ© | Le jeton Gitea est rĂ©solu dans l'ordre : `$GITEA_TOKEN`, `.gitea-token` Ă  la racine du dĂ©pĂŽt, `~/.config/gitea/token`, puis repli sur `~/.git-credentials`. Aucun secret n'est stockĂ© dans ce dĂ©pĂŽt. Pour monter de version, Ă©ditez `VERSION` et ajoutez la section correspondante au `CHANGELOG.md` : Gradle, le tag git et les notes de release suivront seuls. --- ## 🔐 Permissions et vie privĂ©e | Permission | Pourquoi | |---|---| | `READ_MEDIA_AUDIO` / `READ_MEDIA_VIDEO` | Lister la musique et les vidĂ©os de l'appareil | | `INTERNET` | TĂ©lĂ©charger ce que vous avez choisi de garder | | `POST_NOTIFICATIONS` | Commandes de lecture et progression des tĂ©lĂ©chargements | | `FOREGROUND_SERVICE_*` | Continuer la lecture et les tĂ©lĂ©chargements Ă©cran Ă©teint | | `REQUEST_INSTALL_PACKAGES` | Proposer l'installation d'une mise Ă  jour tĂ©lĂ©chargĂ©e | Aucune donnĂ©e ne quitte l'appareil : pas de compte, pas de tĂ©lĂ©mĂ©trie, pas de sauvegarde cloud (`dataExtractionRules` exclut explicitement le contenu mis en cache). Les seules connexions sortantes sont celles des URL que vous ajoutez et des catalogues que vous interrogez. > ⚠ `usesCleartextTraffic` est activĂ© pour permettre l'usage d'un serveur mĂ©dia auto-hĂ©bergĂ© en > `http://` sur le rĂ©seau local. C'est un compromis assumĂ© ; prĂ©fĂ©rez `https` dĂšs que possible. --- > ⚠ **Changement de nom en 1.6.0.** L'application s'appelait Cachette et son identifiant Ă©tait > `fr.tuxgyver.cachette`. Android traite le nouvel identifiant `fr.tuxgyver.noxely` comme une > application distincte : une installation en 1.5.0 ne se met pas Ă  jour toute seule, il faut la > dĂ©sinstaller puis installer Noxely. Les enregistrements dĂ©jĂ  rangĂ©s dans `Music/Cachette` > survivent — ils sont dans le stockage partagĂ© — et restent lisibles par tout lecteur. --- ## đŸ“Œ OĂč vont les enregistrements À partir d'Android 10, dans `Music/Noxely/`, dĂ©clarĂ©s dans MediaStore : ils sont donc visibles de tous les lecteurs de l'appareil et survivent Ă  la dĂ©sinstallation. En deçà, ils restent dans le dossier privĂ© de l'application, MediaStore y rĂ©clamant une permission de stockage dont Noxely n'a pas besoin par ailleurs. Le nom du fichier porte le titre du morceau — et pas seulement la colonne `TITLE` : en publiant le fichier, MediaStore le relit et réécrit ce champ Ă  partir du nom. Le scan local, lui, ignore `Music/Noxely` pour ne pas afficher deux fois ce que la bibliothĂšque connaĂźt dĂ©jĂ . Chaque enregistrement se supprime individuellement depuis l'Ă©cran **Hors ligne** : fichier, entrĂ©e MediaStore et rĂ©fĂ©rence en bibliothĂšque partent ensemble. « Tout libĂ©rer » ne les touche pas. --- ## 🆕 Mises Ă  jour Noxely Ă©tant distribuĂ©e hors magasin, elle surveille elle-mĂȘme les releases du dĂ©pĂŽt. ``` Release publiĂ©e ──â–ș vĂ©rification quotidienne (WorkManager, Wi-Fi) │ ├─ version plus rĂ©cente ? ──â–ș tĂ©lĂ©chargement de l'APK │ │ └─ sinon : rien â–Œ notification « prĂȘte Ă  installer » │ installation = geste utilisateur ``` L'installation reste un geste explicite : Android l'impose Ă  toute application hors magasin. L'Ă©cran **Sources** porte la mĂȘme vĂ©rification Ă  la demande, et affiche la version installĂ©e. Le dĂ©pĂŽt Ă©tant privĂ©, la lecture des releases demande un jeton. Il est lu depuis `update-token.properties` (ignorĂ© par git) et injectĂ© dans `BuildConfig` Ă  la compilation : ```properties giteaUpdateToken=
 ``` > ⚠ Un jeton embarquĂ© dans un APK est extractible. Celui-ci est volontairement limitĂ© Ă  la portĂ©e > `read:repository` et rĂ©vocable depuis Gitea. Il donne un accĂšs en lecture aux dĂ©pĂŽts du compte — > ne pas lui accorder davantage. --- ## đŸ—‚ïž Structure ``` app/src/main/java/fr/tuxgyver/cachette/ ├── NoxelyApp.kt Application + conteneur de dĂ©pendances (assemblĂ© Ă  la main) ├── MainActivity.kt ├── model/ MediaEntry, MediaKind, SourceKind ├── library/ MediaStore, persistance des imports, bibliothĂšque unifiĂ©e ├── sources/ Import URL et RSS, catalogues Internet Archive et LibriVox ├── offline/ Cache Media3, gestionnaire et service de tĂ©lĂ©chargement ├── playback/ MediaSessionService, ExoPlayer, contrĂŽleur d'interface ├── radio/ Annuaire des stations, service d'enregistrement du direct ├── update/ VĂ©rification des releases, tĂ©lĂ©chargement, notification └── ui/ Compose — thĂšme, bibliothĂšque, hors ligne, playlists, sources, parcours des catalogues, choix des pistes, lecteur ``` --- ## đŸ§Ș État de vĂ©rification ValidĂ© sur Ă©mulateur Pixel (Android 17, API 37) : - ✅ Scan MediaStore audio et vidĂ©o, vignettes comprises - ✅ Lecture avec session mĂ©dia systĂšme et position qui progresse - ✅ Import d'un flux RSS et de ses Ă©pisodes - ✅ TĂ©lĂ©chargement d'un Ă©pisode jusqu'Ă  l'Ă©tat « disponible hors connexion » - ✅ Recherche Internet Archive, rĂ©solution du fichier et mise en cache - ✅ Android Lint sans erreur --- ## 📄 Licence **GNU General Public License v3.0** — le texte intĂ©gral est dans [LICENSE](LICENSE). Copyright © 2026 Johnny (tuxgyver) Noxely est un logiciel libre : vous pouvez le redistribuer et le modifier selon les termes de la GPL v3, telle que publiĂ©e par la Free Software Foundation, au choix dans sa version 3 ou dans une version ultĂ©rieure. Il est distribuĂ© dans l'espoir d'ĂȘtre utile, mais **sans aucune garantie**, sans mĂȘme la garantie implicite de qualitĂ© marchande ou d'adĂ©quation Ă  un usage particulier. Voir la licence pour le dĂ©tail. Ce choix engage aussi celui qui redistribue : toute version modifiĂ©e diffusĂ©e doit l'ĂȘtre sous la mĂȘme licence, code source compris.