Files
plezy/lib/models/transcode_quality_preset.dart
edde746 1ee648f442 fix(plex): direct play a source the quality preset already covers
Any quality preset other than Original started a transcode even when the
file was already well under it: a 6.2 Mbps 1080p h264 source under the
"1080p 10 Mbps" preset came back from PMS as a 10.4 Mbps encode, so capping
quality on mobile data spent more data than leaving it uncapped, plus server
CPU nobody asked for (#2152).

A preset is a ceiling, so a version that fits under both its bitrate and its
resolution is now served by the file itself, on the same direct-play path
Original takes, and no transcode decision is sent at all. Height counts as
well as bitrate because the preset's label promises a resolution too, and a
device that asked for 1080p may not decode the 4K source a bitrate-only
comparison would hand it. An unreported bitrate or height leaves the
transcode standing.

The comparison has to happen in the client: PMS answers directPlay=1 with
"Direct play OK" whatever bitrate cap the request carries, measured on 1.43
with a 65 Mbps 4K source under a 10 Mbps cap, so its MDE cannot be asked to
enforce one. Plex Web reaches its own directPlay/directStream flags the same
way, folding the preset's bitrate into its client-side direct-play profile
first. Jellyfin already gets this right server-side.

close #2152
2026-08-27 20:37:56 +02:00

88 lines
3.5 KiB
Dart

/// Video transcode quality presets modeled on Plex Web's custom-quality table.
///
/// When a non-[original] preset is selected, playback asks the active backend
/// for a capped transcode stream. [original] bypasses transcoding entirely and
/// uses the direct-play URL.
enum TranscodeQualityPreset {
original(null, null, null),
p240_320(320, '420x240', 30),
p320_720(720, '576x320', 40),
p480_1_5mbps(1500, '720x480', 60),
p720_2mbps(2000, '1280x720', 60),
p720_3mbps(3000, '1280x720', 75),
p720_4mbps(4000, '1280x720', 100),
p1080_8mbps(8000, '1920x1080', 60),
p1080_10mbps(10000, '1920x1080', 75),
p1080_12mbps(12000, '1920x1080', 90),
p1080_20mbps(20000, '1920x1080', 100);
const TranscodeQualityPreset(this.videoBitrateKbps, this.videoResolution, this.videoQuality);
final int? videoBitrateKbps;
final String? videoResolution;
final int? videoQuality;
bool get isOriginal => this == TranscodeQualityPreset.original;
/// Resolution height (e.g. 720, 1080) parsed from [videoResolution]. Null for original.
int? get resolutionHeight {
final r = videoResolution;
if (r == null) return null;
final parts = r.split('x');
if (parts.length != 2) return null;
return int.tryParse(parts[1]);
}
/// Whether a source of [bitrateKbps] and [heightPx] already fits inside this
/// preset's ceiling, so re-encoding it could not improve on the file.
///
/// Height counts as well as bitrate, because the label promises a resolution
/// too and a device that asked for 1080p may not decode the 4K source a
/// bitrate-only comparison would pass. An unreported bitrate or height
/// answers false, as does [original], which has no ceiling.
bool coversSource({required int? bitrateKbps, required int? heightPx}) {
final capKbps = videoBitrateKbps;
final capHeight = resolutionHeight;
if (capKbps == null || capHeight == null) return false;
if (bitrateKbps == null || bitrateKbps <= 0 || heightPx == null || heightPx <= 0) return false;
return bitrateKbps <= capKbps && heightPx <= capHeight;
}
/// Order shared by every picker surface so they can't drift apart:
/// [original] pinned first, then transcode presets highest-bitrate first.
static final List<TranscodeQualityPreset> displayOrder = List.unmodifiable([
original,
...values.where((p) => !p.isOriginal).toList().reversed,
]);
/// The saved default playback starts at, for callers without an explicit
/// per-play pick (those apply their own preset directly).
///
/// Backends that cannot transcode start at [original] regardless of any
/// saved default. Otherwise the cellular default applies on a cellular-only
/// connection when set, else the general default.
static TranscodeQualityPreset resolveStartupDefault({
required bool serverSupportsTranscoding,
required bool onCellularOnly,
required TranscodeQualityPreset? cellularDefault,
required TranscodeQualityPreset generalDefault,
}) {
if (!serverSupportsTranscoding) return original;
if (onCellularOnly && cellularDefault != null) return cellularDefault;
return generalDefault;
}
}
/// Outcome of a transcode decision call.
enum TranscodeDecisionOutcome {
/// Decision indicates transcode is available (`transcodeDecisionCode == 1001`).
transcodeOk,
/// Decision indicates only direct play is available (`transcodeDecisionCode == 1000`).
/// Caller should fall back to the direct-play URL.
directPlayOnly,
/// Decision failed (HTTP error, code >= 2000, or parse error).
failed,
}