m3u8 0.8.2 → 1.8.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.
Files changed (103) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +23 -0
  3. data/.gitignore +1 -1
  4. data/.rubocop.yml +31 -0
  5. data/CHANGELOG.md +107 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +524 -40
  9. data/Rakefile +1 -0
  10. data/bin/m3u8 +6 -0
  11. data/lib/m3u8/attribute_formatter.rb +47 -0
  12. data/lib/m3u8/bitrate_item.rb +31 -0
  13. data/lib/m3u8/builder.rb +48 -0
  14. data/lib/m3u8/byte_range.rb +10 -0
  15. data/lib/m3u8/cli/inspect_command.rb +97 -0
  16. data/lib/m3u8/cli/validate_command.rb +24 -0
  17. data/lib/m3u8/cli.rb +116 -0
  18. data/lib/m3u8/codecs.rb +89 -0
  19. data/lib/m3u8/content_steering_item.rb +45 -0
  20. data/lib/m3u8/date_range_item.rb +135 -64
  21. data/lib/m3u8/define_item.rb +54 -0
  22. data/lib/m3u8/discontinuity_item.rb +3 -0
  23. data/lib/m3u8/encryptable.rb +27 -30
  24. data/lib/m3u8/error.rb +1 -0
  25. data/lib/m3u8/gap_item.rb +14 -0
  26. data/lib/m3u8/key_item.rb +7 -0
  27. data/lib/m3u8/map_item.rb +16 -5
  28. data/lib/m3u8/media_item.rb +48 -76
  29. data/lib/m3u8/part_inf_item.rb +35 -0
  30. data/lib/m3u8/part_item.rb +67 -0
  31. data/lib/m3u8/playback_start.rb +19 -12
  32. data/lib/m3u8/playlist.rb +221 -13
  33. data/lib/m3u8/playlist_item.rb +128 -124
  34. data/lib/m3u8/preload_hint_item.rb +54 -0
  35. data/lib/m3u8/reader.rb +86 -28
  36. data/lib/m3u8/rendition_report_item.rb +48 -0
  37. data/lib/m3u8/scte35.rb +130 -0
  38. data/lib/m3u8/scte35_bit_reader.rb +51 -0
  39. data/lib/m3u8/scte35_segmentation_descriptor.rb +54 -0
  40. data/lib/m3u8/scte35_splice_insert.rb +62 -0
  41. data/lib/m3u8/scte35_splice_null.rb +8 -0
  42. data/lib/m3u8/scte35_time_signal.rb +19 -0
  43. data/lib/m3u8/segment_item.rb +37 -3
  44. data/lib/m3u8/server_control_item.rb +69 -0
  45. data/lib/m3u8/session_data_item.rb +17 -28
  46. data/lib/m3u8/session_key_item.rb +8 -1
  47. data/lib/m3u8/skip_item.rb +48 -0
  48. data/lib/m3u8/time_item.rb +10 -0
  49. data/lib/m3u8/version.rb +1 -1
  50. data/lib/m3u8/writer.rb +24 -1
  51. data/lib/m3u8.rb +30 -6
  52. data/m3u8.gemspec +12 -12
  53. data/spec/fixtures/content_steering.m3u8 +10 -0
  54. data/spec/fixtures/daterange_playlist.m3u8 +14 -0
  55. data/spec/fixtures/encrypted_discontinuity.m3u8 +17 -0
  56. data/spec/fixtures/event_playlist.m3u8 +18 -0
  57. data/spec/fixtures/gap_playlist.m3u8 +14 -0
  58. data/spec/fixtures/ll_hls_advanced.m3u8 +18 -0
  59. data/spec/fixtures/ll_hls_playlist.m3u8 +20 -0
  60. data/spec/fixtures/master_full.m3u8 +14 -0
  61. data/spec/fixtures/master_v13.m3u8 +8 -0
  62. data/spec/lib/m3u8/bitrate_item_spec.rb +26 -0
  63. data/spec/lib/m3u8/builder_spec.rb +352 -0
  64. data/spec/lib/m3u8/byte_range_spec.rb +1 -0
  65. data/spec/lib/m3u8/cli/inspect_command_spec.rb +102 -0
  66. data/spec/lib/m3u8/cli/validate_command_spec.rb +39 -0
  67. data/spec/lib/m3u8/cli_spec.rb +104 -0
  68. data/spec/lib/m3u8/content_steering_item_spec.rb +56 -0
  69. data/spec/lib/m3u8/date_range_item_spec.rb +159 -31
  70. data/spec/lib/m3u8/define_item_spec.rb +59 -0
  71. data/spec/lib/m3u8/discontinuity_item_spec.rb +1 -0
  72. data/spec/lib/m3u8/gap_item_spec.rb +12 -0
  73. data/spec/lib/m3u8/key_item_spec.rb +1 -0
  74. data/spec/lib/m3u8/map_item_spec.rb +1 -0
  75. data/spec/lib/m3u8/media_item_spec.rb +34 -0
  76. data/spec/lib/m3u8/part_inf_item_spec.rb +27 -0
  77. data/spec/lib/m3u8/part_item_spec.rb +67 -0
  78. data/spec/lib/m3u8/playback_start_spec.rb +4 -5
  79. data/spec/lib/m3u8/playlist_item_spec.rb +130 -17
  80. data/spec/lib/m3u8/playlist_spec.rb +545 -13
  81. data/spec/lib/m3u8/preload_hint_item_spec.rb +57 -0
  82. data/spec/lib/m3u8/reader_spec.rb +376 -29
  83. data/spec/lib/m3u8/rendition_report_item_spec.rb +56 -0
  84. data/spec/lib/m3u8/round_trip_spec.rb +152 -0
  85. data/spec/lib/m3u8/scte35_bit_reader_spec.rb +106 -0
  86. data/spec/lib/m3u8/scte35_segmentation_descriptor_spec.rb +143 -0
  87. data/spec/lib/m3u8/scte35_spec.rb +94 -0
  88. data/spec/lib/m3u8/scte35_splice_insert_spec.rb +185 -0
  89. data/spec/lib/m3u8/scte35_splice_null_spec.rb +12 -0
  90. data/spec/lib/m3u8/scte35_time_signal_spec.rb +50 -0
  91. data/spec/lib/m3u8/segment_item_spec.rb +47 -0
  92. data/spec/lib/m3u8/server_control_item_spec.rb +64 -0
  93. data/spec/lib/m3u8/session_data_item_spec.rb +1 -0
  94. data/spec/lib/m3u8/session_key_item_spec.rb +1 -0
  95. data/spec/lib/m3u8/skip_item_spec.rb +48 -0
  96. data/spec/lib/m3u8/time_item_spec.rb +1 -0
  97. data/spec/lib/m3u8/writer_spec.rb +69 -30
  98. data/spec/lib/m3u8_spec.rb +1 -0
  99. data/spec/spec_helper.rb +4 -87
  100. metadata +70 -129
  101. data/.hound.yml +0 -3
  102. data/.travis.yml +0 -8
  103. data/Guardfile +0 -6
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'spec_helper'
3
4
 
4
5
  describe M3u8::MediaItem do
@@ -24,6 +25,16 @@ describe M3u8::MediaItem do
24
25
  expect(item.characteristics).to eq('public.html')
25
26
  expect(item.channels).to eq('6')
26
27
  end
28
+
29
+ it 'assigns v13 attributes from options' do
30
+ options = { type: 'AUDIO', group_id: 'aac', name: 'English',
31
+ stable_rendition_id: 'audio-en', bit_depth: 16,
32
+ sample_rate: 44_100 }
33
+ item = described_class.new(options)
34
+ expect(item.stable_rendition_id).to eq('audio-en')
35
+ expect(item.bit_depth).to eq(16)
36
+ expect(item.sample_rate).to eq(44_100)
37
+ end
27
38
  end
28
39
 
29
40
  describe '.parse' do
@@ -48,6 +59,16 @@ describe M3u8::MediaItem do
48
59
  expect(item.characteristics).to eq('public.html')
49
60
  expect(item.channels).to eq('6')
50
61
  end
62
+
63
+ it 'parses v13 attributes' do
64
+ tag = '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",' \
65
+ 'NAME="English",STABLE-RENDITION-ID="audio-en",' \
66
+ 'BIT-DEPTH=16,SAMPLE-RATE=44100'
67
+ item = described_class.parse(tag)
68
+ expect(item.stable_rendition_id).to eq('audio-en')
69
+ expect(item.bit_depth).to eq(16)
70
+ expect(item.sample_rate).to eq(44_100)
71
+ end
51
72
  end
52
73
 
53
74
  describe '#to_s' do
@@ -86,5 +107,18 @@ describe M3u8::MediaItem do
86
107
  expect(output).to eq(expected)
87
108
  end
88
109
  end
110
+
111
+ context 'when v13 attributes are assigned' do
112
+ it 'returns tag text with new attributes' do
113
+ options = { type: 'AUDIO', group_id: 'aac', name: 'English',
114
+ channels: '2', stable_rendition_id: 'audio-en',
115
+ bit_depth: 16, sample_rate: 44_100 }
116
+ item = M3u8::MediaItem.new(options)
117
+ output = item.to_s
118
+ expect(output).to include('STABLE-RENDITION-ID="audio-en"')
119
+ expect(output).to include('BIT-DEPTH=16')
120
+ expect(output).to include('SAMPLE-RATE=44100')
121
+ end
122
+ end
89
123
  end
90
124
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe M3u8::PartInfItem do
6
+ describe '.new' do
7
+ it 'assigns attributes from options' do
8
+ item = described_class.new(part_target: 0.5)
9
+ expect(item.part_target).to eq(0.5)
10
+ end
11
+ end
12
+
13
+ describe '.parse' do
14
+ it 'parses tag' do
15
+ tag = '#EXT-X-PART-INF:PART-TARGET=0.5'
16
+ item = described_class.parse(tag)
17
+ expect(item.part_target).to eq(0.5)
18
+ end
19
+ end
20
+
21
+ describe '#to_s' do
22
+ it 'returns m3u8 format representation' do
23
+ item = described_class.new(part_target: 0.5)
24
+ expect(item.to_s).to eq('#EXT-X-PART-INF:PART-TARGET=0.5')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe M3u8::PartItem do
6
+ describe '.new' do
7
+ it 'assigns attributes from options' do
8
+ options = { uri: 'part1.ts', duration: 1.5, independent: true,
9
+ gap: false }
10
+ item = described_class.new(options)
11
+ expect(item.uri).to eq('part1.ts')
12
+ expect(item.duration).to eq(1.5)
13
+ expect(item.independent).to be true
14
+ expect(item.gap).to be false
15
+ end
16
+ end
17
+
18
+ describe '.parse' do
19
+ it 'parses tag with all attributes' do
20
+ tag = '#EXT-X-PART:DURATION=1.5,URI="part1.ts",' \
21
+ 'INDEPENDENT=YES,BYTERANGE="500@0",GAP=YES'
22
+ item = described_class.parse(tag)
23
+ expect(item.duration).to eq(1.5)
24
+ expect(item.uri).to eq('part1.ts')
25
+ expect(item.independent).to be true
26
+ expect(item.byterange.length).to eq(500)
27
+ expect(item.byterange.start).to eq(0)
28
+ expect(item.gap).to be true
29
+ end
30
+
31
+ it 'parses tag without optional attributes' do
32
+ tag = '#EXT-X-PART:DURATION=1.5,URI="part1.ts"'
33
+ item = described_class.parse(tag)
34
+ expect(item.duration).to eq(1.5)
35
+ expect(item.uri).to eq('part1.ts')
36
+ expect(item.independent).to be false
37
+ expect(item.byterange).to be_nil
38
+ expect(item.gap).to be false
39
+ end
40
+ end
41
+
42
+ describe '#to_s' do
43
+ it 'returns tag with all attributes' do
44
+ options = { duration: 1.5, uri: 'part1.ts', independent: true,
45
+ byterange: M3u8::ByteRange.new(length: 500, start: 0),
46
+ gap: true }
47
+ item = described_class.new(options)
48
+ expected = '#EXT-X-PART:DURATION=1.5,URI="part1.ts",' \
49
+ 'INDEPENDENT=YES,BYTERANGE="500@0",GAP=YES'
50
+ expect(item.to_s).to eq(expected)
51
+ end
52
+
53
+ it 'returns tag with only required attributes' do
54
+ options = { duration: 1.5, uri: 'part1.ts' }
55
+ item = described_class.new(options)
56
+ expected = '#EXT-X-PART:DURATION=1.5,URI="part1.ts"'
57
+ expect(item.to_s).to eq(expected)
58
+ end
59
+
60
+ it 'should render small float values as floating-point number instead of scientific notation' do
61
+ options = { duration: 0.00001, uri: 'part1.ts' }
62
+ item = described_class.new(options)
63
+ expected = '#EXT-X-PART:DURATION=0.00001,URI="part1.ts"'
64
+ expect(item.to_s).to eq(expected)
65
+ end
66
+ end
67
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'spec_helper'
3
4
 
4
5
  describe M3u8::PlaybackStart do
@@ -10,20 +11,18 @@ describe M3u8::PlaybackStart do
10
11
  end
11
12
  end
12
13
 
13
- describe '#parse' do
14
+ describe '.parse' do
14
15
  it 'parses tag with all attributes' do
15
- start = described_class.new
16
16
  tag = '#EXT-X-START:TIME-OFFSET=20.0,PRECISE=YES'
17
- start.parse(tag)
17
+ start = described_class.parse(tag)
18
18
 
19
19
  expect(start.time_offset).to eq(20.0)
20
20
  expect(start.precise).to be true
21
21
  end
22
22
 
23
23
  it 'parses tag without optional attributes' do
24
- start = described_class.new
25
24
  tag = '#EXT-X-START:TIME-OFFSET=-12.9'
26
- start.parse(tag)
25
+ start = described_class.parse(tag)
27
26
 
28
27
  expect(start.time_offset).to eq(-12.9)
29
28
  expect(start.precise).to be_nil
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'spec_helper'
3
4
 
4
5
  describe M3u8::PlaylistItem do
@@ -32,28 +33,29 @@ describe M3u8::PlaylistItem do
32
33
  expect(item.name).to eq('test_name')
33
34
  expect(item.hdcp_level).to eq('TYPE-0')
34
35
  end
35
- end
36
36
 
37
- describe '.parse' do
38
- it 'returns new instance from parsed tag' do
39
- tag = %(#EXT-X-STREAM-INF:CODECS="avc",BANDWIDTH=540,) +
40
- %(PROGRAM-ID=1,RESOLUTION=1920x1080,FRAME-RATE=23.976,) +
41
- %(AVERAGE-BANDWIDTH=550,AUDIO="test",VIDEO="test2",) +
42
- %(SUBTITLES="subs",CLOSED-CAPTIONS="caps",URI="test.url",) +
43
- %(NAME="1080p",HDCP-LEVEL=TYPE-0)
44
- expect_any_instance_of(described_class).to receive(:parse).with(tag)
45
- item = described_class.parse(tag)
46
- expect(item).to be_a(described_class)
37
+ it 'assigns new v13 attributes from options' do
38
+ options = { bandwidth: 540, uri: 'test.url',
39
+ stable_variant_id: 'hd-1080', video_range: 'SDR',
40
+ allowed_cpc: 'com.example.drm:SMART-TV/PC',
41
+ pathway_id: 'CDN-A',
42
+ req_video_layout: 'CH-MONO',
43
+ supplemental_codecs: 'dvh1.05.06/db4g',
44
+ score: 12.5 }
45
+ item = described_class.new(options)
46
+ expect(item.stable_variant_id).to eq('hd-1080')
47
+ expect(item.video_range).to eq('SDR')
48
+ expect(item.allowed_cpc).to eq('com.example.drm:SMART-TV/PC')
49
+ expect(item.pathway_id).to eq('CDN-A')
50
+ expect(item.req_video_layout).to eq('CH-MONO')
51
+ expect(item.supplemental_codecs).to eq('dvh1.05.06/db4g')
52
+ expect(item.score).to eq(12.5)
47
53
  end
48
54
  end
49
55
 
50
- describe '#parse' do
56
+ describe '.parse' do
51
57
  it 'assigns values from parsed tag' do
52
- input = %(#EXT-X-STREAM-INF:CODECS="avc",BANDWIDTH=540,) +
53
- %(PROGRAM-ID=1,RESOLUTION=1920x1080,FRAME-RATE=23.976,) +
54
- %(AVERAGE-BANDWIDTH=550,AUDIO="test",VIDEO="test2",) +
55
- %(SUBTITLES="subs",CLOSED-CAPTIONS="caps",URI="test.url",) +
56
- %(NAME="1080p",HDCP-LEVEL=TYPE-0)
58
+ input = '#EXT-X-STREAM-INF:CODECS="avc",BANDWIDTH=540,PROGRAM-ID=1,RESOLUTION=1920x1080,FRAME-RATE=23.976,AVERAGE-BANDWIDTH=550,AUDIO="test",VIDEO="test2",SUBTITLES="subs",CLOSED-CAPTIONS="caps",URI="test.url",NAME="1080p",HDCP-LEVEL=TYPE-0'
57
59
  item = M3u8::PlaylistItem.parse(input)
58
60
  expect(item.program_id).to eq '1'
59
61
  expect(item.codecs).to eq 'avc'
@@ -71,6 +73,24 @@ describe M3u8::PlaylistItem do
71
73
  expect(item.iframe).to be false
72
74
  expect(item.hdcp_level).to eq('TYPE-0')
73
75
  end
76
+
77
+ it 'parses v13 attributes' do
78
+ input = '#EXT-X-STREAM-INF:BANDWIDTH=5000000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,STABLE-VARIANT-ID="hd-1080",VIDEO-RANGE=SDR,ALLOWED-CPC="com.example.drm:SMART-TV/PC",PATHWAY-ID="CDN-A",SCORE=12.5,SUPPLEMENTAL-CODECS="dvh1.05.06/db4g",REQ-VIDEO-LAYOUT="CH-MONO"'
79
+ item = M3u8::PlaylistItem.parse(input)
80
+ expect(item.stable_variant_id).to eq('hd-1080')
81
+ expect(item.video_range).to eq('SDR')
82
+ expect(item.allowed_cpc).to eq('com.example.drm:SMART-TV/PC')
83
+ expect(item.pathway_id).to eq('CDN-A')
84
+ expect(item.score).to eq(12.5)
85
+ expect(item.supplemental_codecs).to eq('dvh1.05.06/db4g')
86
+ expect(item.req_video_layout).to eq('CH-MONO')
87
+ end
88
+
89
+ it 'keeps bandwidth nil when BANDWIDTH is missing' do
90
+ input = '#EXT-X-STREAM-INF:CODECS="avc",URI="test.url"'
91
+ item = M3u8::PlaylistItem.parse(input)
92
+ expect(item.bandwidth).to be_nil
93
+ end
74
94
  end
75
95
 
76
96
  describe '#to_s' do
@@ -193,6 +213,27 @@ describe M3u8::PlaylistItem do
193
213
  end
194
214
  end
195
215
 
216
+ context 'when v13 attributes are present' do
217
+ it 'returns tag with new attributes' do
218
+ options = { codecs: 'avc1.640028', bandwidth: 5_000_000,
219
+ uri: '1080p.m3u8', stable_variant_id: 'hd-1080',
220
+ video_range: 'SDR', pathway_id: 'CDN-A',
221
+ score: 12.5,
222
+ supplemental_codecs: 'dvh1.05.06/db4g',
223
+ allowed_cpc: 'com.example.drm:SMART-TV/PC',
224
+ req_video_layout: 'CH-MONO' }
225
+ item = described_class.new(options)
226
+ output = item.to_s
227
+ expect(output).to include('STABLE-VARIANT-ID="hd-1080"')
228
+ expect(output).to include('VIDEO-RANGE=SDR')
229
+ expect(output).to include('PATHWAY-ID="CDN-A"')
230
+ expect(output).to include('SCORE=12.5')
231
+ expect(output).to include('SUPPLEMENTAL-CODECS="dvh1.05.06/db4g"')
232
+ expect(output).to include('ALLOWED-CPC="com.example.drm:SMART-TV/PC"')
233
+ expect(output).to include('REQ-VIDEO-LAYOUT="CH-MONO"')
234
+ end
235
+ end
236
+
196
237
  context 'when iframe is enabled' do
197
238
  it 'returns EXT-X-I-FRAME-STREAM-INF tag' do
198
239
  options = { codecs: 'avc', bandwidth: 540, uri: 'test.url',
@@ -289,4 +330,76 @@ describe M3u8::PlaylistItem do
289
330
  item = M3u8::PlaylistItem.new options
290
331
  expect(item.codecs).to eq 'avc1.640033'
291
332
  end
333
+
334
+ it 'generates HEVC codec strings' do
335
+ options = { profile: 'hevc-main', level: 3.1 }
336
+ item = M3u8::PlaylistItem.new options
337
+ expect(item.codecs).to eq 'hvc1.1.6.L93.B0'
338
+
339
+ options = { profile: 'hevc-main', level: 4.0 }
340
+ item = M3u8::PlaylistItem.new options
341
+ expect(item.codecs).to eq 'hvc1.1.6.L120.B0'
342
+
343
+ options = { profile: 'hevc-main', level: 5.0 }
344
+ item = M3u8::PlaylistItem.new options
345
+ expect(item.codecs).to eq 'hvc1.1.6.L150.B0'
346
+
347
+ options = { profile: 'hevc-main', level: 5.1 }
348
+ item = M3u8::PlaylistItem.new options
349
+ expect(item.codecs).to eq 'hvc1.1.6.L153.B0'
350
+
351
+ options = { profile: 'hevc-main-10', level: 4.0 }
352
+ item = M3u8::PlaylistItem.new options
353
+ expect(item.codecs).to eq 'hvc1.2.4.L120.B0'
354
+
355
+ options = { profile: 'hevc-main-10', level: 5.1 }
356
+ item = M3u8::PlaylistItem.new options
357
+ expect(item.codecs).to eq 'hvc1.2.4.L153.B0'
358
+ end
359
+
360
+ it 'generates AV1 codec strings' do
361
+ options = { profile: 'av1-main', level: 4.0 }
362
+ item = M3u8::PlaylistItem.new options
363
+ expect(item.codecs).to eq 'av01.0.08M.08'
364
+
365
+ options = { profile: 'av1-main', level: 5.1 }
366
+ item = M3u8::PlaylistItem.new options
367
+ expect(item.codecs).to eq 'av01.0.13M.08'
368
+
369
+ options = { profile: 'av1-high', level: 4.0 }
370
+ item = M3u8::PlaylistItem.new options
371
+ expect(item.codecs).to eq 'av01.1.08H.10'
372
+
373
+ options = { profile: 'av1-high', level: 5.1 }
374
+ item = M3u8::PlaylistItem.new options
375
+ expect(item.codecs).to eq 'av01.1.13H.10'
376
+ end
377
+
378
+ it 'generates modern audio codec strings' do
379
+ item = M3u8::PlaylistItem.new audio_codec: 'ac-3'
380
+ expect(item.codecs).to eq 'ac-3'
381
+
382
+ item = M3u8::PlaylistItem.new audio_codec: 'ec-3'
383
+ expect(item.codecs).to eq 'ec-3'
384
+
385
+ item = M3u8::PlaylistItem.new audio_codec: 'e-ac-3'
386
+ expect(item.codecs).to eq 'ec-3'
387
+
388
+ item = M3u8::PlaylistItem.new audio_codec: 'flac'
389
+ expect(item.codecs).to eq 'fLaC'
390
+
391
+ item = M3u8::PlaylistItem.new audio_codec: 'opus'
392
+ expect(item.codecs).to eq 'Opus'
393
+ end
394
+
395
+ it 'accepts integer levels for codec lookup' do
396
+ item = M3u8::PlaylistItem.new(profile: 'main', level: 4)
397
+ expect(item.codecs).to eq 'avc1.4d0028'
398
+
399
+ item = M3u8::PlaylistItem.new(profile: 'baseline', level: 3)
400
+ expect(item.codecs).to eq 'avc1.66.30'
401
+
402
+ item = M3u8::PlaylistItem.new(profile: 'high', level: 4)
403
+ expect(item.codecs).to eq 'avc1.640028'
404
+ end
292
405
  end