m3u8 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +2 -0
- data/README.md +5 -6
- data/lib/m3u8.rb +9 -5
- data/lib/m3u8/playback_start.rb +33 -0
- data/lib/m3u8/playlist.rb +15 -11
- data/lib/m3u8/reader.rb +21 -2
- data/lib/m3u8/version.rb +1 -1
- data/lib/m3u8/writer.rb +39 -12
- data/spec/fixtures/master.m3u8 +2 -0
- data/spec/lib/m3u8/playback_start_spec.rb +47 -0
- data/spec/lib/m3u8/playlist_spec.rb +54 -42
- data/spec/lib/m3u8/reader_spec.rb +10 -4
- data/spec/lib/m3u8/writer_spec.rb +20 -20
- data/spec/lib/m3u8_spec.rb +12 -2
- metadata +6 -4
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb897f32a24dabcd87ebd26a367425c8eae33113
|
4
|
+
data.tar.gz: 5726036c12d84a49d24ae14f21cf52e817c3bc1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db0747bc9be34249f3fbf35d40255763e8b96cddcf9d857495361e8186df81fc4285e6740bc54abab804d2617504c77b8ac7befbb2cc2604e7de0a52d2963d65
|
7
|
+
data.tar.gz: ee311cc358cca4494c6d5c1a4fa8e64e50977076d8001d3a1258ca1a4762354f6886b903293f06a424136044b6112e1f4c501dfe449508238d4e6d5e1e7be0dd
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
0.7.0 (2/3/2017) - Added support for EXT-X-INDEPENDENT-SEGMENTS and EXT-X-START tags. Minor version bumped due to changes of default values for new playlists.
|
2
|
+
|
1
3
|
0.6.9 (4/24/2016) - Merged pull request #17 from [ghn](https://github.com/ghn). Removes return when parsing next line in playlist.
|
2
4
|
|
3
5
|
***
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[](http://badge.fury.io/rb/m3u8)
|
2
2
|
[](https://travis-ci.org/sethdeckard/m3u8)
|
3
|
-
[](https://coveralls.io/github/sethdeckard/m3u8?branch=master)
|
4
4
|
[](https://codeclimate.com/github/sethdeckard/m3u8)
|
5
5
|
[](https://gemnasium.com/sethdeckard/m3u8)
|
6
6
|
[](https://hakiri.io/github/sethdeckard/m3u8/master)
|
@@ -145,7 +145,7 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
|
|
145
145
|
* Supports session data in master playlists.
|
146
146
|
* Supports keys for encrypted media segments (EXT-X-KEY, EXT-SESSION-KEY).
|
147
147
|
|
148
|
-
## HLS Spec Status (version
|
148
|
+
## HLS Spec Status (version 20)
|
149
149
|
### Implemented:
|
150
150
|
* EXTM3U
|
151
151
|
* EXT-X-VERSION
|
@@ -165,11 +165,10 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
|
|
165
165
|
* EXT-X-I-FRAME-STREAM-INF
|
166
166
|
* EXT-X-SESSION-DATA
|
167
167
|
* EXT-X-SESSION-KEY
|
168
|
+
* EXT-X-START
|
168
169
|
|
169
170
|
### TODO:
|
170
|
-
* EXT-X-
|
171
|
-
* EXT-X-INDEPENDENT-SEGMENTS
|
172
|
-
* EXT-X-START
|
171
|
+
* EXT-X-DATERANGE
|
173
172
|
|
174
173
|
## Roadmap
|
175
174
|
* Add the last remaining tags for latest version of the spec.
|
@@ -187,4 +186,4 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
|
|
187
186
|
|
188
187
|
|
189
188
|
## License
|
190
|
-
MIT License - See [LICENSE.txt](https://github.com/sethdeckard/m3u8/blob/master/LICENSE.txt) for details.
|
189
|
+
MIT License - See [LICENSE.txt](https://github.com/sethdeckard/m3u8/blob/master/LICENSE.txt) for details.
|
data/lib/m3u8.rb
CHANGED
@@ -4,6 +4,13 @@ Dir[File.dirname(__FILE__) + '/m3u8/*.rb'].sort.each { |file| require file }
|
|
4
4
|
|
5
5
|
# M3u8 provides parsing, generation, and validation of m3u8 playlists
|
6
6
|
module M3u8
|
7
|
+
def intialize_with_byterange(params = {})
|
8
|
+
params.each do |key, value|
|
9
|
+
value = ByteRange.new(value) if value.is_a?(Hash)
|
10
|
+
instance_variable_set("@#{key}", value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
def parse_attributes(line)
|
8
15
|
array = line.delete("\n").scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
|
9
16
|
Hash[array.map { |key, value| [key, value.delete('"')] }]
|
@@ -13,10 +20,7 @@ module M3u8
|
|
13
20
|
value == 'YES' ? true : false
|
14
21
|
end
|
15
22
|
|
16
|
-
def
|
17
|
-
|
18
|
-
value = ByteRange.new(value) if value.is_a?(Hash)
|
19
|
-
instance_variable_set("@#{key}", value)
|
20
|
-
end
|
23
|
+
def to_yes_no(boolean)
|
24
|
+
boolean == true ? 'YES' : 'NO'
|
21
25
|
end
|
22
26
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module M3u8
|
2
|
+
# PlaybackStart represents a #EXT-X-START tag and attributes
|
3
|
+
class PlaybackStart
|
4
|
+
include M3u8
|
5
|
+
attr_accessor :time_offset, :precise
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
options.each do |key, value|
|
9
|
+
instance_variable_set("@#{key}", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(text)
|
14
|
+
attributes = parse_attributes(text)
|
15
|
+
@time_offset = attributes['TIME-OFFSET'].to_f
|
16
|
+
precise = attributes['PRECISE']
|
17
|
+
@precise = parse_yes_no(precise) unless precise.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
attributes = ["TIME-OFFSET=#{time_offset}",
|
22
|
+
precise_format].compact.join(',')
|
23
|
+
"#EXT-X-START:#{attributes}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def precise_format
|
29
|
+
return if precise.nil?
|
30
|
+
"PRECISE=#{to_yes_no(precise)}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/m3u8/playlist.rb
CHANGED
@@ -3,7 +3,7 @@ module M3u8
|
|
3
3
|
# of media segments
|
4
4
|
class Playlist
|
5
5
|
attr_accessor :items, :version, :cache, :target, :sequence, :type,
|
6
|
-
:iframes_only
|
6
|
+
:iframes_only, :independent_segments
|
7
7
|
|
8
8
|
def initialize(options = {})
|
9
9
|
assign_options options
|
@@ -21,8 +21,8 @@ module M3u8
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def write(output)
|
24
|
-
writer = Writer.new
|
25
|
-
writer.write
|
24
|
+
writer = Writer.new(output)
|
25
|
+
writer.write(self)
|
26
26
|
end
|
27
27
|
|
28
28
|
def master?
|
@@ -32,7 +32,7 @@ module M3u8
|
|
32
32
|
|
33
33
|
def to_s
|
34
34
|
output = StringIO.open
|
35
|
-
write
|
35
|
+
write(output)
|
36
36
|
output.string
|
37
37
|
end
|
38
38
|
|
@@ -52,13 +52,7 @@ module M3u8
|
|
52
52
|
private
|
53
53
|
|
54
54
|
def assign_options(options)
|
55
|
-
options =
|
56
|
-
version: 3,
|
57
|
-
sequence: 0,
|
58
|
-
cache: true,
|
59
|
-
target: 10,
|
60
|
-
iframes_only: false
|
61
|
-
}.merge options
|
55
|
+
options = defaults.merge(options)
|
62
56
|
|
63
57
|
self.version = options[:version]
|
64
58
|
self.sequence = options[:sequence]
|
@@ -66,6 +60,16 @@ module M3u8
|
|
66
60
|
self.target = options[:target]
|
67
61
|
self.type = options[:type]
|
68
62
|
self.iframes_only = options[:iframes_only]
|
63
|
+
self.independent_segments = options[:independent_segments]
|
64
|
+
end
|
65
|
+
|
66
|
+
def defaults
|
67
|
+
{
|
68
|
+
sequence: 0,
|
69
|
+
target: 10,
|
70
|
+
iframes_only: false,
|
71
|
+
independent_segments: false
|
72
|
+
}
|
69
73
|
end
|
70
74
|
|
71
75
|
def playlist_size
|
data/lib/m3u8/reader.rb
CHANGED
@@ -8,13 +8,14 @@ module M3u8
|
|
8
8
|
@tags = [basic_tags,
|
9
9
|
media_segment_tags,
|
10
10
|
media_playlist_tags,
|
11
|
-
master_playlist_tags
|
11
|
+
master_playlist_tags,
|
12
|
+
universal_tags].inject(:merge)
|
12
13
|
end
|
13
14
|
|
14
15
|
def read(input)
|
15
16
|
self.playlist = Playlist.new
|
16
17
|
input.each_line do |line|
|
17
|
-
parse_line
|
18
|
+
parse_line(line)
|
18
19
|
end
|
19
20
|
playlist
|
20
21
|
end
|
@@ -53,6 +54,18 @@ module M3u8
|
|
53
54
|
}
|
54
55
|
end
|
55
56
|
|
57
|
+
def universal_tags
|
58
|
+
{ '#EXT-X-START' => ->(line) { parse_start(line) },
|
59
|
+
'#EXT-X-INDEPENDENT-SEGMENTS' => proc do
|
60
|
+
playlist.independent_segments = true
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_independent_segments(line)
|
66
|
+
parse_independent_segments
|
67
|
+
end
|
68
|
+
|
56
69
|
def parse_line(line)
|
57
70
|
return if match_tag(line)
|
58
71
|
parse_next_line(line) if !item.nil? && open
|
@@ -151,6 +164,12 @@ module M3u8
|
|
151
164
|
playlist.items << item
|
152
165
|
end
|
153
166
|
|
167
|
+
def parse_start(line)
|
168
|
+
item = M3u8::PlaybackStart.new
|
169
|
+
item.parse(line)
|
170
|
+
playlist.items << item
|
171
|
+
end
|
172
|
+
|
154
173
|
def parse_time(line)
|
155
174
|
if open
|
156
175
|
item.program_date_time = M3u8::TimeItem.parse(line)
|
data/lib/m3u8/version.rb
CHANGED
data/lib/m3u8/writer.rb
CHANGED
@@ -8,10 +8,8 @@ module M3u8
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def write(playlist)
|
11
|
-
validate
|
12
|
-
|
13
|
-
io.puts '#EXTM3U'
|
14
|
-
write_header(playlist) unless playlist.master?
|
11
|
+
validate(playlist)
|
12
|
+
write_header(playlist)
|
15
13
|
|
16
14
|
playlist.items.each do |item|
|
17
15
|
io.puts item.to_s
|
@@ -23,26 +21,55 @@ module M3u8
|
|
23
21
|
|
24
22
|
private
|
25
23
|
|
24
|
+
def target_duration_format(playlist)
|
25
|
+
format('#EXT-X-TARGETDURATION:%d', playlist.target)
|
26
|
+
end
|
27
|
+
|
26
28
|
def validate(playlist)
|
27
29
|
return if playlist.valid?
|
28
|
-
|
30
|
+
raise PlaylistTypeError, 'Playlist is invalid.'
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_cache_tag(cache)
|
34
|
+
return if cache.nil?
|
35
|
+
|
36
|
+
io.puts "#EXT-X-ALLOW-CACHE:#{cache ? 'YES' : 'NO'}"
|
29
37
|
end
|
30
38
|
|
31
39
|
def write_header(playlist)
|
40
|
+
io.puts '#EXTM3U'
|
41
|
+
if playlist.master?
|
42
|
+
write_master_playlist_header(playlist)
|
43
|
+
else
|
44
|
+
write_media_playlist_header(playlist)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_independent_segments_tag(independent_segments)
|
49
|
+
return unless independent_segments
|
50
|
+
|
51
|
+
io.puts '#EXT-X-INDEPENDENT-SEGMENTS'
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_master_playlist_header(playlist)
|
55
|
+
write_version_tag(playlist.version)
|
56
|
+
write_independent_segments_tag(playlist.independent_segments)
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_media_playlist_header(playlist)
|
32
60
|
io.puts "#EXT-X-PLAYLIST-TYPE:#{playlist.type}" unless playlist.type.nil?
|
33
|
-
|
61
|
+
write_version_tag(playlist.version)
|
62
|
+
write_independent_segments_tag(playlist.independent_segments)
|
34
63
|
io.puts '#EXT-X-I-FRAMES-ONLY' if playlist.iframes_only
|
35
64
|
io.puts "#EXT-X-MEDIA-SEQUENCE:#{playlist.sequence}"
|
36
|
-
|
65
|
+
write_cache_tag(playlist.cache)
|
37
66
|
io.puts target_duration_format(playlist)
|
38
67
|
end
|
39
68
|
|
40
|
-
def
|
41
|
-
|
42
|
-
end
|
69
|
+
def write_version_tag(version)
|
70
|
+
return if version.nil?
|
43
71
|
|
44
|
-
|
45
|
-
format('#EXT-X-TARGETDURATION:%d', playlist.target)
|
72
|
+
io.puts "#EXT-X-VERSION:#{version}"
|
46
73
|
end
|
47
74
|
end
|
48
75
|
end
|
data/spec/fixtures/master.m3u8
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#EXTM3U
|
2
|
+
#EXT-X-INDEPENDENT-SEGMENTS
|
2
3
|
#EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
|
4
|
+
#EXT-X-START:TIME-OFFSET=20.2
|
3
5
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=5042000
|
4
6
|
hls/1080-7mbps/1080-7mbps.m3u8
|
5
7
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=4853000
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::PlaybackStart do
|
4
|
+
describe '#intialize' do
|
5
|
+
it 'assigns attributes' do
|
6
|
+
start = described_class.new(time_offset: -12.9, precise: true)
|
7
|
+
expect(start.time_offset).to eq(-12.9)
|
8
|
+
expect(start.precise).to be true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#parse' do
|
13
|
+
it 'parses tag with all attributes' do
|
14
|
+
start = described_class.new
|
15
|
+
tag = '#EXT-X-START:TIME-OFFSET=20.0,PRECISE=YES'
|
16
|
+
start.parse(tag)
|
17
|
+
|
18
|
+
expect(start.time_offset).to eq(20.0)
|
19
|
+
expect(start.precise).to be true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'parses tag without optional attributes' do
|
23
|
+
start = described_class.new
|
24
|
+
tag = '#EXT-X-START:TIME-OFFSET=-12.9'
|
25
|
+
start.parse(tag)
|
26
|
+
|
27
|
+
expect(start.time_offset).to eq(-12.9)
|
28
|
+
expect(start.precise).to be_nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#to_s' do
|
33
|
+
it 'returns tag with attributes' do
|
34
|
+
start = described_class.new(time_offset: 9.2, precise: true)
|
35
|
+
tag = start.to_s
|
36
|
+
|
37
|
+
expect(tag).to eq('#EXT-X-START:TIME-OFFSET=9.2,PRECISE=YES')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns tag without optional attributes' do
|
41
|
+
start = described_class.new(time_offset: 9.2)
|
42
|
+
tag = start.to_s
|
43
|
+
|
44
|
+
expect(tag).to eq('#EXT-X-START:TIME-OFFSET=9.2')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,6 +1,33 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe M3u8::Playlist do
|
4
|
+
let(:playlist) { described_class.new }
|
5
|
+
|
6
|
+
describe '#new' do
|
7
|
+
it 'initializes with defaults' do
|
8
|
+
expect(playlist.version).to be_nil
|
9
|
+
expect(playlist.cache).to be_nil
|
10
|
+
expect(playlist.target).to be 10
|
11
|
+
expect(playlist.sequence).to be 0
|
12
|
+
expect(playlist.type).to be_nil
|
13
|
+
expect(playlist.iframes_only).to be false
|
14
|
+
expect(playlist.independent_segments).to be false
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'initializes from hash' do
|
18
|
+
options = { version: 7, cache: false, target: 12, sequence: 1,
|
19
|
+
type: 'VOD', independent_segments: true }
|
20
|
+
playlist = M3u8::Playlist.new(options)
|
21
|
+
expect(playlist.version).to be 7
|
22
|
+
expect(playlist.cache).to be false
|
23
|
+
expect(playlist.target).to be 12
|
24
|
+
expect(playlist.sequence).to be 1
|
25
|
+
expect(playlist.type).to eq('VOD')
|
26
|
+
expect(playlist.iframes_only).to be false
|
27
|
+
expect(playlist.independent_segments).to be true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
4
31
|
it 'should generate codecs string' do
|
5
32
|
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
|
6
33
|
codecs = M3u8::Playlist.codecs options
|
@@ -11,10 +38,11 @@ describe M3u8::Playlist do
|
|
11
38
|
options = { uri: 'playlist_url', bandwidth: 6400,
|
12
39
|
audio_codec: 'mp3' }
|
13
40
|
item = M3u8::PlaylistItem.new options
|
14
|
-
playlist = M3u8::Playlist.new
|
15
|
-
playlist.items
|
41
|
+
playlist = M3u8::Playlist.new(independent_segments: true)
|
42
|
+
playlist.items << item
|
16
43
|
|
17
|
-
output = "#EXTM3U\n"
|
44
|
+
output = "#EXTM3U\n" \
|
45
|
+
"#EXT-X-INDEPENDENT-SEGMENTS\n" +
|
18
46
|
%(#EXT-X-STREAM-INF:CODECS="mp4a.40.34") +
|
19
47
|
",BANDWIDTH=6400\nplaylist_url\n"
|
20
48
|
expect(playlist.to_s).to eq output
|
@@ -23,7 +51,7 @@ describe M3u8::Playlist do
|
|
23
51
|
audio_codec: 'mp3' }
|
24
52
|
item = M3u8::PlaylistItem.new options
|
25
53
|
playlist = M3u8::Playlist.new
|
26
|
-
playlist.items
|
54
|
+
playlist.items << item
|
27
55
|
|
28
56
|
output = "#EXTM3U\n" +
|
29
57
|
%(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
|
@@ -35,7 +63,7 @@ describe M3u8::Playlist do
|
|
35
63
|
audio_codec: 'aac-lc' }
|
36
64
|
item = M3u8::PlaylistItem.new options
|
37
65
|
playlist = M3u8::Playlist.new
|
38
|
-
playlist.items
|
66
|
+
playlist.items << item
|
39
67
|
|
40
68
|
output = "#EXTM3U\n" \
|
41
69
|
'#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
|
@@ -48,12 +76,12 @@ describe M3u8::Playlist do
|
|
48
76
|
options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
|
49
77
|
audio_codec: 'mp3' }
|
50
78
|
item = M3u8::PlaylistItem.new options
|
51
|
-
playlist.items
|
79
|
+
playlist.items << item
|
52
80
|
options = { program_id: '2', uri: 'playlist_url', bandwidth: 50_000,
|
53
81
|
width: 1920, height: 1080, profile: 'high', level: 4.1,
|
54
82
|
audio_codec: 'aac-lc' }
|
55
83
|
item = M3u8::PlaylistItem.new options
|
56
|
-
playlist.items
|
84
|
+
playlist.items << item
|
57
85
|
|
58
86
|
output = "#EXTM3U\n" +
|
59
87
|
%(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
|
@@ -65,14 +93,12 @@ describe M3u8::Playlist do
|
|
65
93
|
|
66
94
|
it 'should render playlist' do
|
67
95
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
68
|
-
item = M3u8::SegmentItem.new
|
96
|
+
item = M3u8::SegmentItem.new(options)
|
69
97
|
playlist = M3u8::Playlist.new
|
70
|
-
playlist.items
|
98
|
+
playlist.items << item
|
71
99
|
|
72
100
|
output = "#EXTM3U\n" \
|
73
|
-
"#EXT-X-VERSION:3\n" \
|
74
101
|
"#EXT-X-MEDIA-SEQUENCE:0\n" \
|
75
|
-
"#EXT-X-ALLOW-CACHE:YES\n" \
|
76
102
|
"#EXT-X-TARGETDURATION:10\n" \
|
77
103
|
"#EXTINF:11.344644,\n" \
|
78
104
|
"1080-7mbps00000.ts\n" \
|
@@ -80,13 +106,11 @@ describe M3u8::Playlist do
|
|
80
106
|
expect(playlist.to_s).to eq output
|
81
107
|
|
82
108
|
options = { duration: 11.261233, segment: '1080-7mbps00001.ts' }
|
83
|
-
item = M3u8::SegmentItem.new
|
84
|
-
playlist.items
|
109
|
+
item = M3u8::SegmentItem.new(options)
|
110
|
+
playlist.items << item
|
85
111
|
|
86
112
|
output = "#EXTM3U\n" \
|
87
|
-
"#EXT-X-VERSION:3\n" \
|
88
113
|
"#EXT-X-MEDIA-SEQUENCE:0\n" \
|
89
|
-
"#EXT-X-ALLOW-CACHE:YES\n" \
|
90
114
|
"#EXT-X-TARGETDURATION:10\n" \
|
91
115
|
"#EXTINF:11.344644,\n" \
|
92
116
|
"1080-7mbps00000.ts\n" \
|
@@ -95,16 +119,16 @@ describe M3u8::Playlist do
|
|
95
119
|
"#EXT-X-ENDLIST\n"
|
96
120
|
expect(playlist.to_s).to eq output
|
97
121
|
|
98
|
-
options = { version:
|
122
|
+
options = { version: 7, cache: false, target: 12, sequence: 1,
|
99
123
|
type: 'VOD' }
|
100
124
|
playlist = M3u8::Playlist.new options
|
101
125
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
102
126
|
item = M3u8::SegmentItem.new options
|
103
|
-
playlist.items
|
127
|
+
playlist.items << item
|
104
128
|
|
105
129
|
output = "#EXTM3U\n" \
|
106
130
|
"#EXT-X-PLAYLIST-TYPE:VOD\n" \
|
107
|
-
"#EXT-X-VERSION:
|
131
|
+
"#EXT-X-VERSION:7\n" \
|
108
132
|
"#EXT-X-MEDIA-SEQUENCE:1\n" \
|
109
133
|
"#EXT-X-ALLOW-CACHE:NO\n" \
|
110
134
|
"#EXT-X-TARGETDURATION:12\n" \
|
@@ -121,7 +145,7 @@ describe M3u8::Playlist do
|
|
121
145
|
options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
|
122
146
|
audio_codec: 'mp3' }
|
123
147
|
item = M3u8::PlaylistItem.new options
|
124
|
-
playlist.items
|
148
|
+
playlist.items << item
|
125
149
|
playlist.write test_io
|
126
150
|
|
127
151
|
output = "#EXTM3U\n" +
|
@@ -146,7 +170,7 @@ describe M3u8::Playlist do
|
|
146
170
|
options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
|
147
171
|
audio_codec: 'mp3' }
|
148
172
|
item = M3u8::PlaylistItem.new options
|
149
|
-
playlist.items
|
173
|
+
playlist.items << item
|
150
174
|
|
151
175
|
expect(playlist.master?).to be true
|
152
176
|
end
|
@@ -157,11 +181,11 @@ describe M3u8::Playlist do
|
|
157
181
|
hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
|
158
182
|
bandwidth: 540, uri: 'test.url' }
|
159
183
|
item = M3u8::PlaylistItem.new(hash)
|
160
|
-
playlist.items
|
184
|
+
playlist.items << item
|
161
185
|
|
162
186
|
hash = { duration: 10.991, segment: 'test.ts' }
|
163
187
|
item = M3u8::SegmentItem.new(hash)
|
164
|
-
playlist.items
|
188
|
+
playlist.items << item
|
165
189
|
|
166
190
|
message = 'Playlist is invalid.'
|
167
191
|
io = StringIO.new
|
@@ -176,51 +200,39 @@ describe M3u8::Playlist do
|
|
176
200
|
hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
|
177
201
|
bandwidth: 540, uri: 'test.url' }
|
178
202
|
item = M3u8::PlaylistItem.new(hash)
|
179
|
-
playlist.items
|
203
|
+
playlist.items << item
|
180
204
|
expect(playlist.valid?).to be true
|
181
205
|
|
182
206
|
hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
|
183
207
|
bandwidth: 540, uri: 'test.url' }
|
184
208
|
item = M3u8::PlaylistItem.new(hash)
|
185
|
-
playlist.items
|
209
|
+
playlist.items << item
|
186
210
|
expect(playlist.valid?).to be true
|
187
211
|
|
188
212
|
hash = { duration: 10.991, segment: 'test.ts' }
|
189
213
|
item = M3u8::SegmentItem.new(hash)
|
190
|
-
playlist.items
|
214
|
+
playlist.items << item
|
191
215
|
|
192
216
|
expect(playlist.valid?).to be false
|
193
217
|
end
|
194
218
|
|
195
|
-
it 'should expose options as attributes' do
|
196
|
-
options = { version: 1, cache: false, target: 12, sequence: 1,
|
197
|
-
type: 'VOD' }
|
198
|
-
playlist = M3u8::Playlist.new options
|
199
|
-
expect(playlist.version).to be 1
|
200
|
-
expect(playlist.cache).to be false
|
201
|
-
expect(playlist.target).to be 12
|
202
|
-
expect(playlist.sequence).to be 1
|
203
|
-
expect(playlist.type).to eq('VOD')
|
204
|
-
expect(playlist.iframes_only).to be false
|
205
|
-
end
|
206
|
-
|
207
219
|
it 'should allow reading of playlists' do
|
208
220
|
file = File.open 'spec/fixtures/master.m3u8'
|
209
221
|
playlist = M3u8::Playlist.read file
|
210
222
|
expect(playlist.master?).to be true
|
211
|
-
expect(playlist.items.size).to eq(
|
223
|
+
expect(playlist.items.size).to eq(8)
|
212
224
|
end
|
213
225
|
|
214
226
|
it 'should return the total duration of a playlist' do
|
215
227
|
playlist = M3u8::Playlist.new
|
216
228
|
item = M3u8::SegmentItem.new(duration: 10.991, segment: 'test_01.ts')
|
217
|
-
playlist.items
|
229
|
+
playlist.items << item
|
218
230
|
item = M3u8::SegmentItem.new(duration: 9.891, segment: 'test_02.ts')
|
219
|
-
playlist.items
|
231
|
+
playlist.items << item
|
220
232
|
item = M3u8::SegmentItem.new(duration: 10.556, segment: 'test_03.ts')
|
221
|
-
playlist.items
|
233
|
+
playlist.items << item
|
222
234
|
item = M3u8::SegmentItem.new(duration: 8.790, segment: 'test_04.ts')
|
223
|
-
playlist.items
|
235
|
+
playlist.items << item
|
224
236
|
|
225
237
|
expect(playlist.duration.round(3)).to eq(40.228)
|
226
238
|
end
|
@@ -7,12 +7,18 @@ describe M3u8::Reader do
|
|
7
7
|
playlist = reader.read file
|
8
8
|
expect(playlist.master?).to be true
|
9
9
|
|
10
|
+
expect(playlist.independent_segments).to be true
|
11
|
+
|
10
12
|
item = playlist.items[0]
|
11
13
|
expect(item).to be_a(M3u8::SessionKeyItem)
|
12
14
|
expect(item.method).to eq('AES-128')
|
13
15
|
expect(item.uri).to eq('https://priv.example.com/key.php?r=52')
|
14
16
|
|
15
17
|
item = playlist.items[1]
|
18
|
+
expect(item).to be_a(M3u8::PlaybackStart)
|
19
|
+
expect(item.time_offset).to eq(20.2)
|
20
|
+
|
21
|
+
item = playlist.items[2]
|
16
22
|
expect(item).to be_a(M3u8::PlaylistItem)
|
17
23
|
expect(item.uri).to eq('hls/1080-7mbps/1080-7mbps.m3u8')
|
18
24
|
expect(item.program_id).to eq('1')
|
@@ -24,7 +30,7 @@ describe M3u8::Reader do
|
|
24
30
|
expect(item.iframe).to be false
|
25
31
|
expect(item.average_bandwidth).to be_nil
|
26
32
|
|
27
|
-
item = playlist.items[
|
33
|
+
item = playlist.items[7]
|
28
34
|
expect(item).to be_a(M3u8::PlaylistItem)
|
29
35
|
expect(item.uri).to eq('hls/64k/64k.m3u8')
|
30
36
|
expect(item.program_id).to eq('1')
|
@@ -36,7 +42,7 @@ describe M3u8::Reader do
|
|
36
42
|
expect(item.iframe).to be false
|
37
43
|
expect(item.average_bandwidth).to be_nil
|
38
44
|
|
39
|
-
expect(playlist.items.size).to eq(
|
45
|
+
expect(playlist.items.size).to eq(8)
|
40
46
|
|
41
47
|
item = playlist.items.last
|
42
48
|
expect(item.resolution).to be_nil
|
@@ -174,12 +180,12 @@ describe M3u8::Reader do
|
|
174
180
|
reader = M3u8::Reader.new
|
175
181
|
playlist = reader.read file
|
176
182
|
|
177
|
-
expect(playlist.items.size).to eq(
|
183
|
+
expect(playlist.items.size).to eq(8)
|
178
184
|
|
179
185
|
file = File.open 'spec/fixtures/master.m3u8'
|
180
186
|
playlist = reader.read file
|
181
187
|
|
182
|
-
expect(playlist.items.size).to eq(
|
188
|
+
expect(playlist.items.size).to eq(8)
|
183
189
|
end
|
184
190
|
|
185
191
|
it 'should parse playlist with session data' do
|
@@ -4,11 +4,13 @@ describe M3u8::Writer do
|
|
4
4
|
it 'should render master playlist' do
|
5
5
|
options = { uri: 'playlist_url', bandwidth: 6400,
|
6
6
|
audio_codec: 'mp3' }
|
7
|
-
item = M3u8::PlaylistItem.new
|
8
|
-
playlist = M3u8::Playlist.new
|
9
|
-
playlist.items
|
7
|
+
item = M3u8::PlaylistItem.new(options)
|
8
|
+
playlist = M3u8::Playlist.new(version: 6, independent_segments: true)
|
9
|
+
playlist.items << item
|
10
10
|
|
11
|
-
output = "#EXTM3U\n"
|
11
|
+
output = "#EXTM3U\n" \
|
12
|
+
"#EXT-X-VERSION:6\n" \
|
13
|
+
"#EXT-X-INDEPENDENT-SEGMENTS\n" +
|
12
14
|
%(#EXT-X-STREAM-INF:CODECS="mp4a.40.34") +
|
13
15
|
",BANDWIDTH=6400\nplaylist_url\n"
|
14
16
|
|
@@ -19,7 +21,7 @@ describe M3u8::Writer do
|
|
19
21
|
|
20
22
|
options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
|
21
23
|
audio_codec: 'mp3' }
|
22
|
-
item = M3u8::PlaylistItem.new
|
24
|
+
item = M3u8::PlaylistItem.new(options)
|
23
25
|
playlist = M3u8::Playlist.new
|
24
26
|
playlist.items.push item
|
25
27
|
|
@@ -80,37 +82,35 @@ describe M3u8::Writer do
|
|
80
82
|
|
81
83
|
it 'should render playlist' do
|
82
84
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
83
|
-
item = M3u8::SegmentItem.new
|
84
|
-
playlist = M3u8::Playlist.new
|
85
|
-
playlist.items
|
85
|
+
item = M3u8::SegmentItem.new(options)
|
86
|
+
playlist = M3u8::Playlist.new(version: 7)
|
87
|
+
playlist.items << item
|
86
88
|
|
87
89
|
output = "#EXTM3U\n" \
|
88
|
-
"#EXT-X-VERSION:
|
90
|
+
"#EXT-X-VERSION:7\n" \
|
89
91
|
"#EXT-X-MEDIA-SEQUENCE:0\n" \
|
90
|
-
"#EXT-X-ALLOW-CACHE:YES\n" \
|
91
92
|
"#EXT-X-TARGETDURATION:10\n" \
|
92
93
|
"#EXTINF:11.344644,\n" \
|
93
94
|
"1080-7mbps00000.ts\n" \
|
94
95
|
"#EXT-X-ENDLIST\n"
|
95
96
|
io = StringIO.open
|
96
|
-
writer = M3u8::Writer.new
|
97
|
+
writer = M3u8::Writer.new(io)
|
97
98
|
writer.write playlist
|
98
99
|
expect(io.string).to eq output
|
99
100
|
|
100
101
|
options = { method: 'AES-128', uri: 'http://test.key',
|
101
102
|
iv: 'D512BBF', key_format: 'identity',
|
102
103
|
key_format_versions: '1/3' }
|
103
|
-
item = M3u8::KeyItem.new
|
104
|
-
playlist.items
|
104
|
+
item = M3u8::KeyItem.new(options)
|
105
|
+
playlist.items << item
|
105
106
|
|
106
107
|
options = { duration: 11.261233, segment: '1080-7mbps00001.ts' }
|
107
108
|
item = M3u8::SegmentItem.new options
|
108
|
-
playlist.items
|
109
|
+
playlist.items << item
|
109
110
|
|
110
111
|
output = "#EXTM3U\n" \
|
111
|
-
"#EXT-X-VERSION:
|
112
|
+
"#EXT-X-VERSION:7\n" \
|
112
113
|
"#EXT-X-MEDIA-SEQUENCE:0\n" \
|
113
|
-
"#EXT-X-ALLOW-CACHE:YES\n" \
|
114
114
|
"#EXT-X-TARGETDURATION:10\n" \
|
115
115
|
"#EXTINF:11.344644,\n" \
|
116
116
|
"1080-7mbps00000.ts\n" +
|
@@ -120,16 +120,16 @@ describe M3u8::Writer do
|
|
120
120
|
"1080-7mbps00001.ts\n" \
|
121
121
|
"#EXT-X-ENDLIST\n"
|
122
122
|
io = StringIO.open
|
123
|
-
writer = M3u8::Writer.new
|
123
|
+
writer = M3u8::Writer.new(io)
|
124
124
|
writer.write playlist
|
125
125
|
expect(io.string).to eq output
|
126
126
|
|
127
127
|
options = { version: 4, cache: false, target: 12, sequence: 1,
|
128
128
|
type: 'EVENT', iframes_only: true }
|
129
|
-
playlist = M3u8::Playlist.new
|
129
|
+
playlist = M3u8::Playlist.new(options)
|
130
130
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
131
|
-
item = M3u8::SegmentItem.new
|
132
|
-
playlist.items
|
131
|
+
item = M3u8::SegmentItem.new(options)
|
132
|
+
playlist.items << item
|
133
133
|
|
134
134
|
output = "#EXTM3U\n" \
|
135
135
|
"#EXT-X-PLAYLIST-TYPE:EVENT\n" \
|
data/spec/lib/m3u8_spec.rb
CHANGED
@@ -13,8 +13,18 @@ describe do
|
|
13
13
|
|
14
14
|
it 'should parse yes/no string' do
|
15
15
|
value = 'YES'
|
16
|
-
expect(test_class.parse_yes_no
|
16
|
+
expect(test_class.parse_yes_no(value)).to be true
|
17
17
|
value = 'NO'
|
18
|
-
expect(test_class.parse_yes_no
|
18
|
+
expect(test_class.parse_yes_no(value)).to be false
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#to_yes_no' do
|
22
|
+
it 'converts true to YES' do
|
23
|
+
expect(test_class.to_yes_no(true)).to eq('YES')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'converts false to NO' do
|
27
|
+
expect(test_class.to_yes_no(false)).to eq('NO')
|
28
|
+
end
|
19
29
|
end
|
20
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: m3u8
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seth Deckard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -119,7 +119,6 @@ files:
|
|
119
119
|
- ".hound.yml"
|
120
120
|
- ".rspec"
|
121
121
|
- ".rubocop.yml"
|
122
|
-
- ".ruby-version"
|
123
122
|
- ".travis.yml"
|
124
123
|
- CHANGELOG.md
|
125
124
|
- Gemfile
|
@@ -135,6 +134,7 @@ files:
|
|
135
134
|
- lib/m3u8/key_item.rb
|
136
135
|
- lib/m3u8/map_item.rb
|
137
136
|
- lib/m3u8/media_item.rb
|
137
|
+
- lib/m3u8/playback_start.rb
|
138
138
|
- lib/m3u8/playlist.rb
|
139
139
|
- lib/m3u8/playlist_item.rb
|
140
140
|
- lib/m3u8/reader.rb
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- spec/lib/m3u8/key_item_spec.rb
|
162
162
|
- spec/lib/m3u8/map_item_spec.rb
|
163
163
|
- spec/lib/m3u8/media_item_spec.rb
|
164
|
+
- spec/lib/m3u8/playback_start_spec.rb
|
164
165
|
- spec/lib/m3u8/playlist_item_spec.rb
|
165
166
|
- spec/lib/m3u8/playlist_spec.rb
|
166
167
|
- spec/lib/m3u8/reader_spec.rb
|
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
192
|
version: '0'
|
192
193
|
requirements: []
|
193
194
|
rubyforge_project:
|
194
|
-
rubygems_version: 2.
|
195
|
+
rubygems_version: 2.6.8
|
195
196
|
signing_key:
|
196
197
|
specification_version: 4
|
197
198
|
summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
|
@@ -212,6 +213,7 @@ test_files:
|
|
212
213
|
- spec/lib/m3u8/key_item_spec.rb
|
213
214
|
- spec/lib/m3u8/map_item_spec.rb
|
214
215
|
- spec/lib/m3u8/media_item_spec.rb
|
216
|
+
- spec/lib/m3u8/playback_start_spec.rb
|
215
217
|
- spec/lib/m3u8/playlist_item_spec.rb
|
216
218
|
- spec/lib/m3u8/playlist_spec.rb
|
217
219
|
- spec/lib/m3u8/reader_spec.rb
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.2.3
|