/// 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 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, }