Skip to content

Commit 02d2cb9

Browse files
committed
Introduce unrecognized cases to keep parsing forward compatible
1 parent 34de4a9 commit 02d2cb9

File tree

4 files changed

+74
-54
lines changed

4 files changed

+74
-54
lines changed

mambaSharedFramework/HLSValueTypes.swift

+46-40
Original file line numberDiff line numberDiff line change
@@ -255,25 +255,41 @@ public struct HLSChannels: Equatable, FailableStringLiteralConvertible {
255255
/// This parameter is an array of Special Usage Identifiers.
256256
public let specialUsageIdentifiers: [SpecialUsageIdentifier]
257257

258-
public enum SpecialUsageIdentifier: String {
258+
public enum SpecialUsageIdentifier: RawRepresentable, Equatable {
259259
/// The audio is binaural (either recorded or synthesized). It SHOULD NOT be dynamically spatialized. It is best
260260
/// suited for delivery to headphones.
261-
case binaural = "BINAURAL"
261+
case binaural
262262
/// The audio is pre-processed content that SHOULD NOT be dynamically spatialized. It is suitable to deliver to
263263
/// either headphones or speakers.
264-
case immersive = "IMMERSIVE"
264+
case immersive
265265
/// The audio is a downmix derivative of some other audio. If desired, the downmix may be used as a subtitute
266266
/// for alternative Renditions in the same group with compatible attributes and a greater channel count. It MAY
267267
/// be dynamically spatialized.
268-
case downmix = "DOWNMIX"
268+
case downmix
269+
/// The audio identifier is not recognized by this library; however, we provide the raw identifier string that
270+
/// existed in the manifest.
271+
case unrecognized(String)
272+
273+
public var rawValue: String {
274+
switch self {
275+
case .binaural: return "BINAURAL"
276+
case .immersive: return "IMMERSIVE"
277+
case .downmix: return "DOWNMIX"
278+
case .unrecognized(let string): return string
279+
}
280+
}
281+
282+
public init?(rawValue: String) {
283+
self.init(str: Substring(rawValue))
284+
}
269285

270286
/// Allows `init` without having to allocate a new `String` object.
271-
init?(str: Substring) {
287+
init(str: Substring) {
272288
switch str {
273289
case "BINAURAL": self = .binaural
274290
case "IMMERSIVE": self = .immersive
275291
case "DOWNMIX": self = .downmix
276-
default: return nil
292+
default: self = .unrecognized(String(str))
277293
}
278294
}
279295
}
@@ -287,15 +303,7 @@ public struct HLSChannels: Equatable, FailableStringLiteralConvertible {
287303
switch index {
288304
case 0: count = Self.parseChannelCount(str: str)
289305
case 1: spatialAudioCodingIdentifiers = Self.parseSpatialAudioCodingIdentifiers(str: str)
290-
case 2:
291-
guard let ids = Self.parseSpecialUsageIdentifiers(str: str) else {
292-
// In the case that we don't recognize one of the special usage identifiers, leading to nil being
293-
// parsed out, I believe it is better to fail the entire parsing, as otherwise we could mislead the
294-
// user of the library into thinking that there are less special usage identifiers than there
295-
// actually are in the CHANNELS attribtue.
296-
return nil
297-
}
298-
specialUsageIdentifiers = ids
306+
case 2: specialUsageIdentifiers = Self.parseSpecialUsageIdentifiers(str: str)
299307
default: break // In the future there may be more parameters defined.
300308
}
301309
}
@@ -331,16 +339,8 @@ public struct HLSChannels: Equatable, FailableStringLiteralConvertible {
331339
return identifiers
332340
}
333341

334-
private static func parseSpecialUsageIdentifiers(str: Substring) -> [SpecialUsageIdentifier]? {
335-
let split = str.split(separator: ",")
336-
var identifiers = [SpecialUsageIdentifier]()
337-
for id in split {
338-
guard let specialUsageId = SpecialUsageIdentifier(str: id) else {
339-
return nil
340-
}
341-
identifiers.append(specialUsageId)
342-
}
343-
return identifiers
342+
private static func parseSpecialUsageIdentifiers(str: Substring) -> [SpecialUsageIdentifier] {
343+
str.split(separator: ",").map { SpecialUsageIdentifier(str: $0) }
344344
}
345345
}
346346

@@ -435,36 +435,42 @@ public struct HLSVideoLayout: Equatable, FailableStringLiteralConvertible {
435435
/// is predominantly monoscopic then the Multivariant Playlist SHOULD specify `REQ-VIDEO-LAYOUT="CH-MONO,CH-STEREO"`.
436436
public let predominantLayout: VideoLayoutIdentifier
437437

438-
public enum VideoLayoutIdentifier: String {
438+
public enum VideoLayoutIdentifier: RawRepresentable, Equatable {
439439
/// Monoscopic.
440440
///
441441
/// Indicates that a single image is present.
442-
case chMono = "CH-MONO"
442+
case chMono
443443
/// Stereoscopic.
444444
///
445445
/// Indicates that both left and right eye images are present.
446-
case chStereo = "CH-STEREO"
446+
case chStereo
447+
/// The video layout identifier is not recognized by this library; however, we provide the raw identifier string
448+
/// that existed in the manifest.
449+
case unrecognized(String)
450+
451+
public var rawValue: String {
452+
switch self {
453+
case .chMono: return "CH-MONO"
454+
case .chStereo: return "CH-STEREO"
455+
case .unrecognized(let string): return string
456+
}
457+
}
458+
459+
public init?(rawValue: String) {
460+
self.init(str: Substring(rawValue))
461+
}
447462

448-
init?(str: Substring) {
463+
init(str: Substring) {
449464
switch str {
450465
case "CH-MONO": self = .chMono
451466
case "CH-STEREO": self = .chStereo
452-
default: return nil
467+
default: self = .unrecognized(String(str))
453468
}
454469
}
455470
}
456471

457472
public init?(string: String) {
458-
var layouts = [VideoLayoutIdentifier]()
459-
for str in string.split(separator: ",") {
460-
if let layout = VideoLayoutIdentifier(str: str) {
461-
layouts.append(layout)
462-
} else {
463-
// Favor failing to parse the whole array if we find an unrecognized layout, so that we don't risk mis-
464-
// reporting the existing layouts.
465-
return nil
466-
}
467-
}
473+
let layouts = string.split(separator: ",").map { VideoLayoutIdentifier(str: $0) }
468474
guard let firstLayout = layouts.first else {
469475
return nil
470476
}

mambaTests/Tag Validator Tests/GenericDictionaryTagValidatorTests.swift

+2-4
Original file line numberDiff line numberDiff line change
@@ -762,8 +762,7 @@ class GenericDictionaryTagValidatorTests: XCTestCase {
762762
.resolution,
763763
.frameRate,
764764
.hdcpLevel,
765-
.videoRange,
766-
.reqVideoLayout]
765+
.videoRange]
767766

768767
validate(tag: PantosTag.EXT_X_STREAM_INF,
769768
tagData: tagData,
@@ -913,8 +912,7 @@ class GenericDictionaryTagValidatorTests: XCTestCase {
913912
.programId,
914913
.resolution,
915914
.hdcpLevel,
916-
.videoRange,
917-
.reqVideoLayout]
915+
.videoRange]
918916

919917
validate(tag: PantosTag.EXT_X_I_FRAME_STREAM_INF,
920918
tagData: tagData,

mambaTests/Util Tests/Value Types/HLSChannelsTests.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,13 @@ class HLSChannelsTests: XCTestCase {
124124
XCTAssertEqual(expectedChannels, actualChannels)
125125
}
126126

127-
// In the case that we don't recognize the special usage identifier, I think it is better to fail parsing the entire
128-
// CHANNELS attribute, as otherwise we risk misleading the user of the library into thinking that the special usage
129-
// is less than it actually is.
130127
func test_sixChannelUnknownSpecialUsageIdentifier() {
131128
let actualChannels = HLSChannels(string: sixChannelUnknownSpecialUsageIdentifier)
132-
XCTAssertNil(actualChannels)
129+
let expectedChannels = HLSChannels(
130+
count: 6,
131+
spatialAudioCodingIdentifiers: [],
132+
specialUsageIdentifiers: [.unrecognized("NEW-IDENTIFIER")]
133+
)
134+
XCTAssertEqual(expectedChannels, actualChannels)
133135
}
134136
}

mambaTests/Util Tests/Value Types/HLSVideoLayoutTests.swift

+20-6
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@ import mamba
2323

2424
class HLSVideoLayoutTests: XCTestCase {
2525
let empty = ""
26-
let invalidVideoLayout = "CH-TRI"
26+
let unrecognizedVideoLayout = "CH-TRI"
2727
let monoLayout = "CH-MONO"
2828
let stereoLayout = "CH-STEREO"
2929
let stereoWithMonoLayout = "CH-STEREO,CH-MONO"
3030
let monoWithStereoLayout = "CH-MONO,CH-STEREO"
31-
let monoWithStereoWithUnknownLayout = "CH-MONO,CH-STEREO,CH-TRI"
31+
let monoWithStereoWithUnrecognizedLayout = "CH-MONO,CH-STEREO,CH-TRI"
3232

3333
func test_empty() {
3434
XCTAssertNil(HLSVideoLayout(string: empty))
3535
}
3636

37-
func test_invalidVideoLayout() {
38-
XCTAssertNil(HLSVideoLayout(string: invalidVideoLayout))
37+
func test_unrecognizedVideoLayout() {
38+
guard let videoLayout = HLSVideoLayout(string: unrecognizedVideoLayout) else {
39+
return XCTFail("Expected to parse REQ-VIDEO-LAYOUT from \(unrecognizedVideoLayout).")
40+
}
41+
XCTAssertEqual(videoLayout.layouts, [.unrecognized(unrecognizedVideoLayout)])
42+
XCTAssertEqual(videoLayout.predominantLayout, .unrecognized(unrecognizedVideoLayout))
43+
XCTAssertFalse(videoLayout.contains(.chMono))
44+
XCTAssertFalse(videoLayout.contains(.chStereo))
45+
XCTAssertTrue(videoLayout.contains(.unrecognized(unrecognizedVideoLayout)))
3946
}
4047

4148
func test_monoLayout() {
@@ -78,7 +85,14 @@ class HLSVideoLayoutTests: XCTestCase {
7885
XCTAssertTrue(videoLayout.contains(.chStereo))
7986
}
8087

81-
func test_monoWithStereoWithUnknownLayout() {
82-
XCTAssertNil(HLSVideoLayout(string: monoWithStereoWithUnknownLayout))
88+
func test_monoWithStereoWithUnrecognizedLayout() {
89+
guard let videoLayout = HLSVideoLayout(string: monoWithStereoWithUnrecognizedLayout) else {
90+
return XCTFail("Expected to parse REQ-VIDEO-LAYOUT from \(monoWithStereoWithUnrecognizedLayout).")
91+
}
92+
XCTAssertEqual(videoLayout.layouts, [.chMono, .chStereo, .unrecognized("CH-TRI")])
93+
XCTAssertEqual(videoLayout.predominantLayout, .chMono)
94+
XCTAssertTrue(videoLayout.contains(.chMono))
95+
XCTAssertTrue(videoLayout.contains(.chStereo))
96+
XCTAssertTrue(videoLayout.contains(.unrecognized("CH-TRI")))
8397
}
8498
}

0 commit comments

Comments
 (0)