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.
124 lines
4.8 KiB
Dart
124 lines
4.8 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/media/media_backend.dart';
|
|
import 'package:plezy/media/media_item.dart';
|
|
import 'package:plezy/media/media_kind.dart';
|
|
import 'package:plezy/media/media_version.dart';
|
|
import 'package:plezy/models/transcode_quality_preset.dart';
|
|
import 'package:plezy/services/playback_context.dart';
|
|
import 'package:plezy/services/playback_initialization_types.dart';
|
|
import 'package:plezy/services/playback_session.dart';
|
|
|
|
PlaybackContext _context(PlaybackInitializationResult result) {
|
|
return PlaybackContext(
|
|
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
|
result: result,
|
|
sourceKind: result.usesLocalMedia ? PlaybackSourceKind.localFile : PlaybackSourceKind.remoteDirect,
|
|
reportingMode: PlaybackReportingMode.online,
|
|
streamHeaders: const {'X-Test': 'token'},
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('PlaybackSession.fromContext', () {
|
|
test('keeps the requested preset when no fallback occurred', () {
|
|
final session = PlaybackSession.fromContext(
|
|
_context(PlaybackInitializationResult(availableVersions: const [], videoUrl: 'u')),
|
|
requestedQualityPreset: TranscodeQualityPreset.p1080_8mbps,
|
|
);
|
|
expect(session.qualityPreset, TranscodeQualityPreset.p1080_8mbps);
|
|
});
|
|
|
|
test('falls back to original when the backend rejected the preset', () {
|
|
final session = PlaybackSession.fromContext(
|
|
_context(
|
|
PlaybackInitializationResult(
|
|
availableVersions: const [],
|
|
videoUrl: 'u',
|
|
fallbackReason: TranscodeFallbackReason.values.first,
|
|
),
|
|
),
|
|
requestedQualityPreset: TranscodeQualityPreset.p1080_8mbps,
|
|
);
|
|
expect(session.qualityPreset, TranscodeQualityPreset.original);
|
|
});
|
|
|
|
test('an original-quality request ignores the fallback reason', () {
|
|
final session = PlaybackSession.fromContext(
|
|
_context(
|
|
PlaybackInitializationResult(
|
|
availableVersions: const [],
|
|
videoUrl: 'u',
|
|
fallbackReason: TranscodeFallbackReason.values.first,
|
|
),
|
|
),
|
|
requestedQualityPreset: TranscodeQualityPreset.original,
|
|
);
|
|
expect(session.qualityPreset, TranscodeQualityPreset.original);
|
|
});
|
|
|
|
test('refines the media source id from the clamped version index', () {
|
|
final versions = [MediaVersion(id: 'v0'), MediaVersion(id: 'v1')];
|
|
final session = PlaybackSession.fromContext(
|
|
_context(PlaybackInitializationResult(availableVersions: versions, videoUrl: 'u', selectedMediaIndex: 1)),
|
|
requestedQualityPreset: TranscodeQualityPreset.original,
|
|
requestedMediaSourceId: 'requested',
|
|
);
|
|
expect(session.mediaSourceId, 'v1');
|
|
expect(session.mediaIndex, 1);
|
|
});
|
|
|
|
test('keeps the requested source id when the index is out of range', () {
|
|
final session = PlaybackSession.fromContext(
|
|
_context(PlaybackInitializationResult(availableVersions: const [], videoUrl: 'u', selectedMediaIndex: 2)),
|
|
requestedQualityPreset: TranscodeQualityPreset.original,
|
|
requestedMediaSourceId: 'requested',
|
|
);
|
|
expect(session.mediaSourceId, 'requested');
|
|
});
|
|
|
|
test('prefers the result source id over derived and requested ids', () {
|
|
// Offline fallback playback: the result names the downloaded version,
|
|
// which must win over the (stale) requested id even when a version
|
|
// list would derive something else.
|
|
final versions = [MediaVersion(id: 'v0'), MediaVersion(id: 'v1')];
|
|
final session = PlaybackSession.fromContext(
|
|
_context(
|
|
PlaybackInitializationResult(
|
|
availableVersions: versions,
|
|
videoUrl: 'u',
|
|
selectedMediaIndex: 1,
|
|
selectedMediaSourceId: 'downloaded',
|
|
),
|
|
),
|
|
requestedQualityPreset: TranscodeQualityPreset.original,
|
|
requestedMediaSourceId: 'requested',
|
|
);
|
|
expect(session.mediaSourceId, 'downloaded');
|
|
});
|
|
});
|
|
|
|
test('forwarding getters mirror the resolver output', () {
|
|
final result = PlaybackInitializationResult(
|
|
availableVersions: [MediaVersion(id: 'v0')],
|
|
videoUrl: 'u',
|
|
isTranscoding: true,
|
|
playSessionId: 'psid',
|
|
playMethod: 'Transcode',
|
|
activeAudioStreamId: 7,
|
|
);
|
|
final session = PlaybackSession.fromContext(
|
|
_context(result),
|
|
requestedQualityPreset: TranscodeQualityPreset.original,
|
|
);
|
|
|
|
expect(session.isTranscoding, isTrue);
|
|
expect(session.isOffline, isFalse);
|
|
expect(session.playSessionId, 'psid');
|
|
expect(session.playMethod, 'Transcode');
|
|
expect(session.audioStreamId, 7);
|
|
expect(session.availableVersions, hasLength(1));
|
|
expect(session.streamHeaders, containsPair('X-Test', 'token'));
|
|
expect(session.metadata.id, 'item-1');
|
|
});
|
|
}
|