Skip to content

feat(serveur): lire en streaming depuis le serveur - #17

Merged
InstaZDLL merged 3 commits into
mainfrom
feat/lecture-serveur
Aug 11, 2026
Merged

feat(serveur): lire en streaming depuis le serveur#17
InstaZDLL merged 3 commits into
mainfrom
feat/lecture-serveur

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Troisième étape de la source distante. Un morceau du catalogue serveur se lit, avec la file de son album, dans le même lecteur que les fichiers locaux — notification média, écran de verrouillage et lecture en arrière-plan compris, sans rien y ajouter.

Le ticket plutôt que le jeton

POST /tracks/{id}/stream-ticket rend une URL qui ne demande aucun en-tête d'autorisation. C'est ce qui permet de la confier telle quelle à ExoPlayer, requêtes de plage comprises — un jeton porté en en-tête aurait demandé un DataSource.Factory sur mesure, dans une couche qui gère déjà ses propres reconnexions.

Le ticket est demandé quand le lecteur ouvre la piste, pas à la constitution de la file. Il ne vit qu'une heure : une file un peu longue dépasserait l'échéance avant d'atteindre ses derniers morceaux. Un ResolvingDataSource échange le marqueur waveflow://track/<id> contre l'URL réelle, sur le fil de chargement. Un déplacement dans le morceau rouvre la source et en redemande un, ce qui règle aussi l'expiration en cours d'écoute.

Les fichiers locaux traversent la même chaîne sans être touchés : un seul lecteur, une seule file.

Le morceau courant n'est plus forcément local

PlaybackState portait un currentSongId: Long? que PlayerViewModel résolvait dans la bibliothèque. Une piste du serveur n'existe pas dans le MediaStore — l'y chercher ne rendait rien à afficher.

Il porte désormais un PlayingTrack décrit par le lecteur lui-même. Conséquences :

  • PlayerViewModel n'a plus besoin du LibraryStore ; sa projection, réévaluée à chaque tic de position, n'a plus de recherche à faire ;
  • le mediaId est préfixé par sa source. Sans ça, un identifiant distant purement numérique se convertirait en Long et passerait pour une piste locale — le test un identifiant distant purement numerique reste distant échoue si on retire le préfixe ;
  • l'en-tête du lecteur plein écran affiche la source quand l'album est inconnu.

Validation

./gradlew clean testDebugUnitTest assembleDebug → BUILD SUCCESSFUL, 166 tests, 0 échec, aucun avertissement. 13 tests nouveaux.

Contre le vrai serveur : ticket obtenu, URL absolue résolue depuis le chemin relatif que rend le serveur, lecture sans en-tête en audio/mpeg, requête de plage en 206, et deux tickets successifs bien distincts.

Deux comportements validés par retrait : sans le préfixe de source, la distinction local/distant tombe ; sans la conversion en IOException, l'échec du serveur remonterait brut jusqu'au lecteur au lieu de signaler une piste illisible.

Limites connues

  • Pas d'aléatoire ni de « Lecture » sur un album distant : l'en-tête distant n'a pas ces boutons, seuls les morceaux sont cliquables. À ajouter quand playShuffled acceptera les deux sources.
  • La lecture n'a jamais tourné sur un appareil réel. C'est vrai de tout le dépôt, mais ça pèse plus ici : le résolveur s'exécute sur le fil de chargement de Media3, et seul un vrai appareil dira si l'attente du ticket se voit au démarrage d'une piste.
  • Rien n'est mis en cache : chaque écoute retélécharge.

https://claude.ai/code/session_01F89rkrDB9TxcwHbfgNoyY1

Summary by CodeRabbit

  • Nouvelles fonctionnalités

    • Ajout de la lecture en streaming des morceaux distants depuis un serveur WaveFlow.
    • Lancement depuis un morceau précis ou le début d’un album distant.
    • Affichage du morceau en cours avec distinction entre sources locales et distantes.
    • Résolution automatique des adresses de streaming au moment de la lecture.
  • Documentation

    • Documentation du streaming serveur, du mappage des morceaux et de la recherche par plages.
  • Tests

    • Renforcement des tests sur les pistes distantes, les URL de streaming et les files de lecture.

Troisième étape de la source distante : un morceau du catalogue serveur se lit,
avec la file de son album, dans le même lecteur que les fichiers locaux.

La diffusion passe par un ticket. `POST /tracks/{id}/stream-ticket` rend une URL
qui ne demande aucun en-tête d'autorisation, ce qui permet de la confier telle
quelle à ExoPlayer — requêtes de plage d'un déplacement comprises, ce qu'un
jeton porté en en-tête aurait compliqué au sein d'une couche qui gère ses
propres reconnexions.

Le ticket est demandé au moment où le lecteur ouvre la piste, et non à la
constitution de la file : il ne vit qu'une heure, et une longue file dépasserait
l'échéance avant d'atteindre ses derniers morceaux. Un `ResolvingDataSource`
échange le marqueur `waveflow://track/<id>` contre l'URL réelle sur le fil de
chargement. Les fichiers locaux traversent la même chaîne sans être touchés, ce
qui garde un seul lecteur et une seule file.

Le morceau courant cesse d'être un identifiant à résoudre dans la bibliothèque.
`PlaybackState` porte désormais un `PlayingTrack` décrit par le lecteur
lui-même : une piste du serveur n'existe pas dans le MediaStore, et l'y chercher
ne rendait rien à afficher. `PlayerViewModel` n'a plus besoin du `LibraryStore`,
et sa projection — réévaluée à chaque tic de position — n'a plus de recherche à
faire.

Le `mediaId` est préfixé par sa source. Sans ça, un identifiant distant
purement numérique se convertirait en Long et passerait pour une piste locale.

Validé contre un waveflow-server 2.0.0-beta.0 local : ticket obtenu, URL
absolue résolue depuis le chemin relatif du serveur, lecture sans en-tête en
audio/mpeg, requête de plage en 206, et deux tickets successifs bien distincts.

Claude-Session: https://claude.ai/code/session_01F89rkrDB9TxcwHbfgNoyY1
@github-actions github-actions Bot added scope: docs Docs, README, assets scope: data Persistence, scanning, repositories scope: playback Audio playback engine and queue scope: ui Views, components, theming, assets scope: tests Unit and UI tests type: feat New feature size: xl > 500 lines labels Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d80827bf-587d-4e5a-958b-fc2c84784575

📥 Commits

Reviewing files that changed from the base of the PR and between 9b7938c and 83ef167.

📒 Files selected for processing (1)
  • app/src/test/java/app/waveflow/data/remote/HttpCatalogApiTest.kt

📝 Walkthrough

Walkthrough

Le lecteur prend désormais en charge les pistes distantes. Le changement ajoute les tickets de streaming, la résolution d’URL, les files distantes et l’état commun PlayingTrack. L’interface affiche et lance les pistes distantes.

Changes

Lecture distante

Layer / File(s) Summary
Contrats et identité des pistes
app/src/main/java/app/waveflow/playback/PlayingTrack.kt, .../MediaItemMapper.kt, .../PlaybackController.kt
Les pistes locales et distantes utilisent des identifiants et des URI distincts. PlaybackState expose désormais PlayingTrack.
Obtention des tickets de streaming
app/src/main/java/app/waveflow/data/remote/*
L’API demande un ticket authentifié, décode StreamTicketResponse et construit une URL absolue.
Chargement Media3 des pistes distantes
app/src/main/java/app/waveflow/playback/RemoteStreamResolver.kt, .../PlaybackService.kt, .../Media3PlaybackController.kt
Le contrôleur charge une file distante. RemoteStreamResolver remplace les URI waveflow:// au chargement et conserve les sources locales.
État et interaction de lecture
app/src/main/java/app/waveflow/ui/player/*, .../MainActivity.kt, .../RemoteDetailScreens.kt, .../MediaRow.kt
Le ViewModel et les écrans utilisent PlayingTrack. Les morceaux distants deviennent sélectionnables dans le détail d’album.
Validation et documentation
app/src/test/java/app/waveflow/playback/*, app/src/test/java/app/waveflow/ui/player/*, app/src/test/java/app/waveflow/testing/*, README.md
Les tests couvrent le mappage, la résolution des tickets, les files distantes et l’état du lecteur. Le README décrit le streaming serveur.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AlbumDetail
  participant PlayerViewModel
  participant PlaybackController
  participant ExoPlayer
  participant RemoteStreamResolver
  participant CatalogRepository

  AlbumDetail->>PlayerViewModel: playRemoteFrom(queue, song)
  PlayerViewModel->>PlaybackController: playRemote(queue, startIndex)
  PlaybackController->>ExoPlayer: charger les MediaItem distants
  ExoPlayer->>RemoteStreamResolver: résoudre waveflow://track/id
  RemoteStreamResolver->>CatalogRepository: streamUrl(trackId)
  CatalogRepository-->>RemoteStreamResolver: URL avec ticket
  RemoteStreamResolver-->>ExoPlayer: DataSpec résolu
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Le titre décrit clairement l’ajout de la lecture en streaming serveur et suit le format Conventional Commits avec un scope valide.
Description check ✅ Passed La description est détaillée, couvre le fonctionnement, les tests exécutés, les limites connues et les objectifs principaux du changement.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lecture-serveur

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/app/waveflow/data/remote/ServerHttp.kt`:
- Around line 142-145: Corrigez absoluteUrl pour résoudre le chemin de ticket
relativement au chemin d’application déjà présent dans serverUrl, afin de
conserver les préfixes de proxy. Validez et rejetez les chemins absolus ainsi
que les références réseau commençant par //, conformément au contrat attendu.
Ajoutez un test couvrant une serverUrl avec préfixe de proxy et vérifiez que
l’URL finale conserve ce préfixe.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1bfe7788-a28e-4ae4-9d56-1ceb7166bc37

📥 Commits

Reviewing files that changed from the base of the PR and between d7710dc and 0b040cb.

📒 Files selected for processing (24)
  • README.md
  • app/src/main/java/app/waveflow/MainActivity.kt
  • app/src/main/java/app/waveflow/data/remote/CatalogApi.kt
  • app/src/main/java/app/waveflow/data/remote/CatalogRepository.kt
  • app/src/main/java/app/waveflow/data/remote/Dto.kt
  • app/src/main/java/app/waveflow/data/remote/HttpCatalogApi.kt
  • app/src/main/java/app/waveflow/data/remote/ServerHttp.kt
  • app/src/main/java/app/waveflow/playback/Media3PlaybackController.kt
  • app/src/main/java/app/waveflow/playback/MediaItemMapper.kt
  • app/src/main/java/app/waveflow/playback/PlaybackController.kt
  • app/src/main/java/app/waveflow/playback/PlaybackService.kt
  • app/src/main/java/app/waveflow/playback/PlayingTrack.kt
  • app/src/main/java/app/waveflow/playback/RemoteStreamResolver.kt
  • app/src/main/java/app/waveflow/ui/components/MediaRow.kt
  • app/src/main/java/app/waveflow/ui/player/MiniPlayer.kt
  • app/src/main/java/app/waveflow/ui/player/NowPlayingScreen.kt
  • app/src/main/java/app/waveflow/ui/player/PlayerUiState.kt
  • app/src/main/java/app/waveflow/ui/player/PlayerViewModel.kt
  • app/src/main/java/app/waveflow/ui/server/catalog/RemoteDetailScreens.kt
  • app/src/test/java/app/waveflow/playback/MediaItemMapperTest.kt
  • app/src/test/java/app/waveflow/playback/RemoteStreamResolverTest.kt
  • app/src/test/java/app/waveflow/testing/Fakes.kt
  • app/src/test/java/app/waveflow/testing/ServerFakes.kt
  • app/src/test/java/app/waveflow/ui/player/PlayerViewModelTest.kt

Comment thread app/src/main/java/app/waveflow/data/remote/ServerHttp.kt Outdated
Le serveur rend le ticket sous la forme `/api/v2/stream/<ticket>`, sans savoir
qu'un proxy le préfixe. Résoudre ce chemin absolu contre l'adresse de base en
écrasait le chemin : un serveur exposé sur `https://hôte/musique` voyait son
préfixe disparaître, et l'URL de lecture n'atteignait plus rien.

Le chemin est désormais traité comme n'importe quel chemin d'API, par la même
construction que les appels — celle qui conserve déjà le préfixe.

Seul un chemin absolu du serveur est accepté au passage. Une URL complète ou une
référence réseau `//hôte/…` enverrait la lecture ailleurs que sur le serveur où
l'utilisateur s'est authentifié ; le serveur n'en produit pas, autant le refuser
franchement.

Claude-Session: https://claude.ai/code/session_01F89rkrDB9TxcwHbfgNoyY1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/test/java/app/waveflow/data/remote/HttpCatalogApiTest.kt`:
- Around line 152-162: Ajoutez dans HttpCatalogApiTest, à côté de `un ticket qui
designe un autre hote est refuse`, un test avec une URL absolue telle que
`https://ailleurs.test/api/v2/stream/x`. Appelez `api.streamTicket` via
`echecDe` et vérifiez que l’erreur obtenue est également une instance de
`ServerException.Unexpected`, en conservant le même scénario d’expiration et de
réponse serveur.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f280925-a0c5-4cb7-a260-8fb3410e96f7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b040cb and 9b7938c.

📒 Files selected for processing (2)
  • app/src/main/java/app/waveflow/data/remote/ServerHttp.kt
  • app/src/test/java/app/waveflow/data/remote/HttpCatalogApiTest.kt

Comment thread app/src/test/java/app/waveflow/data/remote/HttpCatalogApiTest.kt Outdated
La garde refusait déjà tout ce qui ne commence pas par `/`, mais seule la
référence réseau `//hôte/…` était vérifiée. Le cas de l'URL complète passait par
une branche non couverte : retirer la condition `!path.startsWith("/")` ne
faisait tomber aucun test.

Les deux cas sont maintenant distingués, et le second tombe bien sans la garde.

Claude-Session: https://claude.ai/code/session_01F89rkrDB9TxcwHbfgNoyY1
@InstaZDLL
InstaZDLL merged commit cc3d7b1 into main Aug 11, 2026
2 checks passed
@InstaZDLL
InstaZDLL deleted the feat/lecture-serveur branch August 11, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: data Persistence, scanning, repositories scope: docs Docs, README, assets scope: playback Audio playback engine and queue scope: tests Unit and UI tests scope: ui Views, components, theming, assets size: xl > 500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant