fix: report YouTube videos that never load instead of keeping the previous one - #590
Draft
FelipeDefensor wants to merge 2 commits into
Draft
fix: report YouTube videos that never load instead of keeping the previous one#590FelipeDefensor wants to merge 2 commits into
FelipeDefensor wants to merge 2 commits into
Conversation
…vious one Loading is fire-and-forget: _engine_load_media fires loadVideo() into the page and returns True unconditionally, so on_media_load_done always runs and TiLiA records the new URL as loaded. YouTube reports some failures through onError, but not all of them -- loading an unplayable video over one that is already playing leaves the previous video in place and raises nothing. The file then appears to open cleanly while the old video keeps playing under the new URL, and the duration query happily picks up the old video's duration. The load is now checked rather than assumed. getLoadState() reports which video the player actually holds alongside its duration, and each load polls it until that video is the requested one. A load that never arrives is reported to the user and unloads the media, so the player stops claiming a video it does not have. media_path is deliberately restored after unload_media(): it is what the file declares, and clearing it would drop the URL from the file on the next save. Two things fall out of routing every load through the poll: - PlayerTracker no longer queries the duration on UNSTARTED. That query could not detect a failed load (a stale video never changes state) and would now race the poll into a duplicate error. It loses the page, on_duration_available and get_video_id constructor arguments with it. - The loadFinished connection for a deferred load is single-shot. A plain connect left load_video attached and re-ran it on every later page load, which would now also re-start its poll. The player needs a real QWebEngineView, the IFrame API and a network connection, so the tests stand in a stub view and drive the load-outcome logic. The user-visible scenario is in the repro bundle.
Two fixtures and their generator, plus the acceptance criteria. Delete the .tla files before merge -- the generator is the durable part.
FelipeDefensor
marked this pull request as draft
August 27, 2026 19:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Reported while testing #450, but pre-existing on
dev— the code involved is untouched by that PR.Open a file with a valid YouTube URL, then open a file whose URL points at a video that cannot be played. No error appears, the file looks like it opened, and the previous video is still loaded and playing — while TiLiA reports the new URL as the loaded media. The duration query then picks up the old video's duration and applies it to the new file.
Why it happens
Two independent things:
_engine_load_mediafiresloadVideo()into the page andreturn Trueunconditionally. Loading is asynchronous, soPlayer.load_mediaalways takes the success path:on_media_load_donesetsmedia_path, postsPLAYER_URL_CHANGEDand setsis_media_loaded = Truebefore anything knows whether the video exists.onError, but not all of them. Loading an unplayable video over one that is already playing leaves the previous video in place and raises nothing. On a fresh session the same file does produce an error, which is why the failure only shows up on the second open.So the load has to be checked, not assumed.
The fix
getLoadState()reports which video the player actually holds alongside its duration. Every load polls it (500 ms, up to ~10 s) until that video is the requested one. A load that never arrives is reported to the user and unloads the media, so the player stops claiming a video it does not have.media_pathis deliberately restored afterunload_media(): it is what the file declares, and clearing it would drop the URL from the file on the next save.Two things fall out of routing every load through the poll:
PlayerTrackerno longer queries the duration onUNSTARTED. That query could not detect a failed load — a stale video never changes state — and would now race the poll into a duplicate error. It loses thepage,on_duration_availableandget_video_idconstructor arguments with it.loadFinishedconnection for a deferred load is single-shot. A plainconnectleftload_videoattached and re-ran it on every later page load, which would now also re-start its poll.Repro bundle
Following the guideline in #580 (not merged yet, but this PR is a fix that wants human testing).
One-time setup — not part of the launch line, since re-running it aborts when the branch is checked out in another worktree and
--forcediscards local edits:Then, from the repo root:
uv run tilia "$PWD/repro/yt-ok.tla"The reported scenario: with
yt-ok.tlaplaying, File → Openrepro/yt-missing-video.tla.The two other criteria — a fresh session with the missing video, and a regression check that a video which does load still loads — are in
repro/REPRO.md. Both fixtures come out of TiLiA's own CLI viarepro/build.sh; no.tlaJSON is hand-authored. Deleterepro/*.tlabefore merge.Base-branch check: the same two files on
devshow the broken behavior. The fixtures are branch-agnostic, sogit switch devand relaunch is the whole comparison.Tests
tests/player/test_youtube_player.py, 10 tests. The player needs a realQWebEngineView, the IFrame API and a network connection, so they stand in a stub view and drive the load-outcome logic: failure is reported, media is unloaded,media_pathsurvives, a slow load is not mistaken for a failure, and a poll for a superseded video is ignored.Full suite passes serially. Under
-n auto13 unrelated tests fail on this branch and pass on their own — the known local xdist flakiness, not this change.Related
fix/youtube-error-messagesrewrites the wording of theonErrormessages. It touches a different part ofyoutube.htmland does not overlap with this.