From f29030c3a08cdec4abba3ff8dc40d59843441b8e Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:15:53 +0200 Subject: [PATCH] fix: correct jellyfin trickplay sheet geometry close #1417 --- lib/services/jellyfin_trickplay_service.dart | 21 +++++++------------ .../jellyfin_trickplay_service_test.dart | 15 ++++++------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/lib/services/jellyfin_trickplay_service.dart b/lib/services/jellyfin_trickplay_service.dart index 9685d9a4..9051adbc 100644 --- a/lib/services/jellyfin_trickplay_service.dart +++ b/lib/services/jellyfin_trickplay_service.dart @@ -1,4 +1,3 @@ -import 'dart:math' as math; import 'dart:ui'; import 'package:cached_network_image_ce/cached_network_image.dart'; @@ -77,9 +76,8 @@ class JellyfinTrickplayService implements ScrubPreviewSource { } /// Pure math helper exposed for unit tests: maps a timestamp to the - /// sheet index, tile coordinates within the sheet, and the sheet's - /// (possibly partial) row/column count. Returns `null` only when the - /// manifest is degenerate. + /// sheet index, tile coordinates within the sheet, and the sheet's full + /// row/column count. Returns `null` only when the manifest is degenerate. TrickplayTileLocation? tileLocationFor(Duration time) { if (_disposed) return null; final info = _info; @@ -94,17 +92,12 @@ class JellyfinTrickplayService implements ScrubPreviewSource { final tileColumn = tileInSheet % info.tileWidth; final tileRow = tileInSheet ~/ info.tileWidth; - final firstThumbInSheet = sheetIndex * tilesPerSheet; - final thumbsInSheet = math.min(tilesPerSheet, info.thumbnailCount - firstThumbInSheet); - final sheetColumns = thumbsInSheet >= info.tileWidth ? info.tileWidth : thumbsInSheet; - final sheetRows = (thumbsInSheet + info.tileWidth - 1) ~/ info.tileWidth; - return TrickplayTileLocation( sheetIndex: sheetIndex, tileColumn: tileColumn, tileRow: tileRow, - sheetColumns: sheetColumns, - sheetRows: sheetRows, + sheetColumns: info.tileWidth, + sheetRows: info.tileHeight, sourceTileSize: Size(info.width.toDouble(), info.height.toDouble()), ); } @@ -176,9 +169,9 @@ class JellyfinTrickplayService implements ScrubPreviewSource { } /// Pure data: which sheet to fetch, which tile within it to display, the -/// sheet's (possibly partial) row/column count, and the source tile's -/// pixel dimensions for aspect-correct scaling at render time. Returned -/// by [JellyfinTrickplayService.tileLocationFor] for unit tests. +/// sheet's row/column count, and the source tile's pixel dimensions for +/// aspect-correct scaling at render time. Returned by +/// [JellyfinTrickplayService.tileLocationFor] for unit tests. class TrickplayTileLocation { final int sheetIndex; final int tileColumn; diff --git a/test/services/jellyfin_trickplay_service_test.dart b/test/services/jellyfin_trickplay_service_test.dart index 5dde0e1e..56f3fbd4 100644 --- a/test/services/jellyfin_trickplay_service_test.dart +++ b/test/services/jellyfin_trickplay_service_test.dart @@ -95,7 +95,7 @@ void main() { // 250 thumbnails, 1s apart, 10×10 tiles per sheet ⇒ 3 sheets. // sheet 0: indices 0..99 // sheet 1: indices 100..199 (full) - // sheet 2: indices 200..249 (last sheet, only 50 thumbs ⇒ 5 rows) + // sheet 2: indices 200..249 (last sheet, padded to a full 10x10 grid) setUp(() async { client = await JellyfinClient.create(_conn()); @@ -143,9 +143,10 @@ void main() { expect(loc.sheetIndex, 2); expect(loc.tileColumn, 9); expect(loc.tileRow, 4); - // Sheet 2 has 50 thumbs ⇒ 5 rows, full 10 cols + // Jellyfin pads the final tile image to the full configured grid, so + // the renderer must keep full sheet geometry even for partial sheets. expect(loc.sheetColumns, 10); - expect(loc.sheetRows, 5); + expect(loc.sheetRows, 10); }); test('clamps past the end to the last available thumbnail', () { @@ -168,7 +169,7 @@ void main() { }); }); - group('JellyfinTrickplayService — partial last sheet sizing', () { + group('JellyfinTrickplayService — padded last sheet sizing', () { late JellyfinClient client; setUp(() async { @@ -177,7 +178,7 @@ void main() { tearDown(() => client.close()); - test('last sheet with 1 thumbnail reports 1 col × 1 row', () { + test('last sheet with 1 thumbnail still reports the full sheet grid', () { // 17 thumbs, 4×4 sheet ⇒ sheet 0 full (16), sheet 1 has 1 thumb only. final svc = JellyfinTrickplayService.create( client: client, @@ -192,8 +193,8 @@ void main() { expect(loc.sheetIndex, 1); expect(loc.tileColumn, 0); expect(loc.tileRow, 0); - expect(loc.sheetColumns, 1); - expect(loc.sheetRows, 1); + expect(loc.sheetColumns, 4); + expect(loc.sheetRows, 4); }); });