Files
simvision/KSPlayer-main/Sources/KSPlayer/AVPlayer/KSPictureInPictureController.swift
Michael Simard 872354b834 Initial commit: SimVision tvOS streaming app
Features:
- VOD library with movie grouping and version detection
- TV show library with season/episode organization
- TMDB integration for trending shows and recently aired episodes
- Recent releases section with TMDB release date sorting
- Watch history tracking with continue watching
- Playlist caching (12-hour TTL) for offline support
- M3U playlist parsing with XStream API support
- Authentication with credential storage

Technical:
- SwiftUI for tvOS
- Actor-based services for thread safety
- Persistent caching for playlists, TMDB data, and watch history
- KSPlayer integration for video playback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:12:08 -06:00

111 lines
4.4 KiB
Swift

//
// KSPictureInPictureController.swift
// KSPlayer
//
// Created by kintan on 2023/1/28.
//
import AVKit
@available(tvOS 14.0, *)
public class KSPictureInPictureController: AVPictureInPictureController {
private static var pipController: KSPictureInPictureController?
private var originalViewController: UIViewController?
private var view: KSPlayerLayer?
private weak var viewController: UIViewController?
private weak var presentingViewController: UIViewController?
#if canImport(UIKit)
private weak var navigationController: UINavigationController?
#endif
func stop(restoreUserInterface: Bool) {
stopPictureInPicture()
delegate = nil
guard KSOptions.isPipPopViewController else {
return
}
KSPictureInPictureController.pipController = nil
if restoreUserInterface {
#if canImport(UIKit)
runOnMainThread { [weak self] in
guard let self, let viewController, let originalViewController else { return }
if let nav = viewController as? UINavigationController,
nav.viewControllers.isEmpty || (nav.viewControllers.count == 1 && nav.viewControllers[0] != originalViewController)
{
nav.viewControllers = [originalViewController]
}
if let navigationController {
var viewControllers = navigationController.viewControllers
if viewControllers.count > 1, let last = viewControllers.last, type(of: last) == type(of: viewController) {
viewControllers[viewControllers.count - 1] = viewController
navigationController.viewControllers = viewControllers
}
if viewControllers.firstIndex(of: viewController) == nil {
// swiftUI pushviewemptyView
navigationController.pushViewController(viewController, animated: true)
}
} else {
presentingViewController?.present(originalViewController, animated: true)
}
}
#endif
view?.player.isMuted = false
view?.play()
}
originalViewController = nil
view = nil
}
func start(view: KSPlayerLayer) {
startPictureInPicture()
delegate = view
guard KSOptions.isPipPopViewController else {
#if canImport(UIKit)
// 退
runOnMainThread {
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
}
#endif
return
}
self.view = view
#if canImport(UIKit)
runOnMainThread { [weak self] in
guard let self, let viewController = view.player.view?.viewController else { return }
originalViewController = viewController
if let navigationController = viewController.navigationController, navigationController.viewControllers.count == 1 {
self.viewController = navigationController
} else {
self.viewController = viewController
}
navigationController = self.viewController?.navigationController
if let pre = KSPictureInPictureController.pipController {
view.player.isMuted = true
pre.view?.isPipActive = false
} else {
if let navigationController {
navigationController.popViewController(animated: true)
#if os(iOS)
if navigationController.tabBarController != nil, navigationController.viewControllers.count == 1 {
DispatchQueue.main.async { [weak self] in
self?.navigationController?.setToolbarHidden(false, animated: true)
}
}
#endif
} else {
presentingViewController = originalViewController?.presentingViewController
originalViewController?.dismiss(animated: true)
}
}
}
#endif
KSPictureInPictureController.pipController = self
}
static func mute() {
pipController?.view?.player.isMuted = true
}
}