m3u8 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -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
|