m3u8 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -0
- data/.travis.yml +4 -3
- data/CHANGELOG.md +2 -0
- data/Gemfile +2 -0
- data/Guardfile +5 -0
- data/README.md +67 -25
- data/lib/m3u8.rb +3 -16
- data/lib/m3u8/encryptable.rb +50 -0
- data/lib/m3u8/key_item.rb +6 -37
- data/lib/m3u8/playlist_item.rb +21 -9
- data/lib/m3u8/reader.rb +40 -34
- data/lib/m3u8/session_key_item.rb +23 -0
- data/lib/m3u8/version.rb +1 -1
- data/m3u8.gemspec +3 -3
- data/spec/fixtures/master.m3u8 +1 -0
- data/spec/{byte_range_spec.rb → lib/m3u8/byte_range_spec.rb} +0 -0
- data/spec/{discontinuity_item_spec.rb → lib/m3u8/discontinuity_item_spec.rb} +0 -0
- data/spec/{key_item_spec.rb → lib/m3u8/key_item_spec.rb} +0 -0
- data/spec/{map_item_spec.rb → lib/m3u8/map_item_spec.rb} +0 -0
- data/spec/{media_item_spec.rb → lib/m3u8/media_item_spec.rb} +0 -0
- data/spec/{playlist_item_spec.rb → lib/m3u8/playlist_item_spec.rb} +1 -1
- data/spec/{playlist_spec.rb → lib/m3u8/playlist_spec.rb} +4 -4
- data/spec/{reader_spec.rb → lib/m3u8/reader_spec.rb} +10 -5
- data/spec/{segment_item_spec.rb → lib/m3u8/segment_item_spec.rb} +0 -0
- data/spec/{session_data_item_spec.rb → lib/m3u8/session_data_item_spec.rb} +0 -0
- data/spec/lib/m3u8/session_key_item_spec.rb +46 -0
- data/spec/{time_item_spec.rb → lib/m3u8/time_item_spec.rb} +0 -0
- data/spec/{writer_spec.rb → lib/m3u8/writer_spec.rb} +2 -2
- data/spec/{m3u8_spec.rb → lib/m3u8_spec.rb} +0 -0
- data/spec/spec_helper.rb +2 -2
- metadata +45 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 113427414ddf5ec31124dbcc67b905d8f4d29ab9
|
4
|
+
data.tar.gz: 0aa8693ced40c395ade39b026ffce25f99fe49d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12f12fe680621b9bd4bdb8eca4f5042de4f063772fff5ac331db34f12458131e47f1f8420a40af7072f85ed835e623ed4a6324cfb1a912f2e6d251ea11a3a3dc
|
7
|
+
data.tar.gz: 038002c0468e4c332ad06d6b38ff748aa6bdead131e0b5eb356da563c85648a5609c87fe233a1343768a532aebe99e12a3b7853b4843d487997fe89193eb9640
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
#### 0.6.2 (2/16/2016) - Added support for EXT-X-SESSION-KEY tags. Merged pulled request #6 from [jviney](https://github.com/jviney), fixing bug with codec string value.
|
2
|
+
|
1
3
|
#### 0.6.1 (8/15/2015) - Added support for EXT-X-PROGRAM-DATE-TIME tags.
|
2
4
|
|
3
5
|
#### 0.6.0 (6/20/2015) - Added support for EXT-X-MAP tags, introduced ByteRange class to handled shared byterange parsing/serialization functionality. Breaking changes: SegmentItem now exposes a single byterange instance accessor rather than byterange_length and byterange_start, made all parse methods static for consistency (ex: MediaItem.parse).
|
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -27,17 +27,19 @@ Or install it yourself as:
|
|
27
27
|
## Usage (creating playlists)
|
28
28
|
|
29
29
|
Create a master playlist and add child playlists for adaptive bitrate streaming:
|
30
|
+
|
30
31
|
```ruby
|
31
32
|
require 'm3u8'
|
32
33
|
playlist = M3u8::Playlist.new
|
33
34
|
```
|
34
35
|
|
35
36
|
Create a new playlist item with options:
|
37
|
+
|
36
38
|
```ruby
|
37
39
|
options = { width: 1920, height: 1080, profile: 'high', level: 4.1,
|
38
40
|
audio_codec: 'aac-lc', bandwidth: 540, uri: 'test.url' }
|
39
|
-
item = M3u8::PlaylistItem.new
|
40
|
-
playlist.items
|
41
|
+
item = M3u8::PlaylistItem.new(options)
|
42
|
+
playlist.items << item
|
41
43
|
```
|
42
44
|
|
43
45
|
Add alternate audio, camera angles, closed captions and subtitles by creating MediaItem instances and adding them to the Playlist:
|
@@ -47,42 +49,51 @@ hash = { type: 'AUDIO', group_id: 'audio-lo', language: 'fre',
|
|
47
49
|
assoc_language: 'spoken', name: 'Francais', autoselect: true,
|
48
50
|
default: false, forced: true, uri: 'frelo/prog_index.m3u8' }
|
49
51
|
item = M3u8::MediaItem.new(hash)
|
50
|
-
playlist.items
|
52
|
+
playlist.items << item
|
51
53
|
```
|
52
54
|
|
53
55
|
Create a standard playlist and add MPEG-TS segments via SegmentItem. You can also specify options for this type of playlist, however these options are ignored if playlist becomes a master playlist (anything but segments added):
|
56
|
+
|
54
57
|
```ruby
|
55
58
|
options = { version: 1, cache: false, target: 12, sequence: 1 }
|
56
|
-
playlist = M3u8::Playlist.new
|
59
|
+
playlist = M3u8::Playlist.new(options)
|
57
60
|
|
58
|
-
item = M3u8::SegmentItem.new
|
59
|
-
playlist.items
|
61
|
+
item = M3u8::SegmentItem.new(duration: 11, segment: 'test.ts')
|
62
|
+
playlist.items << item
|
60
63
|
```
|
61
64
|
|
62
65
|
You can pass an IO object to the write method:
|
66
|
+
|
63
67
|
```ruby
|
64
68
|
require 'tempfile'
|
65
|
-
|
66
|
-
playlist.write
|
69
|
+
file = Tempfile.new('test')
|
70
|
+
playlist.write(file)
|
67
71
|
```
|
72
|
+
|
68
73
|
You can also access the playlist as a string:
|
74
|
+
|
69
75
|
```ruby
|
70
76
|
playlist.to_s
|
71
77
|
```
|
78
|
+
|
72
79
|
M3u8::Writer is the class that handles generating the playlist output.
|
73
80
|
|
74
81
|
Alternatively you can set codecs rather than having it generated automatically:
|
82
|
+
|
75
83
|
```ruby
|
76
84
|
options = { width: 1920, height: 1080, codecs: 'avc1.66.30,mp4a.40.2',
|
77
85
|
bandwidth: 540, uri: 'test.url' }
|
78
|
-
item = M3u8::PlaylistItem.new
|
86
|
+
item = M3u8::PlaylistItem.new(options)
|
79
87
|
```
|
88
|
+
|
80
89
|
Just get the codec string for custom use:
|
90
|
+
|
81
91
|
```ruby
|
82
92
|
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
|
83
|
-
codecs = M3u8::Playlist.codecs
|
93
|
+
codecs = M3u8::Playlist.codecs(options)
|
84
94
|
# => "avc1.66.30,mp4a.40.2"
|
85
|
-
```
|
95
|
+
```
|
96
|
+
|
86
97
|
Values for audio_codec (codec name): aac-lc, he-aac, mp3
|
87
98
|
|
88
99
|
Possible values for profile (H.264 Profile): baseline, main, high.
|
@@ -95,50 +106,81 @@ Not all Levels and Profiles can be combined, consult H.264 documentation
|
|
95
106
|
|
96
107
|
```ruby
|
97
108
|
file = File.open 'spec/fixtures/master.m3u8'
|
98
|
-
playlist = M3u8::Playlist.read
|
109
|
+
playlist = M3u8::Playlist.read(file)
|
99
110
|
playlist.master?
|
100
111
|
# => true
|
101
112
|
```
|
102
|
-
|
113
|
+
|
114
|
+
Access items in playlist:
|
115
|
+
|
103
116
|
```ruby
|
104
117
|
playlist.items.first
|
105
118
|
# => #<M3u8::PlaylistItem:0x007fa569bc7698 @program_id="1", @resolution="1920x1080",
|
106
119
|
# @codecs="avc1.640028,mp4a.40.2", @bandwidth="5042000",
|
107
120
|
# @playlist="hls/1080-7mbps/1080-7mbps.m3u8">
|
108
121
|
```
|
122
|
+
|
109
123
|
Create a new playlist item with options:
|
124
|
+
|
110
125
|
```ruby
|
111
126
|
options = { width: 1920, height: 1080, profile: 'high', level: 4.1,
|
112
127
|
audio_codec: 'aac-lc', bandwidth: 540, uri: 'test.url' }
|
113
|
-
item = M3u8::PlaylistItem.new
|
128
|
+
item = M3u8::PlaylistItem.new(options)
|
114
129
|
#add it to the top of the playlist
|
115
|
-
playlist.items.
|
130
|
+
playlist.items.unshift(item)
|
116
131
|
```
|
132
|
+
|
117
133
|
M3u8::Reader is the class handles parsing if you want more control over the process.
|
118
134
|
|
119
135
|
## Features
|
120
136
|
* Distinction between segment and master playlists is handled automatically (no need to use a different class).
|
121
|
-
* Automatically generates the audio/video codec string based on names and options you are
|
137
|
+
* Automatically generates the audio/video codec string based on names and options you are familiar with.
|
122
138
|
* Provides validation of input when adding playlists or segments.
|
123
139
|
* Allows all options to be configured on a playlist (caching, version, etc.)
|
124
|
-
* Supports I-Frames (Intra frames) and Byte Ranges in Segments.
|
125
|
-
* Supports subtitles, closed captions, alternate audio and video, and comments.
|
126
|
-
# Supports Session Data in master playlists.
|
127
|
-
* Supports keys for encrypted media segments (EXT-X-KEY).
|
128
|
-
* Supports EXT-X-DISCONTINUITY in media segments.
|
129
140
|
* Can write playlist to an IO object (StringIO/File, etc) or access string via to_s.
|
130
141
|
* Can read playlists into a model (Playlist and Items) from an IO object.
|
131
142
|
* Any tag or attribute supported by the object model is supported both parsing and generation of m3u8 playlists.
|
132
|
-
|
133
|
-
|
143
|
+
* Supports I-frames (Intra frames) and byte ranges in Segments.
|
144
|
+
* Supports subtitles, closed captions, alternate audio and video, and comments.
|
145
|
+
* Supports session data in master playlists.
|
146
|
+
* Supports keys for encrypted media segments (EXT-X-KEY, EXT-SESSION-KEY).
|
147
|
+
|
148
|
+
## HLS Spec Status (version 17)
|
149
|
+
### Implemented:
|
150
|
+
* EXTM3U
|
151
|
+
* EXT-X-VERSION
|
152
|
+
* EXTINF
|
153
|
+
* EXT-X-BYTERANGE
|
154
|
+
* EXT-X-DISCONTINUITY
|
155
|
+
* EXT-X-KEY
|
156
|
+
* EXT-X-MAP
|
157
|
+
* EXT-X-PROGRAM-DATE-TIME
|
158
|
+
* EXT-X-TARGETDURATION
|
159
|
+
* EXT-X-MEDIA-SEQUENCE
|
160
|
+
* EXT-X-ENDLIST
|
161
|
+
* EXT-X-PLAYLIST-TYPE
|
162
|
+
* EXT-X-I-FRAMES-ONLY
|
163
|
+
* EXT-X-MEDIA
|
164
|
+
* EXT-X-STREAM-INF
|
165
|
+
* EXT-X-I-FRAME-STREAM-INF
|
166
|
+
* EXT-X-SESSION-DATA
|
167
|
+
* EXT-X-SESSION-KEY
|
168
|
+
|
169
|
+
### TODO:
|
170
|
+
* EXT-X-DISCONTINUITY-SEQUENCE
|
171
|
+
* EXT-X-INDEPENDENT-SEGMENTS
|
172
|
+
* EXT-X-START
|
173
|
+
|
174
|
+
## Roadmap
|
175
|
+
* Add the last remaining tags for latest version of the spec.
|
134
176
|
* Validation of all attributes and their values to match the rules defined in the spec.
|
135
|
-
*
|
177
|
+
* Support for different versions of spec, defaulting to latest.
|
136
178
|
|
137
179
|
## Contributing
|
138
180
|
|
139
181
|
1. Fork it ( https://github.com/sethdeckard/m3u8/fork )
|
140
182
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
141
|
-
3. Run the specs, make sure they pass and that new features are covered
|
183
|
+
3. Run the specs, make sure they pass and that new features are covered. Code coverage should be 100%.
|
142
184
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
143
185
|
5. Push to the branch (`git push origin my-new-feature`)
|
144
186
|
6. Create a new Pull Request
|
data/lib/m3u8.rb
CHANGED
@@ -1,24 +1,11 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
|
3
|
-
require 'm3u8/playlist'
|
4
|
-
require 'm3u8/playlist_item'
|
5
|
-
require 'm3u8/segment_item'
|
6
|
-
require 'm3u8/discontinuity_item'
|
7
|
-
require 'm3u8/key_item'
|
8
|
-
require 'm3u8/map_item'
|
9
|
-
require 'm3u8/media_item'
|
10
|
-
require 'm3u8/time_item'
|
11
|
-
require 'm3u8/session_data_item'
|
12
|
-
require 'm3u8/byte_range'
|
13
|
-
require 'm3u8/reader'
|
14
|
-
require 'm3u8/writer'
|
15
|
-
require 'm3u8/error'
|
2
|
+
Dir[File.dirname(__FILE__) + '/m3u8/*.rb'].each { |file| require file }
|
16
3
|
|
17
4
|
# M3u8 provides parsing, generation, and validation of m3u8 playlists
|
18
5
|
module M3u8
|
19
6
|
def parse_attributes(line)
|
20
|
-
array = line.
|
21
|
-
Hash[array.map { |key, value| [key, value.
|
7
|
+
array = line.delete("\n").scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
|
8
|
+
Hash[array.map { |key, value| [key, value.delete('"')] }]
|
22
9
|
end
|
23
10
|
|
24
11
|
def parse_yes_no(value)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module M3u8
|
2
|
+
# Encapsulates logic common to encryption key tags
|
3
|
+
module Encryptable
|
4
|
+
def self.included(base)
|
5
|
+
base.send :attr_accessor, :method
|
6
|
+
base.send :attr_accessor, :uri
|
7
|
+
base.send :attr_accessor, :iv
|
8
|
+
base.send :attr_accessor, :key_format
|
9
|
+
base.send :attr_accessor, :key_format_versions
|
10
|
+
end
|
11
|
+
|
12
|
+
def attributes_to_s
|
13
|
+
[method_format,
|
14
|
+
uri_format,
|
15
|
+
iv_format,
|
16
|
+
key_format_format,
|
17
|
+
key_format_versions_format].compact.join(',')
|
18
|
+
end
|
19
|
+
|
20
|
+
def convert_key_names(attributes)
|
21
|
+
{ method: attributes['METHOD'], uri: attributes['URI'],
|
22
|
+
iv: attributes['IV'], key_format: attributes['KEYFORMAT'],
|
23
|
+
key_format_versions: attributes['KEYFORMATVERSIONS'] }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def method_format
|
29
|
+
"METHOD=#{method}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def uri_format
|
33
|
+
%(URI="#{uri}") unless uri.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def iv_format
|
37
|
+
"IV=#{iv}" unless iv.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def key_format_format
|
41
|
+
%(KEYFORMAT="#{key_format}") unless key_format.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def key_format_versions_format
|
45
|
+
return if key_format_versions.nil?
|
46
|
+
|
47
|
+
%(KEYFORMATVERSIONS="#{key_format_versions}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/m3u8/key_item.rb
CHANGED
@@ -1,54 +1,23 @@
|
|
1
1
|
module M3u8
|
2
2
|
# KeyItem represents a set of EXT-X-KEY attributes
|
3
3
|
class KeyItem
|
4
|
+
include Encryptable
|
4
5
|
extend M3u8
|
5
|
-
attr_accessor :method, :uri, :iv, :key_format, :key_format_versions
|
6
6
|
|
7
7
|
def initialize(params = {})
|
8
|
-
|
8
|
+
options = convert_key_names(params)
|
9
|
+
options.merge(params).each do |key, value|
|
9
10
|
instance_variable_set("@#{key}", value)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.parse(text)
|
14
|
-
attributes = parse_attributes
|
15
|
-
|
16
|
-
iv: attributes['IV'], key_format: attributes['KEYFORMAT'],
|
17
|
-
key_format_versions: attributes['KEYFORMATVERSIONS'] }
|
18
|
-
KeyItem.new options
|
15
|
+
attributes = parse_attributes(text)
|
16
|
+
KeyItem.new(attributes)
|
19
17
|
end
|
20
18
|
|
21
19
|
def to_s
|
22
|
-
|
23
|
-
uri_format,
|
24
|
-
iv_format,
|
25
|
-
key_format_format,
|
26
|
-
key_format_versions_format].compact.join(',')
|
27
|
-
"#EXT-X-KEY:#{attributes}"
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def method_format
|
33
|
-
"METHOD=#{method}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def uri_format
|
37
|
-
%(URI="#{uri}") unless uri.nil?
|
38
|
-
end
|
39
|
-
|
40
|
-
def iv_format
|
41
|
-
"IV=#{iv}" unless iv.nil?
|
42
|
-
end
|
43
|
-
|
44
|
-
def key_format_format
|
45
|
-
%(KEYFORMAT="#{key_format}") unless key_format.nil?
|
46
|
-
end
|
47
|
-
|
48
|
-
def key_format_versions_format
|
49
|
-
return if key_format_versions.nil?
|
50
|
-
|
51
|
-
%(KEYFORMATVERSIONS="#{key_format_versions}")
|
20
|
+
"#EXT-X-KEY:#{attributes_to_s}"
|
52
21
|
end
|
53
22
|
end
|
54
23
|
end
|
data/lib/m3u8/playlist_item.rb
CHANGED
@@ -148,15 +148,27 @@ module M3u8
|
|
148
148
|
def video_codec(profile, level)
|
149
149
|
return if profile.nil? || level.nil?
|
150
150
|
|
151
|
-
profile
|
152
|
-
return
|
153
|
-
return
|
154
|
-
return
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
return 'avc1.
|
159
|
-
|
151
|
+
profile.downcase!
|
152
|
+
return baseline_codec_string(level) if profile == 'baseline'
|
153
|
+
return main_codec_string(level) if profile == 'main'
|
154
|
+
return high_codec_string(level) if profile == 'high'
|
155
|
+
end
|
156
|
+
|
157
|
+
def baseline_codec_string(level)
|
158
|
+
return 'avc1.66.30' if level == 3.0
|
159
|
+
return 'avc1.42001f' if level == 3.1
|
160
|
+
end
|
161
|
+
|
162
|
+
def main_codec_string(level)
|
163
|
+
return 'avc1.77.30' if level == 3.0
|
164
|
+
return 'avc1.4d001f' if level == 3.1
|
165
|
+
return 'avc1.4d0028' if level == 4.0
|
166
|
+
end
|
167
|
+
|
168
|
+
def high_codec_string(level)
|
169
|
+
return 'avc1.64001f' if level == 3.1
|
170
|
+
return 'avc1.640028' if level == 4.0
|
171
|
+
return 'avc1.640029' if level == 4.1
|
160
172
|
end
|
161
173
|
end
|
162
174
|
end
|
data/lib/m3u8/reader.rb
CHANGED
@@ -22,45 +22,46 @@ module M3u8
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def basic_tags
|
25
|
-
{ '#EXT-X-VERSION' => ->(line) { parse_version
|
25
|
+
{ '#EXT-X-VERSION' => ->(line) { parse_version(line) } }
|
26
26
|
end
|
27
27
|
|
28
28
|
def media_segment_tags
|
29
|
-
{ '#EXTINF' => ->(line) { parse_segment
|
30
|
-
'#EXT-X-DISCONTINUITY' => ->(line) { parse_discontinuity
|
31
|
-
'#EXT-X-BYTERANGE' => ->(line) { parse_byterange
|
32
|
-
'#EXT-X-KEY' => ->(line) { parse_key
|
33
|
-
'#EXT-X-MAP' => ->(line) { parse_map
|
34
|
-
'#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time
|
29
|
+
{ '#EXTINF' => ->(line) { parse_segment(line) },
|
30
|
+
'#EXT-X-DISCONTINUITY' => ->(line) { parse_discontinuity(line) },
|
31
|
+
'#EXT-X-BYTERANGE' => ->(line) { parse_byterange(line) },
|
32
|
+
'#EXT-X-KEY' => ->(line) { parse_key(line) },
|
33
|
+
'#EXT-X-MAP' => ->(line) { parse_map(line) },
|
34
|
+
'#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time(line) }
|
35
35
|
}
|
36
36
|
end
|
37
37
|
|
38
38
|
def media_playlist_tags
|
39
|
-
{ '#EXT-X-MEDIA-SEQUENCE' => ->(line) { parse_sequence
|
40
|
-
'#EXT-X-ALLOW-CACHE' => ->(line) { parse_cache
|
41
|
-
'#EXT-X-TARGETDURATION' => ->(line) { parse_target
|
39
|
+
{ '#EXT-X-MEDIA-SEQUENCE' => ->(line) { parse_sequence(line) },
|
40
|
+
'#EXT-X-ALLOW-CACHE' => ->(line) { parse_cache(line) },
|
41
|
+
'#EXT-X-TARGETDURATION' => ->(line) { parse_target(line) },
|
42
42
|
'#EXT-X-I-FRAMES-ONLY' => proc { playlist.iframes_only = true },
|
43
|
-
'#EXT-X-PLAYLIST-TYPE' => ->(line) { parse_playlist_type
|
43
|
+
'#EXT-X-PLAYLIST-TYPE' => ->(line) { parse_playlist_type(line) }
|
44
44
|
}
|
45
45
|
end
|
46
46
|
|
47
47
|
def master_playlist_tags
|
48
|
-
{ '#EXT-X-MEDIA' => ->(line) { parse_media
|
49
|
-
'#EXT-X-SESSION-DATA' => ->(line) { parse_session_data
|
50
|
-
'#EXT-X-
|
51
|
-
'#EXT-X-
|
48
|
+
{ '#EXT-X-MEDIA' => ->(line) { parse_media(line) },
|
49
|
+
'#EXT-X-SESSION-DATA' => ->(line) { parse_session_data(line) },
|
50
|
+
'#EXT-X-SESSION-KEY' => ->(line) { parse_session_key(line) },
|
51
|
+
'#EXT-X-STREAM-INF' => ->(line) { parse_stream(line) },
|
52
|
+
'#EXT-X-I-FRAME-STREAM-INF' => ->(line) { parse_iframe_stream(line) }
|
52
53
|
}
|
53
54
|
end
|
54
55
|
|
55
56
|
def parse_line(line)
|
56
57
|
return if match_tag(line)
|
57
|
-
parse_next_line
|
58
|
+
parse_next_line(line) if !item.nil? && open
|
58
59
|
end
|
59
60
|
|
60
61
|
def match_tag(line)
|
61
62
|
tag = @tags.select { |key| line.start_with? key }
|
62
63
|
return unless tag.values.first
|
63
|
-
tag.values.first.call
|
64
|
+
tag.values.first.call(line)
|
64
65
|
true
|
65
66
|
end
|
66
67
|
|
@@ -89,16 +90,16 @@ module M3u8
|
|
89
90
|
self.master = true
|
90
91
|
self.open = true
|
91
92
|
|
92
|
-
self.item = M3u8::PlaylistItem.parse
|
93
|
+
self.item = M3u8::PlaylistItem.parse(line)
|
93
94
|
end
|
94
95
|
|
95
96
|
def parse_iframe_stream(line)
|
96
97
|
self.master = true
|
97
98
|
self.open = false
|
98
99
|
|
99
|
-
self.item = M3u8::PlaylistItem.parse
|
100
|
+
self.item = M3u8::PlaylistItem.parse(line)
|
100
101
|
item.iframe = true
|
101
|
-
playlist.items
|
102
|
+
playlist.items << item
|
102
103
|
end
|
103
104
|
|
104
105
|
def parse_discontinuity(*)
|
@@ -106,22 +107,22 @@ module M3u8
|
|
106
107
|
self.open = false
|
107
108
|
|
108
109
|
self.item = M3u8::DiscontinuityItem.new
|
109
|
-
playlist.items
|
110
|
+
playlist.items << item
|
110
111
|
end
|
111
112
|
|
112
113
|
def parse_key(line)
|
113
|
-
item = M3u8::KeyItem.parse
|
114
|
-
playlist.items
|
114
|
+
item = M3u8::KeyItem.parse(line)
|
115
|
+
playlist.items << item
|
115
116
|
end
|
116
117
|
|
117
118
|
def parse_map(line)
|
118
|
-
item = M3u8::MapItem.parse
|
119
|
-
playlist.items
|
119
|
+
item = M3u8::MapItem.parse(line)
|
120
|
+
playlist.items << item
|
120
121
|
end
|
121
122
|
|
122
123
|
def parse_segment(line)
|
123
124
|
self.item = M3u8::SegmentItem.new
|
124
|
-
values = line.gsub('#EXTINF:', '').
|
125
|
+
values = line.gsub('#EXTINF:', '').tr("\n", ',').split(',')
|
125
126
|
item.duration = values[0].to_f
|
126
127
|
item.comment = values[1] unless values[1].nil?
|
127
128
|
|
@@ -130,34 +131,39 @@ module M3u8
|
|
130
131
|
end
|
131
132
|
|
132
133
|
def parse_byterange(line)
|
133
|
-
values = line.gsub('#EXT-X-BYTERANGE:', '').
|
134
|
+
values = line.gsub('#EXT-X-BYTERANGE:', '').delete("\n")
|
134
135
|
item.byterange = M3u8::ByteRange.parse values
|
135
136
|
end
|
136
137
|
|
137
138
|
def parse_session_data(line)
|
138
|
-
item = M3u8::SessionDataItem.parse
|
139
|
-
playlist.items
|
139
|
+
item = M3u8::SessionDataItem.parse(line)
|
140
|
+
playlist.items << item
|
141
|
+
end
|
142
|
+
|
143
|
+
def parse_session_key(line)
|
144
|
+
item = M3u8::SessionKeyItem.parse(line)
|
145
|
+
playlist.items << item
|
140
146
|
end
|
141
147
|
|
142
148
|
def parse_media(line)
|
143
149
|
self.open = false
|
144
|
-
self.item = M3u8::MediaItem.parse
|
145
|
-
playlist.items
|
150
|
+
self.item = M3u8::MediaItem.parse(line)
|
151
|
+
playlist.items << item
|
146
152
|
end
|
147
153
|
|
148
154
|
def parse_time(line)
|
149
155
|
self.open = false
|
150
|
-
playlist.items
|
156
|
+
playlist.items << M3u8::TimeItem.parse(line)
|
151
157
|
end
|
152
158
|
|
153
159
|
def parse_next_line(line)
|
154
|
-
value = line.
|
160
|
+
value = line.delete("\n")
|
155
161
|
if master
|
156
162
|
item.uri = value
|
157
163
|
else
|
158
164
|
item.segment = value
|
159
165
|
end
|
160
|
-
playlist.items
|
166
|
+
playlist.items << item
|
161
167
|
self.open = false
|
162
168
|
end
|
163
169
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module M3u8
|
2
|
+
# KeyItem represents a set of EXT-X-SESSION-KEY attributes
|
3
|
+
class SessionKeyItem
|
4
|
+
include Encryptable
|
5
|
+
extend M3u8
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
options = convert_key_names(params)
|
9
|
+
options.merge(params).each do |key, value|
|
10
|
+
instance_variable_set("@#{key}", value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(text)
|
15
|
+
attributes = parse_attributes(text)
|
16
|
+
SessionKeyItem.new(attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"#EXT-X-SESSION-KEY:#{attributes_to_s}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/m3u8/version.rb
CHANGED
data/m3u8.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler'
|
22
|
-
spec.add_development_dependency 'rake'
|
23
|
-
spec.add_development_dependency 'rspec', '
|
21
|
+
spec.add_development_dependency 'bundler'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
24
24
|
end
|
data/spec/fixtures/master.m3u8
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#EXTM3U
|
2
|
+
#EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
|
2
3
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=5042000
|
3
4
|
hls/1080-7mbps/1080-7mbps.m3u8
|
4
5
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=4853000
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -162,7 +162,7 @@ describe M3u8::PlaylistItem do
|
|
162
162
|
|
163
163
|
options = { profile: 'high', level: 4.1 }
|
164
164
|
item = M3u8::PlaylistItem.new options
|
165
|
-
expect(item.codecs).to eq 'avc1.
|
165
|
+
expect(item.codecs).to eq 'avc1.640029'
|
166
166
|
end
|
167
167
|
|
168
168
|
it 'should raise error if codecs are missing' do
|
@@ -39,7 +39,7 @@ describe M3u8::Playlist do
|
|
39
39
|
|
40
40
|
output = "#EXTM3U\n" \
|
41
41
|
'#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
|
42
|
-
%(CODECS="avc1.
|
42
|
+
%(CODECS="avc1.640029,mp4a.40.2",BANDWIDTH=50000\n) +
|
43
43
|
"playlist_url\n"
|
44
44
|
|
45
45
|
expect(playlist.to_s).to eq output
|
@@ -58,7 +58,7 @@ describe M3u8::Playlist do
|
|
58
58
|
output = "#EXTM3U\n" +
|
59
59
|
%(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
|
60
60
|
",BANDWIDTH=6400\nplaylist_url\n#EXT-X-STREAM-INF:PROGRAM-ID=2," +
|
61
|
-
%(RESOLUTION=1920x1080,CODECS="avc1.
|
61
|
+
%(RESOLUTION=1920x1080,CODECS="avc1.640029,mp4a.40.2") +
|
62
62
|
",BANDWIDTH=50000\nplaylist_url\n"
|
63
63
|
expect(playlist.to_s).to eq output
|
64
64
|
end
|
@@ -208,7 +208,7 @@ describe M3u8::Playlist do
|
|
208
208
|
file = File.open 'spec/fixtures/master.m3u8'
|
209
209
|
playlist = M3u8::Playlist.read file
|
210
210
|
expect(playlist.master?).to be true
|
211
|
-
expect(playlist.items.size).to eq
|
211
|
+
expect(playlist.items.size).to eq(7)
|
212
212
|
end
|
213
213
|
|
214
214
|
it 'should return the total duration of a playlist' do
|
@@ -222,6 +222,6 @@ describe M3u8::Playlist do
|
|
222
222
|
item = M3u8::SegmentItem.new(duration: 8.790, segment: 'test_04.ts')
|
223
223
|
playlist.items.push item
|
224
224
|
|
225
|
-
expect(playlist.duration.round(3)).to eq
|
225
|
+
expect(playlist.duration.round(3)).to eq(40.228)
|
226
226
|
end
|
227
227
|
end
|
@@ -8,6 +8,11 @@ describe M3u8::Reader do
|
|
8
8
|
expect(playlist.master?).to be true
|
9
9
|
|
10
10
|
item = playlist.items[0]
|
11
|
+
expect(item).to be_a(M3u8::SessionKeyItem)
|
12
|
+
expect(item.method).to eq('AES-128')
|
13
|
+
expect(item.uri).to eq('https://priv.example.com/key.php?r=52')
|
14
|
+
|
15
|
+
item = playlist.items[1]
|
11
16
|
expect(item).to be_a(M3u8::PlaylistItem)
|
12
17
|
expect(item.uri).to eq('hls/1080-7mbps/1080-7mbps.m3u8')
|
13
18
|
expect(item.program_id).to eq('1')
|
@@ -19,7 +24,7 @@ describe M3u8::Reader do
|
|
19
24
|
expect(item.iframe).to be false
|
20
25
|
expect(item.average_bandwidth).to be_nil
|
21
26
|
|
22
|
-
item = playlist.items[
|
27
|
+
item = playlist.items[6]
|
23
28
|
expect(item).to be_a(M3u8::PlaylistItem)
|
24
29
|
expect(item.uri).to eq('hls/64k/64k.m3u8')
|
25
30
|
expect(item.program_id).to eq('1')
|
@@ -31,7 +36,7 @@ describe M3u8::Reader do
|
|
31
36
|
expect(item.iframe).to be false
|
32
37
|
expect(item.average_bandwidth).to be_nil
|
33
38
|
|
34
|
-
expect(playlist.items.size).to eq
|
39
|
+
expect(playlist.items.size).to eq(7)
|
35
40
|
|
36
41
|
item = playlist.items.last
|
37
42
|
expect(item.resolution).to be_nil
|
@@ -43,7 +48,7 @@ describe M3u8::Reader do
|
|
43
48
|
playlist = reader.read file
|
44
49
|
expect(playlist.master?).to be true
|
45
50
|
|
46
|
-
expect(playlist.items.size).to eq
|
51
|
+
expect(playlist.items.size).to eq(7)
|
47
52
|
|
48
53
|
item = playlist.items[1]
|
49
54
|
expect(item).to be_a(M3u8::PlaylistItem)
|
@@ -169,12 +174,12 @@ describe M3u8::Reader do
|
|
169
174
|
reader = M3u8::Reader.new
|
170
175
|
playlist = reader.read file
|
171
176
|
|
172
|
-
expect(playlist.items.size).to eq
|
177
|
+
expect(playlist.items.size).to eq(7)
|
173
178
|
|
174
179
|
file = File.open 'spec/fixtures/master.m3u8'
|
175
180
|
playlist = reader.read file
|
176
181
|
|
177
|
-
expect(playlist.items.size).to eq
|
182
|
+
expect(playlist.items.size).to eq(7)
|
178
183
|
end
|
179
184
|
|
180
185
|
it 'should parse playlist with session data' do
|
File without changes
|
File without changes
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::SessionKeyItem do
|
4
|
+
it 'should initialize with hash' do
|
5
|
+
hash = { method: 'AES-128', uri: 'http://test.key',
|
6
|
+
iv: 'D512BBF', key_format: 'identity',
|
7
|
+
key_format_versions: '1/3' }
|
8
|
+
item = M3u8::SessionKeyItem.new(hash)
|
9
|
+
expect(item.method).to eq 'AES-128'
|
10
|
+
expect(item.uri).to eq 'http://test.key'
|
11
|
+
expect(item.iv).to eq 'D512BBF'
|
12
|
+
expect(item.key_format).to eq 'identity'
|
13
|
+
expect(item.key_format_versions).to eq '1/3'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should provide m3u8 format representation' do
|
17
|
+
hash = { method: 'AES-128', uri: 'http://test.key',
|
18
|
+
iv: 'D512BBF', key_format: 'identity',
|
19
|
+
key_format_versions: '1/3' }
|
20
|
+
item = M3u8::SessionKeyItem.new(hash)
|
21
|
+
expected = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key",) +
|
22
|
+
%(IV=D512BBF,KEYFORMAT="identity",KEYFORMATVERSIONS="1/3")
|
23
|
+
expect(item.to_s).to eq expected
|
24
|
+
|
25
|
+
hash = { method: 'AES-128', uri: 'http://test.key' }
|
26
|
+
item = M3u8::SessionKeyItem.new(hash)
|
27
|
+
expected = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key")
|
28
|
+
expect(item.to_s).to eq expected
|
29
|
+
|
30
|
+
hash = { method: 'NONE' }
|
31
|
+
item = M3u8::SessionKeyItem.new(hash)
|
32
|
+
expected = '#EXT-X-SESSION-KEY:METHOD=NONE'
|
33
|
+
expect(item.to_s).to eq expected
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should parse m3u8 format into instance' do
|
37
|
+
format = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key",) +
|
38
|
+
%(IV=D512BBF,KEYFORMAT="identity",KEYFORMATVERSIONS="1/3")
|
39
|
+
item = M3u8::KeyItem.parse format
|
40
|
+
expect(item.method).to eq 'AES-128'
|
41
|
+
expect(item.uri).to eq 'http://test.key'
|
42
|
+
expect(item.iv).to eq 'D512BBF'
|
43
|
+
expect(item.key_format).to eq 'identity'
|
44
|
+
expect(item.key_format_versions).to eq '1/3'
|
45
|
+
end
|
46
|
+
end
|
File without changes
|
@@ -41,7 +41,7 @@ describe M3u8::Writer do
|
|
41
41
|
|
42
42
|
output = "#EXTM3U\n" \
|
43
43
|
'#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
|
44
|
-
%(CODECS="avc1.
|
44
|
+
%(CODECS="avc1.640029,mp4a.40.2",BANDWIDTH=50000\n) +
|
45
45
|
"playlist_url\n"
|
46
46
|
|
47
47
|
io = StringIO.open
|
@@ -67,7 +67,7 @@ describe M3u8::Writer do
|
|
67
67
|
output = "#EXTM3U\n" +
|
68
68
|
%(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
|
69
69
|
",BANDWIDTH=6400\nplaylist_url\n#EXT-X-STREAM-INF:PROGRAM-ID=2," +
|
70
|
-
%(RESOLUTION=1920x1080,CODECS="avc1.
|
70
|
+
%(RESOLUTION=1920x1080,CODECS="avc1.640029,mp4a.40.2") +
|
71
71
|
",BANDWIDTH=50000\nplaylist_url\n" +
|
72
72
|
%(#EXT-X-SESSION-DATA:DATA-ID="com.test.movie.title",) +
|
73
73
|
%(VALUE="Test",URI="http://test",LANGUAGE="en"\n)
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
@@ -2,10 +2,10 @@ require 'm3u8'
|
|
2
2
|
require 'simplecov'
|
3
3
|
require 'coveralls'
|
4
4
|
|
5
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
6
6
|
SimpleCov::Formatter::HTMLFormatter,
|
7
7
|
Coveralls::SimpleCov::Formatter
|
8
|
-
]
|
8
|
+
])
|
9
9
|
SimpleCov.start
|
10
10
|
|
11
11
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
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.6.
|
4
|
+
version: 0.6.2
|
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: 2016-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '3.3'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '3.3'
|
55
55
|
description: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
|
56
56
|
email:
|
57
57
|
- seth@deckard.me
|
@@ -63,15 +63,18 @@ files:
|
|
63
63
|
- ".hound.yml"
|
64
64
|
- ".rspec"
|
65
65
|
- ".rubocop.yml"
|
66
|
+
- ".ruby-version"
|
66
67
|
- ".travis.yml"
|
67
68
|
- CHANGELOG.md
|
68
69
|
- Gemfile
|
70
|
+
- Guardfile
|
69
71
|
- LICENSE.txt
|
70
72
|
- README.md
|
71
73
|
- Rakefile
|
72
74
|
- lib/m3u8.rb
|
73
75
|
- lib/m3u8/byte_range.rb
|
74
76
|
- lib/m3u8/discontinuity_item.rb
|
77
|
+
- lib/m3u8/encryptable.rb
|
75
78
|
- lib/m3u8/error.rb
|
76
79
|
- lib/m3u8/key_item.rb
|
77
80
|
- lib/m3u8/map_item.rb
|
@@ -81,12 +84,11 @@ files:
|
|
81
84
|
- lib/m3u8/reader.rb
|
82
85
|
- lib/m3u8/segment_item.rb
|
83
86
|
- lib/m3u8/session_data_item.rb
|
87
|
+
- lib/m3u8/session_key_item.rb
|
84
88
|
- lib/m3u8/time_item.rb
|
85
89
|
- lib/m3u8/version.rb
|
86
90
|
- lib/m3u8/writer.rb
|
87
91
|
- m3u8.gemspec
|
88
|
-
- spec/byte_range_spec.rb
|
89
|
-
- spec/discontinuity_item_spec.rb
|
90
92
|
- spec/fixtures/encrypted.m3u8
|
91
93
|
- spec/fixtures/iframes.m3u8
|
92
94
|
- spec/fixtures/map_playlist.m3u8
|
@@ -97,18 +99,21 @@ files:
|
|
97
99
|
- spec/fixtures/session_data.m3u8
|
98
100
|
- spec/fixtures/variant_angles.m3u8
|
99
101
|
- spec/fixtures/variant_audio.m3u8
|
100
|
-
- spec/
|
101
|
-
- spec/
|
102
|
-
- spec/
|
103
|
-
- spec/
|
104
|
-
- spec/
|
105
|
-
- spec/
|
106
|
-
- spec/
|
107
|
-
- spec/
|
108
|
-
- spec/
|
102
|
+
- spec/lib/m3u8/byte_range_spec.rb
|
103
|
+
- spec/lib/m3u8/discontinuity_item_spec.rb
|
104
|
+
- spec/lib/m3u8/key_item_spec.rb
|
105
|
+
- spec/lib/m3u8/map_item_spec.rb
|
106
|
+
- spec/lib/m3u8/media_item_spec.rb
|
107
|
+
- spec/lib/m3u8/playlist_item_spec.rb
|
108
|
+
- spec/lib/m3u8/playlist_spec.rb
|
109
|
+
- spec/lib/m3u8/reader_spec.rb
|
110
|
+
- spec/lib/m3u8/segment_item_spec.rb
|
111
|
+
- spec/lib/m3u8/session_data_item_spec.rb
|
112
|
+
- spec/lib/m3u8/session_key_item_spec.rb
|
113
|
+
- spec/lib/m3u8/time_item_spec.rb
|
114
|
+
- spec/lib/m3u8/writer_spec.rb
|
115
|
+
- spec/lib/m3u8_spec.rb
|
109
116
|
- spec/spec_helper.rb
|
110
|
-
- spec/time_item_spec.rb
|
111
|
-
- spec/writer_spec.rb
|
112
117
|
homepage: https://github.com/sethdeckard/m3u8
|
113
118
|
licenses:
|
114
119
|
- MIT
|
@@ -129,13 +134,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
134
|
version: '0'
|
130
135
|
requirements: []
|
131
136
|
rubyforge_project:
|
132
|
-
rubygems_version: 2.4.5
|
137
|
+
rubygems_version: 2.4.5.1
|
133
138
|
signing_key:
|
134
139
|
specification_version: 4
|
135
140
|
summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
|
136
141
|
test_files:
|
137
|
-
- spec/byte_range_spec.rb
|
138
|
-
- spec/discontinuity_item_spec.rb
|
139
142
|
- spec/fixtures/encrypted.m3u8
|
140
143
|
- spec/fixtures/iframes.m3u8
|
141
144
|
- spec/fixtures/map_playlist.m3u8
|
@@ -146,15 +149,18 @@ test_files:
|
|
146
149
|
- spec/fixtures/session_data.m3u8
|
147
150
|
- spec/fixtures/variant_angles.m3u8
|
148
151
|
- spec/fixtures/variant_audio.m3u8
|
149
|
-
- spec/
|
150
|
-
- spec/
|
151
|
-
- spec/
|
152
|
-
- spec/
|
153
|
-
- spec/
|
154
|
-
- spec/
|
155
|
-
- spec/
|
156
|
-
- spec/
|
157
|
-
- spec/
|
152
|
+
- spec/lib/m3u8/byte_range_spec.rb
|
153
|
+
- spec/lib/m3u8/discontinuity_item_spec.rb
|
154
|
+
- spec/lib/m3u8/key_item_spec.rb
|
155
|
+
- spec/lib/m3u8/map_item_spec.rb
|
156
|
+
- spec/lib/m3u8/media_item_spec.rb
|
157
|
+
- spec/lib/m3u8/playlist_item_spec.rb
|
158
|
+
- spec/lib/m3u8/playlist_spec.rb
|
159
|
+
- spec/lib/m3u8/reader_spec.rb
|
160
|
+
- spec/lib/m3u8/segment_item_spec.rb
|
161
|
+
- spec/lib/m3u8/session_data_item_spec.rb
|
162
|
+
- spec/lib/m3u8/session_key_item_spec.rb
|
163
|
+
- spec/lib/m3u8/time_item_spec.rb
|
164
|
+
- spec/lib/m3u8/writer_spec.rb
|
165
|
+
- spec/lib/m3u8_spec.rb
|
158
166
|
- spec/spec_helper.rb
|
159
|
-
- spec/time_item_spec.rb
|
160
|
-
- spec/writer_spec.rb
|