Files
plezy/test/utils/downloaded_version_match_test.dart
T
edde746 fd4291aafb fix(playback): play the downloaded version when offline close #1440
Plain Play requests the default media version (index 0 or the saved
preference), but the offline resolvers rejected the single downloaded
row when a non-default version was downloaded, then threw "No video
URL available" with no client to fall back to.

Three-part fix sharing one matcher (downloadedVersionMatches):
- getPlaybackData falls back to the downloaded version when there is no
  client to stream from; the result now carries the effective
  mediaIndex/mediaSourceId so cached media info and the committed
  session describe the file actually played.
- navigateToVideoPlayer seeds the selection from the download record
  for isOffline plays with no explicit version, covering the external
  player branch and offline-library plays with a reachable server.
- PlaybackSession.fromContext prefers the result source id over the
  requested one, keeping in-player state in sync after a fallback.

Online pinning is untouched: with a live client an explicitly requested
non-downloaded version still streams from the server.
2026-07-03 10:19:55 +02:00

83 lines
2.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/models/download_models.dart';
import 'package:plezy/utils/downloaded_version_match.dart';
DownloadedMediaItem _row({int mediaIndex = 0, String? mediaSourceId}) {
return DownloadedMediaItem(
id: 1,
serverId: 'srv',
ratingKey: 'movie-1',
globalKey: 'srv:movie-1',
type: 'movie',
status: DownloadStatus.completed.index,
progress: 100,
downloadedBytes: 0,
retryCount: 0,
mediaIndex: mediaIndex,
mediaSourceId: mediaSourceId,
);
}
void main() {
group('downloadedVersionMatches', () {
test('source id wins when both sides have one', () {
// Equal ids match even when the index disagrees (Jellyfin merged
// versions reorder between fetches).
expect(
downloadedVersionMatches(
_row(mediaIndex: 1, mediaSourceId: 'src-a'),
requestedMediaIndex: 0,
requestedMediaSourceId: 'src-a',
),
isTrue,
);
// Different ids reject even when the index agrees.
expect(
downloadedVersionMatches(
_row(mediaIndex: 0, mediaSourceId: 'src-a'),
requestedMediaIndex: 0,
requestedMediaSourceId: 'src-b',
),
isFalse,
);
});
test('falls back to index when either side lacks a source id', () {
// Legacy pre-v15 row: NULL source id.
expect(
downloadedVersionMatches(_row(mediaIndex: 0), requestedMediaIndex: 0, requestedMediaSourceId: 'src-a'),
isTrue,
);
expect(
downloadedVersionMatches(_row(mediaIndex: 1), requestedMediaIndex: 0, requestedMediaSourceId: 'src-a'),
isFalse,
);
// Caller without a source id.
expect(downloadedVersionMatches(_row(mediaIndex: 1, mediaSourceId: 'src-a'), requestedMediaIndex: 1), isTrue);
expect(downloadedVersionMatches(_row(mediaIndex: 1, mediaSourceId: 'src-a'), requestedMediaIndex: 0), isFalse);
});
test('blank requested source id is treated as absent', () {
expect(
downloadedVersionMatches(
_row(mediaIndex: 0, mediaSourceId: 'src-a'),
requestedMediaIndex: 0,
requestedMediaSourceId: ' ',
),
isTrue,
);
});
test('null requested index means any version', () {
expect(downloadedVersionMatches(_row(mediaIndex: 1, mediaSourceId: 'src-a')), isTrue);
expect(downloadedVersionMatches(_row(mediaIndex: 1)), isTrue);
// But a source-id mismatch still rejects.
expect(
downloadedVersionMatches(_row(mediaIndex: 1, mediaSourceId: 'src-a'), requestedMediaSourceId: 'src-b'),
isFalse,
);
});
});
}