Dispatcharr/apps/proxy/live_proxy/constants.py
Cédric Marcoux 2781b21a89 feat: built-in XC catch-up (timeshift) support
Adds native catch-up/timeshift replay for Xtream Codes providers through
the same HTTPStreamReader transport pipeline as live TV.

Timeshift proxy (apps/timeshift/):
- URL cascade: 3 candidate timestamp formats per provider, per-account
  format cache for fast-forward seek performance
- MPEG-TS preamble stripping (shared with HTTPStreamReader)
- Stats integration: timeshift viewers appear on /stats with TIMESHIFT badge
- Auth via hmac.compare_digest on XC password

Catchup detection — denormalized for zero-cost output queries:
- Stream.is_catchup + Stream.catchup_days populated at XC import time
- Channel.has_catchup + Channel.catchup_days + Channel.catchup_provider_stream_id
  rolled up via ChannelStream post_save signal (UI path) and explicit SQL
  after bulk_create (import path)
- _xc_channel_entry() reads denormalized fields instead of per-channel
  custom_properties JSON introspection (eliminates N+1 queries)
- Migration 0038 backfills existing data via raw SQL

XC API enhancements:
- server_info.timezone + start/end + time_now use configured timezone
  (triple consistency rule — fixes wrong-programme-plays bug)
- Dynamic has_archive flag + auto prev_days for catch-up channels
- XMLTV timestamps rewritten to local timezone for catch-up clients

HTTPStreamReader extended (apps/proxy/live_proxy/input/http_streamer.py):
- 1 MB pipe buffer via fcntl F_SETPIPE_SZ (eliminates producer/consumer
  ping-pong that halved throughput)
- Pre-opened response= for URL cascade workflows
- strip_ts_preamble= for XC servers emitting PHP warnings before TS
- find_ts_sync() as shared utility
- Builds on upstream O_NONBLOCK + select() write loop

Provider stream_id lookup order:
- stream_xc() and xc_get_epg() try internal Channel.id first, fall back
  to provider stream_id only when needed (avoids unconditional query on
  every request)

Also includes:
- VOD provider cascade in stream_vod() — iterates all M3U relations by
  priority when first provider is at capacity
- Defensive null-safety: custom_sid: None → "" in get_live_streams,
  get_vod_streams, get_vod_info, get_series_info (fixes iPlayTV crash on
  JSON null for string fields)
- Timeshift settings UI (timezone selector, debug toggle)
- StreamConnectionCard violet TIMESHIFT badge
- Orphan cleanup skips timeshift_* virtual channels
2026-05-27 13:13:20 +02:00

124 lines
3.4 KiB
Python

"""
Constants used throughout the TS Proxy application.
Centralizing constants makes it easier to maintain and modify them.
"""
# Redis related constants
REDIS_KEY_PREFIX = "live"
REDIS_TTL_DEFAULT = 3600 # 1 hour
REDIS_TTL_SHORT = 60 # 1 minute
REDIS_TTL_MEDIUM = 300 # 5 minutes
# Channel states
class ChannelState:
INITIALIZING = "initializing"
CONNECTING = "connecting"
WAITING_FOR_CLIENTS = "waiting_for_clients"
ACTIVE = "active"
ERROR = "error"
STOPPING = "stopping"
STOPPED = "stopped"
BUFFERING = "buffering"
# States before a channel is fully active. Used by the stream manager
# finally block to decide whether a failed stream can write ERROR.
PRE_ACTIVE = frozenset([INITIALIZING, CONNECTING, BUFFERING, WAITING_FOR_CLIENTS])
# Event types
class EventType:
STREAM_SWITCH = "stream_switch"
STREAM_SWITCHED = "stream_switched"
CHANNEL_STOP = "channel_stop"
CHANNEL_STOPPED = "channel_stopped"
CLIENT_CONNECTED = "client_connected"
CLIENT_DISCONNECTED = "client_disconnected"
CLIENT_STOP = "client_stop"
ENSURE_OUTPUT_FORMAT = "ensure_output_format"
ENSURE_OUTPUT_PROFILE = "ensure_output_profile"
# Stream types
class StreamType:
HLS = "hls"
RTSP = "rtsp"
UDP = "udp"
TS = "ts"
UNKNOWN = "unknown"
# Channel metadata field names stored in Redis
class ChannelMetadataField:
# Basic fields
URL = "url"
USER_AGENT = "user_agent"
STATE = "state"
OWNER = "owner"
STREAM_ID = "stream_id"
CHANNEL_NAME = "channel_name"
STREAM_NAME = "stream_name"
IS_TIMESHIFT = "is_timeshift"
LOGO_ID = "logo_id"
# Profile fields
STREAM_PROFILE = "stream_profile"
M3U_PROFILE = "m3u_profile"
# Status and error fields
ERROR_MESSAGE = "error_message"
ERROR_TIME = "error_time"
STATE_CHANGED_AT = "state_changed_at"
INIT_TIME = "init_time"
CONNECTION_READY_TIME = "connection_ready_time"
# Buffer and data tracking
BUFFER_CHUNKS = "buffer_chunks"
TOTAL_BYTES = "total_bytes"
# Stream switching
STREAM_SWITCH_TIME = "stream_switch_time"
STREAM_SWITCH_REASON = "stream_switch_reason"
# FFmpeg performance metrics
FFMPEG_SPEED = "ffmpeg_speed"
FFMPEG_FPS = "ffmpeg_fps"
ACTUAL_FPS = "actual_fps"
FFMPEG_OUTPUT_BITRATE = "ffmpeg_output_bitrate"
FFMPEG_BITRATE = "ffmpeg_bitrate"
FFMPEG_STATS_UPDATED = "ffmpeg_stats_updated"
# Video stream info
VIDEO_CODEC = "video_codec"
RESOLUTION = "resolution"
WIDTH = "width"
HEIGHT = "height"
SOURCE_FPS = "source_fps"
PIXEL_FORMAT = "pixel_format"
VIDEO_BITRATE = "video_bitrate"
SOURCE_BITRATE = "source_bitrate"
# Audio stream info
AUDIO_CODEC = "audio_codec"
SAMPLE_RATE = "sample_rate"
AUDIO_CHANNELS = "audio_channels"
AUDIO_BITRATE = "audio_bitrate"
# Stream format info
STREAM_TYPE = "stream_type"
# Stream info timestamp
STREAM_INFO_UPDATED = "stream_info_updated"
# Client metadata fields
CONNECTED_AT = "connected_at"
LAST_ACTIVE = "last_active"
OUTPUT_FORMAT = "output_format"
BYTES_SENT = "bytes_sent"
AVG_RATE_KBPS = "avg_rate_KBps"
CURRENT_RATE_KBPS = "current_rate_KBps"
IP_ADDRESS = "ip_address"
WORKER_ID = "worker_id"
CHUNKS_SENT = "chunks_sent"
STATS_UPDATED_AT = "stats_updated_at"
# TS packet constants
TS_PACKET_SIZE = 188
TS_SYNC_BYTE = 0x47
NULL_PID_HIGH = 0x1F
NULL_PID_LOW = 0xFF