m3u8 0.6.1 → 0.6.2
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/.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
|