131 lines
4.1 KiB
Swift
131 lines
4.1 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import Defaults
|
|
import SwiftUI
|
|
import VLCUI
|
|
|
|
struct VideoPlayer: View {
|
|
|
|
enum OverlayType {
|
|
case chapters
|
|
case confirmClose
|
|
case main
|
|
case smallMenu
|
|
}
|
|
|
|
@Environment(\.scenePhase)
|
|
private var scenePhase
|
|
|
|
@EnvironmentObject
|
|
private var router: VideoPlayerCoordinator.Router
|
|
|
|
@ObservedObject
|
|
private var currentProgressHandler: VideoPlayerManager.CurrentProgressHandler
|
|
@ObservedObject
|
|
private var videoPlayerManager: VideoPlayerManager
|
|
|
|
@State
|
|
private var isPresentingOverlay: Bool = false
|
|
@State
|
|
private var isScrubbing: Bool = false
|
|
|
|
@ViewBuilder
|
|
private var playerView: some View {
|
|
ZStack {
|
|
VLCVideoPlayer(configuration: videoPlayerManager.currentViewModel.vlcVideoPlayerConfiguration)
|
|
.proxy(videoPlayerManager.proxy)
|
|
.onTicksUpdated { ticks, _ in
|
|
|
|
let newSeconds = ticks / 1000
|
|
let newProgress = CGFloat(newSeconds) / CGFloat(videoPlayerManager.currentViewModel.item.runTimeSeconds)
|
|
currentProgressHandler.progress = newProgress
|
|
currentProgressHandler.seconds = newSeconds
|
|
|
|
guard !isScrubbing else { return }
|
|
currentProgressHandler.scrubbedProgress = newProgress
|
|
}
|
|
.onStateUpdated { state, _ in
|
|
|
|
videoPlayerManager.onStateUpdated(newState: state)
|
|
|
|
if state == .ended {
|
|
if let _ = videoPlayerManager.nextViewModel,
|
|
Defaults[.VideoPlayer.autoPlayEnabled]
|
|
{
|
|
videoPlayerManager.selectNextViewModel()
|
|
} else {
|
|
router.dismissCoordinator()
|
|
}
|
|
}
|
|
}
|
|
|
|
VideoPlayer.Overlay()
|
|
.eraseToAnyView()
|
|
.environmentObject(videoPlayerManager)
|
|
.environmentObject(videoPlayerManager.currentProgressHandler)
|
|
.environmentObject(videoPlayerManager.currentViewModel!)
|
|
.environmentObject(videoPlayerManager.proxy)
|
|
.environment(\.isPresentingOverlay, $isPresentingOverlay)
|
|
.environment(\.isScrubbing, $isScrubbing)
|
|
}
|
|
.onChange(of: videoPlayerManager.currentProgressHandler.scrubbedProgress) { _, newValue in
|
|
guard !newValue.isNaN && !newValue.isInfinite else {
|
|
return
|
|
}
|
|
DispatchQueue.main.async {
|
|
videoPlayerManager.currentProgressHandler
|
|
.scrubbedSeconds = Int(CGFloat(videoPlayerManager.currentViewModel.item.runTimeSeconds) * newValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var loadingView: some View {
|
|
Text(L10n.retrievingMediaInformation)
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
|
|
Color.black
|
|
|
|
if let _ = videoPlayerManager.currentViewModel {
|
|
playerView
|
|
} else {
|
|
loadingView
|
|
}
|
|
}
|
|
.ignoresSafeArea()
|
|
.onChange(of: isScrubbing) { _, newValue in
|
|
guard !newValue else { return }
|
|
videoPlayerManager.proxy.setTime(.seconds(currentProgressHandler.scrubbedSeconds))
|
|
}
|
|
.onScenePhase(.active) {
|
|
if Defaults[.VideoPlayer.Transition.playOnActive] {
|
|
videoPlayerManager.proxy.play()
|
|
}
|
|
}
|
|
.onScenePhase(.background) {
|
|
if Defaults[.VideoPlayer.Transition.pauseOnBackground] {
|
|
videoPlayerManager.proxy.pause()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension VideoPlayer {
|
|
|
|
init(manager: VideoPlayerManager) {
|
|
self.init(
|
|
currentProgressHandler: manager.currentProgressHandler,
|
|
videoPlayerManager: manager
|
|
)
|
|
}
|
|
}
|