Skip to content

Сохранение таймкода воспроизведения на стороне Торсервера - #818

Open
s1mptom wants to merge 7 commits into
YouROK:masterfrom
s1mptom:pr/auto-save-position
Open

Сохранение таймкода воспроизведения на стороне Торсервера#818
s1mptom wants to merge 7 commits into
YouROK:masterfrom
s1mptom:pr/auto-save-position

Conversation

@s1mptom

@s1mptom s1mptom commented Aug 4, 2026

Copy link
Copy Markdown

Дисклеймер: код честно навайбкожен (как и описание далее и плагин для лампы). Решал свою давнюю боль (и успешно решил). Буду рад если данный механизм появится в апстриме (мердж ПР или кто-то решит переписать под схожую или более правильную логику)

Сохранение позиции воспроизведения

Внешний плеер, который закрылся штатно, сообщает, где остановился. Тот, который не закрылся —
телевизор выключился на паузе, Android убил приложение — не сообщает ничего, и позиция
теряется. Больнее всего на сериалах: серию потом приходится искать заново.

Здесь позиция записывается на стороне сервера, из того, что и так видно по ридеру.

Включается в Настройках → сохранять позицию воспроизведения. Нужен ffprobe:
установщики предлагают его поставить, а без него переключатель заблокирован с пояснением.

Включить достаточно одну галку. TrackTimecode — флаг хранения таймкодов, доступный только
через API, — подразумевается автоматически, второй настройки искать не нужно.

Что и когда записывается

Пока файл отдаётся, ридер знает смещение в байтах, до которого он выдал данные клиенту. Это
не то место, где находится зритель: плееру отдано всё, что лежит у него в буфере, а смотрит
он позади конца этого буфера.

позиция на экране  =  голова ридера  −  буфер клиента

Смещение переводится в секунды по длительности файла — за этим и нужен ffprobe. Результат
кладётся в запись просмотра каждые 30 секунд и ещё раз при завершении потока.

От мусора в записи защищают три условия:

  • Сессия короче 20 секунд игнорируется — это проба или чтение метаданных, а не просмотр.
  • Голова, не ушедшая дальше буфера, игнорируется: сыграно ещё ничего не было.
  • В пределах 8 МБ от конца файл считается досмотренным, и позиция ставится равной его длине,
    а не «почти концу» — иначе такое кино предлагалось бы продолжить.

Время меряется двумя способами, берётся большее. Как долго клиент держал поток, зависит от
управления потоком TCP; протяжённость реальных чтений — от того, как быстро торрент отдаёт
данные. Каждая величина по отдельности способна обмануть.

ffprobe спрашивается о файле один раз и не чаще, чем раз в десять минут, по одному процессу
за раз. Он читает через тот же самый эндпоинт отдачи, поэтому его собственный запрос несёт
пометку, исключающую его из всего вышеописанного. Без этого замер запускал бы замер.

Как меряется буфер

Чтобы вычесть буфер, его надо знать, но не все плееры о нём не сообщают (или позволяют его настраивать). Зато за ним можно
понаблюдать.

Плеер набивает буфер настолько быстро, насколько позволяет сеть, а затем переходит к
запросам примерно с той скоростью, с какой потребляет данные. Значит на достаточно длинном
отрезке ридер движется со скоростью воспроизведения, а величина, на которую он опережает
зрителя, и есть буфер:

буфер  =  максимум за сессию от  [ (прочитано − старт) − скорость × время ]

Скорость берётся с хвоста сессии, усредняется по 30-секундным окнам и принимается только
когда два соседних окна сходятся в пределах 40%. Иначе торрент, который подвис и потом
нагнал, был бы прочитан как медленный плеер.

Оценка идёт в дело лишь после того, как воспроизведение наблюдалось не меньше времени, чем
набирался отрыв: до этого отрыв неотличим от первоначального наполнения буфера. Результат
ограничен диапазоном от 4 МБ до 1 ГБ.

Замер против Vimu на 4K-ремуксе: 115 МБ при 100 МБ, выставленных в самом плеере — разница
это то, что плеер держит сверх собственной настройки.

Автозамер — отдельный переключатель, по умолчанию выключен. Когда он выключен или пока ему
не хватает данных, берётся заданное запасное значение (32 МБ, если не менять).

Плагин для Lampa

Вторая половина для пользователей Lampa: сервер хранит
позицию, плагин возвращает её в интерфейс.

https://github.com/s1mptom/lampa-ts-resume

Прямая ссылка для добавления в Lampa (Настройки → Плагины → по ссылке):

https://raw.githubusercontent.com/s1mptom/lampa-ts-resume/main/lampa_ts_resume.js

Плагин обращается только к тому адресу TorrServer, который уже прописан в настройках Lampa,
и ничего больше не требует.

Пара деталей, объясняющих, почему он вообще понадобился. Открытие торрента не поднимает в
Lampa активити — это модалка, — поэтому плагин цепляется к Torserver.files(hash), через
которую проходят все пути в список файлов. И записывает он два ключа таймлайна: карточка
хеширует серию по оригинальному названию сериала, а список файлов торрента — по
оригинальному заголовку, и правка одного оставляет второй протухшим. Выглядит это как
полоска прогресса, которая сдвинулась на одном экране и не сдвинулась на другом.

Pavel Turbin added 7 commits July 26, 2026 22:07
On reader close, save the on-screen position (read head minus the client's
buffer) into the viewed record as a 0..1 byte-fraction of the file. Duration-free
(no ffprobe): consumers multiply by media runtime. Gated by new BTSets fields
SavePosition / BufferSizeMB / AutoBuffer; a preload/probe (read < buffer) is skipped.

Reader gains startOffset (first Range-seek target) for future auto-buffer measurement.
TorrServer now records where playback actually was, so a position survives the
player never reporting back - a TV powering off, an app being killed, or an
external player that has no callback.

While a stream runs and when it ends, the position is taken as the read head
minus the client's buffer and stored in the viewed data in seconds, using the
file's real duration from ffprobe (which the feature requires, and which the
installers now offer to install). Offset, length and duration are stored
alongside so clients can show progress without guessing the runtime.

The client buffer is measured from how the player loads data: it fills as fast
as it can, then settles to the playback rate, and the difference is the buffer.
A configured size is used when that measurement is not available. Verified
against a simulated player: within ~1.2s of the true on-screen position.

Probes, preloads and short requests are ignored, and our own ffprobe stream is
marked so probing can never recurse into itself. Marking a file as viewed no
longer clears a stored position.
…able

The buffer measurement now reports what it derived and from what (fill size and
duration, measured playback rate), so the value can be reviewed after a session.

The fallback size stays editable while automatic measurement is on, since it is
what gets used until the measurement becomes available - roughly the first
minute of playback. It is labelled as the fallback in that state.
…t sooner

The buffer log was emitted for every stream, including the many short probe and
preload requests that never reach the save, which buried the useful lines. It is
now written where the value is actually used.

The steady playback phase needed before the buffer can be derived drops from 60s
to 30s: real sessions were ending before the measurement became available and
falling back to the configured size.
Unexport setDuration and probeMarker, which are only used inside the torr
package. Rename durationKey to fileKey, since both caches are keyed by it.
Name the fallback buffer constant instead of repeating a literal, and report
saving under the same [Position] prefix as the buffer measurement.

Also drop the unreachable tail of ListViewed, which go vet flags in the file
this branch rewrites, and rebuild the web assets with the locked dependency
versions.
…rthy

The playback rate is multiplied by the fill duration, so an error in the rate
grows with however long filling took. A slow line fills slowly, which made the
estimate noisy exactly where it is least affordable.

The steady phase now has to last at least as long as the fill did, and never
less than the previous 30s. Until then the configured size is used.
Filling was declared over at the first pause longer than two seconds, but a
client tops its buffer up in bursts, so pauses are normal, and a brief network
hiccup while filling looks exactly like one. That ended the measurement early
and understated the buffer, which moves the saved position forward - past
content the viewer had not reached.

The buffer is how far the reader runs ahead of playback, so it is now taken as
the largest such lead across sampled positions: bytes read beyond the start,
minus what playback consumed in the same time. A hiccup simply is not the
maximum, and neither is the low point of a top-up cycle. Playback speed comes
from the tail of the session and is only trusted once two consecutive windows
agree, which is what separates filling from playing at its own pace.

Verified against a simulated player reporting its true on-screen position:
within 1.5s of it.
@VuzzyM

VuzzyM commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

I don't think you need a plugin for TrackTimecode anymore. The Lampa already supports that natively: yumata/lampa-source#367

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