app: Scaffold Android TV PeerTube client (MVP)
Start the SmartTube-inspired PeerTube TV client as a Kotlin + Compose for TV app. It targets any PeerTube instance (v1 API), with JTube as the default, and covers login 0, local/own video listings 1-2, search 3, subscriptions 4, server-backed history 5 and HLS/progressive playback with watching reports 6. Password is only used for the OAuth password grant and never stored; tokens are kept in EncryptedSharedPreferences. Building needs JDK 17+ (see README); the Gradle wrapper is checked in so no local Gradle is required.
This commit is contained in:
commit
59029438ad
40 changed files with 2605 additions and 0 deletions
86
AGENTS.md
Normal file
86
AGENTS.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# AGENTS.md — guidance for AI agents working on JTube TV
|
||||
|
||||
Read this before making changes. The project is an Android TV app (Kotlin, Compose
|
||||
for TV) that talks to a PeerTube instance over its REST API v1.
|
||||
|
||||
## Environment facts
|
||||
|
||||
- Host is Arch Linux with Android SDK at `~/Android/Sdk` (platforms 34/35/36, build-tools).
|
||||
- The Gradle wrapper is checked in (`gradlew` + `gradle/wrapper/`), so builds need only a
|
||||
JDK 17+ (`java`) on PATH. If `java` is missing, tell the user — do not install things on
|
||||
the host without asking.
|
||||
- Useful commands:
|
||||
- `./gradlew :app:assembleDebug` — build debug APK
|
||||
- `./gradlew :app:lintDebug` — lint
|
||||
- `adb connect <ip>:<port>` + `adb install -r app/build/outputs/apk/debug/app-debug.apk`
|
||||
- This repo is local-only for now. If it gets a remote on git.jeena.net later, set the
|
||||
repo-local deploy key pin right away:
|
||||
`git config core.sshCommand "ssh -i ~/.ssh/agent-deploy-key -o IdentitiesOnly=yes"`
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
app/src/main/java/net/jeena/jtubetv/
|
||||
JTubeApp.kt Application + AppGraph service locator (no DI framework)
|
||||
MainActivity.kt single activity, session state → Setup or Home
|
||||
data/api/PeerTubeApi.kt Retrofit endpoints
|
||||
data/api/Dtos.kt kotlinx-serialization models (ignoreUnknownKeys everywhere)
|
||||
data/auth/TokenStore.kt EncryptedSharedPreferences (baseUrl, username, tokens, client creds)
|
||||
data/network/Network.kt Retrofit/OkHttp: BaseUrlInterceptor, AuthInterceptor,
|
||||
TokenAuthenticator (single-flight refresh)
|
||||
data/PeerTubeRepository.kt all suspend functions used by UI
|
||||
ui/theme/Theme.kt Compose for TV theme
|
||||
ui/screens/… SetupScreen, HomeScreen, VideoListScreen (modes), ChannelsScreen, PlayerScreen
|
||||
ui/list/… VideoListViewModel + PagedList state helper
|
||||
ui/player/…
|
||||
```
|
||||
|
||||
State flow: `MainActivity` observes `AppViewModel.sessionState`; `NeedsSetup` renders the
|
||||
setup/login screen, `Ready` renders `HomeScreen`, which switches content on a
|
||||
`Destination` enum. The player pushes a video onto a state and uses a `BackHandler`.
|
||||
|
||||
## PeerTube API notes (verified against tube.jeena.net)
|
||||
|
||||
- OAuth: `GET /api/v1/oauth-clients/local` → client_id/client_secret;
|
||||
`POST /api/v1/users/token` (form: grant_type=password) → access_token + refresh_token.
|
||||
Access tokens expire (~24h); refresh with grant_type=refresh_token. We never persist
|
||||
the user's password.
|
||||
- Pagination shape: `GET …?start=&count=&sort=` → `{ total, data: [...] }`.
|
||||
Sort `-publishedAt`; page size 25; infinite scroll by issuing the next page when the
|
||||
list is 5 items from the end.
|
||||
- Public/local videos listing, search, channels: unauthenticated endpoints work —
|
||||
- `GET /api/v1/videos?isLocal=true&search=`
|
||||
- `GET /api/v1/video-channels?search=…&searchTarget=local` (local channels only)
|
||||
- Authenticated (`Bearer` header):
|
||||
- `GET /api/v1/users/me/videos` — own videos, ALL privacy levels
|
||||
- `GET /api/v1/users/me/subscriptions/videos`
|
||||
- `GET /api/v1/users/me/history/videos`
|
||||
- `GET /api/v1/accounts/{accountName}/video-channels`
|
||||
- `POST /api/v1/videos/{shortUuid}/watching` `{ "currentTime": <seconds> }` — populates
|
||||
server-side history. Report every ~20 s of wall-clock playback once currentTime ≥ 5 s.
|
||||
- `GET /api/v1/videos/{shortUuid}` for playback URLs: prefer
|
||||
`streamingPlaylists[].playlistUrl` (HLS) else `files[].fileUrl`.
|
||||
- Private/internal media: attach `Authorization: Bearer` to playlist, segment and file
|
||||
requests (configured on the Media3 `HttpDataSource.Factory`).
|
||||
|
||||
## Conventions
|
||||
|
||||
- Kotlin + coroutines; suspend functions in the repository, `runCatching` → `Result<T>`
|
||||
at repository boundaries; view models expose `StateFlow`.
|
||||
- No Dagger/Hilt/Room — keep the dependency surface small; revise only if it earns its keep.
|
||||
- kotlinx-serialization for JSON; DTOs must use `ignoreUnknownKeys` so new PeerTube
|
||||
fields never crash parsing.
|
||||
- Compose for TV (`androidx.tv:tv-material`) for sidebar/cards; make list items
|
||||
focusable + clickable so D-pad navigation works.
|
||||
- Package prefix `net.jeena.jtubetv`. Keep files focused; don't grow `MainActivity`.
|
||||
- minSdk 26 / target+compile 35; versions are pinned in `gradle/libs.versions.toml`.
|
||||
|
||||
## Scope discipline
|
||||
|
||||
v0.1 features live in `README.md`; everything else in `BACKLOG.md`. Do not sneak in
|
||||
uploading, editing, subscriptions management, deletion or content creation — the user
|
||||
explicitly excluded them. If the user asks for a scope change, update `BACKLOG.md`
|
||||
first, then implement.
|
||||
|
||||
When unsure how a PeerTube endpoint behaves, verify against the live instance instead of
|
||||
guessing (401 ⇒ exists & needs auth, 404 ⇒ route missing, 405 ⇒ wrong method on real route).
|
||||
Loading…
Add table
Add a link
Reference in a new issue