m3u8 0.6.9 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/m3u8.svg)](http://badge.fury.io/rb/m3u8)
|
2
2
|
[![Build Status](https://travis-ci.org/sethdeckard/m3u8.svg?branch=master)](https://travis-ci.org/sethdeckard/m3u8)
|
3
|
-
[![Coverage Status](https://coveralls.io/repos/sethdeckard/m3u8/badge.
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/github/sethdeckard/m3u8/badge.svg?branch=master)](https://coveralls.io/github/sethdeckard/m3u8?branch=master)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/sethdeckard/m3u8/badges/gpa.svg)](https://codeclimate.com/github/sethdeckard/m3u8)
|
5
5
|
[![Dependency Status](https://gemnasium.com/sethdeckard/m3u8.svg)](https://gemnasium.com/sethdeckard/m3u8)
|
6
6
|
[![security](https://hakiri.io/github/sethdeckard/m3u8/master.svg)](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
|