feat: configure duration-aware progress in compact titles
This commit is contained in:
@@ -2,6 +2,21 @@ import AppKit
|
||||
import SwiftUI
|
||||
import TerebiConfiguration
|
||||
|
||||
func compactProgressText(position: Double, duration: Double, format: CompactTitleProgress) -> String? {
|
||||
guard format != .none, duration.isFinite, duration > 0, position.isFinite else { return nil }
|
||||
let position = min(max(0, position), duration)
|
||||
if format == .percentage { return String(Int((position / duration * 100).rounded(.down))) }
|
||||
let total = Int(min(duration, Double(Int.max / 2)))
|
||||
let current = Int(min(position, Double(Int.max / 2)))
|
||||
if duration < 60 { return "\(current) / \(total) s" }
|
||||
let divisor = duration < 3600 ? 1 : 60
|
||||
let currentUnits = current / divisor
|
||||
let totalUnits = total / divisor
|
||||
let currentTime = String(format: "%02lld:%02lld", Int64(currentUnits / 60), Int64(currentUnits % 60))
|
||||
let totalTime = String(format: "%02lld:%02lld", Int64(totalUnits / 60), Int64(totalUnits % 60))
|
||||
return "\(currentTime) / \(totalTime) \(duration < 3600 ? "min" : "h")"
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class PlayerModel: ObservableObject {
|
||||
static var startupConfiguration = PlayerConfiguration()
|
||||
@@ -30,6 +45,11 @@ final class PlayerModel: ObservableObject {
|
||||
@Published private(set) var isOnTop = false
|
||||
let compactWindow: CompactWindowController
|
||||
let keyBindings: KeyBindings
|
||||
let compactTitleProgress: CompactTitleProgress
|
||||
var compactProgress: String? {
|
||||
guard !isBusy, !isLiveStream, fileName != nil, state.status != PLAYER_ERROR else { return nil }
|
||||
return compactProgressText(position: state.position, duration: state.duration, format: compactTitleProgress)
|
||||
}
|
||||
var playbackIntent: Bool? { transition.playbackIntent }
|
||||
@Published private(set) var fullScreenIntent: Bool?
|
||||
@Published private(set) var compactIntent: Bool?
|
||||
@@ -85,6 +105,7 @@ final class PlayerModel: ObservableObject {
|
||||
qualityClock: @escaping () -> Double = { ProcessInfo.processInfo.systemUptime }) {
|
||||
self.qualityClock = qualityClock
|
||||
keyBindings = configuration.keys
|
||||
compactTitleProgress = configuration.compactTitleProgress
|
||||
automaticYouTubeQuality = configuration.automaticQuality
|
||||
showsBufferInfo = configuration.showBufferInfo
|
||||
compactWindow = CompactWindowController(snappingEnabled: configuration.snapToEdges)
|
||||
@@ -1639,11 +1660,23 @@ private struct PlayerContent: View {
|
||||
}
|
||||
|
||||
ZStack(alignment: .top) {
|
||||
Text(model.fileName ?? "terebi")
|
||||
let progress = model.isCompact || model.compactWindow.isTransitioning ? model.compactProgress : nil
|
||||
HStack(spacing: 0) {
|
||||
Text(model.fileName ?? "terebi")
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.frame(minWidth: 0, maxWidth: progress == nil ? nil : .infinity, alignment: .leading)
|
||||
.clipped()
|
||||
if let progress {
|
||||
Text(" · \(progress)")
|
||||
.monospacedDigit()
|
||||
.accessibilityLabel(model.compactTitleProgress == .percentage ? "\(progress) percent" : progress)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.help(model.fileName ?? "terebi")
|
||||
.accessibilityElement(children: .combine)
|
||||
.help((model.fileName ?? "terebi") + (progress.map { " · \($0)" } ?? ""))
|
||||
.padding(8)
|
||||
.background(PlayerGlass(cornerRadius: 10).clipShape(RoundedRectangle(cornerRadius: 10)).allowsHitTesting(false))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import Foundation
|
||||
import TOMLDecoder
|
||||
|
||||
public enum CompactTitleProgress: String, Decodable, Sendable {
|
||||
case time, percentage, none
|
||||
}
|
||||
|
||||
public struct PlayerConfiguration: Equatable, Sendable {
|
||||
public var showBufferInfo: Bool
|
||||
public var automaticQuality: Bool
|
||||
public var snapToEdges: Bool
|
||||
public var compactTitleProgress: CompactTitleProgress
|
||||
public var keys: KeyBindings
|
||||
|
||||
public init(showBufferInfo: Bool = false, automaticQuality: Bool = false, snapToEdges: Bool = true,
|
||||
keys: KeyBindings = KeyBindings()) {
|
||||
compactTitleProgress: CompactTitleProgress = .time, keys: KeyBindings = KeyBindings()) {
|
||||
self.showBufferInfo = showBufferInfo
|
||||
self.automaticQuality = automaticQuality
|
||||
self.snapToEdges = snapToEdges
|
||||
self.compactTitleProgress = compactTitleProgress
|
||||
self.keys = keys
|
||||
}
|
||||
|
||||
@@ -25,6 +31,7 @@ public struct PlayerConfiguration: Equatable, Sendable {
|
||||
"", "[ui]",
|
||||
"buffer_info = \(defaults.showBufferInfo)",
|
||||
"compact_snap_to_edges = \(defaults.snapToEdges)",
|
||||
"compact_title_progress = \"\(defaults.compactTitleProgress.rawValue)\"",
|
||||
"", "[keys]"
|
||||
]
|
||||
for (name, action) in PlayerAction.namedActions {
|
||||
@@ -77,6 +84,7 @@ public struct PlayerConfiguration: Equatable, Sendable {
|
||||
return Self(showBufferInfo: values.ui?.bufferInfo ?? false,
|
||||
automaticQuality: values.playback?.automaticQuality ?? false,
|
||||
snapToEdges: values.ui?.compactSnapToEdges ?? true,
|
||||
compactTitleProgress: values.ui?.compactTitleProgress ?? .time,
|
||||
keys: try KeyBindings(overrides: values.keys?.overrides ?? [:]))
|
||||
} catch {
|
||||
throw ConfigurationError(message: "\(url.path): \(diagnostic(for: error, data: data))")
|
||||
@@ -163,11 +171,14 @@ public struct PlayerConfiguration: Equatable, Sendable {
|
||||
private struct UIConfiguration {
|
||||
let bufferInfo: Bool?
|
||||
let compactSnapToEdges: Bool?
|
||||
let compactTitleProgress: CompactTitleProgress?
|
||||
|
||||
init(container: KeyedDecodingContainer<SettingKey>) throws {
|
||||
try validate(container, allowed: ["buffer_info", "compact_snap_to_edges"])
|
||||
try validate(container, allowed: ["buffer_info", "compact_snap_to_edges", "compact_title_progress"])
|
||||
bufferInfo = try container.decodeIfPresent(Bool.self, forKey: SettingKey("buffer_info"))
|
||||
compactSnapToEdges = try container.decodeIfPresent(Bool.self, forKey: SettingKey("compact_snap_to_edges"))
|
||||
compactTitleProgress = try container.decodeIfPresent(CompactTitleProgress.self,
|
||||
forKey: SettingKey("compact_title_progress"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user