m3u8 0.5.3 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -2
- data/Gemfile +1 -0
- data/lib/m3u8.rb +10 -0
- data/lib/m3u8/byte_range.rb +31 -0
- data/lib/m3u8/map_item.rb +32 -0
- data/lib/m3u8/media_item.rb +4 -4
- data/lib/m3u8/playlist.rb +2 -2
- data/lib/m3u8/playlist_item.rb +14 -9
- data/lib/m3u8/reader.rb +30 -22
- data/lib/m3u8/segment_item.rb +5 -12
- data/lib/m3u8/time_item.rb +30 -0
- data/lib/m3u8/version.rb +1 -1
- data/spec/byte_range_spec.rb +29 -0
- data/spec/fixtures/map_playlist.m3u8 +8 -0
- data/spec/fixtures/playlist.m3u8 +1 -0
- data/spec/map_item_spec.rb +41 -0
- data/spec/media_item_spec.rb +1 -2
- data/spec/playlist_item_spec.rb +2 -4
- data/spec/reader_spec.rb +23 -5
- data/spec/segment_item_spec.rb +17 -5
- data/spec/spec_helper.rb +7 -2
- data/spec/time_item_spec.rb +23 -0
- data/spec/writer_spec.rb +1 -1
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bead9657f94696922af4e09d7f19800b80572ff
|
4
|
+
data.tar.gz: b654a6d47b3cfad36b08f58cb95ffd12e46c3b66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc8d26287b07199eaba044e66330f519a6c96b44b52e607ea48cafb286d4a005e22d1f43cdfb2d353c9ccc79e4ea92fd188ed7e4cdee570580110b7b23ff5f6f
|
7
|
+
data.tar.gz: 9b82bbb066aa4ccbb6e3f9827ec7ca56fbb845f56ac57826fbe29d37bdd06631bf1800b43cd42e28b582630c5fd50a6c91818af1c87b223e76cac54ed71b0062
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
|
1
|
+
#### 0.6.1 (8/15/2015) - Added support for EXT-X-PROGRAM-DATE-TIME tags.
|
2
|
+
|
3
|
+
#### 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).
|
4
|
+
|
5
|
+
#### 0.5.3 (2/24/2015) - [stan3](https://github.com/stan3) fixed issue recently introduced in Reader where EXT-X-STREAM-INF missing resolution would break parsing.
|
2
6
|
|
3
7
|
#### 0.5.2 (2/18/2015) - Fix issue where PlaylistItem.average_bandwidth would default to 0 instead of nil when not present in m3u8 being parsed.
|
4
8
|
|
5
|
-
#### 0.5.1 (2/16/2015) - [takashisite](https://github.com/takashisite) added support for [EXT-X-DISCONTINUITY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-14#section-4.3.2.3). Added support for [EXT-X-KEY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-14#section-4.3.2.4) (keys for encrypted segments).
|
9
|
+
#### 0.5.1 (2/16/2015) - [takashisite](https://github.com/takashisite) added support for [EXT-X-DISCONTINUITY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-14#section-4.3.2.3). Added support for [EXT-X-KEY](https://tools.ietf.org/html/draft-pantos-http-live-streaming-14#section-4.3.2.4) (keys for encrypted segments).
|
6
10
|
***
|
7
11
|
#### 0.5.0 (2/10/2015) - BREAKING: renamed PlaylistItem.playlist to PlaylistItem.uri, MediaItem.group to MediaItem.group_id, and PlaylistItem.bitrate to PlaylistItem.bandwidth so attributes more closely match the spec. Added support for EXT-X-I-FRAME-STREAM-INF, EXT-X-I-FRAMES-ONLY, EXT-X-BYTERANGE, and EXT-X-SESSION-DATA.
|
8
12
|
***
|
data/Gemfile
CHANGED
data/lib/m3u8.rb
CHANGED
@@ -5,8 +5,11 @@ require 'm3u8/playlist_item'
|
|
5
5
|
require 'm3u8/segment_item'
|
6
6
|
require 'm3u8/discontinuity_item'
|
7
7
|
require 'm3u8/key_item'
|
8
|
+
require 'm3u8/map_item'
|
8
9
|
require 'm3u8/media_item'
|
10
|
+
require 'm3u8/time_item'
|
9
11
|
require 'm3u8/session_data_item'
|
12
|
+
require 'm3u8/byte_range'
|
10
13
|
require 'm3u8/reader'
|
11
14
|
require 'm3u8/writer'
|
12
15
|
require 'm3u8/error'
|
@@ -21,4 +24,11 @@ module M3u8
|
|
21
24
|
def parse_yes_no(value)
|
22
25
|
value == 'YES' ? true : false
|
23
26
|
end
|
27
|
+
|
28
|
+
def intialize_with_byterange(params = {})
|
29
|
+
params.each do |key, value|
|
30
|
+
value = ByteRange.new(value) if value.is_a?(Hash)
|
31
|
+
instance_variable_set("@#{key}", value)
|
32
|
+
end
|
33
|
+
end
|
24
34
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module M3u8
|
2
|
+
# ByteRange represents sub range of a resource
|
3
|
+
class ByteRange
|
4
|
+
attr_accessor :length, :start
|
5
|
+
|
6
|
+
def initialize(params = {})
|
7
|
+
params.each do |key, value|
|
8
|
+
instance_variable_set("@#{key}", value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(text)
|
13
|
+
values = text.split '@'
|
14
|
+
length_value = values[0].to_i
|
15
|
+
start_value = values[1].to_i unless values[1].nil?
|
16
|
+
options = { length: length_value, start: start_value }
|
17
|
+
ByteRange.new options
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{length}#{start_format}"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def start_format
|
27
|
+
return if start.nil?
|
28
|
+
"@#{start}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module M3u8
|
2
|
+
# MapItem represents a EXT-X-MAP tag which specifies how to obtain the Media
|
3
|
+
# Initialization Section
|
4
|
+
class MapItem
|
5
|
+
extend M3u8
|
6
|
+
include M3u8
|
7
|
+
attr_accessor :uri, :byterange
|
8
|
+
|
9
|
+
def initialize(params = {})
|
10
|
+
intialize_with_byterange(params)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse(text)
|
14
|
+
attributes = parse_attributes text
|
15
|
+
range_value = attributes['BYTERANGE']
|
16
|
+
range = ByteRange.parse(range_value) unless range_value.nil?
|
17
|
+
options = { uri: attributes['URI'], byterange: range }
|
18
|
+
MapItem.new options
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
%(#EXT-X-MAP:URI="#{uri}"#{byterange_format})
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def byterange_format
|
28
|
+
return if byterange.nil?
|
29
|
+
%(,BYTERANGE="#{byterange}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/m3u8/media_item.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module M3u8
|
2
2
|
# MediaItem represents a set of EXT-X-MEDIA attributes
|
3
3
|
class MediaItem
|
4
|
-
|
4
|
+
extend M3u8
|
5
5
|
attr_accessor :type, :group_id, :language, :assoc_language, :name,
|
6
6
|
:autoselect, :default, :uri, :forced
|
7
7
|
|
@@ -11,7 +11,7 @@ module M3u8
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def parse(text)
|
14
|
+
def self.parse(text)
|
15
15
|
attributes = parse_attributes text
|
16
16
|
options = { type: attributes['TYPE'], group_id: attributes['GROUP-ID'],
|
17
17
|
language: attributes['LANGUAGE'],
|
@@ -21,7 +21,7 @@ module M3u8
|
|
21
21
|
default: parse_yes_no(attributes['DEFAULT']),
|
22
22
|
forced: parse_yes_no(attributes['FORCED']),
|
23
23
|
uri: attributes['URI'] }
|
24
|
-
|
24
|
+
MediaItem.new options
|
25
25
|
end
|
26
26
|
|
27
27
|
def to_s
|
@@ -82,7 +82,7 @@ module M3u8
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def to_yes_no(boolean)
|
85
|
-
boolean ==
|
85
|
+
boolean == true ? 'YES' : 'NO'
|
86
86
|
end
|
87
87
|
end
|
88
88
|
end
|
data/lib/m3u8/playlist.rb
CHANGED
@@ -69,11 +69,11 @@ module M3u8
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def playlist_size
|
72
|
-
items.
|
72
|
+
items.count { |item| item.is_a?(PlaylistItem) }
|
73
73
|
end
|
74
74
|
|
75
75
|
def segment_size
|
76
|
-
items.
|
76
|
+
items.count { |item| item.is_a?(SegmentItem) }
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
data/lib/m3u8/playlist_item.rb
CHANGED
@@ -2,7 +2,7 @@ module M3u8
|
|
2
2
|
# PlaylistItem represents a set of EXT-X-STREAM-INF or
|
3
3
|
# EXT-X-I-FRAME-STREAM-INF attributes
|
4
4
|
class PlaylistItem
|
5
|
-
|
5
|
+
extend M3u8
|
6
6
|
attr_accessor :program_id, :width, :height, :codecs, :bandwidth,
|
7
7
|
:audio_codec, :level, :profile, :video, :audio, :uri,
|
8
8
|
:average_bandwidth, :subtitles, :closed_captions, :iframe
|
@@ -15,18 +15,20 @@ module M3u8
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def parse(text)
|
18
|
+
def self.parse(text)
|
19
19
|
attributes = parse_attributes text
|
20
|
+
resolution = parse_resolution attributes['RESOLUTION']
|
20
21
|
average = parse_average_bandwidth attributes['AVERAGE-BANDWIDTH']
|
21
22
|
options = { program_id: attributes['PROGRAM-ID'],
|
22
23
|
codecs: attributes['CODECS'],
|
24
|
+
width: resolution[:width],
|
25
|
+
height: resolution[:height],
|
23
26
|
bandwidth: attributes['BANDWIDTH'].to_i,
|
24
27
|
average_bandwidth: average,
|
25
28
|
video: attributes['VIDEO'], audio: attributes['AUDIO'],
|
26
29
|
uri: attributes['URI'], subtitles: attributes['SUBTITLES'],
|
27
30
|
closed_captions: attributes['CLOSED-CAPTIONS'] }
|
28
|
-
|
29
|
-
parse_resolution attributes['RESOLUTION']
|
31
|
+
PlaylistItem.new options
|
30
32
|
end
|
31
33
|
|
32
34
|
def resolution
|
@@ -58,14 +60,17 @@ module M3u8
|
|
58
60
|
|
59
61
|
private
|
60
62
|
|
61
|
-
def parse_average_bandwidth(value)
|
63
|
+
def self.parse_average_bandwidth(value)
|
62
64
|
value.to_i unless value.nil?
|
63
65
|
end
|
64
66
|
|
65
|
-
def parse_resolution(resolution)
|
66
|
-
return if resolution.nil?
|
67
|
-
|
68
|
-
|
67
|
+
def self.parse_resolution(resolution)
|
68
|
+
return { width: nil, height: nil } if resolution.nil?
|
69
|
+
|
70
|
+
values = resolution.split('x')
|
71
|
+
width = values[0].to_i
|
72
|
+
height = values[1].to_i
|
73
|
+
{ width: width, height: height }
|
69
74
|
end
|
70
75
|
|
71
76
|
def validate
|
data/lib/m3u8/reader.rb
CHANGED
@@ -22,31 +22,33 @@ module M3u8
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def basic_tags
|
25
|
-
{ '#EXT-X-VERSION' =>
|
25
|
+
{ '#EXT-X-VERSION' => ->(line) { parse_version line } }
|
26
26
|
end
|
27
27
|
|
28
28
|
def media_segment_tags
|
29
|
-
{ '#EXTINF' =>
|
30
|
-
'#EXT-X-DISCONTINUITY' =>
|
31
|
-
'#EXT-X-BYTERANGE' =>
|
32
|
-
'#EXT-X-KEY' =>
|
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 }
|
33
35
|
}
|
34
36
|
end
|
35
37
|
|
36
38
|
def media_playlist_tags
|
37
|
-
{ '#EXT-X-MEDIA-SEQUENCE' =>
|
38
|
-
'#EXT-X-ALLOW-CACHE' =>
|
39
|
-
'#EXT-X-TARGETDURATION' =>
|
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 },
|
40
42
|
'#EXT-X-I-FRAMES-ONLY' => proc { playlist.iframes_only = true },
|
41
|
-
'#EXT-X-PLAYLIST-TYPE' =>
|
43
|
+
'#EXT-X-PLAYLIST-TYPE' => ->(line) { parse_playlist_type line }
|
42
44
|
}
|
43
45
|
end
|
44
46
|
|
45
47
|
def master_playlist_tags
|
46
|
-
{ '#EXT-X-MEDIA' =>
|
47
|
-
'#EXT-X-SESSION-DATA' =>
|
48
|
-
'#EXT-X-STREAM-INF' =>
|
49
|
-
'#EXT-X-I-FRAME-STREAM-INF' =>
|
48
|
+
{ '#EXT-X-MEDIA' => ->(line) { parse_media line },
|
49
|
+
'#EXT-X-SESSION-DATA' => ->(line) { parse_session_data line },
|
50
|
+
'#EXT-X-STREAM-INF' => ->(line) { parse_stream line },
|
51
|
+
'#EXT-X-I-FRAME-STREAM-INF' => ->(line) { parse_iframe_stream line }
|
50
52
|
}
|
51
53
|
end
|
52
54
|
|
@@ -87,16 +89,14 @@ module M3u8
|
|
87
89
|
self.master = true
|
88
90
|
self.open = true
|
89
91
|
|
90
|
-
self.item = M3u8::PlaylistItem.
|
91
|
-
item.parse line
|
92
|
+
self.item = M3u8::PlaylistItem.parse line
|
92
93
|
end
|
93
94
|
|
94
95
|
def parse_iframe_stream(line)
|
95
96
|
self.master = true
|
96
97
|
self.open = false
|
97
98
|
|
98
|
-
self.item = M3u8::PlaylistItem.
|
99
|
-
item.parse line
|
99
|
+
self.item = M3u8::PlaylistItem.parse line
|
100
100
|
item.iframe = true
|
101
101
|
playlist.items.push item
|
102
102
|
end
|
@@ -114,6 +114,11 @@ module M3u8
|
|
114
114
|
playlist.items.push item
|
115
115
|
end
|
116
116
|
|
117
|
+
def parse_map(line)
|
118
|
+
item = M3u8::MapItem.parse line
|
119
|
+
playlist.items.push item
|
120
|
+
end
|
121
|
+
|
117
122
|
def parse_segment(line)
|
118
123
|
self.item = M3u8::SegmentItem.new
|
119
124
|
values = line.gsub('#EXTINF:', '').gsub("\n", ',').split(',')
|
@@ -125,9 +130,8 @@ module M3u8
|
|
125
130
|
end
|
126
131
|
|
127
132
|
def parse_byterange(line)
|
128
|
-
values = line.gsub('#EXT-X-BYTERANGE:', '').gsub("\n", ',')
|
129
|
-
item.
|
130
|
-
item.byterange_start = values[1].to_i unless values[1].nil?
|
133
|
+
values = line.gsub('#EXT-X-BYTERANGE:', '').gsub("\n", ',')
|
134
|
+
item.byterange = M3u8::ByteRange.parse values
|
131
135
|
end
|
132
136
|
|
133
137
|
def parse_session_data(line)
|
@@ -137,11 +141,15 @@ module M3u8
|
|
137
141
|
|
138
142
|
def parse_media(line)
|
139
143
|
self.open = false
|
140
|
-
self.item = M3u8::MediaItem.
|
141
|
-
item.parse line
|
144
|
+
self.item = M3u8::MediaItem.parse line
|
142
145
|
playlist.items.push item
|
143
146
|
end
|
144
147
|
|
148
|
+
def parse_time(line)
|
149
|
+
self.open = false
|
150
|
+
playlist.items.push M3u8::TimeItem.parse line
|
151
|
+
end
|
152
|
+
|
145
153
|
def parse_next_line(line)
|
146
154
|
value = line.gsub "\n", ''
|
147
155
|
if master
|
data/lib/m3u8/segment_item.rb
CHANGED
@@ -2,13 +2,11 @@ module M3u8
|
|
2
2
|
# SegmentItem represents EXTINF attributes with the URI that follows,
|
3
3
|
# optionally allowing an EXT-X-BYTERANGE tag to be set.
|
4
4
|
class SegmentItem
|
5
|
-
|
6
|
-
|
5
|
+
include M3u8
|
6
|
+
attr_accessor :duration, :segment, :comment, :byterange
|
7
7
|
|
8
8
|
def initialize(params = {})
|
9
|
-
params
|
10
|
-
instance_variable_set("@#{key}", value)
|
11
|
-
end
|
9
|
+
intialize_with_byterange(params)
|
12
10
|
end
|
13
11
|
|
14
12
|
def to_s
|
@@ -18,13 +16,8 @@ module M3u8
|
|
18
16
|
private
|
19
17
|
|
20
18
|
def byterange_format
|
21
|
-
return if
|
22
|
-
"\n#EXT-X-BYTERANGE:#{
|
23
|
-
end
|
24
|
-
|
25
|
-
def byterange_start_format
|
26
|
-
return if byterange_start.nil?
|
27
|
-
"@#{byterange_start}"
|
19
|
+
return if byterange.nil?
|
20
|
+
"\n#EXT-X-BYTERANGE:#{byterange}"
|
28
21
|
end
|
29
22
|
end
|
30
23
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module M3u8
|
2
|
+
# TimeItem represents EXT-X-PROGRAM-DATE-TIME
|
3
|
+
class TimeItem
|
4
|
+
extend M3u8
|
5
|
+
attr_accessor :time
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
params.each do |key, value|
|
9
|
+
instance_variable_set("@#{key}", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse(text)
|
14
|
+
time = text.gsub('#EXT-X-PROGRAM-DATE-TIME:', '')
|
15
|
+
options = { time: Time.parse(time) }
|
16
|
+
TimeItem.new options
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
%(#EXT-X-PROGRAM-DATE-TIME:#{time_format})
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def time_format
|
26
|
+
return time if time.is_a?(String)
|
27
|
+
time.iso8601
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/m3u8/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::ByteRange do
|
4
|
+
it 'should provide m3u8 format representation' do
|
5
|
+
hash = { length: 4500, start: 600 }
|
6
|
+
item = M3u8::ByteRange.new(hash)
|
7
|
+
output = item.to_s
|
8
|
+
expected = '4500@600'
|
9
|
+
expect(output).to eq expected
|
10
|
+
|
11
|
+
hash = { length: 3300 }
|
12
|
+
item = M3u8::ByteRange.new(hash)
|
13
|
+
output = item.to_s
|
14
|
+
expected = '3300'
|
15
|
+
expect(output).to eq expected
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should parse instance from string' do
|
19
|
+
input = '3500@300'
|
20
|
+
range = M3u8::ByteRange.parse(input)
|
21
|
+
expect(range.length).to eq 3500
|
22
|
+
expect(range.start).to eq 300
|
23
|
+
|
24
|
+
input = '4000'
|
25
|
+
range = M3u8::ByteRange.parse(input)
|
26
|
+
expect(range.length).to eq 4000
|
27
|
+
expect(range.start).to be_nil
|
28
|
+
end
|
29
|
+
end
|
data/spec/fixtures/playlist.m3u8
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::MapItem do
|
4
|
+
it 'should provide m3u8 format representation' do
|
5
|
+
hash = { uri: 'frelo/prog_index.m3u8',
|
6
|
+
byterange: {
|
7
|
+
length: 4500,
|
8
|
+
start: 600
|
9
|
+
}
|
10
|
+
}
|
11
|
+
item = M3u8::MapItem.new(hash)
|
12
|
+
output = item.to_s
|
13
|
+
expected = '#EXT-X-MAP:URI="frelo/prog_index.m3u8",' \
|
14
|
+
'BYTERANGE="4500@600"'
|
15
|
+
expect(output).to eq expected
|
16
|
+
|
17
|
+
hash = { uri: 'frehi/prog_index.m3u8' }
|
18
|
+
item = M3u8::MapItem.new(hash)
|
19
|
+
output = item.to_s
|
20
|
+
expected = '#EXT-X-MAP:URI="frehi/prog_index.m3u8"'
|
21
|
+
expect(output).to eq expected
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should parse m3u8 text into instance' do
|
25
|
+
input = '#EXT-X-MAP:URI="frelo/prog_index.m3u8",' \
|
26
|
+
'BYTERANGE="3500@300"'
|
27
|
+
|
28
|
+
item = M3u8::MapItem.parse(input)
|
29
|
+
|
30
|
+
expect(item.uri).to eq 'frelo/prog_index.m3u8'
|
31
|
+
expect(item.byterange.length).to eq 3500
|
32
|
+
expect(item.byterange.start).to eq 300
|
33
|
+
|
34
|
+
input = '#EXT-X-MAP:URI="frelo/prog_index.m3u8"'
|
35
|
+
|
36
|
+
item = M3u8::MapItem.parse(input)
|
37
|
+
|
38
|
+
expect(item.uri).to eq 'frelo/prog_index.m3u8'
|
39
|
+
expect(item.byterange).to be_nil
|
40
|
+
end
|
41
|
+
end
|
data/spec/media_item_spec.rb
CHANGED
@@ -27,8 +27,7 @@ describe M3u8::MediaItem do
|
|
27
27
|
format = '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",' \
|
28
28
|
'ASSOC-LANGUAGE="spoken",NAME="Francais",AUTOSELECT=YES,' +
|
29
29
|
%("DEFAULT=NO,URI="frelo/prog_index.m3u8",FORCED=YES\n")
|
30
|
-
item = M3u8::MediaItem.
|
31
|
-
item.parse format
|
30
|
+
item = M3u8::MediaItem.parse format
|
32
31
|
|
33
32
|
expect(item.type).to eq 'AUDIO'
|
34
33
|
expect(item.group_id).to eq 'audio-lo'
|
data/spec/playlist_item_spec.rb
CHANGED
@@ -20,8 +20,7 @@ describe M3u8::PlaylistItem do
|
|
20
20
|
%(PROGRAM-ID=1,RESOLUTION=1920x1080,) +
|
21
21
|
%(AVERAGE-BANDWIDTH=550,AUDIO="test",VIDEO="test2",) +
|
22
22
|
%(SUBTITLES="subs",CLOSED-CAPTIONS="caps",URI="test.url")
|
23
|
-
item = M3u8::PlaylistItem.
|
24
|
-
item.parse(format)
|
23
|
+
item = M3u8::PlaylistItem.parse(format)
|
25
24
|
expect(item.program_id).to eq '1'
|
26
25
|
expect(item.codecs).to eq 'avc'
|
27
26
|
expect(item.bandwidth).to eq 540
|
@@ -37,8 +36,7 @@ describe M3u8::PlaylistItem do
|
|
37
36
|
format = %(#EXT-X-STREAM-INF:CODECS="avc",BANDWIDTH=540,) +
|
38
37
|
%(PROGRAM-ID=1,AUDIO="test",VIDEO="test2",) +
|
39
38
|
%(SUBTITLES="subs",CLOSED-CAPTIONS="caps",URI="test.url")
|
40
|
-
item = M3u8::PlaylistItem.
|
41
|
-
item.parse(format)
|
39
|
+
item = M3u8::PlaylistItem.parse(format)
|
42
40
|
expect(item.program_id).to eq '1'
|
43
41
|
expect(item.codecs).to eq 'avc'
|
44
42
|
expect(item.bandwidth).to eq 540
|
data/spec/reader_spec.rb
CHANGED
@@ -68,7 +68,11 @@ describe M3u8::Reader do
|
|
68
68
|
expect(item.duration).to eq 11.344644
|
69
69
|
expect(item.comment).to be_nil
|
70
70
|
|
71
|
-
|
71
|
+
item = playlist.items[4]
|
72
|
+
expect(item).to be_a(M3u8::TimeItem)
|
73
|
+
expect(item.time).to eq(Time.iso8601('2010-02-19T14:54:23Z'))
|
74
|
+
|
75
|
+
expect(playlist.items.size).to eq 140
|
72
76
|
end
|
73
77
|
|
74
78
|
it 'should parse I-Frame playlist' do
|
@@ -82,13 +86,13 @@ describe M3u8::Reader do
|
|
82
86
|
item = playlist.items[0]
|
83
87
|
expect(item).to be_a(M3u8::SegmentItem)
|
84
88
|
expect(item.duration).to eq 4.12
|
85
|
-
expect(item.
|
86
|
-
expect(item.
|
89
|
+
expect(item.byterange.length).to eq 9400
|
90
|
+
expect(item.byterange.start).to eq 376
|
87
91
|
expect(item.segment).to eq 'segment1.ts'
|
88
92
|
|
89
93
|
item = playlist.items[1]
|
90
|
-
expect(item.
|
91
|
-
expect(item.
|
94
|
+
expect(item.byterange.length).to eq 7144
|
95
|
+
expect(item.byterange.start).to be_nil
|
92
96
|
end
|
93
97
|
|
94
98
|
it 'should parse segment playlist with comments' do
|
@@ -198,4 +202,18 @@ describe M3u8::Reader do
|
|
198
202
|
expect(item.method).to eq 'AES-128'
|
199
203
|
expect(item.uri).to eq 'https://priv.example.com/key.php?r=52'
|
200
204
|
end
|
205
|
+
|
206
|
+
it 'should parse map (media intialization section) playlists' do
|
207
|
+
file = File.open 'spec/fixtures/map_playlist.m3u8'
|
208
|
+
reader = M3u8::Reader.new
|
209
|
+
playlist = reader.read file
|
210
|
+
|
211
|
+
expect(playlist.items.size).to eq 1
|
212
|
+
|
213
|
+
item = playlist.items[0]
|
214
|
+
expect(item).to be_a M3u8::MapItem
|
215
|
+
expect(item.uri).to eq 'frelo/prog_index.m3u8'
|
216
|
+
expect(item.byterange.length).to eq 4500
|
217
|
+
expect(item.byterange.start).to eq 600
|
218
|
+
end
|
201
219
|
end
|
data/spec/segment_item_spec.rb
CHANGED
@@ -7,13 +7,18 @@ describe M3u8::SegmentItem do
|
|
7
7
|
expect(item.duration).to eq 10.991
|
8
8
|
expect(item.segment).to eq 'test.ts'
|
9
9
|
expect(item.comment).to be_nil
|
10
|
+
expect(item.byterange).to be_nil
|
10
11
|
|
11
12
|
hash = { duration: 10.991, segment: 'test.ts', comment: 'anything',
|
12
|
-
|
13
|
+
byterange: {
|
14
|
+
length: 4500,
|
15
|
+
start: 600
|
16
|
+
}
|
17
|
+
}
|
13
18
|
item = M3u8::SegmentItem.new(hash)
|
14
19
|
expect(item.duration).to eq 10.991
|
15
|
-
expect(item.
|
16
|
-
expect(item.
|
20
|
+
expect(item.byterange.length).to eq 4500
|
21
|
+
expect(item.byterange.start).to eq 600
|
17
22
|
expect(item.segment).to eq 'test.ts'
|
18
23
|
expect(item.comment).to eq 'anything'
|
19
24
|
end
|
@@ -32,14 +37,21 @@ describe M3u8::SegmentItem do
|
|
32
37
|
expect(output).to eq expected
|
33
38
|
|
34
39
|
hash = { duration: 10.991, segment: 'test.ts', comment: 'anything',
|
35
|
-
|
40
|
+
byterange: {
|
41
|
+
length: 4500,
|
42
|
+
start: 600
|
43
|
+
}
|
44
|
+
}
|
36
45
|
item = M3u8::SegmentItem.new(hash)
|
37
46
|
output = item.to_s
|
38
47
|
expected = "#EXTINF:10.991,anything\n#EXT-X-BYTERANGE:4500@600\ntest.ts"
|
39
48
|
expect(output).to eq expected
|
40
49
|
|
41
50
|
hash = { duration: 10.991, segment: 'test.ts', comment: 'anything',
|
42
|
-
|
51
|
+
byterange: {
|
52
|
+
length: 4500
|
53
|
+
}
|
54
|
+
}
|
43
55
|
item = M3u8::SegmentItem.new(hash)
|
44
56
|
output = item.to_s
|
45
57
|
expected = "#EXTINF:10.991,anything\n#EXT-X-BYTERANGE:4500\ntest.ts"
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require 'm3u8'
|
2
|
-
|
2
|
+
require 'simplecov'
|
3
3
|
require 'coveralls'
|
4
|
-
|
4
|
+
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
6
|
+
SimpleCov::Formatter::HTMLFormatter,
|
7
|
+
Coveralls::SimpleCov::Formatter
|
8
|
+
]
|
9
|
+
SimpleCov.start
|
5
10
|
|
6
11
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
7
12
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::TimeItem do
|
4
|
+
it 'should provide m3u8 format representation' do
|
5
|
+
options = { time: '2010-02-19T14:54:23.031' }
|
6
|
+
item = M3u8::TimeItem.new(options)
|
7
|
+
output = item.to_s
|
8
|
+
expected = '#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031'
|
9
|
+
expect(output).to eq expected
|
10
|
+
|
11
|
+
options = { time: Time.iso8601('2010-02-19T14:54:23.031Z') }
|
12
|
+
item = M3u8::TimeItem.new(options)
|
13
|
+
output = item.to_s
|
14
|
+
expected = '#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23Z'
|
15
|
+
expect(output).to eq expected
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should parse m3u8 text into instance' do
|
19
|
+
input = '#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031Z'
|
20
|
+
item = M3u8::TimeItem.parse(input)
|
21
|
+
expect(item.time).to eq Time.iso8601('2010-02-19T14:54:23.031Z')
|
22
|
+
end
|
23
|
+
end
|
data/spec/writer_spec.rb
CHANGED
@@ -116,7 +116,7 @@ describe M3u8::Writer do
|
|
116
116
|
"1080-7mbps00000.ts\n" +
|
117
117
|
%(#EXT-X-KEY:METHOD=AES-128,URI="http://test.key",) +
|
118
118
|
%(IV=D512BBF,KEYFORMAT="identity",KEYFORMATVERSIONS="1/3"\n) +
|
119
|
-
"#EXTINF:11.261233,\n"
|
119
|
+
"#EXTINF:11.261233,\n" \
|
120
120
|
"1080-7mbps00001.ts\n" \
|
121
121
|
"#EXT-X-ENDLIST\n"
|
122
122
|
io = StringIO.open
|
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.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seth Deckard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,21 +70,26 @@ files:
|
|
70
70
|
- README.md
|
71
71
|
- Rakefile
|
72
72
|
- lib/m3u8.rb
|
73
|
+
- lib/m3u8/byte_range.rb
|
73
74
|
- lib/m3u8/discontinuity_item.rb
|
74
75
|
- lib/m3u8/error.rb
|
75
76
|
- lib/m3u8/key_item.rb
|
77
|
+
- lib/m3u8/map_item.rb
|
76
78
|
- lib/m3u8/media_item.rb
|
77
79
|
- lib/m3u8/playlist.rb
|
78
80
|
- lib/m3u8/playlist_item.rb
|
79
81
|
- lib/m3u8/reader.rb
|
80
82
|
- lib/m3u8/segment_item.rb
|
81
83
|
- lib/m3u8/session_data_item.rb
|
84
|
+
- lib/m3u8/time_item.rb
|
82
85
|
- lib/m3u8/version.rb
|
83
86
|
- lib/m3u8/writer.rb
|
84
87
|
- m3u8.gemspec
|
88
|
+
- spec/byte_range_spec.rb
|
85
89
|
- spec/discontinuity_item_spec.rb
|
86
90
|
- spec/fixtures/encrypted.m3u8
|
87
91
|
- spec/fixtures/iframes.m3u8
|
92
|
+
- spec/fixtures/map_playlist.m3u8
|
88
93
|
- spec/fixtures/master.m3u8
|
89
94
|
- spec/fixtures/master_iframes.m3u8
|
90
95
|
- spec/fixtures/playlist.m3u8
|
@@ -94,6 +99,7 @@ files:
|
|
94
99
|
- spec/fixtures/variant_audio.m3u8
|
95
100
|
- spec/key_item_spec.rb
|
96
101
|
- spec/m3u8_spec.rb
|
102
|
+
- spec/map_item_spec.rb
|
97
103
|
- spec/media_item_spec.rb
|
98
104
|
- spec/playlist_item_spec.rb
|
99
105
|
- spec/playlist_spec.rb
|
@@ -101,6 +107,7 @@ files:
|
|
101
107
|
- spec/segment_item_spec.rb
|
102
108
|
- spec/session_data_item_spec.rb
|
103
109
|
- spec/spec_helper.rb
|
110
|
+
- spec/time_item_spec.rb
|
104
111
|
- spec/writer_spec.rb
|
105
112
|
homepage: https://github.com/sethdeckard/m3u8
|
106
113
|
licenses:
|
@@ -122,14 +129,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
129
|
version: '0'
|
123
130
|
requirements: []
|
124
131
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.4.
|
132
|
+
rubygems_version: 2.4.5
|
126
133
|
signing_key:
|
127
134
|
specification_version: 4
|
128
135
|
summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
|
129
136
|
test_files:
|
137
|
+
- spec/byte_range_spec.rb
|
130
138
|
- spec/discontinuity_item_spec.rb
|
131
139
|
- spec/fixtures/encrypted.m3u8
|
132
140
|
- spec/fixtures/iframes.m3u8
|
141
|
+
- spec/fixtures/map_playlist.m3u8
|
133
142
|
- spec/fixtures/master.m3u8
|
134
143
|
- spec/fixtures/master_iframes.m3u8
|
135
144
|
- spec/fixtures/playlist.m3u8
|
@@ -139,6 +148,7 @@ test_files:
|
|
139
148
|
- spec/fixtures/variant_audio.m3u8
|
140
149
|
- spec/key_item_spec.rb
|
141
150
|
- spec/m3u8_spec.rb
|
151
|
+
- spec/map_item_spec.rb
|
142
152
|
- spec/media_item_spec.rb
|
143
153
|
- spec/playlist_item_spec.rb
|
144
154
|
- spec/playlist_spec.rb
|
@@ -146,4 +156,5 @@ test_files:
|
|
146
156
|
- spec/segment_item_spec.rb
|
147
157
|
- spec/session_data_item_spec.rb
|
148
158
|
- spec/spec_helper.rb
|
159
|
+
- spec/time_item_spec.rb
|
149
160
|
- spec/writer_spec.rb
|