feat(*): subsonic client with avfoundation playback and apple music ui
This commit is contained in:
commit
e5bb559d7f
93 files changed
+7517
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
|
||||
public struct LyricLine: Hashable, Sendable, Identifiable {
|
||||
public let time: TimeInterval
|
||||
public let text: String
|
||||
|
||||
public var id: TimeInterval {
|
||||
time
|
||||
}
|
||||
|
||||
public init(time: TimeInterval, text: String) {
|
||||
self.time = time
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
public struct Lyrics: Hashable, Sendable {
|
||||
public let lines: [LyricLine]
|
||||
public let isSynced: Bool
|
||||
|
||||
public init(lines: [LyricLine], isSynced: Bool) {
|
||||
self.lines = lines
|
||||
self.isSynced = isSynced
|
||||
}
|
||||
|
||||
public init?(lrc: String) {
|
||||
var lines: [LyricLine] = []
|
||||
var plain: [String] = []
|
||||
for raw in lrc.split(whereSeparator: \.isNewline) {
|
||||
var rest = Substring(raw)
|
||||
var stamps: [TimeInterval] = []
|
||||
while rest.first == "[", let close = rest.firstIndex(of: "]") {
|
||||
let tag = rest[rest.index(after: rest.startIndex) ..< close]
|
||||
rest = rest[rest.index(after: close)...]
|
||||
if let time = Self.time(tag) {
|
||||
stamps.append(time)
|
||||
}
|
||||
}
|
||||
let text = rest.trimmingCharacters(in: .whitespaces)
|
||||
if stamps.isEmpty {
|
||||
if !text.isEmpty {
|
||||
plain.append(text)
|
||||
}
|
||||
} else {
|
||||
lines += stamps.map { LyricLine(time: $0, text: text) }
|
||||
}
|
||||
}
|
||||
if !lines.isEmpty {
|
||||
self.init(lines: lines.sorted { $0.time < $1.time }, isSynced: true)
|
||||
} else if !plain.isEmpty {
|
||||
self.init(lines: plain.map { LyricLine(time: 0, text: $0) }, isSynced: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func index(at time: TimeInterval) -> Int? {
|
||||
guard isSynced else { return nil }
|
||||
var found: Int?
|
||||
for (index, line) in lines.enumerated() where line.time <= time {
|
||||
found = index
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
private static func time(_ tag: Substring) -> TimeInterval? {
|
||||
let parts = tag.split(separator: ":")
|
||||
guard parts.count == 2, let minutes = Double(parts[0]), let seconds = Double(parts[1]) else { return nil }
|
||||
return minutes * 60 + seconds
|
||||
}
|
||||
}
|
||||
|
||||
public protocol LyricsSource: LibrarySource {
|
||||
func lyrics(for track: EntityID) async throws -> Lyrics?
|
||||
}
|
||||
Reference in new issue
Block a user