moonhls

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

hls
m3u8
playlist
video
streaming
validator
moon add chenqi-arch/moonhls@0.1.0
Download zip
Version
0.1.0
License
MIT
Last updated
4 days ago
Downloads
2
README

#MoonHLS

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

#解决的问题

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

#安装

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 moon build moon test 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 前缀重写等发布流水线辅助能力。
  • 提供 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 的码率梯度。
  • 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 和码率梯度审计。

#暂不支持范围

  • 不下载或解析 .ts.m4s.mp4 等媒体片段本体。
  • 不进行 DRM、AES 解密或网络请求。
  • 不解析 LL-HLS 的全部低延迟扩展标签。
  • 不验证 URI 是否真实可访问。

#测试与验收命令

moon check moon build moon test 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
)

#
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
)

#
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

#
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
)

#
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
)

#
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
)

#
Severity

pub(all) enum Severity {
Error
Warning
Info
} 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
)

#
attributes_to_string

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

#
audit_uris

fn audit_uris(playlist : HlsPlaylist) -> UriAudit

#
collect_uris

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

#
filter_variants_by_height

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

#
format_ladder_report

fn format_ladder_report(report : LadderReport) -> String

#
format_playlist

fn format_playlist(playlist : HlsPlaylist) -> String

#
format_uri_audit

fn format_uri_audit(audit : UriAudit) -> String

#
format_validation_report

fn format_validation_report(report : ValidationReport) -> String

#
ladder_report

fn ladder_report(playlist : HlsPlaylist) -> LadderReport

#
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_playlist

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

#
playlist_stats

fn playlist_stats(playlist : HlsPlaylist) -> PlaylistStats

#
rewrite_uri_prefix

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

#
sample_master_playlist

let sample_master_playlist : String

#
sample_media_playlist

let sample_media_playlist : String

#
select_variant_by_bandwidth

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

#
summarize_pipeline_readiness

fn summarize_pipeline_readiness(playlist : HlsPlaylist) -> String

#
summarize_playlist

fn summarize_playlist(playlist : HlsPlaylist) -> String

#
uri_role_to_string

fn uri_role_to_string(role : UriRole) -> String

#
validate_playlist

fn validate_playlist(playlist : HlsPlaylist) -> ValidationReport