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