"""Playback telemetry, POST /playback-events (fire-and-forget z apki). Apka po każdej próbie odtwarzania woła to z origin sceny + wynikiem (success/error). Sygnał zasila health/szybkość w rankingu źródeł (run_source_stats agreguje okno 7d). Best-effort: apka NIE czeka i ignoruje błąd, telemetria nie może psuć playbacku. """ from __future__ import annotations import uuid from typing import Annotated, Literal from fastapi import APIRouter, Depends from pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.api.device import get_device_id from app.auth import require_api_key from app.db import get_session from app.models.playback_event import PlaybackEvent from app.playback_gone import is_gone_report, verify_gone_async router = APIRouter(prefix="/playback-events", tags=["telemetry"], dependencies=[Depends(require_api_key)]) class PlaybackEventIn(BaseModel): # 'tube:', zgodne z playback_sources.origin. Bez tego ping bezużyteczny. origin: str = Field(min_length=1, max_length=64) status: Literal["success", "error"] scene_id: uuid.UUID | None = None error_kind: str | None = Field(default=None, max_length=64) # Surowa treść błędu z native playera (ExoPlayer message), diagnostyka realnych # padów których nie da się odtworzyć na emulatorze (np. watchporn player_error). error_detail: str | None = Field(default=None, max_length=512) ttff_ms: int | None = Field(default=None, ge=0, le=600_000) @router.post("", status_code=204) def post_playback_event( body: PlaybackEventIn, session: Annotated[Session, Depends(get_session)], device_id: Annotated[str, Depends(get_device_id)], ) -> None: session.add( PlaybackEvent( origin=body.origin, scene_id=body.scene_id, status=body.status, error_kind=body.error_kind, error_detail=body.error_detail if body.status == "error" else None, ttff_ms=body.ttff_ms if body.status == "success" else None, device_id=device_id, ) ) session.commit() # Plik zniknal (404/410 w odtwarzaczu): serwer sprawdza zrodlo sam i oznacza martwe # dopiero po wlasnym potwierdzeniu (app/playback_gone.py). if is_gone_report(body.status, body.error_kind, body.error_detail): verify_gone_async(body.scene_id, body.origin)