
- 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
143 lines
4.4 KiB
Swift
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 {
|
|
|
|
}
|
|
}
|
|
} |