Files
Flaro/Packages/FlixyKit/Sources/FlixyDesign/MiniPlayerPlacement.swift
T
ScarriffleandClaude Opus 5 12bb49615b Player: die Qualitätsleiter des Servers, Bitrate im Blick, Doppeltipp
Die vier erfundenen Stufen weichen der Leiter, die der Server selbst
meldet: 64, 96, 208, 320, 720, 1500, 2000, 3000, 4000, 8000, 10000,
12000, 20000 kbps mit den Auflösungen, die Plex dafür vorsieht. Eine
eigene Leiter hieße, Stufen anzubieten, die der Transcoder nicht kennt —
er rundet dann still auf die nächste, und die Anzeige log.

Angeboten wird nur, was zur Quelle passt. Eine 720p-Datei auf 1080p
hochzurechnen bringt kein Bild dazu, und die Stufen sähen alle gleich aus.

Oben im Blatt steht, was tatsächlich über die Leitung kommt — gemessene
Bitrate, Auflösung, verworfene Bilder. Der Unterschied zur gewählten
Stufe ist keine Haarspalterei: die eine sagt, was erlaubt ist, die andere,
was ankommt. Ruckelt es trotz hoher Stufe, steht die Antwort dort.

„Nur Ton" gibt es, aber ehrlich beschriftet: Plex kennt für Videos keinen
reinen Tonstrom, also wird die unterste Sprosse geholt und das Bild
ausgeblendet.

Dazu Doppeltipp links und rechts für zehn Sekunden zurück und vor. Die
Reihenfolge der Gesten ist entscheidend — das einfache Tippen verschluckt
sonst das doppelte.

Die Kachel hängt nicht mehr in vier Ecken fest, sondern klebt links oder
rechts und ist in der Höhe frei. Vier Ecken sind schnell gebaut, aber im
Weg: steht sie über einem Knopf, bleiben drei Orte, die ebenso stören.
Größer wird sie durch Zusammen- und Auseinanderziehen; der Griff ist weg,
er saß zwangsläufig auf dem Bild und stritt sich mit dem Verschieben.

Und die Begriffe: „Motor" für engine sagt kein deutscher Entwickler.
Fachwörter, die im Deutschen englisch bleiben, bleiben jetzt englisch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 21:50:39 +02:00

82 lines
3.4 KiB
Swift

import Foundation
import CoreGraphics
/// Wo die schwebende Videokachel sitzt.
///
/// Sie klebt an der linken oder rechten Seite und ist dort **frei in der
/// Höhe** — nicht auf vier Ecken festgelegt. Vier Ecken sind schnell gebaut,
/// aber im Weg: steht die Kachel unten rechts über einem Knopf, bleibt nur
/// die Wahl zwischen vier Orten, von denen drei ebenso stören.
///
/// Eigener Typ statt Rechnungen in der Ansicht, aus demselben Grund wie bei
/// `GuideLayout`: hier geht lautlos schief, was man nicht sieht. Eine Kachel,
/// die halb aus dem Bild ragt, fällt erst auf dem Gerät auf — und dann als
/// Gefühl, nicht als Fehler.
public struct MiniPlayerPlacement: Sendable, Equatable {
public enum Side: String, Sendable, CaseIterable, Codable {
case leading, trailing
}
/// Der sichtbare Bereich, in dem die Kachel sich bewegen darf.
public let bounds: CGSize
public let tile: CGSize
/// Abstand zum Rand. Eine Kachel, die den Rand berührt, sieht aus wie ein
/// Fehler statt wie eine Entscheidung.
public let inset: CGFloat
public init(bounds: CGSize, tile: CGSize, inset: CGFloat = FlixySpacing.regular) {
self.bounds = bounds
self.tile = tile
self.inset = inset
}
private var halfWidth: CGFloat { tile.width / 2 }
private var halfHeight: CGFloat { tile.height / 2 }
private var minimumX: CGFloat { inset + halfWidth }
private var maximumX: CGFloat { max(minimumX, bounds.width - inset - halfWidth) }
private var minimumY: CGFloat { inset + halfHeight }
private var maximumY: CGFloat { max(minimumY, bounds.height - inset - halfHeight) }
/// An welche Seite eine losgelassene Kachel gehört.
///
/// Entschieden wird nach dem **Mittelpunkt** der Kachel, nicht nach dem
/// Finger: sonst springt eine Kachel, die man am Rand angefasst hat, auf
/// die falsche Seite.
public func nearestSide(toCenter center: CGPoint) -> Side {
center.x < bounds.width / 2 ? .leading : .trailing
}
/// Wie weit unten die Kachel sitzt: 0 ganz oben, 1 ganz unten.
///
/// Als Anteil statt als Punktwert, damit die Kachel beim Drehen des Geräts
/// an derselben *relativen* Stelle bleibt, statt unten herauszufallen.
public func verticalFraction(forCenter center: CGPoint) -> Double {
let travel = maximumY - minimumY
guard travel > 0 else { return 0 }
return min(max(Double((center.y - minimumY) / travel), 0), 1)
}
public func center(side: Side, verticalFraction: Double) -> CGPoint {
let fraction = min(max(verticalFraction, 0), 1)
return CGPoint(
x: side == .leading ? minimumX : maximumX,
y: minimumY + (maximumY - minimumY) * CGFloat(fraction)
)
}
/// Hält einen Mittelpunkt im sichtbaren Bereich.
///
/// Während des Ziehens, damit die Kachel nicht hinter den Rand rutscht und
/// dort unerreichbar wird — der Finger wäre dann schneller als das Ziel.
public func clamped(center: CGPoint) -> CGPoint {
CGPoint(
// `max(min…)` in dieser Reihenfolge, weil bei einem Bereich, der
// schmaler ist als die Kachel, sonst der untere Anschlag verliert
// und die Kachel rechts hinausragt.
x: max(minimumX, min(maximumX, center.x)),
y: max(minimumY, min(maximumY, center.y))
)
}
}