Files
yattee/Yattee/Services/Player/BackendSwitcher.swift
Arkadiusz Fal 4770bbe3b9 Remove dead code found by Periphery on all platforms
Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
2026-10-08 23:57:56 +02:00

152 lines
5.1 KiB
Swift

//
// BackendSwitcher.swift
// Yattee
//
// Handles seamless switching between player backends during playback.
//
import Foundation
// MARK: - Backend Switcher
/// Manages seamless hot-swapping between player backends.
@MainActor
final class BackendSwitcher {
// MARK: - Dependencies
/// Factory for creating backend instances.
private let backendFactory: BackendFactory
/// Settings manager for quality preferences.
weak var settingsManager: SettingsManager?
init(backendFactory: BackendFactory, settingsManager: SettingsManager?) {
self.backendFactory = backendFactory
self.settingsManager = settingsManager
}
}
// MARK: - Backend Factory
/// Factory for creating player backend instances with pre-warming pool.
@MainActor
final class BackendFactory {
/// Pool of pre-warmed backends ready for instant playback (1 per type)
private var backendPool: [PlayerBackendType: any PlayerBackend] = [:]
/// Statistics for monitoring pool efficiency
private var poolHits = 0
private var poolMisses = 0
/// Create or retrieve a pre-warmed backend.
func createBackend(type: PlayerBackendType) throws -> any PlayerBackend {
// Try to get from pool first
if let backend = backendPool[type] {
backendPool[type] = nil // Remove from pool
poolHits += 1
LoggingService.shared.debug("BackendFactory: pool hit for \(type.displayName) (hits=\(poolHits), misses=\(poolMisses))", category: .mpv)
// Immediately start warming a replacement in background
Task {
await prewarmBackend(type: type)
}
return backend
}
poolMisses += 1
LoggingService.shared.debug("BackendFactory: pool miss for \(type.displayName) (hits=\(poolHits), misses=\(poolMisses))", category: .mpv)
// Create new backend and begin setup
let backend = createBackendInstance(type: type)
if let mpvBackend = backend as? MPVBackend {
mpvBackend.beginSetup()
}
return backend
}
/// Create a backend instance (without pool).
private func createBackendInstance(type: PlayerBackendType) -> any PlayerBackend {
switch type {
case .mpv:
return MPVBackend()
}
}
/// Pre-warm a backend and add to pool.
func prewarmBackend(type: PlayerBackendType) async {
let startTime = Date()
LoggingService.shared.debug("BackendFactory: pre-warming \(type.displayName)", category: .mpv)
let backend = await MainActor.run {
createBackendInstance(type: type)
}
// Begin async setup
if let mpvBackend = backend as? MPVBackend {
await MainActor.run {
mpvBackend.beginSetup()
}
// Wait for setup to complete
do {
try await mpvBackend.waitForSetup()
} catch {
LoggingService.shared.debug("BackendFactory: pre-warm failed for \(type.displayName): \(error)", category: .mpv)
return
}
}
let duration = Date().timeIntervalSince(startTime)
LoggingService.shared.debug("BackendFactory: \(type.displayName) pre-warmed in \(String(format: "%.3f", duration))s", category: .mpv)
// Add to pool (only if slot is empty - don't accumulate)
await MainActor.run {
if backendPool[type] == nil {
backendPool[type] = backend
LoggingService.shared.debug("BackendFactory: \(type.displayName) added to pool", category: .mpv)
} else {
LoggingService.shared.debug("BackendFactory: \(type.displayName) pool already full, discarding", category: .mpv)
}
}
}
/// Pre-warm all available backends in parallel.
func prewarmAllBackends() async {
let startTime = Date()
LoggingService.shared.debug("BackendFactory: pre-warming all backends", category: .mpv)
// Pre-warm in parallel
await withTaskGroup(of: Void.self) { group in
for type in availableBackends {
group.addTask {
await self.prewarmBackend(type: type)
}
}
}
let duration = Date().timeIntervalSince(startTime)
LoggingService.shared.debug("BackendFactory: all backends pre-warmed in \(String(format: "%.3f", duration))s", category: .mpv)
}
/// Drain the pool (called on memory warning).
func drainPool() {
let count = backendPool.count
backendPool.removeAll()
LoggingService.shared.debug("BackendFactory: pool drained (\(count) backends released)", category: .mpv)
}
/// Check if a backend type is available on this platform.
func isAvailable(_ type: PlayerBackendType) -> Bool {
switch type {
case .mpv:
return true
}
}
/// Get all available backend types.
var availableBackends: [PlayerBackendType] {
PlayerBackendType.allCases.filter { isAvailable($0) }
}
}