m3u8 0.7.0 → 0.7.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 -0
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/Rakefile +1 -0
- data/lib/m3u8.rb +6 -1
- data/lib/m3u8/byte_range.rb +1 -0
- data/lib/m3u8/date_range_item.rb +113 -0
- data/lib/m3u8/discontinuity_item.rb +1 -3
- data/lib/m3u8/encryptable.rb +1 -0
- data/lib/m3u8/error.rb +1 -0
- data/lib/m3u8/key_item.rb +1 -0
- data/lib/m3u8/map_item.rb +1 -0
- data/lib/m3u8/media_item.rb +1 -0
- data/lib/m3u8/playback_start.rb +1 -0
- data/lib/m3u8/playlist.rb +1 -0
- data/lib/m3u8/playlist_item.rb +12 -18
- data/lib/m3u8/reader.rb +17 -9
- data/lib/m3u8/segment_item.rb +1 -0
- data/lib/m3u8/session_data_item.rb +1 -0
- data/lib/m3u8/session_key_item.rb +1 -0
- data/lib/m3u8/time_item.rb +1 -0
- data/lib/m3u8/version.rb +2 -1
- data/lib/m3u8/writer.rb +1 -0
- data/spec/fixtures/date_range_scte35.m3u8 +16 -0
- data/spec/lib/m3u8/byte_range_spec.rb +1 -0
- data/spec/lib/m3u8/date_range_item_spec.rb +124 -0
- data/spec/lib/m3u8/discontinuity_item_spec.rb +1 -0
- data/spec/lib/m3u8/key_item_spec.rb +1 -0
- data/spec/lib/m3u8/map_item_spec.rb +2 -5
- data/spec/lib/m3u8/media_item_spec.rb +1 -0
- data/spec/lib/m3u8/playback_start_spec.rb +1 -0
- data/spec/lib/m3u8/playlist_item_spec.rb +1 -0
- data/spec/lib/m3u8/playlist_spec.rb +1 -0
- data/spec/lib/m3u8/reader_spec.rb +254 -236
- data/spec/lib/m3u8/segment_item_spec.rb +4 -14
- data/spec/lib/m3u8/session_data_item_spec.rb +1 -0
- data/spec/lib/m3u8/session_key_item_spec.rb +1 -0
- data/spec/lib/m3u8/time_item_spec.rb +1 -0
- data/spec/lib/m3u8/writer_spec.rb +174 -171
- data/spec/lib/m3u8_spec.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75266714b1670b4990419287c300fa920604bf1c
|
4
|
+
data.tar.gz: af15a6e305fcbf82517d0b2858aed3fa848a80b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 787197f4b3f4807c17125928e3b976c46f66b914b4bf7566c34e5748dd9200d84492c7fa858874d21cbbb9b7dbbfc9b91f04bac8954760a8e18689514459589e
|
7
|
+
data.tar.gz: 7d279886bd83eacfcd8e2e71463f6b96075dd5e3ed8d742d3b0c5ee7c0dc97932a7c07943fe5b49bad9e44c6631023f916479c5b0a7936c035d9d37adec79e74
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
0.7.1 (2/23/2017) - Added support for the EXT-X-DATERANGE tag. Minor refactoring of existing classes.
|
2
|
+
|
3
|
+
***
|
4
|
+
|
1
5
|
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
6
|
|
7
|
+
***
|
8
|
+
|
3
9
|
0.6.9 (4/24/2016) - Merged pull request #17 from [ghn](https://github.com/ghn). Removes return when parsing next line in playlist.
|
4
10
|
|
5
11
|
***
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/Rakefile
CHANGED
data/lib/m3u8.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'bigdecimal'
|
2
3
|
require 'stringio'
|
3
4
|
Dir[File.dirname(__FILE__) + '/m3u8/*.rb'].sort.each { |file| require file }
|
@@ -12,10 +13,14 @@ module M3u8
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def parse_attributes(line)
|
15
|
-
array = line.delete("\n").scan(/([A-
|
16
|
+
array = line.delete("\n").scan(/([A-z0-9-]+)\s*=\s*("[^"]*"|[^,]*)/)
|
16
17
|
Hash[array.map { |key, value| [key, value.delete('"')] }]
|
17
18
|
end
|
18
19
|
|
20
|
+
def parse_float(value)
|
21
|
+
value.nil? ? nil : value.to_f
|
22
|
+
end
|
23
|
+
|
19
24
|
def parse_yes_no(value)
|
20
25
|
value == 'YES' ? true : false
|
21
26
|
end
|
data/lib/m3u8/byte_range.rb
CHANGED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module M3u8
|
3
|
+
# DateRangeItem represents a #EXT-X-DATERANGE tag
|
4
|
+
class DateRangeItem
|
5
|
+
include M3u8
|
6
|
+
attr_accessor :id, :class_name, :start_date, :end_date, :duration,
|
7
|
+
:planned_duration, :scte35_cmd, :scte35_out, :scte35_in,
|
8
|
+
:end_on_next, :client_attributes
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
options.each do |key, value|
|
12
|
+
instance_variable_set("@#{key}", value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(text)
|
17
|
+
attributes = parse_attributes(text)
|
18
|
+
@id = attributes['ID']
|
19
|
+
@class_name = attributes['CLASS']
|
20
|
+
@start_date = attributes['START-DATE']
|
21
|
+
@end_date = attributes['END-DATE']
|
22
|
+
@duration = parse_float(attributes['DURATION'])
|
23
|
+
@planned_duration = parse_float(attributes['PLANNED-DURATION'])
|
24
|
+
@scte35_cmd = attributes['SCTE35-CMD']
|
25
|
+
@scte35_out = attributes['SCTE35-OUT']
|
26
|
+
@scte35_in = attributes['SCTE35-IN']
|
27
|
+
@end_on_next = attributes.key?('END-ON-NEXT') ? true : false
|
28
|
+
@client_attributes = parse_client_attributes(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"#EXT-X-DATERANGE:#{formatted_attributes}"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def formatted_attributes
|
38
|
+
[%(ID="#{id}"),
|
39
|
+
class_name_format,
|
40
|
+
%(START-DATE="#{start_date}"),
|
41
|
+
end_date_format,
|
42
|
+
duration_format,
|
43
|
+
planned_duration_format,
|
44
|
+
client_attributes_format,
|
45
|
+
scte35_cmd_format,
|
46
|
+
scte35_out_format,
|
47
|
+
scte35_in_format,
|
48
|
+
end_on_next_format].compact.join(',')
|
49
|
+
end
|
50
|
+
|
51
|
+
def class_name_format
|
52
|
+
return if class_name.nil?
|
53
|
+
%(CLASS="#{class_name}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def end_date_format
|
57
|
+
return if end_date.nil?
|
58
|
+
%(END-DATE="#{end_date}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def duration_format
|
62
|
+
return if duration.nil?
|
63
|
+
"DURATION=#{duration}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def planned_duration_format
|
67
|
+
return if planned_duration.nil?
|
68
|
+
"PLANNED-DURATION=#{planned_duration}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def client_attributes_format
|
72
|
+
return if client_attributes.nil?
|
73
|
+
client_attributes.map do |attribute|
|
74
|
+
value = attribute.last
|
75
|
+
value_format = decimal?(value) ? value : %("#{value}")
|
76
|
+
"#{attribute.first}=#{value_format}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def decimal?(value)
|
81
|
+
return true if value =~ /\A\d+\Z/
|
82
|
+
begin
|
83
|
+
return true if Float(value)
|
84
|
+
rescue
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def scte35_cmd_format
|
90
|
+
return if scte35_cmd.nil?
|
91
|
+
"SCTE35-CMD=#{scte35_cmd}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def scte35_out_format
|
95
|
+
return if scte35_out.nil?
|
96
|
+
"SCTE35-OUT=#{scte35_out}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def scte35_in_format
|
100
|
+
return if scte35_in.nil?
|
101
|
+
"SCTE35-IN=#{scte35_in}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def end_on_next_format
|
105
|
+
return unless end_on_next
|
106
|
+
'END-ON-NEXT=YES'
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_client_attributes(attributes)
|
110
|
+
attributes.select { |key| key.start_with?('X-') }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -1,12 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module M3u8
|
2
3
|
# DiscontinuityItem represents a EXT-X-DISCONTINUITY tag to indicate a
|
3
4
|
# discontinuity between the SegmentItems that proceed and follow it.
|
4
5
|
class DiscontinuityItem
|
5
6
|
attr_accessor :tag
|
6
7
|
|
7
|
-
def initialize
|
8
|
-
end
|
9
|
-
|
10
8
|
def to_s
|
11
9
|
"#EXT-X-DISCONTINUITY\n"
|
12
10
|
end
|
data/lib/m3u8/encryptable.rb
CHANGED
data/lib/m3u8/error.rb
CHANGED
data/lib/m3u8/key_item.rb
CHANGED
data/lib/m3u8/map_item.rb
CHANGED
data/lib/m3u8/media_item.rb
CHANGED
data/lib/m3u8/playback_start.rb
CHANGED
data/lib/m3u8/playlist.rb
CHANGED
data/lib/m3u8/playlist_item.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module M3u8
|
2
3
|
# PlaylistItem represents a set of EXT-X-STREAM-INF or
|
3
4
|
# EXT-X-I-FRAME-STREAM-INF attributes
|
@@ -36,17 +37,11 @@ module M3u8
|
|
36
37
|
def codecs
|
37
38
|
return @codecs unless @codecs.nil?
|
38
39
|
|
39
|
-
|
40
|
+
video = video_codec(profile, level)
|
41
|
+
return audio_codec if video.nil?
|
42
|
+
return video if audio_codec.nil?
|
40
43
|
|
41
|
-
|
42
|
-
return audio_codec
|
43
|
-
else
|
44
|
-
if audio_codec.nil?
|
45
|
-
return video_codec
|
46
|
-
else
|
47
|
-
return "#{video_codec},#{audio_codec}"
|
48
|
-
end
|
49
|
-
end
|
44
|
+
"#{video},#{audio_codec}"
|
50
45
|
end
|
51
46
|
|
52
47
|
def to_s
|
@@ -94,7 +89,7 @@ module M3u8
|
|
94
89
|
end
|
95
90
|
|
96
91
|
def validate
|
97
|
-
|
92
|
+
raise MissingCodecError, MISSING_CODEC_MESSAGE if codecs.nil?
|
98
93
|
end
|
99
94
|
|
100
95
|
def m3u8_format
|
@@ -177,18 +172,17 @@ module M3u8
|
|
177
172
|
|
178
173
|
def audio_codec
|
179
174
|
return if @audio_codec.nil?
|
180
|
-
return 'mp4a.40.2' if @audio_codec.
|
181
|
-
return 'mp4a.40.5' if @audio_codec.
|
182
|
-
return 'mp4a.40.34' if @audio_codec.
|
175
|
+
return 'mp4a.40.2' if @audio_codec.casecmp('aac-lc').zero?
|
176
|
+
return 'mp4a.40.5' if @audio_codec.casecmp('he-aac').zero?
|
177
|
+
return 'mp4a.40.34' if @audio_codec.casecmp('mp3').zero?
|
183
178
|
end
|
184
179
|
|
185
180
|
def video_codec(profile, level)
|
186
181
|
return if profile.nil? || level.nil?
|
187
182
|
|
188
|
-
profile.
|
189
|
-
return
|
190
|
-
return
|
191
|
-
return high_codec_string(level) if profile == 'high'
|
183
|
+
return baseline_codec_string(level) if profile.casecmp('baseline').zero?
|
184
|
+
return main_codec_string(level) if profile.casecmp('main').zero?
|
185
|
+
return high_codec_string(level) if profile.casecmp('high').zero?
|
192
186
|
end
|
193
187
|
|
194
188
|
def baseline_codec_string(level)
|
data/lib/m3u8/reader.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module M3u8
|
2
3
|
# Reader provides parsing of m3u8 playlists
|
3
4
|
class Reader
|
@@ -27,17 +28,20 @@ module M3u8
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def media_segment_tags
|
30
|
-
{
|
31
|
+
{
|
32
|
+
'#EXTINF' => ->(line) { parse_segment(line) },
|
31
33
|
'#EXT-X-DISCONTINUITY' => ->(line) { parse_discontinuity(line) },
|
32
34
|
'#EXT-X-BYTERANGE' => ->(line) { parse_byterange(line) },
|
33
35
|
'#EXT-X-KEY' => ->(line) { parse_key(line) },
|
34
36
|
'#EXT-X-MAP' => ->(line) { parse_map(line) },
|
35
|
-
'#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time(line) }
|
37
|
+
'#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time(line) },
|
38
|
+
'#EXT-X-DATERANGE' => ->(line) { parse_date_range(line) }
|
36
39
|
}
|
37
40
|
end
|
38
41
|
|
39
42
|
def media_playlist_tags
|
40
|
-
{
|
43
|
+
{
|
44
|
+
'#EXT-X-MEDIA-SEQUENCE' => ->(line) { parse_sequence(line) },
|
41
45
|
'#EXT-X-ALLOW-CACHE' => ->(line) { parse_cache(line) },
|
42
46
|
'#EXT-X-TARGETDURATION' => ->(line) { parse_target(line) },
|
43
47
|
'#EXT-X-I-FRAMES-ONLY' => proc { playlist.iframes_only = true },
|
@@ -46,7 +50,8 @@ module M3u8
|
|
46
50
|
end
|
47
51
|
|
48
52
|
def master_playlist_tags
|
49
|
-
{
|
53
|
+
{
|
54
|
+
'#EXT-X-MEDIA' => ->(line) { parse_media(line) },
|
50
55
|
'#EXT-X-SESSION-DATA' => ->(line) { parse_session_data(line) },
|
51
56
|
'#EXT-X-SESSION-KEY' => ->(line) { parse_session_key(line) },
|
52
57
|
'#EXT-X-STREAM-INF' => ->(line) { parse_stream(line) },
|
@@ -55,17 +60,14 @@ module M3u8
|
|
55
60
|
end
|
56
61
|
|
57
62
|
def universal_tags
|
58
|
-
{
|
63
|
+
{
|
64
|
+
'#EXT-X-START' => ->(line) { parse_start(line) },
|
59
65
|
'#EXT-X-INDEPENDENT-SEGMENTS' => proc do
|
60
66
|
playlist.independent_segments = true
|
61
67
|
end
|
62
68
|
}
|
63
69
|
end
|
64
70
|
|
65
|
-
def parse_independent_segments(line)
|
66
|
-
parse_independent_segments
|
67
|
-
end
|
68
|
-
|
69
71
|
def parse_line(line)
|
70
72
|
return if match_tag(line)
|
71
73
|
parse_next_line(line) if !item.nil? && open
|
@@ -179,6 +181,12 @@ module M3u8
|
|
179
181
|
end
|
180
182
|
end
|
181
183
|
|
184
|
+
def parse_date_range(line)
|
185
|
+
item = M3u8::DateRangeItem.new
|
186
|
+
item.parse(line)
|
187
|
+
playlist.items << item
|
188
|
+
end
|
189
|
+
|
182
190
|
def parse_next_line(line)
|
183
191
|
value = line.delete("\n").delete("\r")
|
184
192
|
if master
|
data/lib/m3u8/segment_item.rb
CHANGED
data/lib/m3u8/time_item.rb
CHANGED
data/lib/m3u8/version.rb
CHANGED
data/lib/m3u8/writer.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
#EXTM3U
|
2
|
+
#EXT-X-TARGETDURATION:60
|
3
|
+
#EXT-X-DATERANGE:ID="splice-6FFFFFF0",START-DATE="2014-03-05T11:
|
4
|
+
15:00Z",PLANNED-DURATION=59.993,SCTE35-OUT=0xFC002F0000000000FF0
|
5
|
+
00014056FFFFFF000E011622DCAFF000052636200000000000A0008029896F50
|
6
|
+
000008700000000
|
7
|
+
#EXTINF:20.0,
|
8
|
+
http://media.example.com/first.ts
|
9
|
+
#EXTINF:20.0,
|
10
|
+
http://media.example.com/second.ts
|
11
|
+
#EXTINF:20.0,
|
12
|
+
http://media.example.com/third.ts
|
13
|
+
#EXT-X-DATERANGE:ID="splice-6FFFFFF0",DURATION=59.993,SCTE35-IN=
|
14
|
+
0xFC002A0000000000FF00000F056FFFFFF000401162802E6100000000000A00
|
15
|
+
08029896F50000008700000000
|
16
|
+
#EXT-X-ENDLIST
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe M3u8::DateRangeItem do
|
5
|
+
describe '#new' do
|
6
|
+
it 'should assign attributes via option hash' do
|
7
|
+
options = { id: 'test_id', class_name: 'test_class',
|
8
|
+
start_date: '2014-03-05T11:15:00Z',
|
9
|
+
end_date: '2014-03-05T11:16:00Z', duration: 60.1,
|
10
|
+
planned_duration: 59.993,
|
11
|
+
scte35_out: '0xFC002F0000000000FF0',
|
12
|
+
scte35_in: '0xFC002F0000000000FF1',
|
13
|
+
scte35_cmd: '0xFC002F0000000000FF2', end_on_next: true,
|
14
|
+
client_attributes: { 'X-CUSTOM' => 45.3 } }
|
15
|
+
item = described_class.new(options)
|
16
|
+
|
17
|
+
expect(item.id).to eq('test_id')
|
18
|
+
expect(item.class_name).to eq('test_class')
|
19
|
+
expect(item.start_date).to eq('2014-03-05T11:15:00Z')
|
20
|
+
expect(item.end_date).to eq('2014-03-05T11:16:00Z')
|
21
|
+
expect(item.duration).to eq(60.1)
|
22
|
+
expect(item.planned_duration).to eq(59.993)
|
23
|
+
expect(item.scte35_out).to eq('0xFC002F0000000000FF0')
|
24
|
+
expect(item.scte35_in).to eq('0xFC002F0000000000FF1')
|
25
|
+
expect(item.scte35_cmd).to eq('0xFC002F0000000000FF2')
|
26
|
+
expect(item.end_on_next).to be true
|
27
|
+
expect(item.client_attributes.empty?).to be false
|
28
|
+
expect(item.client_attributes['X-CUSTOM']).to eq(45.3)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#parse' do
|
33
|
+
it 'should parse m3u8 tag into instance' do
|
34
|
+
item = described_class.new
|
35
|
+
line = '#EXT-X-DATERANGE:ID="splice-6FFFFFF0",CLASS="test_class"' \
|
36
|
+
'START-DATE="2014-03-05T11:15:00Z",' \
|
37
|
+
'END-DATE="2014-03-05T11:16:00Z",DURATION=60.1,' \
|
38
|
+
'PLANNED-DURATION=59.993,SCTE35-OUT=0xFC002F0000000000FF0,' \
|
39
|
+
'SCTE35-IN=0xFC002F0000000000FF1,' \
|
40
|
+
'SCTE35-CMD=0xFC002F0000000000FF2,' \
|
41
|
+
'END-ON-NEXT=YES'
|
42
|
+
item.parse(line)
|
43
|
+
|
44
|
+
expect(item.id).to eq('splice-6FFFFFF0')
|
45
|
+
expect(item.class_name).to eq('test_class')
|
46
|
+
expect(item.start_date).to eq('2014-03-05T11:15:00Z')
|
47
|
+
expect(item.end_date).to eq('2014-03-05T11:16:00Z')
|
48
|
+
expect(item.duration).to eq(60.1)
|
49
|
+
expect(item.planned_duration).to eq(59.993)
|
50
|
+
expect(item.scte35_out).to eq('0xFC002F0000000000FF0')
|
51
|
+
expect(item.scte35_in).to eq('0xFC002F0000000000FF1')
|
52
|
+
expect(item.scte35_cmd).to eq('0xFC002F0000000000FF2')
|
53
|
+
expect(item.end_on_next).to be true
|
54
|
+
expect(item.client_attributes.empty?).to be true
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should ignore optional attributes' do
|
58
|
+
item = described_class.new
|
59
|
+
line = '#EXT-X-DATERANGE:ID="splice-6FFFFFF0",' \
|
60
|
+
'START-DATE="2014-03-05T11:15:00Z"'
|
61
|
+
item.parse(line)
|
62
|
+
|
63
|
+
expect(item.id).to eq('splice-6FFFFFF0')
|
64
|
+
expect(item.class_name).to be_nil
|
65
|
+
expect(item.start_date).to eq('2014-03-05T11:15:00Z')
|
66
|
+
expect(item.end_date).to be_nil
|
67
|
+
expect(item.duration).to be_nil
|
68
|
+
expect(item.planned_duration).to be_nil
|
69
|
+
expect(item.scte35_out).to be_nil
|
70
|
+
expect(item.scte35_in).to be_nil
|
71
|
+
expect(item.scte35_cmd).to be_nil
|
72
|
+
expect(item.end_on_next).to be false
|
73
|
+
expect(item.client_attributes.empty?).to be true
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should parse client-defined attributes' do
|
77
|
+
item = described_class.new
|
78
|
+
line = '#EXT-X-DATERANGE:ID="splice-6FFFFFF0",' \
|
79
|
+
'START-DATE="2014-03-05T11:15:00Z",' \
|
80
|
+
'X-CUSTOM-VALUE="test_value",'
|
81
|
+
item.parse(line)
|
82
|
+
|
83
|
+
expect(item.client_attributes['X-CUSTOM-VALUE']).to eq('test_value')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#to_s' do
|
88
|
+
it 'should render m3u8 tag' do
|
89
|
+
options = { id: 'test_id', class_name: 'test_class',
|
90
|
+
start_date: '2014-03-05T11:15:00Z',
|
91
|
+
end_date: '2014-03-05T11:16:00Z', duration: 60.1,
|
92
|
+
planned_duration: 59.993,
|
93
|
+
scte35_out: '0xFC002F0000000000FF0',
|
94
|
+
scte35_in: '0xFC002F0000000000FF1',
|
95
|
+
scte35_cmd: '0xFC002F0000000000FF2', end_on_next: true,
|
96
|
+
client_attributes: { 'X-CUSTOM' => 45.3,
|
97
|
+
'X-CUSTOM-TEXT' => 'test_value' } }
|
98
|
+
item = described_class.new(options)
|
99
|
+
|
100
|
+
expected = '#EXT-X-DATERANGE:ID="test_id",CLASS="test_class",' \
|
101
|
+
'START-DATE="2014-03-05T11:15:00Z",' \
|
102
|
+
'END-DATE="2014-03-05T11:16:00Z",DURATION=60.1,' \
|
103
|
+
'PLANNED-DURATION=59.993,' \
|
104
|
+
'X-CUSTOM=45.3,' \
|
105
|
+
'X-CUSTOM-TEXT="test_value",' \
|
106
|
+
'SCTE35-CMD=0xFC002F0000000000FF2,' \
|
107
|
+
'SCTE35-OUT=0xFC002F0000000000FF0,' \
|
108
|
+
'SCTE35-IN=0xFC002F0000000000FF1,' \
|
109
|
+
'END-ON-NEXT=YES'
|
110
|
+
|
111
|
+
expect(item.to_s).to eq(expected)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should ignore optional attributes' do
|
115
|
+
options = { id: 'test_id', start_date: '2014-03-05T11:15:00Z' }
|
116
|
+
item = described_class.new(options)
|
117
|
+
|
118
|
+
expected = '#EXT-X-DATERANGE:ID="test_id",' \
|
119
|
+
'START-DATE="2014-03-05T11:15:00Z"'
|
120
|
+
|
121
|
+
expect(item.to_s).to eq(expected)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|