m3u8 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -4
- data/CHANGELOG.md +7 -1
- data/README.md +9 -5
- data/Rakefile +4 -4
- data/lib/m3u8.rb +1 -0
- data/lib/m3u8/media_item.rb +74 -0
- data/lib/m3u8/playlist.rb +4 -1
- data/lib/m3u8/playlist_item.rb +39 -8
- data/lib/m3u8/reader.rb +73 -7
- data/lib/m3u8/segment_item.rb +2 -2
- data/lib/m3u8/version.rb +1 -1
- data/lib/m3u8/writer.rb +1 -0
- data/m3u8.gemspec +9 -9
- data/spec/fixtures/playlist.m3u8 +1 -0
- data/spec/fixtures/playlist_with_comments.m3u8 +283 -0
- data/spec/fixtures/variant_angles.m3u8 +14 -0
- data/spec/fixtures/variant_audio.m3u8 +15 -0
- data/spec/media_item_spec.rb +25 -0
- data/spec/playlist_item_spec.rb +21 -11
- data/spec/playlist_spec.rb +14 -10
- data/spec/reader_spec.rb +82 -1
- data/spec/segment_item_spec.rb +13 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/writer_spec.rb +8 -6
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b76853323b76f301feb3e6c92d787b8b961d22a
|
4
|
+
data.tar.gz: 1edfa96734f62f346022af27459486337eeb8e85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f7687b04510afe4354969801d1367b2a7ac403895f42d17a8990a2eb79fbad59a1c07e2a1594976d55656a264ef81d1712ac51038eff8cd2f5b7d03451759df
|
7
|
+
data.tar.gz: ff17446d9b23074e4db0cf8b6aa735f7f5c76f8e406aaef17172c1ebb83e7ad25a94f9cf9e6ee8bb222f068150bcd89eaa3c1ae826d2c902e11ac756ed42795e
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
### 0.
|
1
|
+
### 0.4.0 (1/20/2015) - BREAKING: Playlist.audio is now Playlist.audio_codec, audio now represents the newly supported AUDIO attribute, please update your code accordingly. Added support for all EXT-X-MEDIA attributes as well as the following EXT-X-STREAM-INF attributes: AVERAGE-BANDWIDTH, AUDIO, VIDEO, SUBTILES, and CLOSED-CAPTIONS. This means the library now supports alternate audio / camera angles as well as subtitles and closed captions. The EXT-X-PLAYLIST-TYPE attribute is now supported as Playlist.type. SegmentItems now support comments (Thanks @elsurudo). A bug was also fixed in Reader that prevented reuse of the instance.
|
2
|
+
|
3
|
+
### 0.3.2 (1/16/2015) - PROGRAM-ID was removed in protocol version 6, if not provided it will now be omitted. Thanks @elsurudo
|
4
|
+
|
5
|
+
### 0.3.1 (1/15/2015) - Added duration method to Playlist to get the total length of segments contained within it. Thanks @DaKaZ
|
6
|
+
|
7
|
+
### 0.3.0 (11/26/2014) - DEPRECIATED add_playlist and add_segment on Playlist, manipulate the items array directly instead. Extracted writing of playlists to it's own Writer class (only use directly if you want more control over the process). Added read convience method to Playlist so Reader doesn't have to be used directly unless more control is desired. Simplified validation and other aspects during this refactoring.
|
2
8
|
|
3
9
|
### 0.2.1 (11/26/2014) - Moved codec generation / validation to PlaylistItem, allowing for the flexibility to either specify :audio, :level, :profile when creating new instances (codecs value will be automatically generated) or just set the codecs attribute directly.
|
4
10
|
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ Or install it yourself as:
|
|
33
33
|
playlist = M3u8::Playlist.new
|
34
34
|
#create a new playlist item with options
|
35
35
|
options = { program_id: 1, width: 1920, height: 1080, width: 1920, height: 1080,
|
36
|
-
profile: 'high', level: 4.1,
|
36
|
+
profile: 'high', level: 4.1, audio_codec: 'aac-lc', bitrate: 540,
|
37
37
|
playlist: 'test.url' }
|
38
38
|
item = M3u8::PlaylistItem.new options
|
39
39
|
playlist.items.push item
|
@@ -50,7 +50,7 @@ Or install it yourself as:
|
|
50
50
|
playlist.items.push item
|
51
51
|
|
52
52
|
#just get the codec string for custom use
|
53
|
-
options = { profile: 'baseline', level: 3.0,
|
53
|
+
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
|
54
54
|
codecs = M3u8::Playlist.codecs options
|
55
55
|
# => "avc1.66.30,mp4a.40.2"
|
56
56
|
|
@@ -69,7 +69,7 @@ Or install it yourself as:
|
|
69
69
|
|
70
70
|
#There is a M3u8::Writer class if you want more control over the write process
|
71
71
|
|
72
|
-
#values for :
|
72
|
+
#values for :audio_codec (Codec name)
|
73
73
|
#aac-lc, he-aac, mp3
|
74
74
|
|
75
75
|
#values for :profile (H.264 Profile)
|
@@ -97,7 +97,7 @@ Or install it yourself as:
|
|
97
97
|
|
98
98
|
#create a new playlist item with options
|
99
99
|
options = { program_id: 1, width: 1920, height: 1080, width: 1920, height: 1080,
|
100
|
-
profile: 'high', level: 4.1,
|
100
|
+
profile: 'high', level: 4.1, audio_codec: 'aac-lc', bitrate: 540,
|
101
101
|
playlist: 'test.url' }
|
102
102
|
item = M3u8::PlaylistItem.new options
|
103
103
|
#add it to the top of the playlist
|
@@ -115,10 +115,14 @@ Or install it yourself as:
|
|
115
115
|
|
116
116
|
## Missing (but planned) Features
|
117
117
|
* Support for encryption keys and DRM.
|
118
|
-
* Support
|
118
|
+
* Support I-Frame and Byte-Range.
|
119
|
+
* Validation of all attributes to match the spec completely.
|
120
|
+
* Support for all additional attributes in the latest version of the spec.
|
119
121
|
|
120
122
|
## Contributing
|
121
123
|
|
124
|
+
(Be sure to check to dev branch to make sure I'm not already working on it)
|
125
|
+
|
122
126
|
1. Fork it ( https://github.com/sethdeckard/m3u8/fork )
|
123
127
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
124
128
|
3. Run the specs, make sure they pass and that new features are covered
|
data/Rakefile
CHANGED
data/lib/m3u8.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
module M3u8
|
2
|
+
# MediaItem represents a set of EXT-X-MEDIA attributes
|
3
|
+
class MediaItem
|
4
|
+
attr_accessor :type, :group, :language, :assoc_language, :name,
|
5
|
+
:autoselect, :default, :uri, :forced
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
params.each do |key, value|
|
9
|
+
instance_variable_set("@#{key}", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
attributes = [type_format,
|
15
|
+
group_format,
|
16
|
+
language_format,
|
17
|
+
assoc_language_format,
|
18
|
+
name_format,
|
19
|
+
autoselect_format,
|
20
|
+
default_format,
|
21
|
+
uri_format,
|
22
|
+
forced_format].compact.join(',')
|
23
|
+
"#EXT-X-MEDIA:#{attributes}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def type_format
|
29
|
+
"TYPE=#{type}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def group_format
|
33
|
+
%(GROUP-ID="#{group}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def language_format
|
37
|
+
return if language.nil?
|
38
|
+
%(LANGUAGE="#{language}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def assoc_language_format
|
42
|
+
return if assoc_language.nil?
|
43
|
+
%(ASSOC-LANGUAGE="#{assoc_language}")
|
44
|
+
end
|
45
|
+
|
46
|
+
def name_format
|
47
|
+
%(NAME="#{name}")
|
48
|
+
end
|
49
|
+
|
50
|
+
def autoselect_format
|
51
|
+
return if autoselect.nil?
|
52
|
+
"AUTOSELECT=#{to_yes_no autoselect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_format
|
56
|
+
return if default.nil?
|
57
|
+
"DEFAULT=#{to_yes_no default}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def uri_format
|
61
|
+
return if uri.nil?
|
62
|
+
%(URI="#{uri}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def forced_format
|
66
|
+
return if forced.nil?
|
67
|
+
"FORCED=#{to_yes_no forced}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_yes_no(boolean)
|
71
|
+
boolean == true ? 'YES' : 'NO'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/m3u8/playlist.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module M3u8
|
2
|
+
# Playlist represents an m3u8 playlist, it can be a master playlist or a set
|
3
|
+
# of media segments
|
2
4
|
class Playlist
|
3
|
-
attr_accessor :items, :version, :cache, :target, :sequence
|
5
|
+
attr_accessor :items, :version, :cache, :target, :sequence, :type
|
4
6
|
|
5
7
|
def initialize(options = {})
|
6
8
|
assign_options options
|
@@ -60,6 +62,7 @@ module M3u8
|
|
60
62
|
self.sequence = options[:sequence]
|
61
63
|
self.cache = options[:cache]
|
62
64
|
self.target = options[:target]
|
65
|
+
self.type = options[:type]
|
63
66
|
end
|
64
67
|
|
65
68
|
def playlist_size
|
data/lib/m3u8/playlist_item.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
module M3u8
|
2
|
+
# PlaylistItem represents a set of EXT-X-STREAM-INF attributes
|
2
3
|
class PlaylistItem
|
3
4
|
attr_accessor :program_id, :width, :height, :codecs, :bitrate, :playlist,
|
4
|
-
:
|
5
|
+
:audio_codec, :level, :profile, :video, :audio,
|
6
|
+
:average_bandwidth, :subtitles, :closed_captions
|
5
7
|
MISSING_CODEC_MESSAGE = 'Audio or video codec info should be provided.'
|
6
8
|
|
7
9
|
def initialize(params = {})
|
@@ -18,7 +20,6 @@ module M3u8
|
|
18
20
|
def codecs
|
19
21
|
return @codecs unless @codecs.nil?
|
20
22
|
|
21
|
-
audio_codec = audio_codec audio
|
22
23
|
video_codec = video_codec profile, level
|
23
24
|
|
24
25
|
if video_codec.nil?
|
@@ -38,7 +39,12 @@ module M3u8
|
|
38
39
|
attributes = [program_id_format,
|
39
40
|
resolution_format,
|
40
41
|
codecs_format,
|
41
|
-
bandwidth_format
|
42
|
+
bandwidth_format,
|
43
|
+
average_bandwidth_format,
|
44
|
+
audio_format,
|
45
|
+
video_format,
|
46
|
+
subtitles_format,
|
47
|
+
closed_captions_format].compact.join(',')
|
42
48
|
"#EXT-X-STREAM-INF:#{attributes}\n#{playlist}"
|
43
49
|
end
|
44
50
|
|
@@ -59,18 +65,43 @@ module M3u8
|
|
59
65
|
end
|
60
66
|
|
61
67
|
def codecs_format
|
62
|
-
|
68
|
+
%(CODECS="#{codecs}")
|
63
69
|
end
|
64
70
|
|
65
71
|
def bandwidth_format
|
66
72
|
"BANDWIDTH=#{bitrate}"
|
67
73
|
end
|
68
74
|
|
69
|
-
def
|
75
|
+
def average_bandwidth_format
|
76
|
+
return if average_bandwidth.nil?
|
77
|
+
"AVERAGE-BANDWIDTH=#{average_bandwidth}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def audio_format
|
70
81
|
return if audio.nil?
|
71
|
-
|
72
|
-
|
73
|
-
|
82
|
+
%(AUDIO="#{audio}")
|
83
|
+
end
|
84
|
+
|
85
|
+
def video_format
|
86
|
+
return if video.nil?
|
87
|
+
%(VIDEO="#{video}")
|
88
|
+
end
|
89
|
+
|
90
|
+
def subtitles_format
|
91
|
+
return if subtitles.nil?
|
92
|
+
%(SUBTITLES="#{subtitles}")
|
93
|
+
end
|
94
|
+
|
95
|
+
def closed_captions_format
|
96
|
+
return if closed_captions.nil?
|
97
|
+
%(CLOSED-CAPTIONS="#{closed_captions}")
|
98
|
+
end
|
99
|
+
|
100
|
+
def audio_codec
|
101
|
+
return if @audio_codec.nil?
|
102
|
+
return 'mp4a.40.2' if @audio_codec.downcase == 'aac-lc'
|
103
|
+
return 'mp4a.40.5' if @audio_codec.downcase == 'he-aac'
|
104
|
+
return 'mp4a.40.34' if @audio_codec.downcase == 'mp3'
|
74
105
|
end
|
75
106
|
|
76
107
|
def video_codec(profile, level)
|
data/lib/m3u8/reader.rb
CHANGED
@@ -2,22 +2,37 @@ module M3u8
|
|
2
2
|
class Reader
|
3
3
|
attr_accessor :playlist, :item, :open, :master
|
4
4
|
PLAYLIST_START = '#EXTM3U'
|
5
|
+
PLAYLIST_TYPE_START = '#EXT-X-PLAYLIST-TYPE:'
|
5
6
|
VERSION_START = '#EXT-X-VERSION:'
|
6
7
|
SEQUENCE_START = '#EXT-X-MEDIA-SEQUENCE:'
|
7
8
|
CACHE_START = '#EXT-X-ALLOW-CACHE:'
|
8
9
|
TARGET_START = '#EXT-X-TARGETDURATION:'
|
9
10
|
STREAM_START = '#EXT-X-STREAM-INF:'
|
11
|
+
MEDIA_START = '#EXT-X-MEDIA:'
|
10
12
|
SEGMENT_START = '#EXTINF:'
|
13
|
+
# EXT-X-STREAM-INF:
|
11
14
|
PROGRAM_ID = 'PROGRAM-ID'
|
12
15
|
RESOLUTION = 'RESOLUTION'
|
13
16
|
CODECS = 'CODECS'
|
14
17
|
BANDWIDTH = 'BANDWIDTH'
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
AVERAGE_BANDWIDTH = 'AVERAGE-BANDWIDTH'
|
19
|
+
VIDEO = 'VIDEO'
|
20
|
+
AUDIO = 'AUDIO'
|
21
|
+
SUBTITLES = 'SUBTITLES'
|
22
|
+
CLOSED_CAPTIONS = 'CLOSED-CAPTIONS'
|
23
|
+
# EXT-X-MEDIA:
|
24
|
+
TYPE = 'TYPE'
|
25
|
+
GROUP_ID = 'GROUP-ID'
|
26
|
+
LANGUAGE = 'LANGUAGE'
|
27
|
+
ASSOC_LANGUAGE = 'ASSOC-LANGUAGE'
|
28
|
+
NAME = 'NAME'
|
29
|
+
AUTOSELECT = 'AUTOSELECT'
|
30
|
+
DEFAULT = 'DEFAULT'
|
31
|
+
URI = 'URI'
|
32
|
+
FORCED = 'FORCED'
|
19
33
|
|
20
34
|
def read(input)
|
35
|
+
self.playlist = Playlist.new
|
21
36
|
input.each_line do |line|
|
22
37
|
parse_line line
|
23
38
|
end
|
@@ -29,7 +44,9 @@ module M3u8
|
|
29
44
|
def parse_line(line)
|
30
45
|
return if line.start_with? PLAYLIST_START
|
31
46
|
|
32
|
-
if line.start_with?
|
47
|
+
if line.start_with? PLAYLIST_TYPE_START
|
48
|
+
parse_playlist_type line
|
49
|
+
elsif line.start_with? VERSION_START
|
33
50
|
parse_version line
|
34
51
|
elsif line.start_with? SEQUENCE_START
|
35
52
|
parse_sequence line
|
@@ -41,11 +58,17 @@ module M3u8
|
|
41
58
|
parse_stream line
|
42
59
|
elsif line.start_with? SEGMENT_START
|
43
60
|
parse_segment line
|
61
|
+
elsif line.start_with? MEDIA_START
|
62
|
+
parse_media line
|
44
63
|
elsif !item.nil? && open
|
45
64
|
parse_value(line)
|
46
65
|
end
|
47
66
|
end
|
48
67
|
|
68
|
+
def parse_playlist_type(line)
|
69
|
+
playlist.type = line.gsub(PLAYLIST_TYPE_START, '').delete!("\n")
|
70
|
+
end
|
71
|
+
|
49
72
|
def parse_version(line)
|
50
73
|
playlist.version = line.gsub(VERSION_START, '').to_i
|
51
74
|
end
|
@@ -85,6 +108,16 @@ module M3u8
|
|
85
108
|
item.codecs = value
|
86
109
|
when BANDWIDTH
|
87
110
|
item.bitrate = value.to_i
|
111
|
+
when AVERAGE_BANDWIDTH
|
112
|
+
item.average_bandwidth = value.to_i
|
113
|
+
when AUDIO
|
114
|
+
item.audio = value
|
115
|
+
when VIDEO
|
116
|
+
item.video = value
|
117
|
+
when SUBTITLES
|
118
|
+
item.subtitles = value
|
119
|
+
when CLOSED_CAPTIONS
|
120
|
+
item.closed_captions = value
|
88
121
|
end
|
89
122
|
end
|
90
123
|
end
|
@@ -93,9 +126,42 @@ module M3u8
|
|
93
126
|
self.master = false
|
94
127
|
self.open = true
|
95
128
|
|
129
|
+
values = line.gsub(SEGMENT_START, '').gsub("\n", ',').split(',')
|
130
|
+
|
96
131
|
self.item = M3u8::SegmentItem.new
|
97
|
-
item.duration =
|
98
|
-
|
132
|
+
item.duration = values[0].to_f
|
133
|
+
item.comment = values[1] unless values[1].nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_media(line)
|
137
|
+
self.open = false
|
138
|
+
self.item = M3u8::MediaItem.new
|
139
|
+
line = line.gsub MEDIA_START, ''
|
140
|
+
attributes = line.scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
|
141
|
+
attributes.each do |pair|
|
142
|
+
value = pair[1].gsub("\n", '').gsub('"', '')
|
143
|
+
case pair[0]
|
144
|
+
when TYPE
|
145
|
+
item.type = value
|
146
|
+
when GROUP_ID
|
147
|
+
item.group = value
|
148
|
+
when LANGUAGE
|
149
|
+
item.language = value
|
150
|
+
when ASSOC_LANGUAGE
|
151
|
+
item.assoc_language = value
|
152
|
+
when NAME
|
153
|
+
item.name = value
|
154
|
+
when AUTOSELECT
|
155
|
+
item.autoselect = parse_yes_no value
|
156
|
+
when DEFAULT
|
157
|
+
item.default = parse_yes_no value
|
158
|
+
when URI
|
159
|
+
item.uri = value
|
160
|
+
when FORCED
|
161
|
+
item.forced = parse_yes_no value
|
162
|
+
end
|
163
|
+
end
|
164
|
+
playlist.items.push item
|
99
165
|
end
|
100
166
|
|
101
167
|
def parse_resolution(resolution)
|
data/lib/m3u8/segment_item.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module M3u8
|
2
2
|
class SegmentItem
|
3
|
-
attr_accessor :duration, :segment
|
3
|
+
attr_accessor :duration, :segment, :comment
|
4
4
|
|
5
5
|
def initialize(params = {})
|
6
6
|
params.each do |key, value|
|
@@ -9,7 +9,7 @@ module M3u8
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_s
|
12
|
-
"#EXTINF:#{duration}
|
12
|
+
"#EXTINF:#{duration},#{comment}\n#{segment}"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/lib/m3u8/version.rb
CHANGED
data/lib/m3u8/writer.rb
CHANGED
@@ -28,6 +28,7 @@ module M3u8
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def write_header(playlist)
|
31
|
+
io.puts "#EXT-X-PLAYLIST-TYPE:#{playlist.type}" unless playlist.type.nil?
|
31
32
|
io.puts "#EXT-X-VERSION:#{playlist.version}"
|
32
33
|
io.puts "#EXT-X-MEDIA-SEQUENCE:#{playlist.sequence}"
|
33
34
|
io.puts "#EXT-X-ALLOW-CACHE:#{cache(playlist)}"
|
data/m3u8.gemspec
CHANGED
@@ -4,21 +4,21 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'm3u8/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'm3u8'
|
8
8
|
spec.version = M3u8::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Seth Deckard']
|
10
|
+
spec.email = ['seth@deckard.me']
|
11
11
|
spec.summary = %q{Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).}
|
12
12
|
spec.description = %q{Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).}
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
13
|
+
spec.homepage = 'https://github.com/sethdeckard/m3u8'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
21
|
+
spec.add_development_dependency 'bundler', '>= 1.6'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '>= 2.11'
|
24
24
|
end
|
data/spec/fixtures/playlist.m3u8
CHANGED
@@ -0,0 +1,283 @@
|
|
1
|
+
#EXTM3U
|
2
|
+
#EXT-X-PLAYLIST-TYPE:VOD
|
3
|
+
#EXT-X-VERSION:4
|
4
|
+
#EXT-X-MEDIA-SEQUENCE:1
|
5
|
+
#EXT-X-ALLOW-CACHE:NO
|
6
|
+
#EXT-X-TARGETDURATION:12
|
7
|
+
#EXTINF:11.344644,anything
|
8
|
+
1080-7mbps00000.ts
|
9
|
+
#EXTINF:11.261233,anything
|
10
|
+
1080-7mbps00001.ts
|
11
|
+
#EXTINF:7.507489,anything
|
12
|
+
1080-7mbps00002.ts
|
13
|
+
#EXTINF:11.261233,anything
|
14
|
+
1080-7mbps00003.ts
|
15
|
+
#EXTINF:11.261233,anything
|
16
|
+
1080-7mbps00004.ts
|
17
|
+
#EXTINF:7.507489,anything
|
18
|
+
1080-7mbps00005.ts
|
19
|
+
#EXTINF:11.261233,anything
|
20
|
+
1080-7mbps00006.ts
|
21
|
+
#EXTINF:11.261233,anything
|
22
|
+
1080-7mbps00007.ts
|
23
|
+
#EXTINF:7.507478,anything
|
24
|
+
1080-7mbps00008.ts
|
25
|
+
#EXTINF:11.261233,anything
|
26
|
+
1080-7mbps00009.ts
|
27
|
+
#EXTINF:11.261233,anything
|
28
|
+
1080-7mbps00010.ts
|
29
|
+
#EXTINF:7.507478,anything
|
30
|
+
1080-7mbps00011.ts
|
31
|
+
#EXTINF:11.261233,anything
|
32
|
+
1080-7mbps00012.ts
|
33
|
+
#EXTINF:11.261233,anything
|
34
|
+
1080-7mbps00013.ts
|
35
|
+
#EXTINF:7.507489,anything
|
36
|
+
1080-7mbps00014.ts
|
37
|
+
#EXTINF:11.261233,anything
|
38
|
+
1080-7mbps00015.ts
|
39
|
+
#EXTINF:11.261233,anything
|
40
|
+
1080-7mbps00016.ts
|
41
|
+
#EXTINF:7.507489,anything
|
42
|
+
1080-7mbps00017.ts
|
43
|
+
#EXTINF:11.261233,anything
|
44
|
+
1080-7mbps00018.ts
|
45
|
+
#EXTINF:11.261233,anything
|
46
|
+
1080-7mbps00019.ts
|
47
|
+
#EXTINF:7.507478,anything
|
48
|
+
1080-7mbps00020.ts
|
49
|
+
#EXTINF:11.261233,anything
|
50
|
+
1080-7mbps00021.ts
|
51
|
+
#EXTINF:11.261233,anything
|
52
|
+
1080-7mbps00022.ts
|
53
|
+
#EXTINF:7.507478,anything
|
54
|
+
1080-7mbps00023.ts
|
55
|
+
#EXTINF:11.261233,anything
|
56
|
+
1080-7mbps00024.ts
|
57
|
+
#EXTINF:11.261233,anything
|
58
|
+
1080-7mbps00025.ts
|
59
|
+
#EXTINF:7.507489,anything
|
60
|
+
1080-7mbps00026.ts
|
61
|
+
#EXTINF:11.261233,anything
|
62
|
+
1080-7mbps00027.ts
|
63
|
+
#EXTINF:11.261233,anything
|
64
|
+
1080-7mbps00028.ts
|
65
|
+
#EXTINF:7.507489,anything
|
66
|
+
1080-7mbps00029.ts
|
67
|
+
#EXTINF:11.261233,anything
|
68
|
+
1080-7mbps00030.ts
|
69
|
+
#EXTINF:11.261233,anything
|
70
|
+
1080-7mbps00031.ts
|
71
|
+
#EXTINF:7.507478,anything
|
72
|
+
1080-7mbps00032.ts
|
73
|
+
#EXTINF:11.261233,anything
|
74
|
+
1080-7mbps00033.ts
|
75
|
+
#EXTINF:11.261233,anything
|
76
|
+
1080-7mbps00034.ts
|
77
|
+
#EXTINF:7.507489,anything
|
78
|
+
1080-7mbps00035.ts
|
79
|
+
#EXTINF:11.261233,anything
|
80
|
+
1080-7mbps00036.ts
|
81
|
+
#EXTINF:11.261222,anything
|
82
|
+
1080-7mbps00037.ts
|
83
|
+
#EXTINF:7.507489,anything
|
84
|
+
1080-7mbps00038.ts
|
85
|
+
#EXTINF:11.261233,anything
|
86
|
+
1080-7mbps00039.ts
|
87
|
+
#EXTINF:11.261233,anything
|
88
|
+
1080-7mbps00040.ts
|
89
|
+
#EXTINF:7.507489,anything
|
90
|
+
1080-7mbps00041.ts
|
91
|
+
#EXTINF:11.261233,anything
|
92
|
+
1080-7mbps00042.ts
|
93
|
+
#EXTINF:11.261233,anything
|
94
|
+
1080-7mbps00043.ts
|
95
|
+
#EXTINF:7.507478,anything
|
96
|
+
1080-7mbps00044.ts
|
97
|
+
#EXTINF:11.261233,anything
|
98
|
+
1080-7mbps00045.ts
|
99
|
+
#EXTINF:11.261233,anything
|
100
|
+
1080-7mbps00046.ts
|
101
|
+
#EXTINF:7.507489,anything
|
102
|
+
1080-7mbps00047.ts
|
103
|
+
#EXTINF:11.261233,anything
|
104
|
+
1080-7mbps00048.ts
|
105
|
+
#EXTINF:11.261222,anything
|
106
|
+
1080-7mbps00049.ts
|
107
|
+
#EXTINF:7.507489,anything
|
108
|
+
1080-7mbps00050.ts
|
109
|
+
#EXTINF:11.261233,anything
|
110
|
+
1080-7mbps00051.ts
|
111
|
+
#EXTINF:11.261233,anything
|
112
|
+
1080-7mbps00052.ts
|
113
|
+
#EXTINF:7.507489,anything
|
114
|
+
1080-7mbps00053.ts
|
115
|
+
#EXTINF:11.261233,anything
|
116
|
+
1080-7mbps00054.ts
|
117
|
+
#EXTINF:11.261233,anything
|
118
|
+
1080-7mbps00055.ts
|
119
|
+
#EXTINF:7.507478,anything
|
120
|
+
1080-7mbps00056.ts
|
121
|
+
#EXTINF:11.261233,anything
|
122
|
+
1080-7mbps00057.ts
|
123
|
+
#EXTINF:11.261233,anything
|
124
|
+
1080-7mbps00058.ts
|
125
|
+
#EXTINF:7.507489,anything
|
126
|
+
1080-7mbps00059.ts
|
127
|
+
#EXTINF:11.261233,anything
|
128
|
+
1080-7mbps00060.ts
|
129
|
+
#EXTINF:11.261222,anything
|
130
|
+
1080-7mbps00061.ts
|
131
|
+
#EXTINF:7.507489,anything
|
132
|
+
1080-7mbps00062.ts
|
133
|
+
#EXTINF:11.261233,anything
|
134
|
+
1080-7mbps00063.ts
|
135
|
+
#EXTINF:11.261233,anything
|
136
|
+
1080-7mbps00064.ts
|
137
|
+
#EXTINF:7.507489,anything
|
138
|
+
1080-7mbps00065.ts
|
139
|
+
#EXTINF:11.261233,anything
|
140
|
+
1080-7mbps00066.ts
|
141
|
+
#EXTINF:11.261233,anything
|
142
|
+
1080-7mbps00067.ts
|
143
|
+
#EXTINF:7.507478,anything
|
144
|
+
1080-7mbps00068.ts
|
145
|
+
#EXTINF:11.261233,anything
|
146
|
+
1080-7mbps00069.ts
|
147
|
+
#EXTINF:11.261233,anything
|
148
|
+
1080-7mbps00070.ts
|
149
|
+
#EXTINF:7.507489,anything
|
150
|
+
1080-7mbps00071.ts
|
151
|
+
#EXTINF:11.261233,anything
|
152
|
+
1080-7mbps00072.ts
|
153
|
+
#EXTINF:11.261233,anything
|
154
|
+
1080-7mbps00073.ts
|
155
|
+
#EXTINF:7.507489,anything
|
156
|
+
1080-7mbps00074.ts
|
157
|
+
#EXTINF:11.261222,anything
|
158
|
+
1080-7mbps00075.ts
|
159
|
+
#EXTINF:11.261233,anything
|
160
|
+
1080-7mbps00076.ts
|
161
|
+
#EXTINF:7.507489,anything
|
162
|
+
1080-7mbps00077.ts
|
163
|
+
#EXTINF:11.261233,anything
|
164
|
+
1080-7mbps00078.ts
|
165
|
+
#EXTINF:11.261233,anything
|
166
|
+
1080-7mbps00079.ts
|
167
|
+
#EXTINF:7.507478,anything
|
168
|
+
1080-7mbps00080.ts
|
169
|
+
#EXTINF:11.261233,anything
|
170
|
+
1080-7mbps00081.ts
|
171
|
+
#EXTINF:11.261233,anything
|
172
|
+
1080-7mbps00082.ts
|
173
|
+
#EXTINF:7.507489,anything
|
174
|
+
1080-7mbps00083.ts
|
175
|
+
#EXTINF:11.261233,anything
|
176
|
+
1080-7mbps00084.ts
|
177
|
+
#EXTINF:11.261233,anything
|
178
|
+
1080-7mbps00085.ts
|
179
|
+
#EXTINF:7.507489,anything
|
180
|
+
1080-7mbps00086.ts
|
181
|
+
#EXTINF:11.261222,anything
|
182
|
+
1080-7mbps00087.ts
|
183
|
+
#EXTINF:11.261233,anything
|
184
|
+
1080-7mbps00088.ts
|
185
|
+
#EXTINF:7.507489,anything
|
186
|
+
1080-7mbps00089.ts
|
187
|
+
#EXTINF:11.261233,anything
|
188
|
+
1080-7mbps00090.ts
|
189
|
+
#EXTINF:11.261233,anything
|
190
|
+
1080-7mbps00091.ts
|
191
|
+
#EXTINF:7.507478,anything
|
192
|
+
1080-7mbps00092.ts
|
193
|
+
#EXTINF:11.261233,anything
|
194
|
+
1080-7mbps00093.ts
|
195
|
+
#EXTINF:11.261233,anything
|
196
|
+
1080-7mbps00094.ts
|
197
|
+
#EXTINF:7.507489,anything
|
198
|
+
1080-7mbps00095.ts
|
199
|
+
#EXTINF:11.261233,anything
|
200
|
+
1080-7mbps00096.ts
|
201
|
+
#EXTINF:11.261233,anything
|
202
|
+
1080-7mbps00097.ts
|
203
|
+
#EXTINF:7.507489,anything
|
204
|
+
1080-7mbps00098.ts
|
205
|
+
#EXTINF:11.261222,anything
|
206
|
+
1080-7mbps00099.ts
|
207
|
+
#EXTINF:11.261233,anything
|
208
|
+
1080-7mbps00100.ts
|
209
|
+
#EXTINF:7.507489,anything
|
210
|
+
1080-7mbps00101.ts
|
211
|
+
#EXTINF:11.261233,anything
|
212
|
+
1080-7mbps00102.ts
|
213
|
+
#EXTINF:11.261233,anything
|
214
|
+
1080-7mbps00103.ts
|
215
|
+
#EXTINF:7.507478,anything
|
216
|
+
1080-7mbps00104.ts
|
217
|
+
#EXTINF:11.261233,anything
|
218
|
+
1080-7mbps00105.ts
|
219
|
+
#EXTINF:11.261233,anything
|
220
|
+
1080-7mbps00106.ts
|
221
|
+
#EXTINF:7.507489,anything
|
222
|
+
1080-7mbps00107.ts
|
223
|
+
#EXTINF:11.261233,anything
|
224
|
+
1080-7mbps00108.ts
|
225
|
+
#EXTINF:11.261233,anything
|
226
|
+
1080-7mbps00109.ts
|
227
|
+
#EXTINF:7.507489,anything
|
228
|
+
1080-7mbps00110.ts
|
229
|
+
#EXTINF:11.261233,anything
|
230
|
+
1080-7mbps00111.ts
|
231
|
+
#EXTINF:11.261233,anything
|
232
|
+
1080-7mbps00112.ts
|
233
|
+
#EXTINF:7.507478,anything
|
234
|
+
1080-7mbps00113.ts
|
235
|
+
#EXTINF:11.261233,anything
|
236
|
+
1080-7mbps00114.ts
|
237
|
+
#EXTINF:11.261233,anything
|
238
|
+
1080-7mbps00115.ts
|
239
|
+
#EXTINF:7.507478,anything
|
240
|
+
1080-7mbps00116.ts
|
241
|
+
#EXTINF:11.261233,anything
|
242
|
+
1080-7mbps00117.ts
|
243
|
+
#EXTINF:7.507478,anything
|
244
|
+
1080-7mbps00118.ts
|
245
|
+
#EXTINF:11.261233,anything
|
246
|
+
1080-7mbps00119.ts
|
247
|
+
#EXTINF:11.261233,anything
|
248
|
+
1080-7mbps00120.ts
|
249
|
+
#EXTINF:7.507489,anything
|
250
|
+
1080-7mbps00121.ts
|
251
|
+
#EXTINF:11.261233,anything
|
252
|
+
1080-7mbps00122.ts
|
253
|
+
#EXTINF:11.261233,anything
|
254
|
+
1080-7mbps00123.ts
|
255
|
+
#EXTINF:7.507489,anything
|
256
|
+
1080-7mbps00124.ts
|
257
|
+
#EXTINF:11.261222,anything
|
258
|
+
1080-7mbps00125.ts
|
259
|
+
#EXTINF:11.261233,anything
|
260
|
+
1080-7mbps00126.ts
|
261
|
+
#EXTINF:7.507489,anything
|
262
|
+
1080-7mbps00127.ts
|
263
|
+
#EXTINF:11.261233,anything
|
264
|
+
1080-7mbps00128.ts
|
265
|
+
#EXTINF:11.261233,anything
|
266
|
+
1080-7mbps00129.ts
|
267
|
+
#EXTINF:7.507478,anything
|
268
|
+
1080-7mbps00130.ts
|
269
|
+
#EXTINF:11.261233,anything
|
270
|
+
1080-7mbps00131.ts
|
271
|
+
#EXTINF:11.261233,anything
|
272
|
+
1080-7mbps00132.ts
|
273
|
+
#EXTINF:7.507489,anything
|
274
|
+
1080-7mbps00133.ts
|
275
|
+
#EXTINF:11.261233,anything
|
276
|
+
1080-7mbps00134.ts
|
277
|
+
#EXTINF:11.261233,anything
|
278
|
+
1080-7mbps00135.ts
|
279
|
+
#EXTINF:7.507489,anything
|
280
|
+
1080-7mbps00136.ts
|
281
|
+
#EXTINF:1.793444,anything
|
282
|
+
1080-7mbps00137.ts
|
283
|
+
#EXT-X-ENDLIST
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#EXTM3U
|
2
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle1",AUTOSELECT=YES,DEFAULT=YES
|
3
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle2",AUTOSELECT=YES,DEFAULT=NO,URI="Angle2/200kbs/prog_index.m3u8"
|
4
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="200kbs",NAME="Angle3",AUTOSELECT=YES,DEFAULT=NO,URI="Angle3/200kbs/prog_index.m3u8"
|
5
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle1",AUTOSELECT=YES,DEFAULT=YES
|
6
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle2",AUTOSELECT=YES,DEFAULT=NO,URI="Angle2/500kbs/prog_index.m3u8"
|
7
|
+
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="500kbs",NAME="Angle3",AUTOSELECT=YES,DEFAULT=NO,URI="Angle3/500kbs/prog_index.m3u8"
|
8
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="eng",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8"
|
9
|
+
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Subtiles",AUTOSELECT=NO,DEFAULT=NO,URI="titles_file"
|
10
|
+
#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="captions",NAME="Closed Captions",AUTOSELECT=NO,DEFAULT=NO,URI="captions_file"
|
11
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=300000,CODECS="mp4a.40.2,avc1.4d401e",VIDEO="200kbs",AUDIO="aac",AVERAGE-BANDWIDTH=300001,SUBTITLES="subs",CLOSED-CAPTIONS="captions"
|
12
|
+
Angle1/200kbs/prog_index.m3u8
|
13
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=754857,CODECS="mp4a.40.2,avc1.4d401e",VIDEO="500kbs",AUDIO="aac"
|
14
|
+
Angle1/500kbs/prog_index.m3u8
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#EXTM3U
|
2
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="eng",ASSOC-LANGUAGE="spoken",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="englo/prog_index.m3u8",FORCED=YES
|
3
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES,DEFAULT=NO,URI="frelo/prog_index.m3u8"
|
4
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES,DEFAULT=NO,URI="splo/prog_index.m3u8"
|
5
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="eng",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="eng/prog_index.m3u8"
|
6
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES,DEFAULT=NO,URI="fre/prog_index.m3u8"
|
7
|
+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES,DEFAULT=NO,URI="sp/prog_index.m3u8"
|
8
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="mp4a.40.5",AUDIO="audio-lo"
|
9
|
+
lo/prog_index.m3u8
|
10
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=260000,CODECS="avc1.42e01e,mp4a.40.2",AUDIO="audio-lo"
|
11
|
+
hi/prog_index.m3u8
|
12
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=591680,CODECS="mp4a.40.2, avc1.64001e",AUDIO="audio-hi"
|
13
|
+
lo/prog_index.m3u8
|
14
|
+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=650000,CODECS="avc1.42e01e,mp4a.40.2",AUDIO="audio-hi"
|
15
|
+
hi/prog_index.m3u8
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe M3u8::MediaItem do
|
4
|
+
it 'should provide m3u8 format representation' do
|
5
|
+
hash = { type: 'AUDIO', group: 'audio-lo', language: 'fre',
|
6
|
+
name: 'Francais', autoselect: true, default: false,
|
7
|
+
uri: 'frelo/prog_index.m3u8' }
|
8
|
+
item = M3u8::MediaItem.new(hash)
|
9
|
+
output = item.to_s
|
10
|
+
expected = '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",' \
|
11
|
+
'NAME="Francais",AUTOSELECT=YES,DEFAULT=NO,' \
|
12
|
+
'URI="frelo/prog_index.m3u8"'
|
13
|
+
expect(output).to eq expected
|
14
|
+
|
15
|
+
hash = { type: 'AUDIO', group: 'audio-lo', language: 'fre',
|
16
|
+
assoc_language: 'spoken', name: 'Francais', autoselect: true,
|
17
|
+
default: false, forced: true, uri: 'frelo/prog_index.m3u8' }
|
18
|
+
item = M3u8::MediaItem.new(hash)
|
19
|
+
output = item.to_s
|
20
|
+
expected = '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-lo",LANGUAGE="fre",' \
|
21
|
+
'ASSOC-LANGUAGE="spoken",NAME="Francais",AUTOSELECT=YES,' \
|
22
|
+
'DEFAULT=NO,URI="frelo/prog_index.m3u8",FORCED=YES'
|
23
|
+
expect(output).to eq expected
|
24
|
+
end
|
25
|
+
end
|
data/spec/playlist_item_spec.rb
CHANGED
@@ -29,6 +29,16 @@ describe M3u8::PlaylistItem do
|
|
29
29
|
expected = '#EXT-X-STREAM-INF:PROGRAM-ID=1,' +
|
30
30
|
%(CODECS="avc",BANDWIDTH=540\ntest.url)
|
31
31
|
expect(output).to eq expected
|
32
|
+
|
33
|
+
hash = { codecs: 'avc', bitrate: 540, playlist: 'test.url', audio: 'test',
|
34
|
+
video: 'test2', average_bandwidth: 550, subtitles: 'subs',
|
35
|
+
closed_captions: 'caps' }
|
36
|
+
item = M3u8::PlaylistItem.new(hash)
|
37
|
+
output = item.to_s
|
38
|
+
expected = %(#EXT-X-STREAM-INF:CODECS="avc",BANDWIDTH=540,) +
|
39
|
+
%(AVERAGE-BANDWIDTH=550,AUDIO="test",VIDEO="test2",) +
|
40
|
+
%(SUBTITLES="subs",CLOSED-CAPTIONS="caps"\ntest.url)
|
41
|
+
expect(output).to eq expected
|
32
42
|
end
|
33
43
|
|
34
44
|
it 'should generate codecs string' do
|
@@ -38,36 +48,36 @@ describe M3u8::PlaylistItem do
|
|
38
48
|
item = M3u8::PlaylistItem.new codecs: 'test'
|
39
49
|
expect(item.codecs).to eq 'test'
|
40
50
|
|
41
|
-
item = M3u8::PlaylistItem.new
|
51
|
+
item = M3u8::PlaylistItem.new audio_codec: 'aac-lc'
|
42
52
|
expect(item.codecs).to eq 'mp4a.40.2'
|
43
53
|
|
44
|
-
item = M3u8::PlaylistItem.new
|
54
|
+
item = M3u8::PlaylistItem.new audio_codec: 'AAC-LC'
|
45
55
|
expect(item.codecs).to eq 'mp4a.40.2'
|
46
56
|
|
47
|
-
item = M3u8::PlaylistItem.new
|
57
|
+
item = M3u8::PlaylistItem.new audio_codec: 'he-aac'
|
48
58
|
expect(item.codecs).to eq 'mp4a.40.5'
|
49
59
|
|
50
|
-
item = M3u8::PlaylistItem.new
|
60
|
+
item = M3u8::PlaylistItem.new audio_codec: 'HE-AAC'
|
51
61
|
expect(item.codecs).to eq 'mp4a.40.5'
|
52
62
|
|
53
|
-
item = M3u8::PlaylistItem.new
|
63
|
+
item = M3u8::PlaylistItem.new audio_codec: 'he-acc1'
|
54
64
|
expect(item.codecs).to be_nil
|
55
65
|
|
56
|
-
item = M3u8::PlaylistItem.new
|
66
|
+
item = M3u8::PlaylistItem.new audio_codec: 'mp3'
|
57
67
|
expect(item.codecs).to eq 'mp4a.40.34'
|
58
68
|
|
59
|
-
item = M3u8::PlaylistItem.new
|
69
|
+
item = M3u8::PlaylistItem.new audio_codec: 'MP3'
|
60
70
|
expect(item.codecs).to eq 'mp4a.40.34'
|
61
71
|
|
62
72
|
options = { profile: 'baseline', level: 3.0 }
|
63
73
|
item = M3u8::PlaylistItem.new options
|
64
74
|
expect(item.codecs).to eq 'avc1.66.30'
|
65
75
|
|
66
|
-
options = { profile: 'baseline', level: 3.0,
|
76
|
+
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
|
67
77
|
item = M3u8::PlaylistItem.new options
|
68
78
|
expect(item.codecs).to eq 'avc1.66.30,mp4a.40.2'
|
69
79
|
|
70
|
-
options = { profile: 'baseline', level: 3.0,
|
80
|
+
options = { profile: 'baseline', level: 3.0, audio_codec: 'mp3' }
|
71
81
|
item = M3u8::PlaylistItem.new options
|
72
82
|
expect(item.codecs).to eq 'avc1.66.30,mp4a.40.34'
|
73
83
|
|
@@ -75,7 +85,7 @@ describe M3u8::PlaylistItem do
|
|
75
85
|
item = M3u8::PlaylistItem.new options
|
76
86
|
expect(item.codecs).to eq 'avc1.42001f'
|
77
87
|
|
78
|
-
options = { profile: 'baseline', level: 3.1,
|
88
|
+
options = { profile: 'baseline', level: 3.1, audio_codec: 'he-aac' }
|
79
89
|
item = M3u8::PlaylistItem.new options
|
80
90
|
expect(item.codecs).to eq 'avc1.42001f,mp4a.40.5'
|
81
91
|
|
@@ -83,7 +93,7 @@ describe M3u8::PlaylistItem do
|
|
83
93
|
item = M3u8::PlaylistItem.new options
|
84
94
|
expect(item.codecs).to eq 'avc1.77.30'
|
85
95
|
|
86
|
-
options = { profile: 'main', level: 3.0,
|
96
|
+
options = { profile: 'main', level: 3.0, audio_codec: 'aac-lc' }
|
87
97
|
item = M3u8::PlaylistItem.new options
|
88
98
|
expect(item.codecs).to eq 'avc1.77.30,mp4a.40.2'
|
89
99
|
|
data/spec/playlist_spec.rb
CHANGED
@@ -2,14 +2,14 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe M3u8::Playlist do
|
4
4
|
it 'should generate codecs string' do
|
5
|
-
options = { profile: 'baseline', level: 3.0,
|
5
|
+
options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
|
6
6
|
codecs = M3u8::Playlist.codecs options
|
7
7
|
expect(codecs).to eq 'avc1.66.30,mp4a.40.2'
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'should render master playlist' do
|
11
11
|
options = { playlist: 'playlist_url', bitrate: 6400,
|
12
|
-
|
12
|
+
audio_codec: 'mp3' }
|
13
13
|
item = M3u8::PlaylistItem.new options
|
14
14
|
playlist = M3u8::Playlist.new
|
15
15
|
playlist.items.push item
|
@@ -20,7 +20,7 @@ describe M3u8::Playlist do
|
|
20
20
|
expect(playlist.to_s).to eq output
|
21
21
|
|
22
22
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
23
|
-
|
23
|
+
audio_codec: 'mp3' }
|
24
24
|
item = M3u8::PlaylistItem.new options
|
25
25
|
playlist = M3u8::Playlist.new
|
26
26
|
playlist.items.push item
|
@@ -32,7 +32,7 @@ describe M3u8::Playlist do
|
|
32
32
|
|
33
33
|
options = { program_id: '2', playlist: 'playlist_url', bitrate: 50_000,
|
34
34
|
width: 1920, height: 1080, profile: 'high', level: 4.1,
|
35
|
-
|
35
|
+
audio_codec: 'aac-lc' }
|
36
36
|
item = M3u8::PlaylistItem.new options
|
37
37
|
playlist = M3u8::Playlist.new
|
38
38
|
playlist.items.push item
|
@@ -46,12 +46,12 @@ describe M3u8::Playlist do
|
|
46
46
|
|
47
47
|
playlist = M3u8::Playlist.new
|
48
48
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
49
|
-
|
49
|
+
audio_codec: 'mp3' }
|
50
50
|
item = M3u8::PlaylistItem.new options
|
51
51
|
playlist.items.push item
|
52
52
|
options = { program_id: '2', playlist: 'playlist_url', bitrate: 50_000,
|
53
53
|
width: 1920, height: 1080, profile: 'high', level: 4.1,
|
54
|
-
|
54
|
+
audio_codec: 'aac-lc' }
|
55
55
|
item = M3u8::PlaylistItem.new options
|
56
56
|
playlist.items.push item
|
57
57
|
|
@@ -95,13 +95,15 @@ describe M3u8::Playlist do
|
|
95
95
|
"#EXT-X-ENDLIST\n"
|
96
96
|
expect(playlist.to_s).to eq output
|
97
97
|
|
98
|
-
options = { version: 1, cache: false, target: 12, sequence: 1
|
98
|
+
options = { version: 1, cache: false, target: 12, sequence: 1,
|
99
|
+
type: 'VOD' }
|
99
100
|
playlist = M3u8::Playlist.new options
|
100
101
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
101
102
|
item = M3u8::SegmentItem.new options
|
102
103
|
playlist.items.push item
|
103
104
|
|
104
105
|
output = "#EXTM3U\n" \
|
106
|
+
"#EXT-X-PLAYLIST-TYPE:VOD\n" \
|
105
107
|
"#EXT-X-VERSION:1\n" \
|
106
108
|
"#EXT-X-MEDIA-SEQUENCE:1\n" \
|
107
109
|
"#EXT-X-ALLOW-CACHE:NO\n" \
|
@@ -117,7 +119,7 @@ describe M3u8::Playlist do
|
|
117
119
|
test_io = StringIO.new
|
118
120
|
playlist = M3u8::Playlist.new
|
119
121
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
120
|
-
|
122
|
+
audio_codec: 'mp3' }
|
121
123
|
item = M3u8::PlaylistItem.new options
|
122
124
|
playlist.items.push item
|
123
125
|
playlist.write test_io
|
@@ -142,7 +144,7 @@ describe M3u8::Playlist do
|
|
142
144
|
playlist = M3u8::Playlist.new
|
143
145
|
expect(playlist.master?).to be false
|
144
146
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
145
|
-
|
147
|
+
audio_codec: 'mp3' }
|
146
148
|
item = M3u8::PlaylistItem.new options
|
147
149
|
playlist.items.push item
|
148
150
|
|
@@ -191,12 +193,14 @@ describe M3u8::Playlist do
|
|
191
193
|
end
|
192
194
|
|
193
195
|
it 'should expose options as attributes' do
|
194
|
-
options = { version: 1, cache: false, target: 12, sequence: 1
|
196
|
+
options = { version: 1, cache: false, target: 12, sequence: 1,
|
197
|
+
type: 'VOD' }
|
195
198
|
playlist = M3u8::Playlist.new options
|
196
199
|
expect(playlist.version).to be 1
|
197
200
|
expect(playlist.cache).to be false
|
198
201
|
expect(playlist.target).to be 12
|
199
202
|
expect(playlist.sequence).to be 1
|
203
|
+
expect(playlist.type).to eq('VOD')
|
200
204
|
end
|
201
205
|
|
202
206
|
it 'should allow reading of playlists' do
|
data/spec/reader_spec.rb
CHANGED
@@ -20,7 +20,7 @@ describe M3u8::Reader do
|
|
20
20
|
expect(playlist.items.size).to eq 6
|
21
21
|
|
22
22
|
item = playlist.items.last
|
23
|
-
expect(item.resolution).to
|
23
|
+
expect(item.resolution).to be_nil
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'should parse segment playlist' do
|
@@ -28,15 +28,96 @@ describe M3u8::Reader do
|
|
28
28
|
reader = M3u8::Reader.new
|
29
29
|
playlist = reader.read file
|
30
30
|
expect(playlist.master?).to be false
|
31
|
+
expect(playlist.version).to eq 4
|
32
|
+
expect(playlist.sequence).to eq 1
|
33
|
+
expect(playlist.cache).to be false
|
34
|
+
expect(playlist.target).to eq 12
|
35
|
+
expect(playlist.type).to eq 'VOD'
|
36
|
+
|
37
|
+
item = playlist.items[0]
|
38
|
+
expect(item).to be_a(M3u8::SegmentItem)
|
39
|
+
expect(item.duration).to eq 11.344644
|
40
|
+
expect(item.comment).to be_nil
|
41
|
+
|
42
|
+
expect(playlist.items.size).to eq 138
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should parse segment playlist with comments' do
|
46
|
+
file = File.open 'spec/fixtures/playlist_with_comments.m3u8'
|
47
|
+
reader = M3u8::Reader.new
|
48
|
+
playlist = reader.read file
|
49
|
+
expect(playlist.master?).to be false
|
31
50
|
expect(playlist.version).to be 4
|
32
51
|
expect(playlist.sequence).to be 1
|
33
52
|
expect(playlist.cache).to be false
|
34
53
|
expect(playlist.target).to be 12
|
54
|
+
expect(playlist.type).to eq 'VOD'
|
35
55
|
|
36
56
|
item = playlist.items[0]
|
37
57
|
expect(item).to be_a(M3u8::SegmentItem)
|
38
58
|
expect(item.duration).to eq 11.344644
|
59
|
+
expect(item.comment).to eq 'anything'
|
39
60
|
|
40
61
|
expect(playlist.items.size).to eq 138
|
41
62
|
end
|
63
|
+
|
64
|
+
it 'should parse variant playlist with audio options and groups' do
|
65
|
+
file = File.open 'spec/fixtures/variant_audio.m3u8'
|
66
|
+
reader = M3u8::Reader.new
|
67
|
+
playlist = reader.read file
|
68
|
+
|
69
|
+
expect(playlist.master?).to be true
|
70
|
+
expect(playlist.items.size).to eq 10
|
71
|
+
|
72
|
+
item = playlist.items[0]
|
73
|
+
expect(item).to be_a M3u8::MediaItem
|
74
|
+
expect(item.type).to eq 'AUDIO'
|
75
|
+
expect(item.group).to eq 'audio-lo'
|
76
|
+
expect(item.language).to eq 'eng'
|
77
|
+
expect(item.assoc_language).to eq 'spoken'
|
78
|
+
expect(item.name).to eq 'English'
|
79
|
+
expect(item.autoselect).to be true
|
80
|
+
expect(item.default).to be true
|
81
|
+
expect(item.uri).to eq 'englo/prog_index.m3u8'
|
82
|
+
expect(item.forced).to be true
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should parse variant playlist with camera angles' do
|
86
|
+
file = File.open 'spec/fixtures/variant_angles.m3u8'
|
87
|
+
reader = M3u8::Reader.new
|
88
|
+
playlist = reader.read file
|
89
|
+
|
90
|
+
expect(playlist.master?).to be true
|
91
|
+
expect(playlist.items.size).to eq 11
|
92
|
+
|
93
|
+
item = playlist.items[1]
|
94
|
+
expect(item).to be_a M3u8::MediaItem
|
95
|
+
expect(item.type).to eq 'VIDEO'
|
96
|
+
expect(item.group).to eq '200kbs'
|
97
|
+
expect(item.language).to be_nil
|
98
|
+
expect(item.name).to eq 'Angle2'
|
99
|
+
expect(item.autoselect).to be true
|
100
|
+
expect(item.default).to be false
|
101
|
+
expect(item.uri).to eq 'Angle2/200kbs/prog_index.m3u8'
|
102
|
+
|
103
|
+
item = playlist.items[9]
|
104
|
+
expect(item.average_bandwidth).to eq 300_001
|
105
|
+
expect(item.audio).to eq 'aac'
|
106
|
+
expect(item.video).to eq '200kbs'
|
107
|
+
expect(item.closed_captions).to eq 'captions'
|
108
|
+
expect(item.subtitles).to eq 'subs'
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should process multiple reads as separate playlists' do
|
112
|
+
file = File.open 'spec/fixtures/master.m3u8'
|
113
|
+
reader = M3u8::Reader.new
|
114
|
+
playlist = reader.read file
|
115
|
+
|
116
|
+
expect(playlist.items.size).to eq 6
|
117
|
+
|
118
|
+
file = File.open 'spec/fixtures/master.m3u8'
|
119
|
+
playlist = reader.read file
|
120
|
+
|
121
|
+
expect(playlist.items.size).to eq 6
|
122
|
+
end
|
42
123
|
end
|
data/spec/segment_item_spec.rb
CHANGED
@@ -6,6 +6,13 @@ describe M3u8::SegmentItem do
|
|
6
6
|
item = M3u8::SegmentItem.new(hash)
|
7
7
|
expect(item.duration).to eq 10.991
|
8
8
|
expect(item.segment).to eq 'test.ts'
|
9
|
+
expect(item.comment).to be_nil
|
10
|
+
|
11
|
+
hash = { duration: 10.991, segment: 'test.ts', comment: 'anything' }
|
12
|
+
item = M3u8::SegmentItem.new(hash)
|
13
|
+
expect(item.duration).to eq 10.991
|
14
|
+
expect(item.segment).to eq 'test.ts'
|
15
|
+
expect(item.comment).to eq 'anything'
|
9
16
|
end
|
10
17
|
|
11
18
|
it 'should provide m3u8 format representation' do
|
@@ -14,5 +21,11 @@ describe M3u8::SegmentItem do
|
|
14
21
|
output = item.to_s
|
15
22
|
expected = "#EXTINF:10.991,\ntest.ts"
|
16
23
|
expect(output).to eq expected
|
24
|
+
|
25
|
+
hash = { duration: 10.991, segment: 'test.ts', comment: 'anything' }
|
26
|
+
item = M3u8::SegmentItem.new(hash)
|
27
|
+
output = item.to_s
|
28
|
+
expected = "#EXTINF:10.991,anything\ntest.ts"
|
29
|
+
expect(output).to eq expected
|
17
30
|
end
|
18
31
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/writer_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe M3u8::Writer do
|
4
4
|
it 'should render master playlist' do
|
5
5
|
options = { playlist: 'playlist_url', bitrate: 6400,
|
6
|
-
|
6
|
+
audio_codec: 'mp3' }
|
7
7
|
item = M3u8::PlaylistItem.new options
|
8
8
|
playlist = M3u8::Playlist.new
|
9
9
|
playlist.items.push item
|
@@ -18,7 +18,7 @@ describe M3u8::Writer do
|
|
18
18
|
expect(io.string).to eq output
|
19
19
|
|
20
20
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
21
|
-
|
21
|
+
audio_codec: 'mp3' }
|
22
22
|
item = M3u8::PlaylistItem.new options
|
23
23
|
playlist = M3u8::Playlist.new
|
24
24
|
playlist.items.push item
|
@@ -34,7 +34,7 @@ describe M3u8::Writer do
|
|
34
34
|
|
35
35
|
options = { program_id: '2', playlist: 'playlist_url', bitrate: 50_000,
|
36
36
|
width: 1920, height: 1080, profile: 'high', level: 4.1,
|
37
|
-
|
37
|
+
audio_codec: 'aac-lc' }
|
38
38
|
item = M3u8::PlaylistItem.new options
|
39
39
|
playlist = M3u8::Playlist.new
|
40
40
|
playlist.items.push item
|
@@ -51,12 +51,12 @@ describe M3u8::Writer do
|
|
51
51
|
|
52
52
|
playlist = M3u8::Playlist.new
|
53
53
|
options = { program_id: '1', playlist: 'playlist_url', bitrate: 6400,
|
54
|
-
|
54
|
+
audio_codec: 'mp3' }
|
55
55
|
item = M3u8::PlaylistItem.new options
|
56
56
|
playlist.items.push item
|
57
57
|
options = { program_id: '2', playlist: 'playlist_url', bitrate: 50_000,
|
58
58
|
width: 1920, height: 1080, profile: 'high', level: 4.1,
|
59
|
-
|
59
|
+
audio_codec: 'aac-lc' }
|
60
60
|
item = M3u8::PlaylistItem.new options
|
61
61
|
playlist.items.push item
|
62
62
|
|
@@ -110,13 +110,15 @@ describe M3u8::Writer do
|
|
110
110
|
writer.write playlist
|
111
111
|
expect(io.string).to eq output
|
112
112
|
|
113
|
-
options = { version: 1, cache: false, target: 12, sequence: 1
|
113
|
+
options = { version: 1, cache: false, target: 12, sequence: 1,
|
114
|
+
type: 'EVENT' }
|
114
115
|
playlist = M3u8::Playlist.new options
|
115
116
|
options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
|
116
117
|
item = M3u8::SegmentItem.new options
|
117
118
|
playlist.items.push item
|
118
119
|
|
119
120
|
output = "#EXTM3U\n" \
|
121
|
+
"#EXT-X-PLAYLIST-TYPE:EVENT\n" \
|
120
122
|
"#EXT-X-VERSION:1\n" \
|
121
123
|
"#EXT-X-MEDIA-SEQUENCE:1\n" \
|
122
124
|
"#EXT-X-ALLOW-CACHE:NO\n" \
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seth Deckard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- Rakefile
|
72
72
|
- lib/m3u8.rb
|
73
73
|
- lib/m3u8/error.rb
|
74
|
+
- lib/m3u8/media_item.rb
|
74
75
|
- lib/m3u8/playlist.rb
|
75
76
|
- lib/m3u8/playlist_item.rb
|
76
77
|
- lib/m3u8/reader.rb
|
@@ -80,6 +81,10 @@ files:
|
|
80
81
|
- m3u8.gemspec
|
81
82
|
- spec/fixtures/master.m3u8
|
82
83
|
- spec/fixtures/playlist.m3u8
|
84
|
+
- spec/fixtures/playlist_with_comments.m3u8
|
85
|
+
- spec/fixtures/variant_angles.m3u8
|
86
|
+
- spec/fixtures/variant_audio.m3u8
|
87
|
+
- spec/media_item_spec.rb
|
83
88
|
- spec/playlist_item_spec.rb
|
84
89
|
- spec/playlist_spec.rb
|
85
90
|
- spec/reader_spec.rb
|
@@ -113,6 +118,10 @@ summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
|
|
113
118
|
test_files:
|
114
119
|
- spec/fixtures/master.m3u8
|
115
120
|
- spec/fixtures/playlist.m3u8
|
121
|
+
- spec/fixtures/playlist_with_comments.m3u8
|
122
|
+
- spec/fixtures/variant_angles.m3u8
|
123
|
+
- spec/fixtures/variant_audio.m3u8
|
124
|
+
- spec/media_item_spec.rb
|
116
125
|
- spec/playlist_item_spec.rb
|
117
126
|
- spec/playlist_spec.rb
|
118
127
|
- spec/reader_spec.rb
|