Skip to content

Commit

Permalink
Add Locator.Locations.time (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
domkm authored Jan 30, 2024
1 parent 451d0e9 commit efba3a0
Show file tree
Hide file tree
Showing 5 changed files with 441 additions and 64 deletions.
24 changes: 1 addition & 23 deletions Sources/Navigator/Audiobook/AudioNavigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ open class _AudioNavigator: _MediaNavigator, AudioSessionUser, Loggable {
}

// Seeks to time
let time = locator.time(forDuration: resourceDuration) ?? 0
let time = locator.locations.time?.begin ?? ((resourceDuration ?? 0) * (locator.locations.progression ?? 0))
player.seek(to: CMTime(seconds: time, preferredTimescale: 1000)) { [weak self] finished in
if let self = self, finished {
self.delegate?.navigator(self, didJumpTo: locator)
Expand Down Expand Up @@ -385,28 +385,6 @@ open class _AudioNavigator: _MediaNavigator, AudioSessionUser, Loggable {
}
}

private extension Locator {
private static let timeFragmentRegex = try! NSRegularExpression(pattern: #"t=(\d+(?:\.\d+)?)"#)

// FIXME: Should probably be in `Locator` itself.
func time(forDuration duration: Double? = nil) -> Double? {
if let progression = locations.progression, let duration = duration {
return progression * duration
} else {
for fragment in locations.fragments {
let range = NSRange(fragment.startIndex ..< fragment.endIndex, in: fragment)
if let match = Self.timeFragmentRegex.firstMatch(in: fragment, range: range) {
let matchRange = match.range(at: 1)
if matchRange.location != NSNotFound, let range = Range(matchRange, in: fragment) {
return Double(fragment[range])
}
}
}
}
return nil
}
}

private extension MediaPlaybackState {
init(_ timeControlStatus: AVPlayer.TimeControlStatus) {
switch timeControlStatus {
Expand Down
75 changes: 75 additions & 0 deletions Sources/Shared/Publication/Extensions/Audio/Locator+Audio.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Copyright 2024 Readium Foundation. All rights reserved.
// Use of this source code is governed by the BSD-style license
// available in the top-level LICENSE file of the project.
//

import Foundation

/// Audio extensions for `Locator.Locations`.
public extension Locator.Locations {
enum TimeFragment: Equatable, Sendable {
case begin(Double)
case end(Double)
case interval(Double, Double)

init?(begin: Double?, end: Double?) {
switch (begin, end) {
case let (.some(begin), .some(end)):
self = .interval(begin, end)
case let (.some(begin), .none):
self = .begin(begin)
case let (.none, .some(end)):
self = .end(end)
case (.none, .none):
return nil
}
}

public var begin: Double? {
switch self {
case let .begin(begin):
return begin
case let .interval(begin, _):
return begin
default:
return nil
}
}

public var end: Double? {
switch self {
case let .end(end):
return end
case let .interval(_, end):
return end
default:
return nil
}
}
}

private static let timeFragmentRegex = try! NSRegularExpression(pattern: #"t=([^,]*),?([^,]*)"#)

/// The Temporal Dimension media fragment, if it exists.
/// https://www.w3.org/TR/media-frags/#media-fragment-syntax
var time: TimeFragment? {
for fragment in fragments {
let range = NSRange(fragment.startIndex ..< fragment.endIndex, in: fragment)
if let match = Self.timeFragmentRegex.firstMatch(in: fragment, range: range) {
let group1NSRange = match.range(at: 1)
let group2NSRange = match.range(at: 2)
var begin: Double?
var end: Double?
if group1NSRange.location != NSNotFound, let group1Range = Range(group1NSRange, in: fragment) {
begin = Double(fragment[group1Range])
}
if group2NSRange.location != NSNotFound, let group2Range = Range(group2NSRange, in: fragment) {
end = Double(fragment[group2Range])
}
return TimeFragment(begin: begin, end: end)
}
}
return nil
}
}
Loading

0 comments on commit efba3a0

Please sign in to comment.