RTSDA-iOS/Views/SharedVideoPlayerView.swift
RTSDA 00679f927c docs: Update README for v2.0 release and fix git remote URL
- Comprehensive README update documenting v2.0 architectural changes
- Updated git remote to ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/RTSDA-iOS.git
- Documented unified ChurchService and 60% code reduction
- Added new features: Home Feed, responsive reading, enhanced UI
- Corrected license information (GPL v3 with church content copyright)
- Updated build instructions and technical stack details
2025-08-16 18:41:51 -04:00

143 lines
4.4 KiB
Swift

import SwiftUI
import AVKit
@preconcurrency import MediaPlayer
// Global video player state
class SharedVideoManager: ObservableObject {
static let shared = SharedVideoManager()
@Published var currentVideoURL: URL?
@Published var showFullScreenPlayer = false
@Published var isInPiPMode = false
// Track the current player to force PiP stop
weak var currentPlayerController: AVPlayerViewController?
// Store current media info for lock screen controls
var currentTitle: String?
var currentArtworkURL: String?
private init() {
}
func playVideo(url: URL, title: String? = nil, artworkURL: String? = nil) {
// Store media info for lock screen controls
currentTitle = title
currentArtworkURL = artworkURL
// If we already have a video playing, we need to switch to the new one
if currentVideoURL != nil {
if isInPiPMode {
// Force stop PiP if it's active
if let playerController = currentPlayerController {
// Force stop the player to close PiP
playerController.player?.pause()
playerController.player = nil
}
}
// Clear the old video first to ensure proper cleanup
currentVideoURL = nil
showFullScreenPlayer = false
isInPiPMode = false
currentPlayerController = nil
// Small delay to let old player fully cleanup before starting new one
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.currentVideoURL = url
self.showFullScreenPlayer = true
self.isInPiPMode = false
}
} else {
// No existing video, start immediately
currentVideoURL = url
showFullScreenPlayer = true
isInPiPMode = false
}
}
func hideFullScreenPlayer() {
showFullScreenPlayer = false
}
func stopVideo() {
// Stop the actual player first
if let playerController = currentPlayerController {
playerController.player?.pause()
playerController.player = nil
}
// Clear all state
currentVideoURL = nil
showFullScreenPlayer = false
isInPiPMode = false
currentPlayerController = nil
currentTitle = nil
currentArtworkURL = nil
}
func setPiPMode(_ isPiP: Bool) {
isInPiPMode = isPiP
if isPiP {
// When PiP starts, hide full screen but DON'T touch currentVideoURL
showFullScreenPlayer = false
} else {
// When PiP ends, show full screen again
showFullScreenPlayer = true
}
}
}
// Shared video overlay that exists at app level - GLOBAL PERSISTENT PLAYER
struct SharedVideoOverlay: View {
@StateObject private var videoManager = SharedVideoManager.shared
@State private var currentPlayerURL: URL?
var body: some View {
ZStack {
// ALWAYS render the persistent player when we have a URL
// Force recreate when URL changes to ensure old player is destroyed
if let url = videoManager.currentVideoURL {
PersistentVideoPlayer(url: url)
.id(url.absoluteString) // Force recreate when URL changes
.opacity(videoManager.showFullScreenPlayer ? 1 : 0) // Show/hide but never destroy
.allowsHitTesting(videoManager.showFullScreenPlayer) // Only interactive when visible
.background(videoManager.showFullScreenPlayer ? Color.black : Color.clear)
.ignoresSafeArea()
.onAppear {
}
}
}
.onChange(of: videoManager.showFullScreenPlayer) { _, isVisible in
}
.onChange(of: videoManager.currentVideoURL) { oldURL, newURL in
if let old = oldURL, let new = newURL, old != new {
} else if newURL == nil {
}
}
}
}
// Persistent video player that stays alive
struct PersistentVideoPlayer: View {
let url: URL
var body: some View {
VideoPlayerView(url: url)
.onAppear {
}
.onDisappear {
}
}
}