moonhls

    Pure MoonBit HLS/M3U8 playlist parser, validator, and serializer.

    hls
    m3u8
    playlist
    video
    streaming
    validator
    Download zip
    Version
    0.2.0
    License
    MIT
    Last updated
    26 days ago
    Downloads
    8

    #MoonHLS

    MoonHLS 是一个纯 MoonBit 实现的 HLS/M3U8 播放列表解析、校验与序列化库。它面向视频点播、直播切片流水线、边缘分发、教学示例和 WebAssembly 工具场景,帮助 MoonBit 项目读取并检查 media playlist 与 master playlist。

    #解决的问题

    HLS 播放列表通常由文本标签、属性列表和 URI 行共同组成,线上问题经常来自目标时长错误、密钥字段缺失、码率梯度配置不完整、字幕或音轨分组不一致、直播窗口裁剪不稳定、低延迟 HLS hint 配置错误等细节。MoonHLS 提供结构化模型、错误路径、语义校验、URI 审计、码率梯度报告、片段时间线、播放列表差异报告和可复现输出,使这些问题可以在构建、发布或测试阶段提前暴露。

    #生态定位

    截至 2026 年 8 月 18 日,在 mooncakes.io 使用 HLSM3U8 关键词检索,未发现除 MoonHLS 外功能高度重合的包。MoonHLS 不只是文本解析器:它同时提供直播窗口、增量兼容性、LL-HLS hint 和发布策略检查,适合作为视频发布流水线中的纯 MoonBit release gate。

    #安装

    moon add chenqi-arch/moonhls

    包名:

    chenqi-arch/moonhls

    Mooncakes 文档地址:

    https://mooncakes.io/docs/chenqi-arch/moonhls

    #最小示例

    test {
    let text = #|#EXTM3U
    #|#EXT-X-VERSION:7
    #|#EXT-X-TARGETDURATION:10
    #|#EXTINF:9.009,opening
    #|segment-1.ts
    #|#EXT-X-ENDLIST
    match @moonhls.parse_playlist(text) {
    Ok(playlist) => {
    let report = @moonhls.validate_playlist(playlist)
    inspect(report.ok, content="true")
    inspect(@moonhls.summarize_playlist(playlist), content="kind=media, segments=1, variants=0, renditions=0, duration=9.009")
    }
    Err(error) => fail(error.message())
    }
    }

    #本地运行

    moon check --deny-warn moon build moon test --deny-warn moon fmt --check moon info moon run cmd/main moon publish --dry-run

    #核心功能

    • 解析 #EXTM3U#EXTINF#EXT-X-TARGETDURATION#EXT-X-MEDIA-SEQUENCE#EXT-X-KEY#EXT-X-MAP#EXT-X-BYTERANGE#EXT-X-DISCONTINUITY#EXT-X-PROGRAM-DATE-TIME#EXT-X-STREAM-INF#EXT-X-MEDIA#EXT-X-START#EXT-X-ENDLIST 等常用标签。
    • 建模 media playlist 的切片、加密密钥、初始化 map、字节范围、节目时间和断点。
    • 建模 master playlist 的 variant stream、码率、分辨率、帧率、音轨组和字幕组。
    • 提供属性列表解析器,支持引号内逗号和转义字符。
    • 提供语义校验报告,覆盖空播放列表、目标时长过小、无效字节范围、密钥 URI 缺失、variant 带宽无效、音轨/字幕分组缺失等问题。
    • 提供 URI 审计、码率梯度报告、按带宽选择 variant、按高度过滤 variant、CDN URI 前缀重写等发布流水线辅助能力。
    • 提供 media playlist 时间线分析、播放头定位、sequence 定位、直播窗口裁剪和窗口计划报告。
    • 提供新旧播放列表差异比较,识别 append-only 更新、segment 元数据变化、sequence 回退、variant/rendition 增删改等发布风险。
    • 提供低延迟 HLS 常见标签解析与校验,包括 #EXT-X-PART-INF#EXT-X-SERVER-CONTROL#EXT-X-PART#EXT-X-PRELOAD-HINT#EXT-X-RENDITION-REPORT#EXT-X-SKIP
    • 提供可配置发布策略检查,用于 VOD、直播和 master playlist 的发布前 release gate。
    • 提供 format_playlist,可将结构化播放列表重新输出为 M3U8 文本。

    #常用 API

    • parse_playlist(text):解析 M3U8 文本。
    • validate_playlist(playlist):生成结构化校验报告。
    • format_playlist(playlist):重新输出 M3U8 文本。
    • summarize_playlist(playlist):输出播放列表摘要。
    • summarize_pipeline_readiness(playlist):输出解析、校验、URI 和码率梯度综合摘要。
    • audit_uris(playlist):收集并统计 segment、variant、rendition、key、map URI。
    • ladder_report(playlist):分析 master playlist 的码率梯度。
    • build_timeline(playlist):生成 media segment 时间线和 sequence 范围。
    • live_window_plan(playlist, min_window_ms):计算直播窗口保留计划。
    • trim_live_window(playlist, min_window_ms):按最小窗口时长裁剪直播 playlist。
    • compare_playlists(old_playlist, new_playlist):生成新旧 playlist 差异报告。
    • parse_low_latency_profile(text):解析常见 LL-HLS 扩展标签。
    • validate_low_latency_profile(playlist, profile):校验 LL-HLS hint 与基础 playlist 的一致性。
    • evaluate_publish_policy(playlist, policy):执行可配置发布策略检查。
    • select_variant_by_bandwidth(playlist, max_bandwidth):选择不超过目标带宽的最优 variant。
    • filter_variants_by_height(playlist, max_height):按分辨率高度过滤 variants。
    • rewrite_uri_prefix(playlist, old_prefix, new_prefix):重写播放列表内 URI 前缀。

    #支持范围

    • 支持 UTF-8 文本形式的 HLS/M3U8 playlist。
    • 支持 media playlist 与 master playlist 的常见工程字段。
    • 支持毫秒级 duration 与 frame-rate 表示。
    • 支持 unknown tag 保留到 unknown_tags,便于上层工具做兼容统计。
    • 支持发布前 URI、码率梯度、时间线、差异和策略审计。
    • 支持常见 LL-HLS 控制标签和 partial segment hint 的独立解析校验。

    #暂不支持范围

    • 不下载或解析 .ts.m4s.mp4 等媒体片段本体。
    • 不进行 DRM、AES 解密或网络请求。
    • 不覆盖 LL-HLS 的所有历史与厂商扩展标签。
    • 不验证 URI 是否真实可访问。

    #测试与验收命令

    当前测试集共 16 项,覆盖 media/master 解析、错误路径、格式化往返、URI 与码率审计、时间线、直播窗口、增量差异、LL-HLS 和发布策略。

    moon check --deny-warn moon build moon test --deny-warn moon fmt --check moon info moon run cmd/main moon publish --dry-run

    #开源许可证与参考说明

    本项目采用 MIT 许可证。项目为原创 MoonBit 实现,不移植第三方源码,不包含私有代码或来源不明素材。格式行为参考公开的 HLS playlist 规范描述与常见 M3U8 工程实践。

    ByteRange

    pub(all) struct ByteRange {
    length : Int
    offset : Int?
    } derive(Eq,
    Debug
    )

    DeltaChange

    pub(all) struct DeltaChange {
    kind : DeltaKind
    code : String
    message : String
    old_line : Int
    new_line : Int
    } derive(Eq,
    Debug
    )

    DeltaKind

    pub(all) enum DeltaKind {
    SegmentAdded
    SegmentRemoved
    SegmentChanged
    SegmentReordered
    SequenceAdvanced
    SequenceRegressed
    TargetDurationChanged
    EndListChanged
    VariantAdded
    VariantRemoved
    VariantChanged
    RenditionAdded
    RenditionRemoved
    RenditionChanged
    } derive(Eq,
    Debug
    )

    DeltaReport

    pub(all) struct DeltaReport {
    compatible : Bool
    breaking_changes : Int
    additive_changes : Int
    informational_changes : Int
    old_sequence : Int
    new_sequence : Int
    changes : Array[DeltaChange]
    } derive(Eq,
    Debug
    )

    HlsAttribute

    pub(all) struct HlsAttribute {
    name : String
    value : String
    quoted : Bool
    } derive(Eq,
    Debug
    )

    HlsPlaylist

    pub(all) struct HlsPlaylist {
    version : Int
    target_duration : Int
    media_sequence : Int
    discontinuity_sequence : Int
    start_offset_ms : Int?
    end_list : Bool
    independent_segments : Bool
    segments : Array[Segment]
    variants : Array[VariantStream]
    renditions : Array[Rendition]
    unknown_tags : Array[String]
    kind : PlaylistKind
    } derive(Eq,
    Debug
    )

    HlsPlaylist::empty

    fn HlsPlaylist::empty() -> HlsPlaylist

    KeyInfo

    pub(all) struct KeyInfo {
    key_method : String
    uri : String?
    iv : String?
    key_format : String?
    key_format_versions : String?
    line : Int
    } derive(Eq,
    Debug
    )

    LadderReport

    pub(all) struct LadderReport {
    variants : Int
    lowest_bandwidth : Int
    highest_bandwidth : Int
    monotonic : Bool
    duplicate_bandwidths : Int
    has_resolution_gap : Bool
    } derive(Eq,
    Debug
    )

    LowLatencyHintType

    pub(all) enum LowLatencyHintType {
    Part
    Map
    } derive(Eq,
    Debug
    )

    LowLatencyProfile

    pub(all) struct LowLatencyProfile {
    part_info : PartInfo?
    server_control : ServerControl?
    parts : Array[PartialSegment]
    preload_hints : Array[PreloadHint]
    rendition_reports : Array[RenditionReport]
    skip : SkipInfo?
    unknown_low_latency_tags : Array[String]
    } derive(Eq,
    Debug
    )

    ParseError

    pub(all) enum ParseError {
    MissingHeader(Int)
    InvalidTag(Int, String)
    InvalidAttribute(Int, String)
    InvalidNumber(Int, String, String)
    UriWithoutContext(Int, String)
    MissingSegmentUri(Int)
    MissingVariantUri(Int)
    } derive(Eq,
    Debug
    )

    ParseError::message

    fn ParseError::message(self : ParseError) -> String

    PartInfo

    pub(all) struct PartInfo {
    target_duration_ms : Int
    line : Int
    } derive(Eq,
    Debug
    )

    PartialSegment

    pub(all) struct PartialSegment {
    uri : String
    duration_ms : Int
    independent : Bool
    gap : Bool
    byte_range : ByteRange?
    line : Int
    } derive(Eq,
    Debug
    )

    PlaylistKind

    pub(all) enum PlaylistKind {
    Empty
    Media
    Master
    Hybrid
    } derive(Eq,
    Debug
    )

    PlaylistStats

    pub(all) struct PlaylistStats {
    kind : PlaylistKind
    segment_count : Int
    variant_count : Int
    rendition_count : Int
    total_duration_ms : Int
    max_segment_ms : Int
    encrypted_segments : Int
    discontinuities : Int
    } derive(Eq,
    Debug
    )

    PolicyReport

    pub(all) struct PolicyReport {
    ok : Bool
    errors : Int
    warnings : Int
    checks : Int
    issues : Array[ValidationIssue]
    } derive(Eq,
    Debug
    )

    PreloadHint

    pub(all) struct PreloadHint {
    hint_type : LowLatencyHintType
    uri : String
    byte_range_start : Int?
    byte_range_length : Int?
    line : Int
    } derive(Eq,
    Debug
    )

    PublishPolicy

    pub(all) struct PublishPolicy {
    require_end_list : Bool
    allow_absolute_uris : Bool
    allow_parent_traversal : Bool
    max_target_duration : Int
    max_segment_duration_ms : Int
    max_variant_bandwidth : Int
    min_variants : Int
    require_independent_segments : Bool
    require_version_at_least : Int
    } derive(Eq,
    Debug
    )

    PublishPolicy::live_default

    fn PublishPolicy::live_default() -> PublishPolicy

    PublishPolicy::strict_master_default

    fn PublishPolicy::strict_master_default() -> PublishPolicy

    PublishPolicy::vod_default

    fn PublishPolicy::vod_default() -> PublishPolicy

    Rendition

    pub(all) struct Rendition {
    rendition_type : String
    group_id : String
    name : String
    language : String?
    uri : String?
    default : Bool
    autoselect : Bool
    forced : Bool
    line : Int
    } derive(Eq,
    Debug
    )

    RenditionReport

    pub(all) struct RenditionReport {
    uri : String
    last_msn : Int?
    last_part : Int?
    line : Int
    } derive(Eq,
    Debug
    )

    Resolution

    pub(all) struct Resolution {
    width : Int
    height : Int
    } derive(Eq,
    Debug
    )

    Segment

    pub(all) struct Segment {
    uri : String
    title : String
    duration_ms : Int
    byte_range : ByteRange?
    key : KeyInfo?
    map : SegmentMap?
    discontinuity : Bool
    program_date_time : String?
    line : Int
    } derive(Eq,
    Debug
    )

    SegmentMap

    pub(all) struct SegmentMap {
    uri : String
    byte_range : ByteRange?
    line : Int
    } derive(Eq,
    Debug
    )

    ServerControl

    pub(all) struct ServerControl {
    can_skip_until_ms : Int?
    can_block_reload : Bool
    hold_back_ms : Int?
    part_hold_back_ms : Int?
    can_skip_dateranges : Bool
    line : Int
    } derive(Eq,
    Debug
    )

    Severity

    pub(all) enum Severity {
    Error
    Warning
    Info
    } derive(Eq,
    Debug
    )

    SkipInfo

    pub(all) struct SkipInfo {
    skipped_segments : Int
    recently_removed_dateranges : String?
    line : Int
    } derive(Eq,
    Debug
    )

    TimelineEntry

    pub(all) struct TimelineEntry {
    sequence : Int
    uri : String
    start_ms : Int
    end_ms : Int
    duration_ms : Int
    discontinuity_group : Int
    encrypted : Bool
    line : Int
    } derive(Eq,
    Debug
    )

    TimelineReport

    pub(all) struct TimelineReport {
    entries : Array[TimelineEntry]
    first_sequence : Int
    last_sequence : Int
    total_duration_ms : Int
    discontinuity_groups : Int
    encrypted_segments : Int
    longest_segment_ms : Int
    shortest_segment_ms : Int
    average_segment_ms : Int
    open_ended : Bool
    } derive(Eq,
    Debug
    )

    UriAudit

    pub(all) struct UriAudit {
    total : Int
    absolute_uris : Int
    relative_uris : Int
    data_uris : Int
    empty_uris : Int
    parent_traversal_uris : Int
    entries : Array[UriEntry]
    } derive(Eq,
    Debug
    )

    UriEntry

    pub(all) struct UriEntry {
    role : UriRole
    uri : String
    line : Int
    } derive(Eq,
    Debug
    )

    UriRole

    pub(all) enum UriRole {
    SegmentUri
    VariantUri
    RenditionUri
    KeyUri
    MapUri
    } derive(Eq,
    Debug
    )

    ValidationIssue

    pub(all) struct ValidationIssue {
    severity : Severity
    code : String
    message : String
    line : Int
    } derive(Eq,
    Debug
    )

    ValidationIssue::error

    fn ValidationIssue::error(code : String, message : String, line? : Int) -> ValidationIssue

    ValidationIssue::info

    fn ValidationIssue::info(code : String, message : String, line? : Int) -> ValidationIssue

    ValidationIssue::warning

    fn ValidationIssue::warning(code : String, message : String, line? : Int) -> ValidationIssue

    ValidationReport

    pub(all) struct ValidationReport {
    ok : Bool
    errors : Int
    warnings : Int
    infos : Int
    issues : Array[ValidationIssue]
    } derive(Eq,
    Debug
    )

    VariantStream

    pub(all) struct VariantStream {
    uri : String
    bandwidth : Int
    average_bandwidth : Int?
    codecs : String?
    resolution : Resolution?
    frame_rate_milli : Int?
    audio_group : String?
    subtitles_group : String?
    closed_captions : String?
    line : Int
    } derive(Eq,
    Debug
    )

    WindowPlan

    pub(all) struct WindowPlan {
    start_index : Int
    dropped_segments : Int
    kept_segments : Int
    kept_duration_ms : Int
    first_sequence : Int
    last_sequence : Int
    } derive(Eq,
    Debug
    )

    attributes_to_string

    fn attributes_to_string(attrs : Array[HlsAttribute]) -> String

    audit_uri_entries

    fn audit_uri_entries(entries : Array[UriEntry]) -> UriAudit

    audit_uris

    fn audit_uris(playlist : HlsPlaylist) -> UriAudit

    build_timeline

    fn build_timeline(playlist : HlsPlaylist) -> TimelineReport

    collect_uris

    fn collect_uris(playlist : HlsPlaylist) -> Array[UriEntry]

    compare_playlists

    fn compare_playlists(old_playlist : HlsPlaylist, new_playlist : HlsPlaylist) -> DeltaReport

    count_delta_kind

    fn count_delta_kind(report : DeltaReport, kind : DeltaKind) -> Int

    delta_is_append_only

    fn delta_is_append_only(report : DeltaReport) -> Bool

    discontinuity_sequence_for_segment

    fn discontinuity_sequence_for_segment(playlist : HlsPlaylist, sequence : Int) -> Int?

    evaluate_publish_policy

    fn evaluate_publish_policy(playlist : HlsPlaylist, policy : PublishPolicy) -> PolicyReport

    filter_variants_by_height

    fn filter_variants_by_height(playlist : HlsPlaylist, max_height : Int) -> Array[VariantStream]

    format_delta_report

    fn format_delta_report(report : DeltaReport) -> String

    format_ladder_report

    fn format_ladder_report(report : LadderReport) -> String

    format_playlist

    fn format_playlist(playlist : HlsPlaylist) -> String

    format_policy_report

    fn format_policy_report(report : PolicyReport) -> String

    format_timeline_report

    fn format_timeline_report(report : TimelineReport) -> String

    format_uri_audit

    fn format_uri_audit(audit : UriAudit) -> String

    format_validation_report

    fn format_validation_report(report : ValidationReport) -> String

    format_window_plan

    fn format_window_plan(plan : WindowPlan) -> String

    gap_part_count

    fn gap_part_count(profile : LowLatencyProfile) -> Int

    independent_part_count

    fn independent_part_count(profile : LowLatencyProfile) -> Int

    ladder_report

    fn ladder_report(playlist : HlsPlaylist) -> LadderReport

    live_window_plan

    fn live_window_plan(playlist : HlsPlaylist, min_window_ms : Int) -> WindowPlan

    low_latency_uri_audit

    fn low_latency_uri_audit(profile : LowLatencyProfile) -> UriAudit

    millis_to_string

    fn millis_to_string(ms : Int) -> String

    parse_attribute_list

    fn parse_attribute_list(raw : String, line? : Int) -> Result[Array[HlsAttribute], ParseError]

    parse_low_latency_profile

    fn parse_low_latency_profile(input : String) -> Result[LowLatencyProfile, ParseError]

    parse_playlist

    fn parse_playlist(input : String) -> Result[HlsPlaylist, ParseError]

    part_duration_total

    fn part_duration_total(profile : LowLatencyProfile) -> Int

    playlist_duration_seconds_ceil

    fn playlist_duration_seconds_ceil(playlist : HlsPlaylist) -> Int

    playlist_duration_seconds_floor

    fn playlist_duration_seconds_floor(playlist : HlsPlaylist) -> Int

    playlist_stats

    fn playlist_stats(playlist : HlsPlaylist) -> PlaylistStats

    policy_report_to_validation

    fn policy_report_to_validation(report : PolicyReport) -> ValidationReport

    rewrite_uri_prefix

    fn rewrite_uri_prefix(playlist : HlsPlaylist, old_prefix : String, new_prefix : String) -> HlsPlaylist

    sample_low_latency_playlist

    let sample_low_latency_playlist : String

    sample_master_playlist

    let sample_master_playlist : String

    sample_media_playlist

    let sample_media_playlist : String

    sample_media_playlist_next

    let sample_media_playlist_next : String

    segment_end_sequence

    fn segment_end_sequence(playlist : HlsPlaylist) -> Int

    segment_sequence

    fn segment_sequence(playlist : HlsPlaylist, index : Int) -> Int

    select_variant_by_bandwidth

    fn select_variant_by_bandwidth(playlist : HlsPlaylist, max_bandwidth : Int) -> VariantStream?

    sequence_for_playhead

    fn sequence_for_playhead(playlist : HlsPlaylist, position_ms : Int) -> Int?

    summarize_low_latency_profile

    fn summarize_low_latency_profile(profile : LowLatencyProfile) -> String

    summarize_pipeline_readiness

    fn summarize_pipeline_readiness(playlist : HlsPlaylist) -> String

    summarize_playlist

    fn summarize_playlist(playlist : HlsPlaylist) -> String

    time_range_for_sequence

    fn time_range_for_sequence(playlist : HlsPlaylist, sequence : Int) -> (Int, Int)?

    timeline_entry_at

    fn timeline_entry_at(playlist : HlsPlaylist, position_ms : Int) -> TimelineEntry?

    timeline_entry_by_sequence

    fn timeline_entry_by_sequence(playlist : HlsPlaylist, sequence : Int) -> TimelineEntry?

    trim_before_sequence

    fn trim_before_sequence(playlist : HlsPlaylist, first_sequence : Int) -> HlsPlaylist

    trim_live_window

    fn trim_live_window(playlist : HlsPlaylist, min_window_ms : Int) -> HlsPlaylist

    uri_role_to_string

    fn uri_role_to_string(role : UriRole) -> String

    validate_low_latency_profile

    fn validate_low_latency_profile(playlist : HlsPlaylist, profile : LowLatencyProfile) -> ValidationReport

    validate_playlist

    fn validate_playlist(playlist : HlsPlaylist) -> ValidationReport