Skip to content

Commit 4391eda

Browse files
committed
Initial pass at Locator.Locations.time
1 parent 6d47284 commit 4391eda

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// Copyright 2024 Readium Foundation. All rights reserved.
3+
// Use of this source code is governed by the BSD-style license
4+
// available in the top-level LICENSE file of the project.
5+
//
6+
7+
import Foundation
8+
9+
/// Audio extensions for `Locator.Locations`.
10+
public extension Locator.Locations {
11+
enum TimeFragment: Equatable, Sendable {
12+
case offset(Double)
13+
case duration(Double)
14+
case range(Double, Double)
15+
16+
init?(offset: Double?, duration: Double?) {
17+
switch (offset, duration) {
18+
case let (.some(offset), .some(duration)):
19+
self = .range(offset, duration)
20+
case let (.some(offset), .none):
21+
self = .offset(offset)
22+
case let (.none, .some(duration)):
23+
self = .duration(duration)
24+
case (.none, .none):
25+
return nil
26+
}
27+
}
28+
29+
public var offset: Double? {
30+
switch self {
31+
case let .offset(offset):
32+
return offset
33+
case let .range(offset, _):
34+
return offset
35+
default:
36+
return nil
37+
}
38+
}
39+
40+
public var duration: Double? {
41+
switch self {
42+
case let .duration(duration):
43+
return duration
44+
case let .range(_, duration):
45+
return duration
46+
default:
47+
return nil
48+
}
49+
}
50+
}
51+
52+
private static let timeFragmentRegex = try! NSRegularExpression(pattern: #"t=([^,]*),?([^,]*)"#)
53+
54+
var time: TimeFragment? {
55+
for fragment in fragments {
56+
let range = NSRange(fragment.startIndex ..< fragment.endIndex, in: fragment)
57+
if let match = Self.timeFragmentRegex.firstMatch(in: fragment, range: range) {
58+
let group1NSRange = match.range(at: 1)
59+
let group2NSRange = match.range(at: 2)
60+
var offset: Double?
61+
var duration: Double?
62+
if group1NSRange.location != NSNotFound, let group1Range = Range(group1NSRange, in: fragment) {
63+
offset = Double(fragment[group1Range])
64+
}
65+
if group2NSRange.location != NSNotFound, let group2Range = Range(group2NSRange, in: fragment) {
66+
duration = Double(fragment[group2Range])
67+
}
68+
return TimeFragment(offset: offset, duration: duration)
69+
}
70+
}
71+
return nil
72+
}
73+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// Copyright 2024 Readium Foundation. All rights reserved.
3+
// Use of this source code is governed by the BSD-style license
4+
// available in the top-level LICENSE file of the project.
5+
//
6+
7+
@testable import R2Shared
8+
import XCTest
9+
10+
class LocatorLocationsAudioTests: XCTestCase {
11+
func testNoFragment() {
12+
XCTAssertNil(Locator.Locations().time)
13+
}
14+
15+
func testMalformedFragment() {
16+
XCTAssertNil(Locator.Locations(fragments: ["t=one"]).time)
17+
}
18+
19+
func testValidFragments() {
20+
continueAfterFailure = false
21+
for offsetStr in ["", "1", "1.0", "1.1"] {
22+
for durationStr in ["", ",", ",1", ",1.0", ",1.1"] {
23+
let val = offsetStr + durationStr
24+
if val == "" || val == "," {
25+
continue
26+
}
27+
let locations = Locator.Locations(fragments: ["t=\(val)"])
28+
let time = locations.time
29+
switch time {
30+
case let .offset(offset):
31+
XCTAssertEqual(offset, Double(offsetStr))
32+
case let .duration(duration):
33+
XCTAssertEqual(duration, Double(durationStr.replacingPrefix(",", by: "")))
34+
case let .range(offset, duration):
35+
XCTAssertEqual(offset, Double(offsetStr))
36+
XCTAssertEqual(duration, Double(durationStr.replacingPrefix(",", by: "")))
37+
case nil:
38+
XCTAssertNotNil(time)
39+
}
40+
}
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)