feat: multi-domain failover for provider accounts
Each ProviderAccount can now have multiple base URLs (provider_urls table). On stream failure, BroadcastGroup cycles to the next domain immediately with no wait; backs off only after all domains have been tried once. Background health checker pings every domain every 5 min via player_api.php and updates status/response_ms. Admin UI shows domain list with color-coded status badges and a "Verificar todos" button per provider.
This commit is contained in:
+39
-1
@@ -112,9 +112,10 @@ class ProviderPool:
|
||||
provider_name = next(
|
||||
(pm.get("provider_name", "") for pm in provider_maps if pm["provider_account_id"] == account_id), ""
|
||||
)
|
||||
stream_urls = await self._build_stream_urls(account_id, stream_url)
|
||||
group = BroadcastGroup(
|
||||
channel_id=channel_id,
|
||||
stream_url=stream_url,
|
||||
stream_urls=stream_urls,
|
||||
provider_account_id=account_id,
|
||||
channel_name=channel_name,
|
||||
provider_name=provider_name,
|
||||
@@ -239,6 +240,43 @@ class ProviderPool:
|
||||
rest = [pm for pm in provider_maps if pm["provider_account_id"] not in pref_set]
|
||||
return preferred + rest
|
||||
|
||||
async def _build_stream_urls(self, account_id: int, primary_stream_url: str) -> list[str]:
|
||||
"""Build list of stream URLs for failover: one per active ProviderUrl, substituting base URL."""
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
from ..models.provider import ProviderUrl
|
||||
from ..database import AsyncSessionLocal
|
||||
from sqlalchemy import select
|
||||
|
||||
parsed = urlparse(primary_stream_url)
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
result = await db.execute(
|
||||
select(ProviderUrl)
|
||||
.where(
|
||||
ProviderUrl.provider_account_id == account_id,
|
||||
ProviderUrl.is_active == True, # noqa: E712
|
||||
)
|
||||
.order_by(ProviderUrl.priority)
|
||||
)
|
||||
provider_urls = result.scalars().all()
|
||||
|
||||
if not provider_urls:
|
||||
return [primary_stream_url]
|
||||
|
||||
urls = []
|
||||
for pu in provider_urls:
|
||||
alt = urlparse(pu.url.rstrip('/'))
|
||||
reconstructed = urlunparse((
|
||||
alt.scheme or parsed.scheme,
|
||||
alt.netloc,
|
||||
parsed.path,
|
||||
parsed.params,
|
||||
parsed.query,
|
||||
parsed.fragment,
|
||||
))
|
||||
urls.append(reconstructed)
|
||||
return urls
|
||||
|
||||
async def _find_free_slot(self, provider_maps: list[dict]) -> tuple[int | None, str | None]:
|
||||
for pm in provider_maps:
|
||||
account_id = pm["provider_account_id"]
|
||||
|
||||
Reference in New Issue
Block a user