// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A byte range inside a resource (RFC 8216 §4.3.2.2).
///
/// `offset` absent means "immediately after the previous range", which is what
/// the tag itself means when the `@` clause is left out. Keeping the two apart
/// matters: a reader that filled in zero would fetch the wrong bytes.
pub(all) struct ByteRange {
mut length : Int64
mut offset : Int64?
} derive(Eq, Debug)
///|
pub extend ByteRange with Eq::{equal, not_equal}
///|
pub extend ByteRange with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// One range, `length@offset`.
pub fn ByteRange::new(length : Int64, offset? : Int64) -> ByteRange {
{ length, offset, }
}
///|
/// The encryption method `EXT-X-KEY` names (RFC 8216 §4.3.2.4).
///
/// `Other` carries a method this release does not implement, so a playlist that
/// uses one is read and written back unchanged rather than refused.
pub(all) enum Cipher {
None
Aes128
SampleAes
Other(String)
} derive(Eq, Debug)
///|
pub extend Cipher with Eq::{equal, not_equal}
///|
pub extend Cipher with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// The method as it appears on the wire.
pub fn Cipher::code(self : Cipher) -> String {
match self {
None => "NONE"
Aes128 => "AES-128"
SampleAes => "SAMPLE-AES"
Other(name) => name
}
}
///|
/// The method a wire value names; `Other` for one this release does not know.
pub fn Cipher::of(code : StringView) -> Cipher {
if code == "NONE" {
None
} else if code == "AES-128" {
Aes128
} else if code == "SAMPLE-AES" {
SampleAes
} else {
Other(code.to_owned())
}
}
///|
/// A key a segment is encrypted with (RFC 8216 §4.3.2.4).
///
/// Segments carry the key that was in force when they were written, not the
/// tag that introduced it: a tag applies to every following segment until it
/// is replaced, and a caller hunting for "what encrypts this segment" should
/// not have to replay the file to find out.
pub(all) struct Key {
mut cipher : Cipher
mut uri : String?
mut iv : String?
mut key_format : String?
mut key_format_versions : String?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend Key with Eq::{equal, not_equal}
///|
pub extend Key with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A key, with the URI that serves it.
pub fn Key::new(
uri : String,
cipher? : Cipher,
iv? : String,
key_format? : String,
key_format_versions? : String,
) -> Key {
{
cipher: cipher.unwrap_or(Aes128),
uri: Some(uri),
iv,
key_format,
key_format_versions,
extra: [],
}
}
///|
/// Whether the key leaves the stream in the clear.
pub fn Key::is_plain(self : Key) -> Bool {
self.cipher == Cipher::None
}
///|
/// The initialisation section a segment needs before it can be decoded, the
/// `EXT-X-MAP` tag (RFC 8216 §4.3.2.5).
pub(all) struct InitSection {
mut uri : String
mut byte_range : ByteRange?
} derive(Eq, Debug)
///|
pub extend InitSection with Eq::{equal, not_equal}
///|
pub extend InitSection with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// An initialisation section served from `uri`.
pub fn InitSection::new(uri : String, byte_range? : ByteRange) -> InitSection {
{ uri, byte_range, }
}
///|
/// A partial segment, the unit low-latency HLS publishes before a segment is
/// complete (RFC 8216bis §4.4.4.9).
pub(all) struct Part {
mut uri : String
mut duration : Double
mut independent : Bool
mut byte_range : ByteRange?
mut gap : Bool
} derive(Eq, Debug)
///|
pub extend Part with Eq::{equal, not_equal}
///|
pub extend Part with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A part of the segment at `uri` lasting `duration` seconds.
pub fn Part::new(
uri : String,
duration : Double,
independent? : Bool,
byte_range? : ByteRange,
gap? : Bool,
) -> Part {
{
uri,
duration,
independent: independent.unwrap_or(false),
byte_range,
gap: gap.unwrap_or(false),
}
}
///|
/// One media segment: the URI a player fetches and everything the playlist says
/// about it.
pub(all) struct Segment {
mut uri : String
mut duration : Double
mut title : String?
mut byte_range : ByteRange?
mut discontinuity : Bool
mut program_date_time : String?
mut gap : Bool
mut key : Key?
mut map : InitSection?
mut parts : Array[Part]
} derive(Eq, Debug)
///|
pub extend Segment with Eq::{equal, not_equal}
///|
pub extend Segment with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A segment at `uri` lasting `duration` seconds.
pub fn Segment::new(
uri : String,
duration : Double,
title? : String,
) -> Segment {
{
uri,
duration,
title,
byte_range: None,
discontinuity: false,
program_date_time: None,
gap: false,
key: None,
map: None,
parts: [],
}
}
///|
/// A frame size, `1920x1080`.
pub(all) struct Resolution {
mut width : Int32
mut height : Int32
} derive(Eq, Debug)
///|
pub extend Resolution with Eq::{equal, not_equal}
///|
pub extend Resolution with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A video size in pixels.
pub fn Resolution::new(width : Int32, height : Int32) -> Resolution {
{ width, height, }
}
///|
/// One rendition of a master playlist, the body of `EXT-X-STREAM-INF`
/// (RFC 8216 §4.3.4.2).
pub(all) struct Variant {
mut uri : String
mut bandwidth : Int32?
mut average_bandwidth : Int32?
mut codecs : String?
mut resolution : Resolution?
mut frame_rate : Double?
mut hdcp_level : String?
mut audio : String?
mut video : String?
mut subtitles : String?
mut closed_captions : String?
mut video_range : String?
mut pathway_id : String?
mut stable_variant_id : String?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend Variant with Eq::{equal, not_equal}
///|
pub extend Variant with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A variant streaming `uri`, using `bandwidth` bits per second.
pub fn Variant::new(
uri : String,
bandwidth? : Int32,
codecs? : String,
) -> Variant {
{
uri,
bandwidth,
average_bandwidth: None,
codecs,
resolution: None,
frame_rate: None,
hdcp_level: None,
audio: None,
video: None,
subtitles: None,
closed_captions: None,
video_range: None,
pathway_id: None,
stable_variant_id: None,
extra: [],
}
}
///|
/// What an `EXT-X-MEDIA` rendition carries (RFC 8216 §4.3.4.1).
pub(all) enum MediaKind {
Audio
Video
Subtitles
ClosedCaptions
Other(String)
} derive(Eq, Debug)
///|
pub extend MediaKind with Eq::{equal, not_equal}
///|
pub extend MediaKind with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// The kind as it appears on the wire.
pub fn MediaKind::code(self : MediaKind) -> String {
match self {
Audio => "AUDIO"
Video => "VIDEO"
Subtitles => "SUBTITLES"
ClosedCaptions => "CLOSED-CAPTIONS"
Other(name) => name
}
}
///|
/// The kind a wire value names.
pub fn MediaKind::of(code : StringView) -> MediaKind {
if code == "AUDIO" {
Audio
} else if code == "VIDEO" {
Video
} else if code == "SUBTITLES" {
Subtitles
} else if code == "CLOSED-CAPTIONS" {
ClosedCaptions
} else {
Other(code.to_owned())
}
}
///|
/// An alternative rendition a variant can switch to (RFC 8216 §4.3.4.1).
pub(all) struct Rendition {
mut kind : MediaKind
mut group_id : String
mut name : String
mut uri : String?
mut language : String?
mut assoc_language : String?
mut characteristics : String?
mut channels : String?
mut instream_id : String?
mut stable_rendition_id : String?
mut pathway_id : String?
mut default : Bool
mut autoselect : Bool
mut forced : Bool
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend Rendition with Eq::{equal, not_equal}
///|
pub extend Rendition with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A rendition called `name` in the group `group_id`.
pub fn Rendition::new(
kind : MediaKind,
group_id : String,
name : String,
uri? : String,
) -> Rendition {
{
kind,
group_id,
name,
uri,
language: None,
assoc_language: None,
characteristics: None,
channels: None,
instream_id: None,
stable_rendition_id: None,
pathway_id: None,
default: false,
autoselect: false,
forced: false,
extra: [],
}
}
///|
/// An I-frame only rendition, `EXT-X-I-FRAME-STREAM-INF` (RFC 8216 §4.3.4.3).
pub(all) struct IFrameStream {
mut uri : String
mut bandwidth : Int32?
mut average_bandwidth : Int32?
mut codecs : String?
mut resolution : Resolution?
mut hdcp_level : String?
mut video_range : String?
mut pathway_id : String?
mut stable_variant_id : String?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend IFrameStream with Eq::{equal, not_equal}
///|
pub extend IFrameStream with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// I-frame streams served from `uri`.
pub fn IFrameStream::new(uri : String, bandwidth? : Int32) -> IFrameStream {
{
uri,
bandwidth,
average_bandwidth: None,
codecs: None,
resolution: None,
hdcp_level: None,
video_range: None,
pathway_id: None,
stable_variant_id: None,
extra: [],
}
}
///|
/// A piece of session metadata, `EXT-X-SESSION-DATA` (RFC 8216 §4.3.4.4).
pub(all) struct SessionData {
mut data_id : String
mut value : String?
mut uri : String?
mut language : String?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend SessionData with Eq::{equal, not_equal}
///|
pub extend SessionData with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// The metadata named `data_id`, carried inline as `value` or by `uri`.
pub fn SessionData::new(
data_id : String,
value? : String,
uri? : String,
language? : String,
) -> SessionData {
{ data_id, value, uri, language, extra: [], }
}
///|
/// Where playback should start, `EXT-X-START` (RFC 8216 §4.3.5.2).
pub(all) struct Start {
mut time_offset : Double
mut precise : Bool
} derive(Eq, Debug)
///|
pub extend Start with Eq::{equal, not_equal}
///|
pub extend Start with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// Start `time_offset` seconds from the beginning.
pub fn Start::new(time_offset : Double, precise? : Bool) -> Start {
{ time_offset, precise: precise.unwrap_or(false), }
}
///|
/// The reload advice a low-latency server gives, `EXT-X-SERVER-CONTROL`
/// (RFC 8216bis §4.4.3.8).
pub(all) struct ServerControl {
mut can_skip_until : Double?
mut can_skip_date_ranges : Bool
mut hold_back : Double?
mut part_hold_back : Double?
mut can_block_reload : Bool
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend ServerControl with Eq::{equal, not_equal}
///|
pub extend ServerControl with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// The reload advice with nothing set.
pub fn ServerControl::new() -> ServerControl {
{
can_skip_until: None,
can_skip_date_ranges: false,
hold_back: None,
part_hold_back: None,
can_block_reload: false,
extra: [],
}
}
///|
/// What to fetch next before it is listed, `EXT-X-PRELOAD-HINT`
/// (RFC 8216bis §4.4.4.10).
pub(all) struct PreloadHint {
mut kind : String
mut uri : String
mut byte_range : ByteRange?
mut start : Int64?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend PreloadHint with Eq::{equal, not_equal}
///|
pub extend PreloadHint with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A hint that `uri` will hold the next `kind` (`PART` or `MAP`).
pub fn PreloadHint::new(
kind : String,
uri : String,
byte_range? : ByteRange,
start? : Int64,
) -> PreloadHint {
{ kind, uri, byte_range, start, extra: [], }
}
///|
/// How far along a sibling rendition is, `EXT-X-RENDITION-REPORT`
/// (RFC 8216bis §4.4.4.11).
pub(all) struct RenditionReport {
mut uri : String
mut last_msn : Int64?
mut last_part : Int32?
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend RenditionReport with Eq::{equal, not_equal}
///|
pub extend RenditionReport with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A report on the rendition at `uri`.
pub fn RenditionReport::new(
uri : String,
last_msn? : Int64,
last_part? : Int32,
) -> RenditionReport {
{ uri, last_msn, last_part, extra: [], }
}
///|
/// A wall-clock interval the stream marks, `EXT-X-DATERANGE`
/// (RFC 8216 §4.3.2.7).
pub(all) struct DateRange {
mut id : String
mut class_ : String?
mut start_date : String?
mut end_date : String?
mut duration : Double?
mut planned_duration : Double?
mut scte35_cmd : String?
mut scte35_out : String?
mut scte35_in : String?
mut end_on_next : Bool
mut extra : Array[String]
} derive(Eq, Debug)
///|
pub extend DateRange with Eq::{equal, not_equal}
///|
pub extend DateRange with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// The interval named `id`.
pub fn DateRange::new(id : String, start_date? : String) -> DateRange {
{
id,
class_: None,
start_date,
end_date: None,
duration: None,
planned_duration: None,
scte35_cmd: None,
scte35_out: None,
scte35_in: None,
end_on_next: false,
extra: [],
}
}
///|
/// What kind of playlist a media playlist is (RFC 8216 §4.3.3.5).
pub(all) enum PlaylistType {
Event
Vod
Other(String)
} derive(Eq, Debug)
///|
pub extend PlaylistType with Eq::{equal, not_equal}
///|
pub extend PlaylistType with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A variable the playlist defines, `EXT-X-DEFINE` (RFC 8216bis §4.4.2.3).
pub(all) struct Define {
mut name : String?
mut value : String?
mut import_ : String?
mut queryparam : String?
} derive(Eq, Debug)
///|
pub extend Define with Eq::{equal, not_equal}
///|
pub extend Define with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// A variable set to `value`.
pub fn Define::new(name : String, value : String) -> Define {
{ name: Some(name), value: Some(value), import_: None, queryparam: None, }
}
///|
/// A master playlist: which renditions exist and how to reach them
/// (RFC 8216 §4.3.4).
pub(all) struct Master {
mut version : Int32?
mut independent_segments : Bool
mut start : Start?
mut defines : Array[Define]
mut session_data : Array[SessionData]
mut session_keys : Array[Key]
mut renditions : Array[Rendition]
mut variants : Array[Variant]
mut i_frame_streams : Array[IFrameStream]
mut unknown : Array[String]
} derive(Eq, Debug)
///|
pub extend Master with Eq::{equal, not_equal}
///|
pub extend Master with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// An empty master playlist.
pub fn Master::new() -> Master {
{
version: None,
independent_segments: false,
start: None,
defines: [],
session_data: [],
session_keys: [],
renditions: [],
variants: [],
i_frame_streams: [],
unknown: [],
}
}
///|
/// A media playlist: the segments of one rendition (RFC 8216 §4.3.3).
pub(all) struct MediaPlaylist {
mut version : Int32?
mut target_duration : Int32
mut media_sequence : Int64
mut discontinuity_sequence : Int64
mut playlist_type : PlaylistType?
mut i_frames_only : Bool
mut independent_segments : Bool
mut allow_cache : Bool?
mut end_list : Bool
mut start : Start?
mut server_control : ServerControl?
mut part_inf : Double?
mut skip : Int32?
mut defines : Array[Define]
mut date_ranges : Array[DateRange]
mut segments : Array[Segment]
mut next_parts : Array[Part]
mut preload_hint : PreloadHint?
mut rendition_reports : Array[RenditionReport]
mut unknown : Array[String]
} derive(Eq, Debug)
///|
pub extend MediaPlaylist with Eq::{equal, not_equal}
///|
pub extend MediaPlaylist with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// An empty media playlist whose segments are at most `target_duration`
/// seconds long, which the specification requires to be present.
pub fn MediaPlaylist::new(target_duration : Int32) -> MediaPlaylist {
{
version: None,
target_duration,
media_sequence: 0L,
discontinuity_sequence: 0L,
playlist_type: None,
i_frames_only: false,
independent_segments: false,
allow_cache: None,
end_list: false,
start: None,
server_control: None,
part_inf: None,
skip: None,
defines: [],
date_ranges: [],
segments: [],
next_parts: [],
preload_hint: None,
rendition_reports: [],
unknown: [],
}
}
///|
/// A playlist, of one kind or the other.
pub(all) enum Playlist {
MasterPlaylist(Master)
MediaPlaylist(MediaPlaylist)
} derive(Eq, Debug)
///|
pub extend Playlist with Eq::{equal, not_equal}
///|
pub extend Playlist with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// Whether this is a master playlist.
pub fn Playlist::is_master(self : Playlist) -> Bool {
match self {
MasterPlaylist(_) => true
MediaPlaylist(_) => false
}
}
///|
/// The master playlist, when this is one.
pub fn Playlist::master(self : Playlist) -> Master? {
match self {
MasterPlaylist(master) => Some(master)
MediaPlaylist(_) => None
}
}
///|
/// The media playlist, when this is one.
pub fn Playlist::media(self : Playlist) -> MediaPlaylist? {
match self {
MasterPlaylist(_) => None
MediaPlaylist(media) => Some(media)
}
}
///|
/// Why a playlist could not be read.
///
/// `at` is the line the parser stopped on, counting from one; `got` is what was
/// there and `want` what the grammar allows.
pub(all) suberror PlaylistError {
Malformed(at~ : Int32, want~ : String, got~ : String)
}
///|
/// The one-line account of a failure.
pub extend PlaylistError with Show::{to_string, output}
///|
pub impl Show for PlaylistError with fn output(self, logger) {
let Malformed(at~, want~, got~) = self
logger.write_string(
"line " + at.to_string() + ": want " + want + ", got " + got,
)
}