m3u8 provides easy generation and parsing of m3u8 playlists defined in RFC 8216 HTTP Live Streaming and its proposed successor draft-pantos-hls-rfc8216bis.
- Full coverage of RFC 8216 and draft-pantos-hls-rfc8216bis-19 (Protocol Version 13), including Low-Latency HLS and Content Steering.
- Provides parsing of an m3u8 playlist into an object model from any File, StringIO, or string.
- Provides ability to write playlist to a File or StringIO or expose as string via to_s.
- Distinction between a master and media playlist is handled automatically (single Playlist class).
- Automatic generation of codec strings for H.264, HEVC, AV1, AAC, AC-3, E-AC-3, FLAC, Opus, and MP3.
Ruby 3.0+
Add this line to your application's Gemfile:
gem 'm3u8'And then execute:
$ bundle
Or install it yourself as:
$ gem install m3u8
Create a master playlist and add child playlists for adaptive bitrate streaming:
require 'm3u8'
playlist = M3u8::Playlist.newCreate a new playlist item with options:
options = { width: 1920, height: 1080, profile: 'high', level: 4.1,
audio_codec: 'aac-lc', bandwidth: 540, uri: 'test.url' }
item = M3u8::PlaylistItem.new(options)
playlist.items << itemAdd alternate audio, camera angles, closed captions and subtitles by creating MediaItem instances and adding them to the Playlist:
hash = { type: 'AUDIO', group_id: 'audio-lo', language: 'fre',
assoc_language: 'spoken', name: 'Francais', autoselect: true,
default: false, forced: true, uri: 'frelo/prog_index.m3u8' }
item = M3u8::MediaItem.new(hash)
playlist.items << itemAdd Content Steering for dynamic CDN pathway selection:
item = M3u8::ContentSteeringItem.new(
server_uri: 'https://example.com/steering',
pathway_id: 'CDN-A'
)
playlist.items << itemAdd variable definitions:
item = M3u8::DefineItem.new(name: 'base', value: 'https://example.com')
playlist.items << itemAdd a session-level encryption key (master playlists):
item = M3u8::SessionKeyItem.new(
method: 'AES-128', uri: 'https://example.com/key.bin'
)
playlist.items << itemAdd session-level data (master playlists):
item = M3u8::SessionDataItem.new(
data_id: 'com.example.title', value: 'My Video',
language: 'en'
)
playlist.items << itemCreate a standard playlist and add MPEG-TS segments via SegmentItem:
options = { version: 1, cache: false, target: 12, sequence: 1 }
playlist = M3u8::Playlist.new(options)
item = M3u8::SegmentItem.new(duration: 11, segment: 'test.ts')
playlist.items << itemAdd an encryption key for subsequent segments:
item = M3u8::KeyItem.new(
method: 'AES-128',
uri: 'https://example.com/key.bin',
iv: '0x1234567890abcdef1234567890abcdef'
)
playlist.items << itemSpecify an initialization segment (e.g. fMP4 header):
item = M3u8::MapItem.new(
uri: 'init.mp4', byterange: { length: 812, start: 0 }
)
playlist.items << itemInsert a timed metadata date range:
item = M3u8::DateRangeItem.new(
id: 'ad-break-1', start_date: '2024-06-01T12:00:00Z',
planned_duration: 30.0,
client_attributes: { 'X-AD-ID' => '"foo"' }
)
playlist.items << itemSignal an encoding discontinuity:
playlist.items << M3u8::DiscontinuityItem.newAttach a program date/time to the next segment:
item = M3u8::TimeItem.new(time: Time.iso8601('2024-06-01T12:00:00Z'))
playlist.items << itemMark a gap in segment availability:
playlist.items << M3u8::GapItem.newAdd a bitrate hint for upcoming segments:
item = M3u8::BitrateItem.new(bitrate: 1500)
playlist.items << itemCreate an LL-HLS playlist with server control, partial segments, and preload hints:
server_control = M3u8::ServerControlItem.new(
can_skip_until: 24.0, part_hold_back: 1.0,
can_block_reload: true
)
part_inf = M3u8::PartInfItem.new(part_target: 0.5)
playlist = M3u8::Playlist.new(
version: 9, target: 4, sequence: 100,
server_control: server_control, part_inf: part_inf,
live: true
)
item = M3u8::SegmentItem.new(duration: 4.0, segment: 'seg100.mp4')
playlist.items << item
part = M3u8::PartItem.new(
duration: 0.5, uri: 'seg101.0.mp4', independent: true
)
playlist.items << part
hint = M3u8::PreloadHintItem.new(type: 'PART', uri: 'seg101.1.mp4')
playlist.items << hint
report = M3u8::RenditionReportItem.new(
uri: '../alt/index.m3u8', last_msn: 101, last_part: 0
)
playlist.items << reportYou can pass an IO object to the write method:
require 'tempfile'
file = Tempfile.new('test')
playlist.write(file)You can also access the playlist as a string:
playlist.to_sM3u8::Writer is the class that handles generating the playlist output.
Alternatively you can set codecs rather than having it generated automatically:
options = { width: 1920, height: 1080, codecs: 'avc1.66.30,mp4a.40.2',
bandwidth: 540, uri: 'test.url' }
item = M3u8::PlaylistItem.new(options)file = File.open 'spec/fixtures/master.m3u8'
playlist = M3u8::Playlist.read(file)
playlist.master?
# => trueQuery playlist properties:
playlist.master?
# => true (contains variant streams)
playlist.live?
# => false (master playlists are never live)For media playlists, duration returns total segment duration:
media = M3u8::Playlist.read(
File.open('spec/fixtures/event_playlist.m3u8')
)
media.live?
# => false
media.duration
# => 17.0 (sum of all segment durations)Access items and their attributes:
playlist.items.first
# => #<M3u8::PlaylistItem ...>
media.segments.first.duration
# => 6.0
media.segments.first.segment
# => "segment0.mp4"Convenience methods filter items by type:
playlist.playlists # => [PlaylistItem, ...]
playlist.segments # => [SegmentItem, ...]
playlist.media_items # => [MediaItem, ...]
playlist.keys # => [KeyItem, ...]
playlist.maps # => [MapItem, ...]
playlist.date_ranges # => [DateRangeItem, ...]
playlist.parts # => [PartItem, ...]
playlist.session_data # => [SessionDataItem, ...]Parse an LL-HLS playlist:
file = File.open 'spec/fixtures/ll_hls_playlist.m3u8'
playlist = M3u8::Playlist.read(file)
playlist.server_control.can_block_reload
# => true
playlist.part_inf.part_target
# => 0.5M3u8::Reader is the class that handles parsing if you want more control over the process.
Generate the codec string based on audio and video codec options without dealing with a playlist instance:
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
codecs = M3u8::Playlist.codecs(options)
# => "avc1.66.30,mp4a.40.2"| Profile | Description |
|---|---|
baseline, main, high |
H.264/AVC |
hevc-main, hevc-main-10 |
HEVC/H.265 |
av1-main, av1-high |
AV1 |
| Value | Codec |
|---|---|
aac-lc |
AAC-LC |
he-aac |
HE-AAC |
mp3 |
MP3 |
ac-3 |
AC-3 (Dolby Digital) |
ec-3, e-ac-3 |
E-AC-3 (Dolby Digital Plus) |
flac |
FLAC |
opus |
Opus |
EXT-X-STREAM-INF/EXT-X-I-FRAME-STREAM-INF— includingSTABLE-VARIANT-ID,VIDEO-RANGE,ALLOWED-CPC,PATHWAY-ID,REQ-VIDEO-LAYOUT,SUPPLEMENTAL-CODECS,SCOREEXT-X-MEDIA— includingSTABLE-RENDITION-ID,BIT-DEPTH,SAMPLE-RATEEXT-X-SESSION-DATAEXT-X-SESSION-KEYEXT-X-CONTENT-STEERING
EXT-X-TARGETDURATIONEXT-X-MEDIA-SEQUENCEEXT-X-DISCONTINUITY-SEQUENCEEXT-X-PLAYLIST-TYPEEXT-X-I-FRAMES-ONLYEXT-X-ALLOW-CACHEEXT-X-ENDLIST
EXTINFEXT-X-BYTERANGEEXT-X-DISCONTINUITYEXT-X-KEYEXT-X-MAPEXT-X-PROGRAM-DATE-TIMEEXT-X-DATERANGEEXT-X-GAPEXT-X-BITRATE
EXT-X-INDEPENDENT-SEGMENTSEXT-X-STARTEXT-X-DEFINEEXT-X-VERSION
EXT-X-SERVER-CONTROLEXT-X-PART-INFEXT-X-PARTEXT-X-SKIPEXT-X-PRELOAD-HINTEXT-X-RENDITION-REPORT
- Fork it ( https://github.com/sethdeckard/m3u8/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Run the specs, make sure they pass and that new features are covered. Code coverage should be 100%.
- Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
MIT License - See LICENSE.txt for details.