hls

package module
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2025 License: BSD-3-Clause Imports: 12 Imported by: 1

README

hls

go get github.com/as/hls/...

func TestDecodeMaster(t *testing.T) {
	sample := `
	#EXTM3U
	#EXT-X-VERSION:3
	#EXT-X-INDEPENDENT-SEGMENTS
	#EXT-X-STREAM-INF:BANDWIDTH=1111,AVERAGE-BANDWIDTH=1000,RESOLUTION=1x1,FRAME-RATE=29.970,CODECS="avc1.4D401F,mp4a.40.2"
	m1.m3u8
	#EXT-X-STREAM-INF:BANDWIDTH=2222,AVERAGE-BANDWIDTH=2000,RESOLUTION=2x2,FRAME-RATE=29.970,CODECS="avc1.4D401F,mp4a.40.2"
	m2.m3u8
	#EXT-X-STREAM-INF:BANDWIDTH=3333,AVERAGE-BANDWIDTH=3000,RESOLUTION=3x3,FRAME-RATE=29.970,CODECS="avc1.4D401F,mp4a.40.2"
	m3.m3u8
	#EXT-X-STREAM-INF:BANDWIDTH=4444,AVERAGE-BANDWIDTH=4000,RESOLUTION=4x4,FRAME-RATE=29.970,CODECS="avc1.4D401E,mp4a.40.2"
	m4.m3u8
	#EXT-X-STREAM-INF:BANDWIDTH=5555,AVERAGE-BANDWIDTH=5000,RESOLUTION=5x5,FRAME-RATE=29.970,CODECS="avc1.4D401E,mp4a.40.2"
	m5.m3u8
	#EXT-X-STREAM-INF:BANDWIDTH=6666,AVERAGE-BANDWIDTH=6000,RESOLUTION=6x6,FRAME-RATE=29.970,CODECS="avc1.4D400D,mp4a.40.2"
	m6.m3u8
	`
	want := Master{
		Version:     3,
		Independent: true,
		Stream: []StreamInfo{
			{URL: "m1.m3u8", Bandwidth: 1111, BandwidthAvg: 1000, Resolution: image.Pt(1, 1), Codecs: []string{"avc1.4D401F", "mp4a.40.2"}, Framerate: 29.97},
			{URL: "m2.m3u8", Bandwidth: 2222, BandwidthAvg: 2000, Resolution: image.Pt(2, 2), Codecs: []string{"avc1.4D401F", "mp4a.40.2"}, Framerate: 29.97},
			{URL: "m3.m3u8", Bandwidth: 3333, BandwidthAvg: 3000, Resolution: image.Pt(3, 3), Codecs: []string{"avc1.4D401F", "mp4a.40.2"}, Framerate: 29.97},
			{URL: "m4.m3u8", Bandwidth: 4444, BandwidthAvg: 4000, Resolution: image.Pt(4, 4), Codecs: []string{"avc1.4D401E", "mp4a.40.2"}, Framerate: 29.97},
			{URL: "m5.m3u8", Bandwidth: 5555, BandwidthAvg: 5000, Resolution: image.Pt(5, 5), Codecs: []string{"avc1.4D401E", "mp4a.40.2"}, Framerate: 29.97},
			{URL: "m6.m3u8", Bandwidth: 6666, BandwidthAvg: 6000, Resolution: image.Pt(6, 6), Codecs: []string{"avc1.4D400D", "mp4a.40.2"}, Framerate: 29.97},
		},
	}

	m := Master{}
	m.DecodeHLS(strings.NewReader(sample))
	if !reflect.DeepEqual(m, want) {
		t.Fatalf("mismatch:\n\t\thave: %+v\n\t\twant: %+v", m, want)
	}
}
func TestDecodeMedia(t *testing.T) {
	sample := `
	#EXTM3U
	#EXT-X-VERSION:3
	#EXT-X-INDEPENDENT-SEGMENTS
	#EXT-X-PLAYLIST-TYPE:EVENT
	#EXT-X-START:TIME-OFFSET=25,PRECISE=YES
	#EXT-X-TARGETDURATION:10
	#EXT-X-MEDIA-SEQUENCE:1
	#EXT-X-DISCONTINUITY-SEQUENCE:2
	#EXTINF:10.0,
	ad0.ts
	#EXTINF:8.0,
	ad1.ts
	#EXT-X-DISCONTINUITY
	#EXTINF:10.0,
	movieA.ts
	#EXTINF:10.0,
	movieB.ts
	#EXT-X-ENDLIST
	`
	want := Media{
		MediaHeader: MediaHeader{
			Version:       3,
			Independent:   true,
			Type:          "EVENT",
			Duration:      10 * time.Second,
			Sequence:      1,
			Discontinuity: 2,
			Start:         Start{Offset: 25 * time.Second, Precise: true},
			End:           true,
		},
		File: []File{
			{Inf: Inf{10 * time.Second, "ad0.ts"}},
			{Inf: Inf{8 * time.Second, "ad1.ts"}},
			{Inf: Inf{10 * time.Second, "movieA.ts"}, Discontinuous: true},
			{Inf: Inf{10 * time.Second, "movieB.ts"}},
		},
	}

	m := Media{}
	m.DecodeHLS(strings.NewReader(sample))
	if !reflect.DeepEqual(m, want) {
		t.Fatalf("mismatch:\n\t\thave: %+v\n\t\twant: %+v", m, want)
	}
}

Documentation

Overview

Package hls implement an HLS codec for Master and Media files in m3u format At this time, the codec only supports decoding

Index

Constants

View Source
const (
	Vod   = "VOD"   // immutable
	Event = "EVENT" // append-only
	Live  = ""      // sliding-window
)

Media playlist types

Variables

View Source
var (
	ErrHeader = errors.New("hls: no m3u8 tag")
	ErrEmpty  = errors.New("hls: empty playlist")
	ErrType   = errors.New("hls: playlist type mismatch")
)

Functions

func Decode added in v0.1.0

func Decode(r io.Reader) (t []m3u.Tag, master bool, err error)

Decode reads an HLS playlist from the reader and tokenizes it into a list of tags. Master is true if and only if the input looks like a master playlist.

func RegisterTag added in v0.2.0

func RegisterTag(name string, value interface{})

func Runtime added in v0.0.2

func Runtime(f ...File) (cumulative time.Duration)

Runtime measures the cumulative duration of the given window of segments (files)

Types

type AD struct {
	CueOut             Cue       `hls:"EXT-X-CUE-OUT,omitempty" json:",omitempty"`
	CueCont            Cue       `hls:"EXT-X-CUE-OUT-CONT,omitempty" json:",omitempty"`
	CueIn              Cue       `hls:"EXT-X-CUE-IN,omitempty" json:",omitempty"`
	CueAdobe           CueAdobe  `hls:"EXT-X-CUE,omitempty" json:",omitempty"`
	SCTE35             SCTE35    `hls:"EXT-X-SCTE35,omitempty" json:",omitempty"`
	DateRange          DateRange `hls:"EXT-X-DATERANGE,omitempty" json:",omitempty"`
	SCTE35Splice       string    `hls:"EXT-X-SPLICEPOINT-SCTE35,noquote,omitempty" json:",omitempty"`
	SCTE35OatclsSplice string    `hls:"EXT-OATCLS-SCTE35,noquote,omitempty" json:",omitempty"`
}

func (*AD) Cue added in v0.4.0

func (f *AD) Cue() (c Cue)

Cue returns the value of the EXT-X-CUE-OUT, EXT-X-CUE-OUT-CONT, and EXT-X-CUE-IN tags. The Cue.Kind field is set to "in", "out", "cont" or the empty string if there is no queue.

The SCTE35 field is set to the OatcltSplice or SCTE35Splice field in the File if not set in the Cue natively. This can be in binary, hex, or base64 format.

Use: github.com/as/scte35.Parse(...) to decode the bitstream

Example:

if f.IsAD() { fmt.Println("cue is", f.Cue()) }

func (*AD) IsAD added in v0.4.0

func (f *AD) IsAD() bool

IsAD returns true if the segment looks like an AD-break. This currently only handles the three standard EXT-X-CUE-OUT, EXT-X-CUE-OUT-CONT, and EXT-X-CUE-IN tags. Examine the SCTE35 fields manually to handle other formats

type Cue added in v0.3.2

type Cue struct {
	Duration time.Duration `hls:"$1" json:",omitempty"`
	Elapsed  time.Duration `hls:"ELAPSEDTIME" json:",omitempty"`
	ID       string        `hls:"BREAKID" json:",omitempty"`
	SCTE35   string        `hls:"SCTE35" json:",omitempty"`
	Set      bool          `json:",omitempty"`
	Kind     string        `json:",omitempty"`
}

Cue is used by EXT-X-CUE-IN / EXT-X-CUE-OUT pairs the ID field is supported by Google Ad Manager for CUE-OUTs

func (Cue) IsAD added in v0.3.2

func (c Cue) IsAD() bool

IsAD returns true if the cue is a cue-in or cue-out point

type CueAdobe added in v0.3.2

type CueAdobe struct {
	ID       string        `hls:"ID" json:",omitempty"`
	Type     string        `hls:"TYPE" json:",omitempty"`
	Duration time.Duration `hls:"DURATION" json:",omitempty"`
	Time     time.Duration `hls:"TIME" json:",omitempty"`
	Elapsed  time.Duration `hls:"ELAPSED" json:",omitempty"`
}

CueAdobe is used by Adobe Prime Time in EXT-X-CUE tags

type DateRange added in v0.3.2

type DateRange struct {
	ID       string        `hls:"ID,omitempty" json:",omitempty"`
	Class    string        `hls:"CLASS,omitempty" json:",omitempty"`
	Start    time.Time     `hls:"START-DATE,omitempty" json:",omitempty"`
	Cue      string        `hls:"CUE,omitempty" json:",omitempty"`
	End      time.Time     `hls:"END-DATE,omitempty" json:",omitempty"`
	Duration time.Duration `hls:"DURATION" json:",omitempty"`
	Planned  time.Duration `hls:"PLANNED-DURATION" json:",omitempty"`
	CueIn    string        `hls:"SCTE35-IN,noquote,omitempty" json:",omitempty"`
	CueOut   string        `hls:"SCTE35-OUT,noquote,omitempty" json:",omitempty"`
	Cmd      string        `hls:"SCTE35-CMD,noquote,omitempty" json:",omitempty"`
	EndNext  bool          `hls:"END-ON-NEXT,omitempty" json:",omitempty"`
}

DateRange is part of the official HLS specification, located here:

https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.5.1

func (DateRange) IsAD added in v0.3.2

func (c DateRange) IsAD() bool

IsAD returns true if the cue is a cue-in or cue-out point

type File

type File struct {
	Comment       string    `hls:"#,omitempty" json:",omitempty"`
	Discontinuous bool      `hls:"EXT-X-DISCONTINUITY,omitempty" json:",omitempty"`
	Time          time.Time `hls:"EXT-X-PROGRAM-DATE-TIME,omitempty" json:",omitempty"`
	TimeMap       TimeMap   `hls:"EXT-X-TIMESTAMP-MAP,omitempty" json:",omitempty"`
	Range         Range     `hls:"EXT-X-BYTERANGE,omitempty" json:",omitempty"`
	Map           Map       `hls:"EXT-X-MAP,omitempty" json:",omitempty"`
	Key           Key       `hls:"EXT-X-KEY,omitempty" json:",omitempty"`

	// Asset and other AD-related insertion fields. Most of these can be used to signal
	// AD-insertion and many are redundant. The decoder only initializes [AD] if any of
	// its tags are detected during decoding.
	Asset        m3u.Tag `hls:"EXT-X-ASSET,omitempty" json:",omitempty"`
	PlacementOpp bool    `hls:"EXT-X-PLACEMENT-OPPORTUNITY,omitempty" json:",omitempty"`
	AD           *AD     `hls:",embed,omitempty" json:",omitempty"`

	Extra map[string]interface{} `hls:"*,omitempty" json:",omitempty"`
	Inf   Inf                    `hls:"EXTINF" json:",omitempty"`
}

File is an HLS segment in a media playlist and all of its associated tags

func (*File) AddExtra added in v0.2.0

func (f *File) AddExtra(tag string, value interface{})

func (File) Duration added in v0.0.4

func (f File) Duration(target time.Duration) time.Duration

Duration returns the segment duration. An optional target can be provided as a fallback in case the duration was not set.

func (File) Init added in v0.1.1

func (f File) Init(base *url.URL) *url.URL

Init returns the initialization segment for fragmented mp4 files as an absolute url relative to base. If there is no initialization segment it returns an empty URL.

NOTE: Don't use this, use File.Map.Path instead

func (*File) IsAD added in v0.3.0

func (f *File) IsAD() bool

IsAD returns true if the segment looks like an AD-break. This currently only handles the three standard EXT-X-CUE-OUT, EXT-X-CUE-OUT-CONT, and EXT-X-CUE-IN tags. Examine the SCTE35 fields manually to handle other formats

func (File) Location

func (f File) Location(base *url.URL) (u *url.URL)

Location returns the media URL relative to base. It conditionally applies the base URL in cases where the media URL is a relative path. Base may be nil. This function never returns nil, but may return an empty URL. For error handling, process f.Inf.URL manually

NOTE: Don't use this, use File.Path instead

func (*File) Path added in v0.5.0

func (f *File) Path(parent string) string

type Inf

type Inf struct {
	Duration    time.Duration `hls:"$1" json:",omitempty"`
	Description string        `hls:"$2" json:",omitempty"`

	URL string `hls:"$file" json:",omitempty"`
}

type Key

type Key struct {
	Method   string `hls:"METHOD,noquote" json:",omitempty"`
	URI      string `hls:"URI,omitempty" json:",omitempty"`
	IV       string `hls:"IV,omitempty" json:",omitempty"`
	Format   string `hls:"KEYFORMAT,omitempty" json:",omitempty"`
	Versions string `hls:"KEYFORMATVERSIONS,omitempty" json:",omitempty"`
}

func (*Key) Path added in v0.5.0

func (m *Key) Path(parent string) string

type Map

type Map struct {
	URI       string `hls:"URI,omitempty" json:",omitempty"`
	Byterange string `hls:"BYTERANGE,omitempty" json:",omitempty"`
}

func (*Map) Path added in v0.5.0

func (m *Map) Path(parent string) string

type Master

type Master struct {
	M3U         bool         `hls:"EXTM3U" json:",omitempty"`
	Version     int          `hls:"EXT-X-VERSION" json:",omitempty"`
	Independent bool         `hls:"EXT-X-INDEPENDENT-SEGMENTS,omitempty" json:",omitempty"`
	Steering    Steering     `hls:"EXT-X-CONTENT-STEERING,omitempty" json:",omitempty"`
	Media       []MediaInfo  `hls:"EXT-X-MEDIA,aggr,omitempty" json:",omitempty"`
	Stream      []StreamInfo `hls:"EXT-X-STREAM-INF,aggr,omitempty" json:",omitempty"`
	IFrame      []StreamInfo `hls:"EXT-X-I-FRAME-STREAM-INF,aggr,omitempty" json:",omitempty"`

	URL string `json:",omitempty"`
}

Master is a master playlist. It contains a list of streams (variants) and media information associated by group id. By convention, the master playlist is immutable.

func (*Master) Decode added in v0.1.0

func (m *Master) Decode(r io.Reader) error

Decode decodes the master playlist into m.

func (*Master) DecodeTag added in v0.1.0

func (m *Master) DecodeTag(t ...m3u.Tag) error

func (Master) Encode added in v0.4.0

func (m Master) Encode(w io.Writer) (err error)

Encode encodes the master

func (Master) EncodeTag added in v0.4.0

func (m Master) EncodeTag() (t []m3u.Tag, err error)

func (*Master) Len added in v0.0.4

func (m *Master) Len() int

Len returns the number of variant streams

func (Master) Path added in v0.5.0

func (m Master) Path(parent string) string

Path is Path

type Media

type Media struct {
	MediaHeader
	File []File `hls:"" json:",omitempty"`

	URL string `json:",omitempty"`
}

Media is a media playlist. It consists of a header and one or more files. A file is EXTINF and the content of any additional tags that apply to that EXTINF tag.

func (*Media) Current added in v0.0.3

func (m *Media) Current() (f File)

Current returns the most-recent segment in the stream

func (*Media) Decode added in v0.1.0

func (m *Media) Decode(r io.Reader) error

Decode decodes the playlist in r and stores the result in m. It returns ErrEmpty if the playlist is well-formed, but contains no variant streams.

func (*Media) DecodeTag added in v0.1.0

func (m *Media) DecodeTag(t ...m3u.Tag) error

DecodeTag decodes the list of tags as a media playlist

func (Media) Encode added in v0.3.1

func (m Media) Encode(w io.Writer) (err error)

func (Media) EncodeTag added in v0.1.0

func (m Media) EncodeTag() (t []m3u.Tag, err error)

func (*Media) Len added in v0.0.4

func (m *Media) Len() int

Len returns the number of segments visibile to the playlist

func (Media) Path added in v0.5.0

func (m Media) Path(parent string) string

Path is Path

func (Media) Trunc added in v0.2.0

func (m Media) Trunc(dur time.Duration) Media

type MediaHeader

type MediaHeader struct {
	M3U           bool          `hls:"EXTM3U" json:",omitempty"`
	Version       int           `hls:"EXT-X-VERSION" json:",omitempty"`
	Independent   bool          `hls:"EXT-X-INDEPENDENT-SEGMENTS,omitempty" json:",omitempty"`
	Type          string        `hls:"EXT-X-PLAYLIST-TYPE,noquote,omitempty" json:",omitempty"`
	Target        time.Duration `hls:"EXT-X-TARGETDURATION,omitempty" json:",omitempty"`
	Start         Start         `hls:"EXT-X-START,omitempty" json:",omitempty"`
	Sequence      int           `hls:"EXT-X-MEDIA-SEQUENCE,omitempty" json:",omitempty"`
	Discontinuity int           `hls:"EXT-X-DISCONTINUITY-SEQUENCE,omitempty" json:",omitempty"`
	End           bool          `hls:"EXT-X-ENDLIST,omitempty" json:",omitempty"`
}

type MediaInfo

type MediaInfo struct {
	Type       string   `hls:"TYPE,noquote,omitempty" json:",omitempty"`
	Group      string   `hls:"GROUP-ID,omitempty" json:",omitempty"`
	Name       string   `hls:"NAME,omitempty" json:",omitempty"`
	StableID   string   `hls:"STABLE-RENDITION-ID,omitempty" json:",omitempty"`
	Default    bool     `hls:"DEFAULT" json:",omitempty"`
	Autoselect bool     `hls:"AUTOSELECT" json:",omitempty"`
	Character  []string `hls:"CHARACTERISTICS" json:",omitempty"`
	Codecs     []string `hls:"CODECS,omitempty" json:",omitempty"`
	Lang       string   `hls:"LANGUAGE,omitempty" json:",omitempty"`
	Instream   string   `hls:"INSTREAM-ID,omitempty" json:",omitempty"`
	Bitdepth   int      `hls:"BIT-DEPTH,omitempty" json:",omitempty"`
	Samplerate int      `hls:"SAMPLE-RATE,omitempty" json:",omitempty"`
	Channels   string   `hls:"CHANNELS,omitempty" json:",omitempty"`
	URI        string   `hls:"URI,omitempty" json:",omitempty"`
}

func (MediaInfo) Path added in v0.5.0

func (m MediaInfo) Path(parent string) string

Path is Path

type Range

type Range struct {
	V string `hls:"" json:",omitempty"`
}

func (Range) Value

func (r Range) Value(n int) (at, size int, err error)

type SCTE35 added in v0.3.2

type SCTE35 struct {
	ID       string        `hls:"ID,omitempty" json:",omitempty"`
	Cue      string        `hls:"CUE,omitempty" json:",omitempty"`
	Duration time.Duration `hls:"DURATION,omitempty" json:",omitempty"`
	Elapsed  time.Duration `hls:"ELAPSED,omitempty" json:",omitempty"`
	Time     time.Duration `hls:"TIME,omitempty" json:",omitempty"`
	Type     int           `hls:"TYPE,omitempty" json:",omitempty"`
	UPID     string        `hls:"UPID,omitempty" json:",omitempty"`
	Blackout string        `hls:"BLACKOUT,omitempty" json:",omitempty"`
	CueIn    string        `hls:"CUE-IN,omitempty" json:",omitempty"`
	CueOut   string        `hls:"CUE-OUT,omitempty" json:",omitempty"`
	SegNE    string        `hls:"SEGNE,omitempty" json:",omitempty"`
}

func (SCTE35) IsAD added in v0.3.2

func (c SCTE35) IsAD() bool

IsAD returns true if the cue is a cue-in or cue-out point

type Start

type Start struct {
	Offset  time.Duration `hls:"TIME-OFFSET" json:",omitempty"`
	Precise bool          `hls:"PRECISE,omitempty" json:",omitempty"`
}

type Steering added in v0.4.0

type Steering struct {
	URI     string `hls:"SERVER-URI,omitempty" json:",omitempty"`
	Pathway string `hls:"PATHWAY-ID,omitempty" json:",omitempty"`
}

func (Steering) Path added in v0.5.0

func (s Steering) Path(parent string) string

Path is Path

type StreamInfo

type StreamInfo struct {
	URL string `hls:"$file" json:",omitempty"`

	Index        int         `hls:"PROGRAM-ID" json:",omitempty"`
	Framerate    float64     `hls:"FRAME-RATE,omitempty" json:",omitempty"`
	Bandwidth    int         `hls:"BANDWIDTH,omitempty" json:",omitempty"`
	BandwidthAvg int         `hls:"AVERAGE-BANDWIDTH,omitempty" json:",omitempty"`
	Codecs       []string    `hls:"CODECS,omitempty" json:",omitempty"`
	Resolution   image.Point `hls:"RESOLUTION" json:",omitempty"`
	VideoRange   string      `hls:"VIDEO-RANGE,noquote,omitempty" json:",omitempty"`
	HDCP         string      `hls:"HDCP-LEVEL,noquote,omitempty" json:",omitempty"`

	Audio    string `hls:"AUDIO,omitempty" json:",omitempty"`
	Video    string `hls:"VIDEO,omitempty" json:",omitempty"`
	Subtitle string `hls:"SUBTITLES,omitempty" json:",omitempty"`
	Pathway  string `hls:"PATHWAY-ID,omitempty" json:",omitempty"`

	// Caption is unquoted if the value is NONE, this absolute mess of a datatype
	// is handled explicitly in m3u/m3u.go:/CLOSED-CAPTIONS/
	Caption string `hls:"CLOSED-CAPTIONS,ambiguous,omitempty" json:",omitempty"`

	// URI is only set in IFrame stream infos
	URI string `hls:"URI,omitempty" json:",omitempty"`
}

func (StreamInfo) Location

func (s StreamInfo) Location(base *url.URL) *url.URL

Location returns the stream URL relative to base. It conditionally applies the base URL in cases where the stream URL is a relative path. Base may be nil. This function never returns nil, but may return an empty URL. For error handling, process s.URLmanually.

func (StreamInfo) Path added in v0.5.0

func (s StreamInfo) Path(parent string) string

Path is like Location, except its not a pain to use. It returns the path to the stream given an optional parent master path. If parent ends in a slash we assume parent is just the current working directory, otherwise the base name is stripped.

type TimeMap added in v0.2.0

type TimeMap struct {
	MPEG  int       `hls:"MPEGTS" json:",omitempty"`
	Local time.Time `hls:"LOCAL" json:",omitempty"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL