diff --git a/lib/streaming_providers/providers/joyn/channel_manager.py b/lib/streaming_providers/providers/joyn/channel_manager.py index 9178cc1..d0e67c6 100644 --- a/lib/streaming_providers/providers/joyn/channel_manager.py +++ b/lib/streaming_providers/providers/joyn/channel_manager.py @@ -41,7 +41,7 @@ from .constants import ( MODE_VOD, SIGNATURE_SECRET_KEY, ) -from .models import JoynChannel, JoynError, JoynEntitlementError, PlaybackRestrictedException +from .models import JoynChannel, JoynError, JoynEntitlementError, PlaybackRestrictedException, SubscriptionRequiredException def create_video_payload(config: Optional[Dict] = None, compact: bool = True) -> str: @@ -257,6 +257,9 @@ class JoynChannelManager: msg = error.get("msg", "No error message provided") if code == ERROR_CODES["PLAYBACK_RESTRICTED"]: raise PlaybackRestrictedException(f"Playback restricted for {content_id}: {msg}") + elif code == ERROR_CODES["BUSINESS_MODEL_NOT_SUITABLE"]: + raise SubscriptionRequiredException( + f"Subscription required for {content_id} ({code}): {msg}") else: raise JoynEntitlementError(f"Entitlement error for {content_id} ({code}): {msg}") except (json.JSONDecodeError, KeyError, IndexError) as e: diff --git a/lib/streaming_providers/providers/joyn/constants.py b/lib/streaming_providers/providers/joyn/constants.py index 18917e4..576dcb4 100644 --- a/lib/streaming_providers/providers/joyn/constants.py +++ b/lib/streaming_providers/providers/joyn/constants.py @@ -150,6 +150,7 @@ MODE_VOD = "vod" ERROR_CODES = { "PLAYBACK_RESTRICTED": "ENT_RVOD_Playback_Restricted", + "BUSINESS_MODEL_NOT_SUITABLE": "ENT_BUSINESS_MODEL_NOT_SUITABLE", "UNAUTHORIZED": "ENT_Unauthorized", "NOT_FOUND": "ENT_Not_Found", "GEOBLOCKED": "ENT_Geoblocked", diff --git a/lib/streaming_providers/providers/joyn/models.py b/lib/streaming_providers/providers/joyn/models.py index 045d893..cbd8a49 100644 --- a/lib/streaming_providers/providers/joyn/models.py +++ b/lib/streaming_providers/providers/joyn/models.py @@ -28,6 +28,10 @@ class PlaybackRestrictedException(JoynEntitlementError): """ pass +class SubscriptionRequiredException(JoynEntitlementError): + """Raised when content requires a subscription tier (e.g. PLUS) the account doesn't hold.""" + pass + @dataclass class JoynChannel: diff --git a/lib/streaming_providers/providers/joyn/vod_manager.py b/lib/streaming_providers/providers/joyn/vod_manager.py index a471bf0..3e29f0c 100644 --- a/lib/streaming_providers/providers/joyn/vod_manager.py +++ b/lib/streaming_providers/providers/joyn/vod_manager.py @@ -861,6 +861,7 @@ class JoynVodManager: return cached["data"] from .channel_manager import create_video_payload, build_signature + from .models import SubscriptionRequiredException from ...base.models import DRMConfig, DRMSystem, LicenseConfig from .constants import DRM_REQUEST_HEADERS @@ -884,11 +885,9 @@ class JoynVodManager: response.raise_for_status() playlist_data = response.json() - # --- LOG THE JSON RESPONSE --- logger.info(f"=== VOD PLAYLIST API RESPONSE for {video_id} ===") logger.info(json.dumps(playlist_data, indent=2)) logger.info(f"=============================================") - # ----------------------------- manifest_url = playlist_data.get("manifestUrl") if not manifest_url: @@ -926,6 +925,13 @@ class JoynVodManager: except PlaybackRestrictedException as e: logger.warning(f"VOD playback restricted for {video_id}: {e}") return None + except SubscriptionRequiredException as e: + has_plus = self.has_plus_subscription() + logger.warning( + f"VOD {video_id} requires a subscription this account doesn't hold " + f"(hasActivePlus={has_plus}), not retrying: {e}" + ) + return None # permanent, per-account failure — retrying gains nothing except Exception as e: logger.warning(f"VOD attempt {attempt + 1}/{max_retries} failed: {e}") if attempt < max_retries - 1: