Skip to content

Sozo 3.2.1: Telegram-style desktop corner player, no endless loading, downloads and reconnect fixes - #46

Open
professorDeveloper wants to merge 10 commits into
masterfrom
release/3.2.1
Open

professorDeveloper wants to merge 10 commits into
masterfrom
release/3.2.1

Conversation

@professorDeveloper

@professorDeveloper professorDeveloper commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Sozo 3.2.1

Fixes for every real problem reported in support since 3.2.0, plus the Telegram-style desktop corner player.

Desktop: corner player (Telegram-style PiP)

  • On macOS/Windows/Linux, the PiP button (or P) docks the running player into a small floating window in the corner of the app. You can drag it and resize it, and it stays on screen while you move between pages (home, search, details, settings).
  • Clicking the window brings back full-screen playback. Closing it stops playback and saves history.
  • The live player state moves over without reloading the video. Continue Watching keeps updating while the player is docked.
  • The old whole-window mini player is still there as a secondary pop-out (Shift+click / long-press on the PiP button).
  • Mobile and TV paths are unchanged: everything is behind isDesktopPlatform, and Android native PiP works as before.

Player: no endless "Video loading", recovers from dropped connections

  • Each server gets a start deadline. If the video does not start in time, the player moves to the next server automatically with a short notice. When every server fails, it shows a clear localized message.
  • mpv reconnect options are enabled. After a network drop ("Connection reset by peer") playback resumes from the same position.
  • ExoPlayer initialize() is bounded and can no longer hang forever.
  • Quality pinning on ExoPlayer: picking 1080p really locks to the 1080p variant, and Auto shows plain "Auto" on the platform player.
  • media_kit redraw fix after resume.

Downloads: judge a web page by its bytes, not its Content-Type

  • Some hosts (vidapi, anikoto) serve real MPEG-TS segments as text/html. Both the Kotlin foreground service and the Dart transfer engine now sniff the first 512 bytes and refuse only real markup.
  • A tap-time probe gives a three-way verdict: OK / web page / unknown. Clear message DownloadBuildFailure.webPage.
  • Downloads now use the upstream URL instead of the local proxy URL. Sibling qualities keep their extractor.

CloudStream

  • PluginHost falls back to paged search for plugins that only implement it (MovieBox threw NotImplementedError).

Fixes from a pre-PR review

  • Downloads: when the default mirror serves a web page, the episode now fails only if every mirror does. Otherwise the bad mirrors are dropped from the choice. The film page shows the right message, and a relayed stream offers its upstream URL for download.
  • Player: a file cut short by the network reports the error instead of stopping silently. A live stream reconnects at once after a drop. Torrents from torrent search get the 90 s torrent start budget.
  • Corner player: restoring, closing or opening another title during the exit animation no longer puts the same page in two places. Signing out and switching profile close the corner player before account data is wiped.

CI

  • Preview cache prunes run one at a time. This fixes the flaky preview_grid_test that turned master red.

Server-side (already deployed in sozo-backend, listed for context)

  • asilmedia: new file host fayllar.com is signed.
  • TMDB merged/deleted ids follow the redirect.
  • primesrc v3/v4: WebView directive and embed-slash fix.
  • anikage: new servers.
  • Media-segment relay.
  • New provider: Sports Live TV (iptv-org sports channels, only working ones listed, Futbol TV UZ and Setanta Eurasia first). Shipped without a push announcement.
  • rareanimes: back. The site moved posts from /hindi/<slug>/ to /<slug>/.
  • multimovies: back. New domain (m1.multimovies.courses) and new site theme; the iqsmartgames video chain behind it is unchanged.
  • Support: "Could not open codec" on mpv (an AV1 file on hentaiocean, a downloaded uhdmovies episode) is covered by this release's automatic switch to the platform player on Android.

Support tickets covered

Report Fix
Downloads fail with "The source sent a web page instead of the video" (several tickets) Downloads sniffing
"Video loading" forever / request to switch servers automatically Start deadline + auto-switch
"Connection reset by peer" mid-playback Reconnect + resume at position
1080p looks like 480p ExoPlayer quality pinning
MovieBox search error CloudStream paged search
primesrc "Failed to recognize file format" Server v3/v4
TMDB 404 page Server
anikage / asilmedia not playing Server

Tests

  • flutter analyze: clean
  • flutter test: 2387/2387 passed
  • Kotlin unit tests: download tests (13) and PluginSearchTest (6) pass
  • New tests:
    • Flutter: web_page_sniff, transfer_engine, download_media_probe, download_request_builder, download_failure, download_choices_proxy, player_dock_geometry, player_dock_host, player_dock_source, plus the player readiness/retry tests
    • Kotlin: WebPageSniffTest

Notes for release

  • After merging, release with bump: patch (→ 3.2.1), then set the in-app updater in admin → AppVersions.
  • Downloads that already failed keep their old URL. Users should delete them and download again on 3.2.1.

Each new episode folder started a prune without waiting. Two running side
by side picked the same oldest folders; the second delete threw, and the
catch around the whole loop gave up on the rest, leaving 25 of 24. That
was the flaky preview_grid_test that turned master's CI red. Prunes are
chained now, and each delete is guarded on its own.
On macOS, Windows and Linux the player can now dock into a small floating
box in a corner of the app window. It keeps playing while the viewer moves
around Home, Search, a detail page or anything else, because it lives in a
layer above the Navigator (PlayerDockHost, in MaterialApp.router's builder).

Nothing is handed over or restarted. /player now builds PlayerRoute, which
gives PlayerPage a GlobalKey. Docking pops the route, and the same State
moves into the host in the same frame. Expanding pushes /player again, and
the route takes the key back. History ticks, auto-advance, subtitles, party
sync and retries carry on as before, and closing the corner player disposes
the page normally.

- The corner box can be dragged, and on release it snaps to the nearest
  corner, taking the fling into account. Its inner edges resize it, between
  240 px and 60% of the window. The corner and width are saved.
- Hover controls: close, back to the full player, the title, previous and
  next episode, play/pause, 10-second steps when there is room, the time,
  and a timeline that seeks on click or drag.
- I or the PiP button docks the player. Shift+I or a long press keeps the
  old whole-window always-on-top mini, which is still the only way to watch
  over other apps.
- While docked, the keyboard stays with the page underneath. Clicking the
  corner player takes the keyboard. Esc or a click elsewhere gives it back
  without closing the video. I expands.
- Opening another title closes the docked one first. Signing out,
  switching profile and onboarding close it too.
- Fix: in the always-on-top mini window, regaining focus cleared _isPip and
  drew the full control bar into the 426-px window.

Mobile and TV are unchanged. PlayerDock.supported is desktop-only, and the
phone PiP paths are untouched.
'The source sent a web page instead of the video' failed whole episodes
on vidapi and anikoto. Their CDNs serve real MPEG-TS segments as
page-N.html / seg-N.html with Content-Type text/html; the player never
reads that header and plays them, but both download engines refused
them by it.

- Kotlin and Dart engines read the first bytes and refuse only markup
  (<!doctype html, <html, <head, …); TS, fMP4, MKV, QuickTime and
  encrypted segments pass. A real web page fails once, without retries.
- classify() no longer calls a network error 'web page' because a
  segment URL ends in .html.
- A direct download is probed at tap time and refused there when it is
  a web page; a probe with no answer never blocks.
- Mirrors under a WebView directive are sniffed instead of being taken
  as direct, and a locally proxied stream offers its upstream URL.
- libmpv reconnects a dropped HTTP/HLS stream itself, and a drop is
  only fatal once playback actually stops; then the player retries at
  the same position a few times before saying anything. 'Connection
  reset by peer' after two minutes no longer ends the episode.
- Every server gets a start deadline (longer for torrents and WebView
  sniffing). Past it the player moves to the next server with a short
  notice, and when none plays it says so instead of 'Video loading…'
  forever. ExoPlayer's initialize() is bounded too.
- Library error sentences are mapped to localized messages.
- A labelled or remembered quality is pinned on ExoPlayer instead of
  ABR starting at the lowest rung, and Auto no longer claims the size
  it started at.
- media_kit: the picture is forced to redraw once the texture exists, so
  a stream no longer stays black until the first seek.
MovieBox overrides only the paged search, and our host called the plain
one, which CloudStream leaves as NotImplementedError. The host now falls
back to the paged search (and the other way round).
… and the corner player; Continue Watching keeps up while docked
The tap-time probe failed the whole episode when the default mirror served a
web page, even with a working mirror the choice sheet would have offered. Now
only when every mirror does; otherwise the pages are dropped from the choice.
The film page shows the web-page message instead of "play it first", and a
stream played through the media-segment relay offers its upstream url for
download rather than the 127.0.0.1 address that dies with the player.
…t once

An end of file is judged before playhead movement: the cache carries the
playhead past the drop, so a file the connection ended early always looked
recovered and stopped silently mid-episode. The last tcp error is kept for
that judgement. A live stream is no longer held through the 25 s grace once
playing, and an end of a stream without length after a drop is fatal, so it
reconnects. Torrents opened from torrent search get the torrent start budget.
Restoring, closing or opening another title while the left /player route was
still animating out rebuilt the page there too: the same GlobalKey twice, or
the page pulled into a dying route. Signing out and switching profile now
close the corner player first, so its last history save lands before the
account's rows are wiped instead of after.
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.

1 participant