tv-anarchy/Sources/TVAnarchyCore/PlayerTarget.swift
Natalie a86e68c525 feat(apps): add fleet engine mesh core integration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:23:36 -07:00

72 lines
3.2 KiB
Swift

import Foundation
/// Live connection state of a target, kept separate from playback so a single
/// dropped poll renders as "unreachable (last known )" rather than wiping state.
public enum ConnectionState: String, Sendable, Equatable {
case checking, connected, unreachable
}
/// Unified playback state across every target. Volume is normalized to a
/// percentage (100 = normal) regardless of the backend's native scale.
public struct PlaybackStatus: Equatable, Sendable, Codable {
public var playing: Bool
public var paused: Bool?
public var title: String?
public var volume: Double? // percent (100 = normal)
public var position: Double? // seconds
public var duration: Double? // seconds
public var playlistPos: Int?
public var playlistCount: Int?
public init(playing: Bool, paused: Bool? = nil, title: String? = nil,
volume: Double? = nil, position: Double? = nil, duration: Double? = nil,
playlistPos: Int? = nil, playlistCount: Int? = nil) {
self.playing = playing; self.paused = paused; self.title = title
self.volume = volume; self.position = position; self.duration = duration
self.playlistPos = playlistPos; self.playlistCount = playlistCount
}
public static let idle = PlaybackStatus(playing: false)
}
/// Result of polling a target: reachability and (if reachable) the playback
/// state. `status == nil` while reachable means "reachable but no clear state".
public struct PollResult: Sendable {
public var reachable: Bool
public var status: PlaybackStatus?
public init(reachable: Bool, status: PlaybackStatus?) {
self.reachable = reachable; self.status = status
}
public static let unreachable = PollResult(reachable: false, status: nil)
}
/// A place we can send playback to. Reference type so an implementation can hold
/// connection state (e.g. a pinned SSH endpoint). Verbs translate onto an
/// existing backend the playback intelligence lives there, not here.
public protocol PlayerTarget: AnyObject {
var id: String { get }
var name: String { get }
var kind: HostKind { get }
var detail: String { get } // human-readable endpoint, for the Hosts view
var volumeScale: Int { get } // max value for the volume slider (percent)
func poll() async -> PollResult
func playPause() async // toggle
func resume() async // ensure playing
func setVolume(_ percent: Int) async
func seek(relative seconds: Int) async
func seek(toSeconds seconds: Int) async // absolute for the scrubber + resume
func next() async
func previous() async
func stop() async
}
/// A target whose host-side player service can be restarted in place (black:
/// relaunch the root-owned mpv unit when it hangs or its socket goes stale,
/// resuming what was playing). The restart is delegated to a per-host command,
/// so conformance alone isn't enough `canRestartService` reflects whether
/// that command is actually configured. Drives the Devices tab action.
public protocol ServiceRestartable: AnyObject {
var canRestartService: Bool { get }
@discardableResult func restartService() async -> Bool
}