Skip to content

fix(tv): open VLC with the account download token, not the access token - #281

Merged
altaywtf merged 2 commits into
mainfrom
fix/media-urls-download-token
Oct 2, 2026
Merged

altaywtf merged 2 commits into
mainfrom
fix/media-urls-download-token

Conversation

@altaywtf

@altaywtf altaywtf commented Oct 2, 2026

Copy link
Copy Markdown
Member

Change

TV Open in VLC now hands VLC a /files/{id}/stream URL that carries the account's download token instead of the session's full OAuth access token.

  • The URL gets its token from the same account/info?download_token=1 query playback already uses (loadMediaAccount), so no new fetch path was added.
  • If the account has no download token, the result is a typed DownloadTokenUnavailable and the existing "Couldn't prepare the stream" notice shows. The app never falls back to the access token.
  • A 401 from that lookup ends the session like any other files action, and other failures show Couldn't prepare the stream. <reason>.
  • Sweep of main, mobile and tv source sets: this was the only media URL that carried the session token. Downloads, offline playback and share-out send token-free API URLs with an Authorization header, and streaming playback already uses the download token. Those paths are unchanged.
  • Risk: Open in VLC now waits for one account-info request before it launches VLC.

Validation

  • ./gradlew verify :app:assembleMobileProductionDebug :app:assembleTvProductionDebug :app:assembleMobileNightlyDebug :app:assembleTvNightlyDebug: passed (1075 mobile and 747 TV unit tests)
  • SdkFilesStreamUrlsTest builds the URL through a real PutioClient with a stubbed account/info. With the builder reverted to client.config.accessToken, it fails with expected ...oauth_token=narrow-download-token but was ...oauth_token=full-oauth-access-token.
  • putio-tv emulator (API 36), signed in as the shared test identity: Files → Menu on an mp4 → Open in VLC reached the ready path ("VLC isn't installed", screenshot below). That shows the download-token lookup succeeded. VLC isn't installed on the image, so the URL VLC receives wasn't observed on device.
  • Proof pair: app 55b0da6 with a clean worktree, and SDK putio-sdk-kotlin main at 40736da (clean). The SDK API is unchanged; buildOriginalStreamUrl(fileId, accessToken: String) receives the download token's value.

Written by an agent (Claude Code, Opus 5.5)

Open in VLC built /files/{id}/stream with the session's full OAuth token.
It now loads the account's download token through the same account info
query playback uses; a missing token is a typed failure with no fallback.
Copilot AI balanced review requested due to automatic review settings October 2, 2026 09:55
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@altaywtf

altaywtf commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

TV emulator (putio-tv, API 36): Open in VLC on an mp4 reaches the ready path with the download token; the image has no VLC installed.

tv-open-in-vlc

Copilot AI 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.

Copilot review overview

🔵 Needs a closer look

It changes which credential is exposed to an external app (VLC) and the session-termination semantics, a security-sensitive area that relies on SDK token behavior not inspectable here, warranting human review.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

This PR hardens TV "Open in VLC" so the /files/{id}/stream URL handed to the external VLC app carries the account's narrow download token instead of the session's full OAuth access token. The token is fetched through the same account/info?download_token=1 lookup playback already uses (refactored into a shared PutioClient.loadMediaAccount() helper). FilesStreamUrls.originalStreamUrl becomes suspend and returns a typed FilesStreamUrlResult (Ready/DownloadTokenUnavailable/Failure) rather than a nullable String, with no fallback to the access token and a 401 ending the session like any other files action.

Changes:

  • Replace nullable stream-URL string with a suspend API returning a FilesStreamUrlResult sealed hierarchy; build the URL from the account download token.
  • Extract PutioClient.loadMediaAccount() and reuse it in both playback and stream-URL building.
  • Wire the TV UI to the new result type (launch in a coroutine scope, distinct notices, 401 → session verdict) and update docs/strings/tests.
File Description
app/​src/​main/​kotlin/​io/​putdotio/​android/​files/​FilesWatchedRepository.kt New FilesStreamUrlResult type; SdkFilesStreamUrls loads the download token and maps failures to the app taxonomy, with a redacting toString.
app/​src/​main/​kotlin/​io/​putdotio/​android/​playback/​PlaybackRepository.kt Adds shared PutioClient.loadMediaAccount() and reuses it.
app/​src/​tv/​kotlin/​io/​putdotio/​android/​tv/​TvSessionViewModel.kt originalStreamUrl now suspend, returns the result and records a 401 as the session verdict.
app/​src/​tv/​kotlin/​io/​putdotio/​android/​PutioApp.kt Launches the lookup in a coroutine scope and routes the three result cases to notices/session rejection.
app/​src/​tv/​kotlin/​io/​putdotio/​android/​tv/​files/​TvFilesAction.kt Doc comment updated to download token.
app/​src/​tv/​res/​values/​strings.xml Adds tv_files_stream_error for non-401 failures.
docs/​harness.md Documents the download-token behavior.
app/​src/​test/​.../​SdkFilesStreamUrlsTest.kt New test covering ready/missing-token/401 cases against a real PutioClient.
app/​src/​testTv/​.../​TvSessionViewModelTest.kt, app/​src/​androidTestTv/​.../​TvProofSession.kt, app/​src/​androidTestTv/​.../​TvAutoplayProofTest.kt Update stubs/assertions to the new result type.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +281 to +285
suspend fun originalStreamUrl(item: FilesItem): FilesStreamUrlResult =
streamUrls.originalStreamUrl(item.id).also { result ->
val failure = (result as? FilesStreamUrlResult.Failure)?.failure
if (failure is FilesFailure.AuthenticationRequired) mutableFileActionFailure.value = failure
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added in 65c8a8d: TvSessionViewModelTest now stubs a 401 stream lookup and asserts it becomes the session verdict that survives dismissal, which fails with the wiring removed. It also checks that a missing download token or another failure records no verdict.

@altaywtf
altaywtf merged commit 6aa1d0a into main Oct 2, 2026
1 check passed
@altaywtf
altaywtf deleted the fix/media-urls-download-token branch October 2, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants