Files
script.service.ultimate/lib/streaming_providers/providers/_template/catchup_manager.py
T
2026-10-04 16:41:49 +02:00

133 lines
4.4 KiB
Python

# streaming_providers/providers/_template/catchup_manager.py
"""
{TODO: Provider name} catchup manager (optional).
Include this file only if the provider supports timeshift / restart.
Providers without catchup don't create a catchup manager -- the
provider's implements_catchup is False and calls to
get_catchup_manifest return None.
See ../_template/README.md for the contract.
Reference implementations
-------------------------
MoveTV's provider (providers/movetv/provider.py) implements catchup
by resolving an EPG entry, then POSTing to a catchup-source endpoint
that returns a URL + a play-auth header. The URL and header are
coupled, so the manager needs a reference to the EPG manager (to
resolve epg_id) and to the channel manager (for the channel's stream
uid).
Magenta EU's provider (providers/magentaeu/provider.py) implements
catchup by appending start/end query parameters to the live manifest
URL via build_catchup_url().
simpliTV's catchup only takes a start bound: it passes end_time=None
through and documents that it ignores it. The router parses the
"catchup:<codename>@<ts>" id (an explicit branch above _route) and
hands the parsed arguments to this manager.
HRTi has no catchup -- its VOD and EPG are separate domains, and
authorize_session's session id is not reused for timeshift.
State sharing
-------------
The catchup step often shares state with the channel manager (the live
manifest URL) or the EPG manager (the epg_id for the requested window).
Pass those collaborators as explicit keyword-only arguments rather than
reaching back to the provider (the provider's _build_catchup does this).
Do NOT fall back to the live manifest
-------------------------------------
If get_catchup_manifest cannot resolve catchup for the given window,
return None. Do not return the live manifest URL as a "catchup"
manifest -- the DRM pipeline would extract PSSH from the live stream,
which may differ from the catchup stream's encryption context. Callers
that want the live manifest on failure call provider.get_manifest().
end_time is Optional[int]
-------------------------
If the provider's API takes only a start bound, accept None and document
that it is ignored. If the API needs both bounds, raise BadRequestError
on None. Never pass a sentinel (0, start_time + 1800) when the ABC
accepts None.
"""
from typing import List, Optional
from ...base.managers import CatchupManager
from ...base.models import DRMConfig
from ...base.utils.logger import logger
# from ...base.errors import BadRequestError
class YourCatchupManager(CatchupManager):
"""Catchup for {TODO: provider name}."""
def __init__(
self,
*,
http_manager,
auth,
country,
config,
channels=None,
epg=None,
):
super().__init__(
http_manager=http_manager,
auth=auth,
country=country,
config=config,
)
# Common collaborators. Catchup often needs one or both.
# - channels: for resolving a channel's live manifest URL
# (Magenta, simpliTV) or its stream uid (MoveTV).
# - epg: for resolving an epg_id from a start_time
# (MoveTV).
self._channels = channels
self._epg = epg
# ----- Capability -----
@property
def catchup_window_hours(self) -> int:
"""Return the catchup window in hours. 0 means no catchup."""
return 0 # TODO: e.g. 168 for 7 days
# ----- Abstract method -----
def get_catchup_manifest(
self,
content_id: str,
start_time: int,
end_time: Optional[int] = None,
epg_id: Optional[str] = None,
**kw,
) -> Optional[str]:
"""
Return the catchup manifest URL, or None if not resolvable.
start_time / end_time are integer epoch seconds. end_time may be
None (see module docstring).
Do NOT fall back to the live manifest URL here.
"""
raise NotImplementedError("YourCatchupManager.get_catchup_manifest")
# ----- Concrete methods (override when needed) -----
# def get_catchup_drm(
# self,
# content_id: str,
# start_time: int,
# end_time: Optional[int] = None,
# epg_id: Optional[str] = None,
# **kw,
# ) -> List[DRMConfig]:
# """
# Override only if catchup uses a different DRM config from live.
# The default returns [] -- the caller falls back to live DRM.
# """
# return []