class M3U8::Playlist

Overview

Playlist represents an m3u8 playlist, which can be either a Master Playlist or a Media Playlist.

In an HLS playlist, as defined in RFC 8216, the file begins with the EXTM3U tag and may contain a variety of global and segment-specific tags. A Master Playlist includes entries for different Variant Streams (using EXT-X-STREAM-INF or EXT-X-I-FRAME-STREAM-INF tags), while a Media Playlist lists the media segments (with EXTINF, EXT-X-BYTERANGE, etc.).

Basic Tags:

Media Playlist Tags:

Media Segment Tags:

Master Playlist tags:

Media or Master Playlist Tags:

This class maintains various playlist-wide properties:

The class provides methods to create a new Playlist, to parse a playlist from a string input, and to generate the complete playlist as a string by combining #header, #body, and #footer components.

Examples:

Creating a new Playlist with specific parameters:

options = {
  version:                7,
  cache:                  false,
  target:                 12,
  sequence:               1,
  discontinuity_sequence: 2,
  type:                   "VOD",
  independent_segments:   true,
}
playlist = Playlist.new(options)
playlist.items << SegmentItem.new(duration: 10.991, segment: "test_01.ts")
playlist.items << SegmentItem.new(duration: 9.891, segment: "test_02.ts")
playlist.items << SegmentItem.new(duration: 10.556, segment: "test_03.ts")
playlist.items << SegmentItem.new(duration: 8.790, segment: "test_04.ts")
playlist.duration # => 40.227999999999994
playlist.to_s
# => "#EXTM3U\n" +
#    "#EXT-X-PLAYLIST-TYPE:VOD\n" +
#    "#EXT-X-VERSION:7\n" +
#    "#EXT-X-INDEPENDENT-SEGMENTS\n" +
#    "#EXT-X-MEDIA-SEQUENCE:1\n" +
#    "#EXT-X-DISCONTINUITY-SEQUENCE:2\n" +
#    "#EXT-X-ALLOW-CACHE:NO\n" +
#    "#EXT-X-TARGETDURATION:12\n" +
#    "#EXTINF:10.991,\n" +
#    "test_01.ts\n" +
#    "#EXTINF:9.891,\n" +
#    "test_02.ts\n" +
#    "#EXTINF:10.556,\n" +
#    "test_03.ts\n" +
#    "#EXTINF:8.79,\n" +
#    "test_04.ts\n" +
#    "#EXT-X-ENDLIST\n"

Parsing a complete playlist string:

m3u8_string = "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:12\n..."
Playlist.parse(m3u8_string)
# => #<M3U8::Playlist ...>

Included Modules

Extended Modules

Defined in:

m3u8/playlist.cr

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(params : NamedTuple = NamedTuple.new) #
Constructs a new Playlist instance from a NamedTuple of parameters.

The NamedTuple may include keys for properties such as:
  :master, :version, :cache, :discontinuity_sequence, :type,
  :target, :sequence, :iframes_only, :independent_segments, :live, and :items.

Example:

options = { version: 7, cache: false, target: 12, sequence: 1, discontinuity_sequence: 2, type: "VOD", independent_segments: true, } Playlist.new(options)

=> #<M3U8::Playlist:0x79adbb379540

@cache=false,

@discontinuity_sequence=2,

@iframes_only=false,

@independent_segments=true,

@items=[],

@live=false,

@master=nil,

@sequence=1,

@target=12.0,

@type="VOD",

@version=7>


[View source]
def self.new(master : Bool | Nil = nil, version : Int32 | Nil = nil, cache : Bool | Nil = nil, discontinuity_sequence : Int32 | Nil = nil, type : Nil | String = nil, target = nil, sequence = nil, iframes_only = nil, independent_segments = nil, live = nil, items = nil) #

Initializes a new Playlist instance.

Example:

Playlist.new(
  version: 7,
  cache: false,
  target: 12,
  sequence: 1,
  discontinuity_sequence: 2,
  type: "VOD",
  independent_segments: true,
)
# => #<M3U8::Playlist:0x78d37cb334d0
#     @cache=false,
#     @discontinuity_sequence=2,
#     @iframes_only=false,
#     @independent_segments=true,
#     @items=[],
#     @live=false,
#     @master=nil,
#     @sequence=1,
#     @target=12.0,
#     @type="VOD",
#     @version=7>

[View source]

Class Method Detail

def self.codecs(options = NamedTuple.new) #

Generates the CODECS attribute string for the playlist.

This method instantiates a Codecs object with the given options and returns its string representation. The CODECS attribute lists the codecs used in the media, such as "avc1.66.30,mp4a.40.2". For details on how the codecs are determined, refer to the Codecs class.

Example:

options = {
  profile:     "baseline",
  level:       3.0,
  audio_codec: "aac-lc",
}
Playlist.codecs(options) # => "avc1.66.30,mp4a.40.2"

[View source]
def self.parse(input) #

Parses a playlist string into a Playlist instance.

Example:

m3u8_string = "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:12\n..."
Playlist.parse(m3u8_string)
# => #<M3U8::Playlist ...>

[View source]

Instance Method Detail

def body #

Returns the body section of the playlist as a string.

The body consists of all the items (segments or playlist entries) that have been added.

Examples:

playlist = Playlist.new(version: 6, independent_segments: true)
playlist.items << SegmentItem.new(duration: 10.991, segment: "test.ts")
playlist.body
# => "#EXTINF:10.991,\n"
#    "test.ts"

[View source]
def cache : Bool | Nil #

[View source]
def cache=(cache : Bool | Nil) #

[View source]
def discontinuity_sequence : Int32 | Nil #

[View source]
def discontinuity_sequence=(discontinuity_sequence : Int32 | Nil) #

[View source]
def duration #

Calculates the total duration of the playlist by summing the durations of all SegmentItems.

Examples:

playlist = Playlist.new
playlist.items << SegmentItem.new(duration: 10.991, segment: "test_01.ts")
playlist.items << SegmentItem.new(duration: 9.891, segment: "test_02.ts")
playlist.items << SegmentItem.new(duration: 10.556, segment: "test_03.ts")
playlist.items << SegmentItem.new(duration: 8.790, segment: "test_04.ts")
playlist.duration          # => 40.227999999999994
playlist.duration.round(3) # => 40.228

[View source]
def footer #

Returns the footer section of the playlist as a string.

For Video On Demand (VOD) playlists, the footer typically includes the #EXT-X-ENDLIST tag, which signals that no additional segments will be added. This tag is omitted for #live playlists or #master playlists.

Examples:

playlist = Playlist.new(version: 6, independent_segments: true)
playlist.footer # => "#EXT-X-ENDLIST"

playlist = Playlist.new(live: true)
playlist.footer # => ""

playlist = Playlist.new(master: true)
playlist.footer # => ""

[View source]
def header #

Returns the header section of the playlist as a string.

The header tags based on whether the playlist is a Master or Media Playlist.

Examples:

playlist = Playlist.new(master: true, version: 6, independent_segments: true)
playlist.header
# => "#EXTM3U\n" \
#    "#EXT-X-VERSION:6\n" \
#    "#EXT-X-INDEPENDENT-SEGMENTS"

playlist = Playlist.new(version: 6, independent_segments: true)
playlist.header
# => "#EXTM3U\n" \
#    "#EXT-X-VERSION:6\n" \
#    "#EXT-X-INDEPENDENT-SEGMENTS\n" \
#    "#EXT-X-MEDIA-SEQUENCE:0\n" \
#    "#EXT-X-TARGETDURATION:10"

[View source]
def iframes_only : Bool #

Specifies whether the playlist is an I-frame only playlist. If set to true, the playlist header will include the EXT-X-I-FRAMES-ONLY tag, indicating that the playlist contains only I-frame segments (useful for trick play).

Examples:

playlist = Playlist.new
playlist.iframes_only = true
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-I-FRAMES-ONLY\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.iframes_only = false
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.iframes_only = nil
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

[View source]
def iframes_only=(iframes_only : Bool) #

Specifies whether the playlist is an I-frame only playlist. If set to true, the playlist header will include the EXT-X-I-FRAMES-ONLY tag, indicating that the playlist contains only I-frame segments (useful for trick play).

Examples:

playlist = Playlist.new
playlist.iframes_only = true
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-I-FRAMES-ONLY\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.iframes_only = false
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.iframes_only = nil
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

[View source]
def independent_segments : Bool #

When set to true, the playlist header will include the EXT-X-INDEPENDENT-SEGMENTS tag.

This tag indicates that each Media Segment in the playlist can be independently decoded, which is important for certain playback scenarios in HLS.

If the property is false or nil, the EXT-X-INDEPENDENT-SEGMENTS tag will not be output.

Example:

playlist = Playlist.new
playlist.independent_segments = true
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-INDEPENDENT-SEGMENTS\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.independent_segments = false
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.independent_segments = nil
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

[View source]
def independent_segments=(independent_segments : Bool) #

When set to true, the playlist header will include the EXT-X-INDEPENDENT-SEGMENTS tag.

This tag indicates that each Media Segment in the playlist can be independently decoded, which is important for certain playback scenarios in HLS.

If the property is false or nil, the EXT-X-INDEPENDENT-SEGMENTS tag will not be output.

Example:

playlist = Playlist.new
playlist.independent_segments = true
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-INDEPENDENT-SEGMENTS\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.independent_segments = false
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

playlist.independent_segments = nil
playlist.header
# => "#EXTM3U\n" +
#    "#EXT-X-MEDIA-SEQUENCE:0\n" +
#    "#EXT-X-TARGETDURATION:10"

[View source]
def items : Array(Items) #

[View source]
def items=(items : Array(Items)) #

[View source]
def live : Bool #

[View source]
def live=(live : Bool) #

[View source]
def live? #

Returns true if the playlist is considered live.

  • For a Master Playlist, live is always false.
  • For a Media Playlist, the live property is determined by the parsed content.

Example:

playlist = Playlist.new(live: true)
playlist.items << SegmentItem.new(duration: 10.991, segment: "test_01.ts")
playlist.live? # => true

[View source]
def master : Bool | Nil #

[View source]
def master=(master : Bool | Nil) #

[View source]
def master? #

Returns true if the playlist is a Master Playlist.

If the #master property is explicitly set (i.e. not nil), its value is returned. Otherwise, the playlist type is inferred based on the items it contains:

Examples:

playlist = Playlist.new(master: true)
playlist.master? # => true

playlist = Playlist.new
playlist.master? # => false

playlist = Playlist.new
playlist.items << PlaylistItem.new(program_id: 1, width: 1920, height: 1080, codecs: "avc", bandwidth: 540, uri: "test.url")
playlist.master? # => true

playlist = Playlist.new
playlist.items << SegmentItem.new(duration: 10.991, segment: "test.ts")
playlist.master? # => false

[View source]
def sequence : Int32 #

[View source]
def sequence=(sequence : Int32) #

[View source]
def target : Float64 #

[View source]
def target=(target : Float64) #

[View source]
def to_s #

Returns the complete playlist as a string.

The output is generated by concatenating the #header, #body, and #footer sections of the playlist, separated by newline characters.

Examples:

playlist = Playlist.new
playlist.items << PlaylistItem.new(program_id: "1", uri: "playlist_url", bandwidth: 6400, audio_codec: "mp3")
playlist.to_s
# => "#EXTM3U\n" +
#    "#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS=\"mp4a.40.34\",BANDWIDTH=6400\n" +
#    "playlist_url\n"

[View source]
def type : String | Nil #

[View source]
def type=(type : String | Nil) #

[View source]
def valid! #

Validates the playlist and raises an Error::PlaylistType error if it is invalid.

Examples:

playlist = Playlist.new
playlist.items << PlaylistItem.new(program_id: 1, width: 1920, height: 1080, codecs: "avc", bandwidth: 540, uri: "test.url")
playlist.valid! # => nil

playlist.items << SegmentItem.new(duration: 10.991, segment: "test.ts")
playlist.valid! # => raises M3U8::Error::PlaylistType

[View source]
def valid? #

Validates the playlist.

Returns true if either the number of PlaylistItem entries or SegmentItem entries is zero. Otherwise, it returns false, indicating a potential mismatch in playlist types.

Example:

playlist = Playlist.new
playlist.items << PlaylistItem.new(program_id: 1, width: 1920, height: 1080, codecs: "avc", bandwidth: 540, uri: "test.url")
playlist.valid? # => true

playlist.items << SegmentItem.new(duration: 10.991, segment: "test.ts")
playlist.valid? # => false

[View source]
def version : Int32 | Nil #

[View source]
def version=(version : Int32 | Nil) #

[View source]