tv-anarchy/Sources/TVAnarchyCore/PlayerTarget.swift
Natalie 92b38b1bae refactor(tv-anarchy): rename PlumTV→TVAnarchy and land session work
Renames Sources/PlumTV→TVAnarchy and PlumTVCore→TVAnarchyCore (the rename
the auto-commit service couldn't stage — it git-add'd the old, now-gone
paths and aborted every cycle), and commits the accumulated work:

- Library: black-built index fast path (LibraryIndex + scanFromIndex) with
  NFS-walk fallback; incremental --add on download-complete; mtime staleness
  gate; loose-file series-collapse fix; determinate scan/index progress.
- Cover art: keyless TVmaze cartoon-vs-live-action disambiguation (type/year).
- Player: sleep timer (timed + end-of-episode); visibility-gated polling.
- Home: Continue Watching cover art + live refresh; Recently Added; adult gate.
- Logs: multi-line selection + copy; truncated giant tx-list errors.
- Hover previews (opt-in) via black ffmpeg + scp.

Also gitignores foreign project trees (governor/mcp/fleet/recommender) that
sit in this directory but belong to their own repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:04:22 -07:00

62 lines
2.7 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
}