m3u8 0.6.9 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a4f00671fa34a2515f395b91535721e0f9c8801d
4
- data.tar.gz: 6444a8131f48f8534837def5f39cef7e0dc132ca
3
+ metadata.gz: cb897f32a24dabcd87ebd26a367425c8eae33113
4
+ data.tar.gz: 5726036c12d84a49d24ae14f21cf52e817c3bc1c
5
5
  SHA512:
6
- metadata.gz: 70cc595f2f165d3d2f337437f88b9d0021fb74cf764e4393949e83fe2470ea55f40b95f64ef3772a0f572875f6773acdf0e1ad122a9011a2587daf3c93584d87
7
- data.tar.gz: 6bb1ca0567ff9ce9b69accedda38d154050bb9c9e72fcd910694fd4a6c1399c3f69da2c686ddc14e3015f86a61fa1cd06ded8cc5f4373a4c8d82350712081cf3
6
+ metadata.gz: db0747bc9be34249f3fbf35d40255763e8b96cddcf9d857495361e8186df81fc4285e6740bc54abab804d2617504c77b8ac7befbb2cc2604e7de0a52d2963d65
7
+ data.tar.gz: ee311cc358cca4494c6d5c1a4fa8e64e50977076d8001d3a1258ca1a4762354f6886b903293f06a424136044b6112e1f4c501dfe449508238d4e6d5e1e7be0dd
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+ .ruby-version
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
2
  sudo: true
3
3
  rvm:
4
- - 2.3.0
5
- - 2.2.4
4
+ - 2.4.0
5
+ - 2.3.3
6
+ - 2.2.6
6
7
 
7
8
  script: rspec spec
@@ -1,3 +1,5 @@
1
+ 0.7.0 (2/3/2017) - Added support for EXT-X-INDEPENDENT-SEGMENTS and EXT-X-START tags. Minor version bumped due to changes of default values for new playlists.
2
+
1
3
  0.6.9 (4/24/2016) - Merged pull request #17 from [ghn](https://github.com/ghn). Removes return when parsing next line in playlist.
2
4
 
3
5
  ***
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/m3u8.svg)](http://badge.fury.io/rb/m3u8)
2
2
  [![Build Status](https://travis-ci.org/sethdeckard/m3u8.svg?branch=master)](https://travis-ci.org/sethdeckard/m3u8)
3
- [![Coverage Status](https://coveralls.io/repos/sethdeckard/m3u8/badge.png)](https://coveralls.io/r/sethdeckard/m3u8)
3
+ [![Coverage Status](https://coveralls.io/repos/github/sethdeckard/m3u8/badge.svg?branch=master)](https://coveralls.io/github/sethdeckard/m3u8?branch=master)
4
4
  [![Code Climate](https://codeclimate.com/github/sethdeckard/m3u8/badges/gpa.svg)](https://codeclimate.com/github/sethdeckard/m3u8)
5
5
  [![Dependency Status](https://gemnasium.com/sethdeckard/m3u8.svg)](https://gemnasium.com/sethdeckard/m3u8)
6
6
  [![security](https://hakiri.io/github/sethdeckard/m3u8/master.svg)](https://hakiri.io/github/sethdeckard/m3u8/master)
@@ -145,7 +145,7 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
145
145
  * Supports session data in master playlists.
146
146
  * Supports keys for encrypted media segments (EXT-X-KEY, EXT-SESSION-KEY).
147
147
 
148
- ## HLS Spec Status (version 17)
148
+ ## HLS Spec Status (version 20)
149
149
  ### Implemented:
150
150
  * EXTM3U
151
151
  * EXT-X-VERSION
@@ -165,11 +165,10 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
165
165
  * EXT-X-I-FRAME-STREAM-INF
166
166
  * EXT-X-SESSION-DATA
167
167
  * EXT-X-SESSION-KEY
168
+ * EXT-X-START
168
169
 
169
170
  ### TODO:
170
- * EXT-X-DISCONTINUITY-SEQUENCE
171
- * EXT-X-INDEPENDENT-SEGMENTS
172
- * EXT-X-START
171
+ * EXT-X-DATERANGE
173
172
 
174
173
  ## Roadmap
175
174
  * Add the last remaining tags for latest version of the spec.
@@ -187,4 +186,4 @@ M3u8::Reader is the class handles parsing if you want more control over the proc
187
186
 
188
187
 
189
188
  ## License
190
- MIT License - See [LICENSE.txt](https://github.com/sethdeckard/m3u8/blob/master/LICENSE.txt) for details.
189
+ MIT License - See [LICENSE.txt](https://github.com/sethdeckard/m3u8/blob/master/LICENSE.txt) for details.
@@ -4,6 +4,13 @@ Dir[File.dirname(__FILE__) + '/m3u8/*.rb'].sort.each { |file| require file }
4
4
 
5
5
  # M3u8 provides parsing, generation, and validation of m3u8 playlists
6
6
  module M3u8
7
+ def intialize_with_byterange(params = {})
8
+ params.each do |key, value|
9
+ value = ByteRange.new(value) if value.is_a?(Hash)
10
+ instance_variable_set("@#{key}", value)
11
+ end
12
+ end
13
+
7
14
  def parse_attributes(line)
8
15
  array = line.delete("\n").scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
9
16
  Hash[array.map { |key, value| [key, value.delete('"')] }]
@@ -13,10 +20,7 @@ module M3u8
13
20
  value == 'YES' ? true : false
14
21
  end
15
22
 
16
- def intialize_with_byterange(params = {})
17
- params.each do |key, value|
18
- value = ByteRange.new(value) if value.is_a?(Hash)
19
- instance_variable_set("@#{key}", value)
20
- end
23
+ def to_yes_no(boolean)
24
+ boolean == true ? 'YES' : 'NO'
21
25
  end
22
26
  end
@@ -0,0 +1,33 @@
1
+ module M3u8
2
+ # PlaybackStart represents a #EXT-X-START tag and attributes
3
+ class PlaybackStart
4
+ include M3u8
5
+ attr_accessor :time_offset, :precise
6
+
7
+ def initialize(options = {})
8
+ options.each do |key, value|
9
+ instance_variable_set("@#{key}", value)
10
+ end
11
+ end
12
+
13
+ def parse(text)
14
+ attributes = parse_attributes(text)
15
+ @time_offset = attributes['TIME-OFFSET'].to_f
16
+ precise = attributes['PRECISE']
17
+ @precise = parse_yes_no(precise) unless precise.nil?
18
+ end
19
+
20
+ def to_s
21
+ attributes = ["TIME-OFFSET=#{time_offset}",
22
+ precise_format].compact.join(',')
23
+ "#EXT-X-START:#{attributes}"
24
+ end
25
+
26
+ private
27
+
28
+ def precise_format
29
+ return if precise.nil?
30
+ "PRECISE=#{to_yes_no(precise)}"
31
+ end
32
+ end
33
+ end
@@ -3,7 +3,7 @@ module M3u8
3
3
  # of media segments
4
4
  class Playlist
5
5
  attr_accessor :items, :version, :cache, :target, :sequence, :type,
6
- :iframes_only
6
+ :iframes_only, :independent_segments
7
7
 
8
8
  def initialize(options = {})
9
9
  assign_options options
@@ -21,8 +21,8 @@ module M3u8
21
21
  end
22
22
 
23
23
  def write(output)
24
- writer = Writer.new output
25
- writer.write self
24
+ writer = Writer.new(output)
25
+ writer.write(self)
26
26
  end
27
27
 
28
28
  def master?
@@ -32,7 +32,7 @@ module M3u8
32
32
 
33
33
  def to_s
34
34
  output = StringIO.open
35
- write output
35
+ write(output)
36
36
  output.string
37
37
  end
38
38
 
@@ -52,13 +52,7 @@ module M3u8
52
52
  private
53
53
 
54
54
  def assign_options(options)
55
- options = {
56
- version: 3,
57
- sequence: 0,
58
- cache: true,
59
- target: 10,
60
- iframes_only: false
61
- }.merge options
55
+ options = defaults.merge(options)
62
56
 
63
57
  self.version = options[:version]
64
58
  self.sequence = options[:sequence]
@@ -66,6 +60,16 @@ module M3u8
66
60
  self.target = options[:target]
67
61
  self.type = options[:type]
68
62
  self.iframes_only = options[:iframes_only]
63
+ self.independent_segments = options[:independent_segments]
64
+ end
65
+
66
+ def defaults
67
+ {
68
+ sequence: 0,
69
+ target: 10,
70
+ iframes_only: false,
71
+ independent_segments: false
72
+ }
69
73
  end
70
74
 
71
75
  def playlist_size
@@ -8,13 +8,14 @@ module M3u8
8
8
  @tags = [basic_tags,
9
9
  media_segment_tags,
10
10
  media_playlist_tags,
11
- master_playlist_tags].inject(:merge)
11
+ master_playlist_tags,
12
+ universal_tags].inject(:merge)
12
13
  end
13
14
 
14
15
  def read(input)
15
16
  self.playlist = Playlist.new
16
17
  input.each_line do |line|
17
- parse_line line
18
+ parse_line(line)
18
19
  end
19
20
  playlist
20
21
  end
@@ -53,6 +54,18 @@ module M3u8
53
54
  }
54
55
  end
55
56
 
57
+ def universal_tags
58
+ { '#EXT-X-START' => ->(line) { parse_start(line) },
59
+ '#EXT-X-INDEPENDENT-SEGMENTS' => proc do
60
+ playlist.independent_segments = true
61
+ end
62
+ }
63
+ end
64
+
65
+ def parse_independent_segments(line)
66
+ parse_independent_segments
67
+ end
68
+
56
69
  def parse_line(line)
57
70
  return if match_tag(line)
58
71
  parse_next_line(line) if !item.nil? && open
@@ -151,6 +164,12 @@ module M3u8
151
164
  playlist.items << item
152
165
  end
153
166
 
167
+ def parse_start(line)
168
+ item = M3u8::PlaybackStart.new
169
+ item.parse(line)
170
+ playlist.items << item
171
+ end
172
+
154
173
  def parse_time(line)
155
174
  if open
156
175
  item.program_date_time = M3u8::TimeItem.parse(line)
@@ -1,4 +1,4 @@
1
1
  # M3u8 provides parsing, generation, and validation of m3u8 playlists
2
2
  module M3u8
3
- VERSION = '0.6.9'
3
+ VERSION = '0.7.0'
4
4
  end
@@ -8,10 +8,8 @@ module M3u8
8
8
  end
9
9
 
10
10
  def write(playlist)
11
- validate playlist
12
-
13
- io.puts '#EXTM3U'
14
- write_header(playlist) unless playlist.master?
11
+ validate(playlist)
12
+ write_header(playlist)
15
13
 
16
14
  playlist.items.each do |item|
17
15
  io.puts item.to_s
@@ -23,26 +21,55 @@ module M3u8
23
21
 
24
22
  private
25
23
 
24
+ def target_duration_format(playlist)
25
+ format('#EXT-X-TARGETDURATION:%d', playlist.target)
26
+ end
27
+
26
28
  def validate(playlist)
27
29
  return if playlist.valid?
28
- fail PlaylistTypeError, 'Playlist is invalid.'
30
+ raise PlaylistTypeError, 'Playlist is invalid.'
31
+ end
32
+
33
+ def write_cache_tag(cache)
34
+ return if cache.nil?
35
+
36
+ io.puts "#EXT-X-ALLOW-CACHE:#{cache ? 'YES' : 'NO'}"
29
37
  end
30
38
 
31
39
  def write_header(playlist)
40
+ io.puts '#EXTM3U'
41
+ if playlist.master?
42
+ write_master_playlist_header(playlist)
43
+ else
44
+ write_media_playlist_header(playlist)
45
+ end
46
+ end
47
+
48
+ def write_independent_segments_tag(independent_segments)
49
+ return unless independent_segments
50
+
51
+ io.puts '#EXT-X-INDEPENDENT-SEGMENTS'
52
+ end
53
+
54
+ def write_master_playlist_header(playlist)
55
+ write_version_tag(playlist.version)
56
+ write_independent_segments_tag(playlist.independent_segments)
57
+ end
58
+
59
+ def write_media_playlist_header(playlist)
32
60
  io.puts "#EXT-X-PLAYLIST-TYPE:#{playlist.type}" unless playlist.type.nil?
33
- io.puts "#EXT-X-VERSION:#{playlist.version}"
61
+ write_version_tag(playlist.version)
62
+ write_independent_segments_tag(playlist.independent_segments)
34
63
  io.puts '#EXT-X-I-FRAMES-ONLY' if playlist.iframes_only
35
64
  io.puts "#EXT-X-MEDIA-SEQUENCE:#{playlist.sequence}"
36
- io.puts "#EXT-X-ALLOW-CACHE:#{cache(playlist)}"
65
+ write_cache_tag(playlist.cache)
37
66
  io.puts target_duration_format(playlist)
38
67
  end
39
68
 
40
- def cache(playlist)
41
- playlist.cache ? 'YES' : 'NO'
42
- end
69
+ def write_version_tag(version)
70
+ return if version.nil?
43
71
 
44
- def target_duration_format(playlist)
45
- format('#EXT-X-TARGETDURATION:%d', playlist.target)
72
+ io.puts "#EXT-X-VERSION:#{version}"
46
73
  end
47
74
  end
48
75
  end
@@ -1,5 +1,7 @@
1
1
  #EXTM3U
2
+ #EXT-X-INDEPENDENT-SEGMENTS
2
3
  #EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
4
+ #EXT-X-START:TIME-OFFSET=20.2
3
5
  #EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=5042000
4
6
  hls/1080-7mbps/1080-7mbps.m3u8
5
7
  #EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=4853000
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe M3u8::PlaybackStart do
4
+ describe '#intialize' do
5
+ it 'assigns attributes' do
6
+ start = described_class.new(time_offset: -12.9, precise: true)
7
+ expect(start.time_offset).to eq(-12.9)
8
+ expect(start.precise).to be true
9
+ end
10
+ end
11
+
12
+ describe '#parse' do
13
+ it 'parses tag with all attributes' do
14
+ start = described_class.new
15
+ tag = '#EXT-X-START:TIME-OFFSET=20.0,PRECISE=YES'
16
+ start.parse(tag)
17
+
18
+ expect(start.time_offset).to eq(20.0)
19
+ expect(start.precise).to be true
20
+ end
21
+
22
+ it 'parses tag without optional attributes' do
23
+ start = described_class.new
24
+ tag = '#EXT-X-START:TIME-OFFSET=-12.9'
25
+ start.parse(tag)
26
+
27
+ expect(start.time_offset).to eq(-12.9)
28
+ expect(start.precise).to be_nil
29
+ end
30
+ end
31
+
32
+ describe '#to_s' do
33
+ it 'returns tag with attributes' do
34
+ start = described_class.new(time_offset: 9.2, precise: true)
35
+ tag = start.to_s
36
+
37
+ expect(tag).to eq('#EXT-X-START:TIME-OFFSET=9.2,PRECISE=YES')
38
+ end
39
+
40
+ it 'returns tag without optional attributes' do
41
+ start = described_class.new(time_offset: 9.2)
42
+ tag = start.to_s
43
+
44
+ expect(tag).to eq('#EXT-X-START:TIME-OFFSET=9.2')
45
+ end
46
+ end
47
+ end
@@ -1,6 +1,33 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe M3u8::Playlist do
4
+ let(:playlist) { described_class.new }
5
+
6
+ describe '#new' do
7
+ it 'initializes with defaults' do
8
+ expect(playlist.version).to be_nil
9
+ expect(playlist.cache).to be_nil
10
+ expect(playlist.target).to be 10
11
+ expect(playlist.sequence).to be 0
12
+ expect(playlist.type).to be_nil
13
+ expect(playlist.iframes_only).to be false
14
+ expect(playlist.independent_segments).to be false
15
+ end
16
+
17
+ it 'initializes from hash' do
18
+ options = { version: 7, cache: false, target: 12, sequence: 1,
19
+ type: 'VOD', independent_segments: true }
20
+ playlist = M3u8::Playlist.new(options)
21
+ expect(playlist.version).to be 7
22
+ expect(playlist.cache).to be false
23
+ expect(playlist.target).to be 12
24
+ expect(playlist.sequence).to be 1
25
+ expect(playlist.type).to eq('VOD')
26
+ expect(playlist.iframes_only).to be false
27
+ expect(playlist.independent_segments).to be true
28
+ end
29
+ end
30
+
4
31
  it 'should generate codecs string' do
5
32
  options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
6
33
  codecs = M3u8::Playlist.codecs options
@@ -11,10 +38,11 @@ describe M3u8::Playlist do
11
38
  options = { uri: 'playlist_url', bandwidth: 6400,
12
39
  audio_codec: 'mp3' }
13
40
  item = M3u8::PlaylistItem.new options
14
- playlist = M3u8::Playlist.new
15
- playlist.items.push item
41
+ playlist = M3u8::Playlist.new(independent_segments: true)
42
+ playlist.items << item
16
43
 
17
- output = "#EXTM3U\n" +
44
+ output = "#EXTM3U\n" \
45
+ "#EXT-X-INDEPENDENT-SEGMENTS\n" +
18
46
  %(#EXT-X-STREAM-INF:CODECS="mp4a.40.34") +
19
47
  ",BANDWIDTH=6400\nplaylist_url\n"
20
48
  expect(playlist.to_s).to eq output
@@ -23,7 +51,7 @@ describe M3u8::Playlist do
23
51
  audio_codec: 'mp3' }
24
52
  item = M3u8::PlaylistItem.new options
25
53
  playlist = M3u8::Playlist.new
26
- playlist.items.push item
54
+ playlist.items << item
27
55
 
28
56
  output = "#EXTM3U\n" +
29
57
  %(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
@@ -35,7 +63,7 @@ describe M3u8::Playlist do
35
63
  audio_codec: 'aac-lc' }
36
64
  item = M3u8::PlaylistItem.new options
37
65
  playlist = M3u8::Playlist.new
38
- playlist.items.push item
66
+ playlist.items << item
39
67
 
40
68
  output = "#EXTM3U\n" \
41
69
  '#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
@@ -48,12 +76,12 @@ describe M3u8::Playlist do
48
76
  options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
49
77
  audio_codec: 'mp3' }
50
78
  item = M3u8::PlaylistItem.new options
51
- playlist.items.push item
79
+ playlist.items << item
52
80
  options = { program_id: '2', uri: 'playlist_url', bandwidth: 50_000,
53
81
  width: 1920, height: 1080, profile: 'high', level: 4.1,
54
82
  audio_codec: 'aac-lc' }
55
83
  item = M3u8::PlaylistItem.new options
56
- playlist.items.push item
84
+ playlist.items << item
57
85
 
58
86
  output = "#EXTM3U\n" +
59
87
  %(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
@@ -65,14 +93,12 @@ describe M3u8::Playlist do
65
93
 
66
94
  it 'should render playlist' do
67
95
  options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
68
- item = M3u8::SegmentItem.new options
96
+ item = M3u8::SegmentItem.new(options)
69
97
  playlist = M3u8::Playlist.new
70
- playlist.items.push item
98
+ playlist.items << item
71
99
 
72
100
  output = "#EXTM3U\n" \
73
- "#EXT-X-VERSION:3\n" \
74
101
  "#EXT-X-MEDIA-SEQUENCE:0\n" \
75
- "#EXT-X-ALLOW-CACHE:YES\n" \
76
102
  "#EXT-X-TARGETDURATION:10\n" \
77
103
  "#EXTINF:11.344644,\n" \
78
104
  "1080-7mbps00000.ts\n" \
@@ -80,13 +106,11 @@ describe M3u8::Playlist do
80
106
  expect(playlist.to_s).to eq output
81
107
 
82
108
  options = { duration: 11.261233, segment: '1080-7mbps00001.ts' }
83
- item = M3u8::SegmentItem.new options
84
- playlist.items.push item
109
+ item = M3u8::SegmentItem.new(options)
110
+ playlist.items << item
85
111
 
86
112
  output = "#EXTM3U\n" \
87
- "#EXT-X-VERSION:3\n" \
88
113
  "#EXT-X-MEDIA-SEQUENCE:0\n" \
89
- "#EXT-X-ALLOW-CACHE:YES\n" \
90
114
  "#EXT-X-TARGETDURATION:10\n" \
91
115
  "#EXTINF:11.344644,\n" \
92
116
  "1080-7mbps00000.ts\n" \
@@ -95,16 +119,16 @@ describe M3u8::Playlist do
95
119
  "#EXT-X-ENDLIST\n"
96
120
  expect(playlist.to_s).to eq output
97
121
 
98
- options = { version: 1, cache: false, target: 12, sequence: 1,
122
+ options = { version: 7, cache: false, target: 12, sequence: 1,
99
123
  type: 'VOD' }
100
124
  playlist = M3u8::Playlist.new options
101
125
  options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
102
126
  item = M3u8::SegmentItem.new options
103
- playlist.items.push item
127
+ playlist.items << item
104
128
 
105
129
  output = "#EXTM3U\n" \
106
130
  "#EXT-X-PLAYLIST-TYPE:VOD\n" \
107
- "#EXT-X-VERSION:1\n" \
131
+ "#EXT-X-VERSION:7\n" \
108
132
  "#EXT-X-MEDIA-SEQUENCE:1\n" \
109
133
  "#EXT-X-ALLOW-CACHE:NO\n" \
110
134
  "#EXT-X-TARGETDURATION:12\n" \
@@ -121,7 +145,7 @@ describe M3u8::Playlist do
121
145
  options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
122
146
  audio_codec: 'mp3' }
123
147
  item = M3u8::PlaylistItem.new options
124
- playlist.items.push item
148
+ playlist.items << item
125
149
  playlist.write test_io
126
150
 
127
151
  output = "#EXTM3U\n" +
@@ -146,7 +170,7 @@ describe M3u8::Playlist do
146
170
  options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
147
171
  audio_codec: 'mp3' }
148
172
  item = M3u8::PlaylistItem.new options
149
- playlist.items.push item
173
+ playlist.items << item
150
174
 
151
175
  expect(playlist.master?).to be true
152
176
  end
@@ -157,11 +181,11 @@ describe M3u8::Playlist do
157
181
  hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
158
182
  bandwidth: 540, uri: 'test.url' }
159
183
  item = M3u8::PlaylistItem.new(hash)
160
- playlist.items.push item
184
+ playlist.items << item
161
185
 
162
186
  hash = { duration: 10.991, segment: 'test.ts' }
163
187
  item = M3u8::SegmentItem.new(hash)
164
- playlist.items.push item
188
+ playlist.items << item
165
189
 
166
190
  message = 'Playlist is invalid.'
167
191
  io = StringIO.new
@@ -176,51 +200,39 @@ describe M3u8::Playlist do
176
200
  hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
177
201
  bandwidth: 540, uri: 'test.url' }
178
202
  item = M3u8::PlaylistItem.new(hash)
179
- playlist.items.push item
203
+ playlist.items << item
180
204
  expect(playlist.valid?).to be true
181
205
 
182
206
  hash = { program_id: 1, width: 1920, height: 1080, codecs: 'avc',
183
207
  bandwidth: 540, uri: 'test.url' }
184
208
  item = M3u8::PlaylistItem.new(hash)
185
- playlist.items.push item
209
+ playlist.items << item
186
210
  expect(playlist.valid?).to be true
187
211
 
188
212
  hash = { duration: 10.991, segment: 'test.ts' }
189
213
  item = M3u8::SegmentItem.new(hash)
190
- playlist.items.push item
214
+ playlist.items << item
191
215
 
192
216
  expect(playlist.valid?).to be false
193
217
  end
194
218
 
195
- it 'should expose options as attributes' do
196
- options = { version: 1, cache: false, target: 12, sequence: 1,
197
- type: 'VOD' }
198
- playlist = M3u8::Playlist.new options
199
- expect(playlist.version).to be 1
200
- expect(playlist.cache).to be false
201
- expect(playlist.target).to be 12
202
- expect(playlist.sequence).to be 1
203
- expect(playlist.type).to eq('VOD')
204
- expect(playlist.iframes_only).to be false
205
- end
206
-
207
219
  it 'should allow reading of playlists' do
208
220
  file = File.open 'spec/fixtures/master.m3u8'
209
221
  playlist = M3u8::Playlist.read file
210
222
  expect(playlist.master?).to be true
211
- expect(playlist.items.size).to eq(7)
223
+ expect(playlist.items.size).to eq(8)
212
224
  end
213
225
 
214
226
  it 'should return the total duration of a playlist' do
215
227
  playlist = M3u8::Playlist.new
216
228
  item = M3u8::SegmentItem.new(duration: 10.991, segment: 'test_01.ts')
217
- playlist.items.push item
229
+ playlist.items << item
218
230
  item = M3u8::SegmentItem.new(duration: 9.891, segment: 'test_02.ts')
219
- playlist.items.push item
231
+ playlist.items << item
220
232
  item = M3u8::SegmentItem.new(duration: 10.556, segment: 'test_03.ts')
221
- playlist.items.push item
233
+ playlist.items << item
222
234
  item = M3u8::SegmentItem.new(duration: 8.790, segment: 'test_04.ts')
223
- playlist.items.push item
235
+ playlist.items << item
224
236
 
225
237
  expect(playlist.duration.round(3)).to eq(40.228)
226
238
  end
@@ -7,12 +7,18 @@ describe M3u8::Reader do
7
7
  playlist = reader.read file
8
8
  expect(playlist.master?).to be true
9
9
 
10
+ expect(playlist.independent_segments).to be true
11
+
10
12
  item = playlist.items[0]
11
13
  expect(item).to be_a(M3u8::SessionKeyItem)
12
14
  expect(item.method).to eq('AES-128')
13
15
  expect(item.uri).to eq('https://priv.example.com/key.php?r=52')
14
16
 
15
17
  item = playlist.items[1]
18
+ expect(item).to be_a(M3u8::PlaybackStart)
19
+ expect(item.time_offset).to eq(20.2)
20
+
21
+ item = playlist.items[2]
16
22
  expect(item).to be_a(M3u8::PlaylistItem)
17
23
  expect(item.uri).to eq('hls/1080-7mbps/1080-7mbps.m3u8')
18
24
  expect(item.program_id).to eq('1')
@@ -24,7 +30,7 @@ describe M3u8::Reader do
24
30
  expect(item.iframe).to be false
25
31
  expect(item.average_bandwidth).to be_nil
26
32
 
27
- item = playlist.items[6]
33
+ item = playlist.items[7]
28
34
  expect(item).to be_a(M3u8::PlaylistItem)
29
35
  expect(item.uri).to eq('hls/64k/64k.m3u8')
30
36
  expect(item.program_id).to eq('1')
@@ -36,7 +42,7 @@ describe M3u8::Reader do
36
42
  expect(item.iframe).to be false
37
43
  expect(item.average_bandwidth).to be_nil
38
44
 
39
- expect(playlist.items.size).to eq(7)
45
+ expect(playlist.items.size).to eq(8)
40
46
 
41
47
  item = playlist.items.last
42
48
  expect(item.resolution).to be_nil
@@ -174,12 +180,12 @@ describe M3u8::Reader do
174
180
  reader = M3u8::Reader.new
175
181
  playlist = reader.read file
176
182
 
177
- expect(playlist.items.size).to eq(7)
183
+ expect(playlist.items.size).to eq(8)
178
184
 
179
185
  file = File.open 'spec/fixtures/master.m3u8'
180
186
  playlist = reader.read file
181
187
 
182
- expect(playlist.items.size).to eq(7)
188
+ expect(playlist.items.size).to eq(8)
183
189
  end
184
190
 
185
191
  it 'should parse playlist with session data' do
@@ -4,11 +4,13 @@ describe M3u8::Writer do
4
4
  it 'should render master playlist' do
5
5
  options = { uri: 'playlist_url', bandwidth: 6400,
6
6
  audio_codec: 'mp3' }
7
- item = M3u8::PlaylistItem.new options
8
- playlist = M3u8::Playlist.new
9
- playlist.items.push item
7
+ item = M3u8::PlaylistItem.new(options)
8
+ playlist = M3u8::Playlist.new(version: 6, independent_segments: true)
9
+ playlist.items << item
10
10
 
11
- output = "#EXTM3U\n" +
11
+ output = "#EXTM3U\n" \
12
+ "#EXT-X-VERSION:6\n" \
13
+ "#EXT-X-INDEPENDENT-SEGMENTS\n" +
12
14
  %(#EXT-X-STREAM-INF:CODECS="mp4a.40.34") +
13
15
  ",BANDWIDTH=6400\nplaylist_url\n"
14
16
 
@@ -19,7 +21,7 @@ describe M3u8::Writer do
19
21
 
20
22
  options = { program_id: '1', uri: 'playlist_url', bandwidth: 6400,
21
23
  audio_codec: 'mp3' }
22
- item = M3u8::PlaylistItem.new options
24
+ item = M3u8::PlaylistItem.new(options)
23
25
  playlist = M3u8::Playlist.new
24
26
  playlist.items.push item
25
27
 
@@ -80,37 +82,35 @@ describe M3u8::Writer do
80
82
 
81
83
  it 'should render playlist' do
82
84
  options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
83
- item = M3u8::SegmentItem.new options
84
- playlist = M3u8::Playlist.new
85
- playlist.items.push item
85
+ item = M3u8::SegmentItem.new(options)
86
+ playlist = M3u8::Playlist.new(version: 7)
87
+ playlist.items << item
86
88
 
87
89
  output = "#EXTM3U\n" \
88
- "#EXT-X-VERSION:3\n" \
90
+ "#EXT-X-VERSION:7\n" \
89
91
  "#EXT-X-MEDIA-SEQUENCE:0\n" \
90
- "#EXT-X-ALLOW-CACHE:YES\n" \
91
92
  "#EXT-X-TARGETDURATION:10\n" \
92
93
  "#EXTINF:11.344644,\n" \
93
94
  "1080-7mbps00000.ts\n" \
94
95
  "#EXT-X-ENDLIST\n"
95
96
  io = StringIO.open
96
- writer = M3u8::Writer.new io
97
+ writer = M3u8::Writer.new(io)
97
98
  writer.write playlist
98
99
  expect(io.string).to eq output
99
100
 
100
101
  options = { method: 'AES-128', uri: 'http://test.key',
101
102
  iv: 'D512BBF', key_format: 'identity',
102
103
  key_format_versions: '1/3' }
103
- item = M3u8::KeyItem.new options
104
- playlist.items.push item
104
+ item = M3u8::KeyItem.new(options)
105
+ playlist.items << item
105
106
 
106
107
  options = { duration: 11.261233, segment: '1080-7mbps00001.ts' }
107
108
  item = M3u8::SegmentItem.new options
108
- playlist.items.push item
109
+ playlist.items << item
109
110
 
110
111
  output = "#EXTM3U\n" \
111
- "#EXT-X-VERSION:3\n" \
112
+ "#EXT-X-VERSION:7\n" \
112
113
  "#EXT-X-MEDIA-SEQUENCE:0\n" \
113
- "#EXT-X-ALLOW-CACHE:YES\n" \
114
114
  "#EXT-X-TARGETDURATION:10\n" \
115
115
  "#EXTINF:11.344644,\n" \
116
116
  "1080-7mbps00000.ts\n" +
@@ -120,16 +120,16 @@ describe M3u8::Writer do
120
120
  "1080-7mbps00001.ts\n" \
121
121
  "#EXT-X-ENDLIST\n"
122
122
  io = StringIO.open
123
- writer = M3u8::Writer.new io
123
+ writer = M3u8::Writer.new(io)
124
124
  writer.write playlist
125
125
  expect(io.string).to eq output
126
126
 
127
127
  options = { version: 4, cache: false, target: 12, sequence: 1,
128
128
  type: 'EVENT', iframes_only: true }
129
- playlist = M3u8::Playlist.new options
129
+ playlist = M3u8::Playlist.new(options)
130
130
  options = { duration: 11.344644, segment: '1080-7mbps00000.ts' }
131
- item = M3u8::SegmentItem.new options
132
- playlist.items.push item
131
+ item = M3u8::SegmentItem.new(options)
132
+ playlist.items << item
133
133
 
134
134
  output = "#EXTM3U\n" \
135
135
  "#EXT-X-PLAYLIST-TYPE:EVENT\n" \
@@ -13,8 +13,18 @@ describe do
13
13
 
14
14
  it 'should parse yes/no string' do
15
15
  value = 'YES'
16
- expect(test_class.parse_yes_no value).to be true
16
+ expect(test_class.parse_yes_no(value)).to be true
17
17
  value = 'NO'
18
- expect(test_class.parse_yes_no value).to be false
18
+ expect(test_class.parse_yes_no(value)).to be false
19
+ end
20
+
21
+ describe '#to_yes_no' do
22
+ it 'converts true to YES' do
23
+ expect(test_class.to_yes_no(true)).to eq('YES')
24
+ end
25
+
26
+ it 'converts false to NO' do
27
+ expect(test_class.to_yes_no(false)).to eq('NO')
28
+ end
19
29
  end
20
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m3u8
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 0.7.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: 2016-04-24 00:00:00.000000000 Z
11
+ date: 2017-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -119,7 +119,6 @@ files:
119
119
  - ".hound.yml"
120
120
  - ".rspec"
121
121
  - ".rubocop.yml"
122
- - ".ruby-version"
123
122
  - ".travis.yml"
124
123
  - CHANGELOG.md
125
124
  - Gemfile
@@ -135,6 +134,7 @@ files:
135
134
  - lib/m3u8/key_item.rb
136
135
  - lib/m3u8/map_item.rb
137
136
  - lib/m3u8/media_item.rb
137
+ - lib/m3u8/playback_start.rb
138
138
  - lib/m3u8/playlist.rb
139
139
  - lib/m3u8/playlist_item.rb
140
140
  - lib/m3u8/reader.rb
@@ -161,6 +161,7 @@ files:
161
161
  - spec/lib/m3u8/key_item_spec.rb
162
162
  - spec/lib/m3u8/map_item_spec.rb
163
163
  - spec/lib/m3u8/media_item_spec.rb
164
+ - spec/lib/m3u8/playback_start_spec.rb
164
165
  - spec/lib/m3u8/playlist_item_spec.rb
165
166
  - spec/lib/m3u8/playlist_spec.rb
166
167
  - spec/lib/m3u8/reader_spec.rb
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
192
  version: '0'
192
193
  requirements: []
193
194
  rubyforge_project:
194
- rubygems_version: 2.4.5.1
195
+ rubygems_version: 2.6.8
195
196
  signing_key:
196
197
  specification_version: 4
197
198
  summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
@@ -212,6 +213,7 @@ test_files:
212
213
  - spec/lib/m3u8/key_item_spec.rb
213
214
  - spec/lib/m3u8/map_item_spec.rb
214
215
  - spec/lib/m3u8/media_item_spec.rb
216
+ - spec/lib/m3u8/playback_start_spec.rb
215
217
  - spec/lib/m3u8/playlist_item_spec.rb
216
218
  - spec/lib/m3u8/playlist_spec.rb
217
219
  - spec/lib/m3u8/reader_spec.rb
@@ -1 +0,0 @@
1
- 2.2.3