Un enregistrement s'appelait « Station — date », ce qui oblige à rouvrir le fichier pour savoir ce qu'il contient. Le flux ICY annonce le morceau diffusé : le service le demande, le premier titre capté nomme l'enregistrement, la station devient le sous-titre, et le morceau en cours s'affiche pendant la capture. Ces octets de métadonnées ne sont pas de l'audio et sont retirés avant écriture ; laissés dans le fichier, ils le rendraient illisible. La valeur du champ se lit jusqu'à sa fin, et non jusqu'à la première apostrophe, sans quoi « Ain't That a Kick in the Head » se réduirait à « Ain ». L'onglet Hors ligne ignorait par ailleurs les enregistrements : n'ayant aucun état de téléchargement, ils échappaient à son filtre alors qu'ils sont, par nature, ce qu'il y a de plus disponible sans réseau. Ils y figurent désormais, la taille annoncée les additionne au cache, et « Tout libérer » dit clairement qu'il les épargne.
260 lines
12 KiB
Markdown
260 lines
12 KiB
Markdown
# 🎧 Cachette
|
|
|
|
> Mettez la musique et les vidéos **de côté** pendant que vous avez du réseau. Écoutez-les quand vous n'en avez plus.
|
|
|
|
[](https://developer.android.com)
|
|
[](https://kotlinlang.org)
|
|
[](https://developer.android.com/jetpack/compose)
|
|
[](https://developer.android.com/media/media3)
|
|
[](VERSION)
|
|
[](#-comment-ça-marche)
|
|
|
|
Cachette 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 |
|
|
|:---:|:---:|:---:|:---:|
|
|
|  |  |  |  |
|
|
|
|
---
|
|
|
|
## ✨ Fonctionnalités
|
|
|
|
| | |
|
|
|---|---|
|
|
| 🎵 **Audio et vidéo** | Un seul lecteur, une seule file d'attente, la vidéo bascule sur une surface dédiée |
|
|
| 📥 **Mise en cache explicite** | Rien n'est téléchargé sans un geste : vous décidez quoi garder |
|
|
| 🔍 **Parcours des sources** | On entre dans une source, on coche ce qu'on veut, on télécharge en lot |
|
|
| 🎧 **Écoute avant téléchargement** | Chaque piste s'essaie en ligne d'un appui, sans rien mettre en cache |
|
|
| 🧾 **Choix piste par piste** | Un album ou un livre affiche ses pistes avec leur taille : on n'emporte que ce qu'on veut |
|
|
| 🌍 **Filtrage par langue** | Français, anglais, allemand, espagnol, italien, néerlandais |
|
|
| 📋 **Playlists** | Listes nommées, appui long pour y ajouter un titre, lecture enchaînée |
|
|
| 🎼 **Catalogues libres de droits** | Recherche intégrée dans Internet Archive et LibriVox, sans compte ni clé d'API |
|
|
| 📻 **Podcasts RSS** | Un flux, ses épisodes, et le choix de ceux qu'on emporte |
|
|
| 🔗 **Liens directs** | N'importe quelle URL audio ou vidéo, y compris un serveur média auto-hébergé |
|
|
| 📱 **Contenu de l'appareil** | Lecture de la bibliothèque MediaStore, déjà disponible hors connexion par nature |
|
|
| 🎛️ **Lecture en arrière-plan** | Notification système, casque, Bluetooth, écran verrouillé |
|
|
| ⏯️ **Contrôles complets** | Lecture, pause, arrêt, piste précédente et suivante, ±15 s |
|
|
| 🌙 **Thème clair et sombre** | Material 3, palette de marque, contraste garanti |
|
|
| 🔁 **Reprise automatique** | Réseau coupé : mise en attente et reprise. Échec serveur : relance seule après 5 s, 15 s, 45 s |
|
|
| 📻 **Radio** | Les stations les plus écoutées, en direct — et enregistrables d'un appui |
|
|
| 🏷️ **Titrage automatique** | L'enregistrement prend le nom du morceau annoncé par la station |
|
|
| 🆕 **Mise à jour intégrée** | Surveille les releases du dépôt, télécharge l'APK en Wi-Fi et propose l'installation |
|
|
|
|
---
|
|
|
|
## 📦 Sources disponibles
|
|
|
|
| Source | État | Détail |
|
|
|---|:---:|---|
|
|
| 📱 Appareil | ✅ | Musique et vidéos MediaStore |
|
|
| 🔗 Lien direct | ✅ | URL `http(s)` vers un fichier audio ou vidéo |
|
|
| 📻 Podcast RSS | ✅ | Flux standard avec `<enclosure>` |
|
|
| 🏛️ 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) |
|
|
|
|
### 🔎 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 Cachette 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.
|
|
|
|
---
|
|
|
|
## 🆕 Mises à jour
|
|
|
|
Cachette é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/
|
|
├── CachetteApp.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
|
|
|
|
À définir par l'auteur du dépôt.
|