Mini-Player: die Wiedergabe überlebt das Zurückgehen
Wer im Fernsehen etwas sieht und nebenher nachsehen will, was sonst läuft, musste bisher abbrechen. Plex' eigene Handy-Apps können das auch nicht — kein Grund, es hier genauso zu machen. Der eigentliche Umbau ist nicht die Kachel, sondern der Besitz. Die Wiedergabe hing an der Sichtbarkeit der Ansicht, und zwar dreifach: das Modell war `@State` im Player, `onDisappear` hat den Motor abgeräumt, und die Bildebene entstand in `makeUIView` neu. Zurückgehen hieß deshalb: Motor tot, Transcoder aus, beim Live-TV der Tuner freigegeben. Jetzt gehört das Modell der Wurzelansicht, die immer da ist. Verkleinern und Vergrößern wechseln nur noch die Darstellung; beendet wird die Wiedergabe an genau einer Stelle, und zwar weil jemand es will. Die Bildebene hält das Modell selbst — dasselbe Muster, das VLC seit jeher verwendet. Dieselbe `UIView` wandert zwischen Kachel und Vollbild, statt neu zu entstehen; sonst setzte das Bild bei jedem Wechsel neu an. Bild-in-Bild wandert mit und bekommt endlich einen Delegaten: bisher war es eine Einbahnstraße, aus der man nicht in die App zurückkam. Die Kachel schwebt und lässt sich in jede Ecke ziehen, statt als Leiste unten zu kleben — eine Leiste nähme jeder Liste Platz weg und müsste in jedem Bildschirm bedacht werden. Die Eckenberechnung steht in MiniPlayerPlacement und ist geprüft: in einer View wäre eine Kachel, die halb aus dem Bild ragt, erst auf dem Gerät aufgefallen. Der Pfeil oben verkleinert, ein neuer Knopf daneben beendet wirklich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d28df346f2
commit
26c909a2e2
@@ -11,11 +11,20 @@ import FlixyVLC
|
||||
/// `UIView`.
|
||||
struct VideoSurface: View {
|
||||
let engine: (any VideoPlayerEngine)?
|
||||
/// Die Bildebene des Systemplayers, gehalten vom Modell.
|
||||
///
|
||||
/// **Nicht** hier gebaut. Eine `AVPlayerLayer`, die in `makeUIView`
|
||||
/// entsteht, stirbt mit jedem Identitätswechsel der Ansicht — und genau
|
||||
/// das passiert beim Wechsel zwischen Kachel und Vollbild. Das Bild
|
||||
/// setzte dann jedes Mal neu an, sichtbar als Zucken und Schwarzbild.
|
||||
/// VLC macht es seit jeher richtig und reicht seine `videoView` durch;
|
||||
/// hier ist es jetzt derselbe Weg.
|
||||
let surface: PlayerLayerView
|
||||
|
||||
var body: some View {
|
||||
switch engine {
|
||||
case let avEngine as AVPlayerEngine:
|
||||
AVPlayerSurface(player: avEngine.player)
|
||||
case is AVPlayerEngine:
|
||||
UIViewContainer(view: surface)
|
||||
case let vlcEngine as VLCPlayerEngine:
|
||||
UIViewContainer(view: vlcEngine.videoView)
|
||||
default:
|
||||
@@ -24,37 +33,6 @@ struct VideoSurface: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Bildebene des Systemplayers, samt Bild-in-Bild.
|
||||
private struct AVPlayerSurface: UIViewRepresentable {
|
||||
let player: AVPlayer
|
||||
|
||||
func makeUIView(context: Context) -> PlayerLayerView {
|
||||
let view = PlayerLayerView()
|
||||
view.playerLayer.player = player
|
||||
view.playerLayer.videoGravity = .resizeAspect
|
||||
context.coordinator.setUp(with: view.playerLayer)
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: PlayerLayerView, context: Context) {
|
||||
uiView.playerLayer.player = player
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator { Coordinator() }
|
||||
|
||||
final class Coordinator {
|
||||
private var pictureInPicture: AVPictureInPictureController?
|
||||
|
||||
func setUp(with layer: AVPlayerLayer) {
|
||||
guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
|
||||
pictureInPicture = AVPictureInPictureController(playerLayer: layer)
|
||||
// Beim Verlassen der App ins Bild-in-Bild wechseln, statt die
|
||||
// Wiedergabe anzuhalten.
|
||||
pictureInPicture?.canStartPictureInPictureAutomaticallyFromInline = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Eine `UIView`, deren Ebene eine `AVPlayerLayer` ist.
|
||||
///
|
||||
/// Der übliche Weg: eine Ebene von Hand als Unterebene einzuhängen bedeutet,
|
||||
@@ -66,6 +44,9 @@ final class PlayerLayerView: UIView {
|
||||
}
|
||||
|
||||
/// Hängt eine bestehende `UIView` in den SwiftUI-Baum.
|
||||
///
|
||||
/// Umhängen statt neu bauen: dieselbe `UIView` wandert zwischen Kachel und
|
||||
/// Vollbild hin und her, und die Wiedergabe merkt davon nichts.
|
||||
private struct UIViewContainer: UIViewRepresentable {
|
||||
let view: UIView
|
||||
|
||||
|
||||
Reference in New Issue
Block a user