mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(playback): improve browser-player diagnostics (#939)
* feat(playback): diagnose browser stream access errors Entire-Checkpoint: f957cd9849e0 * feat(playback): expose browser-player diagnostic details Entire-Checkpoint: f957cd9849e0 * fix(playback): keep media path extensions authoritative Entire-Checkpoint: f957cd9849e0 * fix(playback): tighten browser access diagnostics Entire-Checkpoint: f957cd9849e0 * fix(playback): align diagnostic followups Entire-Checkpoint: f957cd9849e0 * fix(playback): narrow declared stream metadata Entire-Checkpoint: f957cd9849e0 * fix(playback): preserve explicit extension overrides Entire-Checkpoint: f957cd9849e0 * fix(playback): keep mpeg-ts out of unsupported containers Entire-Checkpoint: f957cd9849e0 * refactor(playback): split diagnostics utilities Entire-Checkpoint: f957cd9849e0 * fix(playback): treat mpegts early eof as media fallback Entire-Checkpoint: f957cd9849e0
This commit is contained in:
1 parent
8232a86a1e
commit
4a4334cb64
33 files changed
+1936
-842
No files matched your search
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "السمة الفاتحة",
|
||||
"SYSTEM_THEME": "سمة النظام"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "إخفاء قائمة القنوات",
|
||||
"SHOW_CHANNELS_LIST": "إظهار قائمة القنوات",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "المظهر الفاتح",
|
||||
"SYSTEM_THEME": "مظهر النظام"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "إخفاء قائمة القنوات",
|
||||
"SHOW_CHANNELS_LIST": "إظهار قائمة القنوات",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Светлая тэма",
|
||||
"SYSTEM_THEME": "Сістэмная тэма"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Схаваць спіс каналаў",
|
||||
"SHOW_CHANNELS_LIST": "Паказаць спіс каналаў",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Hell",
|
||||
"SYSTEM_THEME": "System"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Senderliste ausblenden",
|
||||
"SHOW_CHANNELS_LIST": "Senderliste einblenden",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Ανοιχτόχρωμο θέμα",
|
||||
"SYSTEM_THEME": "Θέμα συστήματος"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Απόκρυψη λίστας καναλιών",
|
||||
"SHOW_CHANNELS_LIST": "Εμφάνιση λίστας καναλιών",
|
||||
|
||||
@@ -366,35 +366,51 @@
|
||||
"LIGHT_THEME": "Light theme",
|
||||
"SYSTEM_THEME": "System theme"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Hide channels list",
|
||||
"SHOW_CHANNELS_LIST": "Show channels list",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Tema claro",
|
||||
"SYSTEM_THEME": "Tema del sistema"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Ocultar lista de canales",
|
||||
"SHOW_CHANNELS_LIST": "Mostrar lista de canales",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Thème clair",
|
||||
"SYSTEM_THEME": "Thème du système"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Masquer la liste des chaînes",
|
||||
"SHOW_CHANNELS_LIST": "Afficher la liste des chaînes",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Tema chiaro",
|
||||
"SYSTEM_THEME": "Tema di sistema"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Nascondi elenco canali",
|
||||
"SHOW_CHANNELS_LIST": "Mostra elenco canali",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "ライトテーマ",
|
||||
"SYSTEM_THEME": "システムテーマ"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "チャンネル一覧を非表示",
|
||||
"SHOW_CHANNELS_LIST": "チャンネル一覧を表示",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "밝은 테마",
|
||||
"SYSTEM_THEME": "시스템 테마"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "채널 목록 숨기기",
|
||||
"SHOW_CHANNELS_LIST": "채널 목록 표시",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Licht thema",
|
||||
"SYSTEM_THEME": "Systeemthema"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Zenderlijst verbergen",
|
||||
"SHOW_CHANNELS_LIST": "Zenderlijst tonen",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Jasny motyw",
|
||||
"SYSTEM_THEME": "Motyw systemowy"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Ukryj listę kanałów",
|
||||
"SHOW_CHANNELS_LIST": "Pokaż listę kanałów",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Tema claro",
|
||||
"SYSTEM_THEME": "Tema do sistema"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Ocultar lista de canais",
|
||||
"SHOW_CHANNELS_LIST": "Mostrar lista de canais",
|
||||
|
||||
@@ -364,35 +364,51 @@
|
||||
"LIGHT_THEME": "Светлая тема",
|
||||
"SYSTEM_THEME": "Системная тема"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "Контейнер потока, вероятно, не поддерживается встроенным браузерным плеером",
|
||||
"DESCRIPTION": "Chromium/Electron может не воспроизводить этот контейнер внутри приложения. Попробуйте MPV или VLC с более широкой поддержкой кодеков."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "Кодек потока, вероятно, не поддерживается встроенным браузерным плеером",
|
||||
"DESCRIPTION": "Поток сообщает кодеки, которые встроенный Chromium-плеер может не декодировать. MPV или VLC часто справляются с такими потоками."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "Браузерный плеер не смог декодировать этот поток",
|
||||
"DESCRIPTION": "Воспроизведение началось, но медиапайплайн завершился ошибкой декодирования. Внешний плеер может обработать этот поток лучше."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "Не удалось загрузить поток",
|
||||
"DESCRIPTION": "Плеер сообщил сетевую ошибку или ошибку загрузки у провайдера. Проверьте URL потока, заголовки или доступность провайдера."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "Поток может быть зашифрован или защищен DRM",
|
||||
"DESCRIPTION": "Браузерный плеер не может продолжить из-за ошибки ключа или шифрования. Внешний плеер тоже может не помочь, если требуется DRM провайдера."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "Браузерный плеер сообщил ошибку воспроизведения",
|
||||
"DESCRIPTION": "Поток не воспроизвелся во встроенном плеере, но ошибка не указала конкретную причину в кодеке, контейнере или сети."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Открыть в MPV",
|
||||
"ACTION_OPEN_VLC": "Открыть в VLC",
|
||||
"ACTION_COPY_URL": "Скопировать URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "Контейнер потока, вероятно, не поддерживается встроенным браузерным плеером",
|
||||
"DESCRIPTION": "Chromium/Electron может не воспроизводить этот контейнер внутри приложения. Попробуйте MPV или VLC с более широкой поддержкой кодеков."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "Кодек потока, вероятно, не поддерживается встроенным браузерным плеером",
|
||||
"DESCRIPTION": "Поток сообщает кодеки, которые встроенный Chromium-плеер может не декодировать. MPV или VLC часто справляются с такими потоками."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "Браузерный плеер не смог декодировать этот поток",
|
||||
"DESCRIPTION": "Воспроизведение началось, но медиапайплайн завершился ошибкой декодирования. Внешний плеер может обработать этот поток лучше."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "Не удалось загрузить поток",
|
||||
"DESCRIPTION": "Плеер сообщил сетевую ошибку или ошибку загрузки у провайдера. Проверьте URL потока, заголовки или доступность провайдера."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "Браузерный плеер не получил доступ к потоку",
|
||||
"DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. MPV или VLC часто обходят такие ограничения браузерного плеера."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "Поток может быть зашифрован или защищен DRM",
|
||||
"DESCRIPTION": "Браузерный плеер не может продолжить из-за ошибки ключа или шифрования. Внешний плеер тоже может не помочь, если требуется DRM провайдера."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "Браузерный плеер сообщил ошибку воспроизведения",
|
||||
"DESCRIPTION": "Поток не воспроизвелся во встроенном плеере, но ошибка не указала конкретную причину в кодеке, контейнере или сети."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Открыть в MPV",
|
||||
"ACTION_OPEN_VLC": "Открыть в VLC",
|
||||
"ACTION_COPY_URL": "Скопировать URL",
|
||||
"DETAILS_SUMMARY": "Технические сведения",
|
||||
"CODEC_HINT": "Обнаружены {{codecs}}. Браузерные плееры Chromium/Electron часто имеют ограниченную поддержку этих кодеков.",
|
||||
"DETAIL_CODE": "Код диагностики",
|
||||
"DETAIL_PLAYER": "Плеер",
|
||||
"DETAIL_SOURCE": "Источник",
|
||||
"DETAIL_CONTAINER": "Контейнер",
|
||||
"DETAIL_MIME_TYPE": "MIME-тип",
|
||||
"DETAIL_VIDEO_CODECS": "Видео кодеки",
|
||||
"DETAIL_AUDIO_CODECS": "Аудио кодеки",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Код ошибки браузера",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Сообщение браузера",
|
||||
"DETAIL_ERROR_DETAILS": "Детали ошибки"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Скрыть список каналов",
|
||||
"SHOW_CHANNELS_LIST": "Показать список каналов",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "Açık tema",
|
||||
"SYSTEM_THEME": "Sistem teması"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "Kanal listesini gizle",
|
||||
"SHOW_CHANNELS_LIST": "Kanal listesini göster",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "明亮模式",
|
||||
"SYSTEM_THEME": "跟随系统"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "隐藏频道列表",
|
||||
"SHOW_CHANNELS_LIST": "显示频道列表",
|
||||
|
||||
@@ -360,35 +360,51 @@
|
||||
"LIGHT_THEME": "亮色主題",
|
||||
"SYSTEM_THEME": "跟隨系統"
|
||||
},
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL"
|
||||
"PLAYBACK_DIAGNOSTICS": {
|
||||
"UNSUPPORTED_CONTAINER": {
|
||||
"TITLE": "This stream container is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "Chromium/Electron may not decode this container inline. Try MPV or VLC for broader codec support."
|
||||
},
|
||||
"UNSUPPORTED_CODEC": {
|
||||
"TITLE": "This stream codec is likely unsupported by the browser player",
|
||||
"DESCRIPTION": "The stream advertises codecs that the built-in Chromium player may not decode. MPV or VLC can often play it."
|
||||
},
|
||||
"MEDIA_DECODE_ERROR": {
|
||||
"TITLE": "The browser player could not decode this stream",
|
||||
"DESCRIPTION": "Playback started but the media pipeline failed while decoding. An external player may handle this stream better."
|
||||
},
|
||||
"NETWORK_ERROR": {
|
||||
"TITLE": "The stream could not be loaded",
|
||||
"DESCRIPTION": "The player reported a network or provider loading error. Check the stream URL, headers, or provider availability."
|
||||
},
|
||||
"BROWSER_ACCESS_ERROR": {
|
||||
"TITLE": "The browser player could not access this stream",
|
||||
"DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits."
|
||||
},
|
||||
"DRM_OR_ENCRYPTION": {
|
||||
"TITLE": "This stream may be encrypted or DRM-protected",
|
||||
"DESCRIPTION": "The browser player cannot continue with the reported encryption/key error. External playback may still fail if provider DRM is required."
|
||||
},
|
||||
"UNKNOWN_PLAYBACK_ERROR": {
|
||||
"TITLE": "The browser player reported a playback error",
|
||||
"DESCRIPTION": "The stream failed in the built-in player, but the error did not identify a specific codec, container, or network cause."
|
||||
},
|
||||
"ACTION_OPEN_MPV": "Open in MPV",
|
||||
"ACTION_OPEN_VLC": "Open in VLC",
|
||||
"ACTION_COPY_URL": "Copy URL",
|
||||
"DETAILS_SUMMARY": "Technical details",
|
||||
"CODEC_HINT": "Detected {{codecs}}. Chromium/Electron browser players often have limited support for these codecs.",
|
||||
"DETAIL_CODE": "Diagnostic code",
|
||||
"DETAIL_PLAYER": "Player",
|
||||
"DETAIL_SOURCE": "Source",
|
||||
"DETAIL_CONTAINER": "Container",
|
||||
"DETAIL_MIME_TYPE": "MIME type",
|
||||
"DETAIL_VIDEO_CODECS": "Video codecs",
|
||||
"DETAIL_AUDIO_CODECS": "Audio codecs",
|
||||
"DETAIL_NATIVE_ERROR_CODE": "Native error code",
|
||||
"DETAIL_NATIVE_ERROR_MESSAGE": "Native error message",
|
||||
"DETAIL_ERROR_DETAILS": "Error details"
|
||||
},
|
||||
"LAYOUT": {
|
||||
"HIDE_CHANNELS_LIST": "隱藏頻道列表",
|
||||
"SHOW_CHANNELS_LIST": "顯示頻道列表",
|
||||
|
||||
@@ -153,9 +153,18 @@ Supported diagnostic codes are:
|
||||
- `unsupported-codec`
|
||||
- `media-decode-error`
|
||||
- `network-error`
|
||||
- `browser-access-error`
|
||||
- `drm-or-encryption`
|
||||
- `unknown-playback-error`
|
||||
|
||||
`network-error` is reserved for provider/network loading failures. Browser security failures such as CORS, mixed content, Content Security Policy, and private-network-access blocks are classified as `browser-access-error` so the UI can explain that the browser player was blocked before playback reached decoding.
|
||||
|
||||
mpegts.js `Early-EOF` failures on MPEG-TS streams are classified as `media-decode-error` instead of generic `network-error`. These failures usually mean the fetch stream ended before mpegts.js expected a complete transport stream, and external players may still handle the same URL more tolerant of short reads or malformed TS boundaries.
|
||||
|
||||
The banner keeps the primary message compact, then exposes technical details on demand: diagnostic code, reporting player/source, detected container/MIME, video/audio codecs, native browser error fields, and raw HLS/mpegts details. HLS manifest codec metadata also drives a concise browser-support hint for codecs that Chromium/Electron commonly cannot decode inline, such as HEVC, AC-3, E-AC-3, DTS, and MPEG-2 video.
|
||||
|
||||
URL extension metadata is filtered before diagnostics and player selection use it. Web script extensions such as `.php` are not shown as stream containers; explicit media query metadata such as `extension=ts` or `format=m3u8` is preferred when present.
|
||||
|
||||
When a diagnostic is actionable in Electron, the inline banner may offer `Open in MPV`, `Open in VLC`, and `Copy URL`. Web builds only expose copy/help text. MPV/VLC fallback requests carry the original `ResolvedPortalPlayback` payload so headers, referer, origin, user-agent, content metadata, and resume offset stay intact.
|
||||
|
||||
`PortalPlayer.openExternalPlayback(playback, player)` is the forced external launch API. It sends the playback payload to MPV or VLC regardless of the current saved player setting, so fallback buttons do not mutate preferences.
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '@angular/core';
|
||||
import Artplayer from 'artplayer';
|
||||
import Hls, { type ErrorData, type ManifestParsedData } from 'hls.js';
|
||||
import { getStreamExtensionFromUrl } from 'm3u-utils';
|
||||
import mpegts from 'mpegts.js';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import {
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
classifyNativePlaybackIssue,
|
||||
classifyUnsupportedHlsManifestCodecs,
|
||||
createPlaybackSourceMetadata,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
|
||||
type AudioTrackSelector = {
|
||||
@@ -131,7 +131,9 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
const el = this.elementRef.nativeElement.querySelector(
|
||||
'.artplayer-container'
|
||||
);
|
||||
const extension = getStreamExtensionFromUrl(this.channel?.url ?? '');
|
||||
const extension = getPlaybackMediaExtensionFromUrl(
|
||||
this.channel?.url ?? ''
|
||||
);
|
||||
const isLive = extension === 'm3u8' || extension === 'ts' || !extension;
|
||||
|
||||
this.player = new Artplayer({
|
||||
@@ -225,7 +227,10 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
'error',
|
||||
this.handleNativePlaybackError
|
||||
);
|
||||
this.player.video.addEventListener('loadeddata', this.clearPlaybackIssue);
|
||||
this.player.video.addEventListener(
|
||||
'loadeddata',
|
||||
this.clearPlaybackIssue
|
||||
);
|
||||
this.player.video.addEventListener('playing', this.clearPlaybackIssue);
|
||||
|
||||
if (this.startTime > 0) {
|
||||
@@ -274,6 +279,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
details: data.details,
|
||||
fatal: data.fatal,
|
||||
message: data.error?.message,
|
||||
error: data.error,
|
||||
},
|
||||
this.createSourceMetadata(url, 'application/x-mpegURL')
|
||||
)
|
||||
@@ -308,7 +314,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
const selectedLabel =
|
||||
typeof item.html === 'string'
|
||||
? item.html
|
||||
: item.html.textContent ?? '';
|
||||
: (item.html.textContent ?? '');
|
||||
const selectedIndex = tracks.findIndex(
|
||||
(t, i) =>
|
||||
(t.name || t.lang || `Track ${i + 1}`) ===
|
||||
@@ -325,7 +331,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
}
|
||||
|
||||
private getVideoType(url: string): string {
|
||||
const extension = getStreamExtensionFromUrl(url);
|
||||
const extension = getPlaybackMediaExtensionFromUrl(url);
|
||||
switch (extension) {
|
||||
case 'mkv':
|
||||
return 'video/matroska';
|
||||
|
||||
@@ -109,4 +109,36 @@ describe('HtmlVideoPlayerComponent', () => {
|
||||
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps raw HLS error object context in emitted playback issue details', () => {
|
||||
const issues: Array<{ details?: string }> = [];
|
||||
component.playbackIssue.subscribe((issue) => {
|
||||
if (issue) issues.push(issue);
|
||||
});
|
||||
|
||||
(
|
||||
component as unknown as {
|
||||
handleHlsError: (
|
||||
url: string,
|
||||
data: {
|
||||
type: string;
|
||||
details: string;
|
||||
fatal: boolean;
|
||||
error?: unknown;
|
||||
}
|
||||
) => void;
|
||||
}
|
||||
).handleHlsError('https://example.com/live/playlist.m3u8', {
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError',
|
||||
fatal: true,
|
||||
error: {
|
||||
context: 'xhr setup failed',
|
||||
status: 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(issues[0].details).toContain('xhr setup failed');
|
||||
expect(issues[0].details).toContain('"status":0');
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from '@angular/core';
|
||||
import Hls, { type ErrorData, type ManifestParsedData } from 'hls.js';
|
||||
import mpegts from 'mpegts.js';
|
||||
import { getStreamExtensionFromUrl } from 'm3u-utils';
|
||||
import { DataService } from 'services';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
import {
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
classifyNativePlaybackIssue,
|
||||
classifyUnsupportedHlsManifestCodecs,
|
||||
createPlaybackSourceMetadata,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
|
||||
/**
|
||||
@@ -82,11 +82,14 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.onVolumeChange();
|
||||
});
|
||||
|
||||
this.videoPlayer.nativeElement.addEventListener('loadedmetadata', () => {
|
||||
if (this.startTime > 0) {
|
||||
this.videoPlayer.nativeElement.currentTime = this.startTime;
|
||||
this.videoPlayer.nativeElement.addEventListener(
|
||||
'loadedmetadata',
|
||||
() => {
|
||||
if (this.startTime > 0) {
|
||||
this.videoPlayer.nativeElement.currentTime = this.startTime;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
this.videoPlayer.nativeElement.addEventListener('timeupdate', () => {
|
||||
this.timeUpdate.emit({
|
||||
@@ -143,7 +146,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (channel.url) {
|
||||
this.playbackIssue.emit(null);
|
||||
const url = channel.url + (channel.epgParams ?? '');
|
||||
const extension = getStreamExtensionFromUrl(channel.url);
|
||||
const extension = getPlaybackMediaExtensionFromUrl(channel.url);
|
||||
|
||||
// Set user agent if specified on channel
|
||||
if (channel.http?.['user-agent']) {
|
||||
@@ -154,7 +157,11 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
if ((extension === 'ts' || !extension) && mpegts.isSupported()) {
|
||||
console.log('Using mpegts.js for TS stream:', channel.name, url);
|
||||
console.log(
|
||||
'Using mpegts.js for TS stream:',
|
||||
channel.name,
|
||||
url
|
||||
);
|
||||
this.mpegtsPlayer = mpegts.createPlayer({
|
||||
type: 'mpegts',
|
||||
isLive: true,
|
||||
@@ -165,11 +172,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
);
|
||||
this.mpegtsPlayer.on(
|
||||
mpegts.Events.ERROR,
|
||||
(
|
||||
type: string,
|
||||
details: string,
|
||||
info: unknown
|
||||
): void => {
|
||||
(type: string, details: string, info: unknown): void => {
|
||||
this.playbackIssue.emit(
|
||||
classifyMpegTsPlaybackIssue(
|
||||
{
|
||||
@@ -285,6 +288,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
details: data.details,
|
||||
fatal: data.fatal,
|
||||
message: data.error?.message,
|
||||
error: data.error,
|
||||
},
|
||||
this.createSourceMetadata(url, 'application/x-mpegURL')
|
||||
)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import type {
|
||||
ExternalPlayerName,
|
||||
ResolvedPortalPlayback,
|
||||
} from 'shared-interfaces';
|
||||
|
||||
export const PlaybackDiagnosticCode = {
|
||||
UnsupportedContainer: 'unsupported-container',
|
||||
UnsupportedCodec: 'unsupported-codec',
|
||||
MediaDecodeError: 'media-decode-error',
|
||||
NetworkError: 'network-error',
|
||||
BrowserAccessError: 'browser-access-error',
|
||||
DrmOrEncryption: 'drm-or-encryption',
|
||||
UnknownPlaybackError: 'unknown-playback-error',
|
||||
} as const;
|
||||
|
||||
export type PlaybackDiagnosticCode =
|
||||
(typeof PlaybackDiagnosticCode)[keyof typeof PlaybackDiagnosticCode];
|
||||
|
||||
export const PlaybackDiagnosticSource = {
|
||||
Source: 'source',
|
||||
Native: 'native',
|
||||
Hls: 'hls',
|
||||
MpegTs: 'mpegts',
|
||||
} as const;
|
||||
|
||||
export type PlaybackDiagnosticSource =
|
||||
(typeof PlaybackDiagnosticSource)[keyof typeof PlaybackDiagnosticSource];
|
||||
|
||||
export const InlinePlaybackPlayer = {
|
||||
VideoJs: 'videojs',
|
||||
Html5: 'html5',
|
||||
ArtPlayer: 'artplayer',
|
||||
} as const;
|
||||
|
||||
export type InlinePlaybackPlayer =
|
||||
(typeof InlinePlaybackPlayer)[keyof typeof InlinePlaybackPlayer];
|
||||
|
||||
export interface PlaybackSourceMetadataInput {
|
||||
readonly url: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs?: readonly string[];
|
||||
readonly videoCodecs?: readonly string[];
|
||||
}
|
||||
|
||||
export interface PlaybackSourceMetadata {
|
||||
readonly url: string;
|
||||
readonly extension: string;
|
||||
readonly container: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs: readonly string[];
|
||||
readonly videoCodecs: readonly string[];
|
||||
}
|
||||
|
||||
export interface NativePlaybackErrorInput {
|
||||
readonly code?: number;
|
||||
readonly message?: string;
|
||||
}
|
||||
|
||||
export interface HlsPlaybackErrorInput {
|
||||
readonly type?: string;
|
||||
readonly details?: string;
|
||||
readonly fatal?: boolean;
|
||||
readonly message?: string;
|
||||
readonly error?: unknown;
|
||||
readonly audioCodecs?: readonly string[];
|
||||
readonly videoCodecs?: readonly string[];
|
||||
}
|
||||
|
||||
export interface MpegTsPlaybackErrorInput {
|
||||
readonly type?: string;
|
||||
readonly details?: string;
|
||||
readonly message?: string;
|
||||
readonly info?: unknown;
|
||||
}
|
||||
|
||||
export interface PlaybackDiagnostic {
|
||||
readonly code: PlaybackDiagnosticCode;
|
||||
readonly source: PlaybackDiagnosticSource;
|
||||
readonly sourceUrl: string;
|
||||
readonly container: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs: readonly string[];
|
||||
readonly videoCodecs: readonly string[];
|
||||
readonly details?: string;
|
||||
readonly nativeErrorCode?: number;
|
||||
readonly nativeErrorMessage?: string;
|
||||
readonly externalFallbackRecommended: boolean;
|
||||
}
|
||||
|
||||
export interface PlaybackFallbackRequest {
|
||||
readonly player: ExternalPlayerName;
|
||||
readonly playback: ResolvedPortalPlayback;
|
||||
readonly diagnostic: PlaybackDiagnostic;
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
classifyMpegTsPlaybackIssue,
|
||||
classifyNativePlaybackIssue,
|
||||
createPlaybackSourceMetadata,
|
||||
getLikelyBrowserUnsupportedCodecLabels,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from './playback-diagnostics.util';
|
||||
|
||||
describe('playback diagnostics', () => {
|
||||
@@ -72,6 +74,34 @@ describe('playback diagnostics', () => {
|
||||
expect(unsupportedSourceIssue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies MIME-only unsupported containers as container diagnostics', () => {
|
||||
const issue = classifyNativePlaybackIssue(
|
||||
{ code: 4, message: 'source not supported' },
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://example.com/archive/stream',
|
||||
mimeType: 'video/x-msvideo',
|
||||
player: 'html5',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.UnsupportedContainer);
|
||||
expect(issue.container).toBe('x-msvideo');
|
||||
});
|
||||
|
||||
it('does not classify MPEG-TS MIME-only failures as unsupported containers', () => {
|
||||
const issue = classifyNativePlaybackIssue(
|
||||
{ code: 4, message: 'source not supported' },
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://example.com/live/stream',
|
||||
mimeType: 'video/mp2t',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.UnsupportedCodec);
|
||||
expect(issue.container).toBe('mp2t');
|
||||
});
|
||||
|
||||
it('classifies HLS network errors without claiming codec incompatibility', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
@@ -89,6 +119,150 @@ describe('playback diagnostics', () => {
|
||||
expect(issue.externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('does not treat provider-side blocked messages as browser access errors', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError Request blocked by rate limiter',
|
||||
fatal: true,
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live/index.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.NetworkError);
|
||||
expect(issue.externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps raw HLS error object context in diagnostic details', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError',
|
||||
fatal: true,
|
||||
error: {
|
||||
context: 'xhr setup failed',
|
||||
status: 0,
|
||||
},
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live/index.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.details).toContain('xhr setup failed');
|
||||
expect(issue.details).toContain('"status":0');
|
||||
});
|
||||
|
||||
it('classifies HLS browser access blocks separately from provider network failures', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details:
|
||||
'manifestLoadError Mixed Content: The page at https://app.example was loaded over HTTPS, but requested an insecure stream http://provider.example/live.m3u8. This request has been blocked.',
|
||||
fatal: true,
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'http://provider.example/live.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe('browser-access-error');
|
||||
expect(issue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies browser security policy blocks as browser access errors', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details:
|
||||
'manifestLoadError Refused to connect because it violates the following Content Security Policy directive: "connect-src"',
|
||||
fatal: true,
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'http://provider.example/live.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.BrowserAccessError);
|
||||
expect(issue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies native CORS failures as browser access errors', () => {
|
||||
const issue = classifyNativePlaybackIssue(
|
||||
{
|
||||
code: 2,
|
||||
message:
|
||||
'Access to media at https://provider.example/live.m3u8 from origin app://iptvnator has been blocked by CORS policy: No Access-Control-Allow-Origin header is present.',
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live.m3u8',
|
||||
player: 'html5',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe('browser-access-error');
|
||||
expect(issue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps opaque native network failures generic when the browser hides access details', () => {
|
||||
const issue = classifyNativePlaybackIssue(
|
||||
{
|
||||
code: 2,
|
||||
message: '',
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live.m3u8',
|
||||
player: 'html5',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.NetworkError);
|
||||
expect(issue.externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('classifies mpegts browser fetch restrictions separately from generic network errors', () => {
|
||||
const issue = classifyMpegTsPlaybackIssue(
|
||||
{
|
||||
type: 'NetworkError',
|
||||
details:
|
||||
'Fetch blocked by access-control policy while loading segment',
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live/channel.ts',
|
||||
mimeType: 'video/mp2t',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe('browser-access-error');
|
||||
expect(issue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies mpegts early EOF failures as fallback-actionable media errors', () => {
|
||||
const issue = classifyMpegTsPlaybackIssue(
|
||||
{
|
||||
type: 'NetworkError',
|
||||
details: 'UnrecoverableEarlyEof',
|
||||
info: { msg: 'Fetch stream meet Early-EOF' },
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/movie/123.ts',
|
||||
mimeType: 'video/mp2t',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.MediaDecodeError);
|
||||
expect(issue.externalFallbackRecommended).toBe(true);
|
||||
expect(issue.details).toContain('Early-EOF');
|
||||
});
|
||||
|
||||
it('classifies mpegts codec errors as unsupported codec fallbacks', () => {
|
||||
const issue = classifyMpegTsPlaybackIssue(
|
||||
{
|
||||
@@ -125,4 +299,85 @@ describe('playback diagnostics', () => {
|
||||
expect(metadata.extension).toBe('');
|
||||
expect(metadata.container).toBe('');
|
||||
});
|
||||
|
||||
it('uses declared media query metadata without exposing web script extensions', () => {
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?format=m3u8&stream=123'
|
||||
)
|
||||
).toBe('m3u8');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?container=m3u8&stream=123'
|
||||
)
|
||||
).toBe('m3u8');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?ext=ts&stream=123'
|
||||
)
|
||||
).toBe('ts');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?stream=123'
|
||||
)
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('keeps trusted media path extensions ahead of generic query metadata', () => {
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://cdn.example/archive/movie.mp4?container=m3u8'
|
||||
)
|
||||
).toBe('mp4');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://cdn.example/live/channel.m3u8?format=mp4'
|
||||
)
|
||||
).toBe('m3u8');
|
||||
});
|
||||
|
||||
it('keeps explicit extension query metadata ahead of trusted media path extensions', () => {
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://cdn.example/live/channel.ts?extension=m3u8'
|
||||
)
|
||||
).toBe('m3u8');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://cdn.example/live/channel.m3u8?ext=ts'
|
||||
)
|
||||
).toBe('ts');
|
||||
});
|
||||
|
||||
it('does not infer media extensions from generic type/output query metadata', () => {
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?type=m3u8&stream=123'
|
||||
)
|
||||
).toBe('');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?output=ts&stream=123'
|
||||
)
|
||||
).toBe('');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.php?format=mpv&stream=123'
|
||||
)
|
||||
).toBe('');
|
||||
expect(
|
||||
getPlaybackMediaExtensionFromUrl(
|
||||
'http://portal.example/play/live.mpv?stream=123'
|
||||
)
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('detects codecs with limited Chromium browser-player support', () => {
|
||||
expect(
|
||||
getLikelyBrowserUnsupportedCodecLabels({
|
||||
audioCodecs: ['mp4a.40.2', 'ac-3', 'ec-3'],
|
||||
videoCodecs: ['avc1.64001f', 'hvc1.1.6.L93.B0'],
|
||||
})
|
||||
).toEqual(['HEVC', 'AC-3', 'E-AC-3']);
|
||||
});
|
||||
});
|
||||
@@ -1,157 +1,55 @@
|
||||
import { getExtensionFromUrl } from 'm3u-utils';
|
||||
import type { ExternalPlayerName, ResolvedPortalPlayback } from 'shared-interfaces';
|
||||
import type {
|
||||
HlsPlaybackErrorInput,
|
||||
MpegTsPlaybackErrorInput,
|
||||
NativePlaybackErrorInput,
|
||||
PlaybackDiagnostic,
|
||||
PlaybackDiagnosticCode,
|
||||
PlaybackDiagnosticSource,
|
||||
PlaybackSourceMetadata,
|
||||
} from './playback-diagnostics.model';
|
||||
import { PlaybackDiagnosticCode as DiagnosticCode } from './playback-diagnostics.model';
|
||||
import { PlaybackDiagnosticSource as DiagnosticSource } from './playback-diagnostics.model';
|
||||
import {
|
||||
isBrowserAccessFailure,
|
||||
isCodecFailure,
|
||||
isDrmOrEncryptionFailure,
|
||||
isEarlyEofFailure,
|
||||
isNetworkFailure,
|
||||
normalizeErrorDetails,
|
||||
} from './playback-error-patterns.util';
|
||||
import {
|
||||
isLikelyContainerIssue,
|
||||
mergeCodecMetadata,
|
||||
} from './playback-media-source.util';
|
||||
|
||||
export const PlaybackDiagnosticCode = {
|
||||
UnsupportedContainer: 'unsupported-container',
|
||||
UnsupportedCodec: 'unsupported-codec',
|
||||
MediaDecodeError: 'media-decode-error',
|
||||
NetworkError: 'network-error',
|
||||
DrmOrEncryption: 'drm-or-encryption',
|
||||
UnknownPlaybackError: 'unknown-playback-error',
|
||||
} as const;
|
||||
|
||||
export type PlaybackDiagnosticCode =
|
||||
(typeof PlaybackDiagnosticCode)[keyof typeof PlaybackDiagnosticCode];
|
||||
|
||||
export const PlaybackDiagnosticSource = {
|
||||
Source: 'source',
|
||||
Native: 'native',
|
||||
Hls: 'hls',
|
||||
MpegTs: 'mpegts',
|
||||
} as const;
|
||||
|
||||
export type PlaybackDiagnosticSource =
|
||||
(typeof PlaybackDiagnosticSource)[keyof typeof PlaybackDiagnosticSource];
|
||||
|
||||
export const InlinePlaybackPlayer = {
|
||||
VideoJs: 'videojs',
|
||||
Html5: 'html5',
|
||||
ArtPlayer: 'artplayer',
|
||||
} as const;
|
||||
|
||||
export type InlinePlaybackPlayer =
|
||||
(typeof InlinePlaybackPlayer)[keyof typeof InlinePlaybackPlayer];
|
||||
|
||||
export interface PlaybackSourceMetadataInput {
|
||||
readonly url: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs?: readonly string[];
|
||||
readonly videoCodecs?: readonly string[];
|
||||
}
|
||||
|
||||
export interface PlaybackSourceMetadata {
|
||||
readonly url: string;
|
||||
readonly extension: string;
|
||||
readonly container: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs: readonly string[];
|
||||
readonly videoCodecs: readonly string[];
|
||||
}
|
||||
|
||||
export interface NativePlaybackErrorInput {
|
||||
readonly code?: number;
|
||||
readonly message?: string;
|
||||
}
|
||||
|
||||
export interface HlsPlaybackErrorInput {
|
||||
readonly type?: string;
|
||||
readonly details?: string;
|
||||
readonly fatal?: boolean;
|
||||
readonly message?: string;
|
||||
readonly error?: unknown;
|
||||
readonly audioCodecs?: readonly string[];
|
||||
readonly videoCodecs?: readonly string[];
|
||||
}
|
||||
|
||||
export interface MpegTsPlaybackErrorInput {
|
||||
readonly type?: string;
|
||||
readonly details?: string;
|
||||
readonly message?: string;
|
||||
readonly info?: unknown;
|
||||
}
|
||||
|
||||
export interface PlaybackDiagnostic {
|
||||
readonly code: PlaybackDiagnosticCode;
|
||||
readonly source: PlaybackDiagnosticSource;
|
||||
readonly sourceUrl: string;
|
||||
readonly container: string;
|
||||
readonly mimeType?: string;
|
||||
readonly player?: InlinePlaybackPlayer;
|
||||
readonly audioCodecs: readonly string[];
|
||||
readonly videoCodecs: readonly string[];
|
||||
readonly details?: string;
|
||||
readonly nativeErrorCode?: number;
|
||||
readonly nativeErrorMessage?: string;
|
||||
readonly externalFallbackRecommended: boolean;
|
||||
}
|
||||
|
||||
export interface PlaybackFallbackRequest {
|
||||
readonly player: ExternalPlayerName;
|
||||
readonly playback: ResolvedPortalPlayback;
|
||||
readonly diagnostic: PlaybackDiagnostic;
|
||||
}
|
||||
|
||||
const UNSUPPORTED_CONTAINER_EXTENSIONS = new Set([
|
||||
'avi',
|
||||
'asf',
|
||||
'divx',
|
||||
'flv',
|
||||
'm2ts',
|
||||
'm4v',
|
||||
'mkv',
|
||||
'mov',
|
||||
'mpeg',
|
||||
'mpg',
|
||||
'rm',
|
||||
'rmvb',
|
||||
'ts',
|
||||
'vob',
|
||||
'wmv',
|
||||
]);
|
||||
|
||||
const NON_MEDIA_URL_EXTENSIONS = new Set([
|
||||
'asp',
|
||||
'aspx',
|
||||
'cgi',
|
||||
'jsp',
|
||||
'php',
|
||||
'pl',
|
||||
]);
|
||||
export * from './playback-diagnostics.model';
|
||||
export {
|
||||
createPlaybackSourceMetadata,
|
||||
getLikelyBrowserUnsupportedCodecLabels,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from './playback-media-source.util';
|
||||
|
||||
const SOURCE_NOT_SUPPORTED_CODE = 4;
|
||||
const DECODE_ERROR_CODE = 3;
|
||||
const NETWORK_ERROR_CODE = 2;
|
||||
|
||||
export function createPlaybackSourceMetadata(
|
||||
input: PlaybackSourceMetadataInput
|
||||
): PlaybackSourceMetadata {
|
||||
const extension = getMediaExtensionFromUrl(input.url);
|
||||
const mimeType = input.mimeType?.trim() || undefined;
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
extension,
|
||||
container: extension || inferContainerFromMimeType(mimeType),
|
||||
mimeType,
|
||||
player: input.player,
|
||||
audioCodecs: normalizeCodecs(input.audioCodecs),
|
||||
videoCodecs: normalizeCodecs(input.videoCodecs),
|
||||
};
|
||||
}
|
||||
|
||||
export function classifyNativePlaybackIssue(
|
||||
error: NativePlaybackErrorInput | MediaError | null | undefined,
|
||||
metadata: PlaybackSourceMetadata
|
||||
): PlaybackDiagnostic {
|
||||
const nativeErrorCode = error?.code;
|
||||
const nativeErrorMessage = error?.message || undefined;
|
||||
const lowerNativeErrorMessage = nativeErrorMessage?.toLowerCase() ?? '';
|
||||
|
||||
if (nativeErrorCode === NETWORK_ERROR_CODE) {
|
||||
// Native MediaError details are often opaque for browser security
|
||||
// failures. Only classify browser access when the runtime exposes a
|
||||
// concrete CORS/mixed-content/CSP-style message.
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.NetworkError,
|
||||
source: PlaybackDiagnosticSource.Native,
|
||||
code: isBrowserAccessFailure(lowerNativeErrorMessage)
|
||||
? DiagnosticCode.BrowserAccessError
|
||||
: DiagnosticCode.NetworkError,
|
||||
source: DiagnosticSource.Native,
|
||||
metadata,
|
||||
nativeErrorCode,
|
||||
nativeErrorMessage,
|
||||
@@ -160,8 +58,8 @@ export function classifyNativePlaybackIssue(
|
||||
|
||||
if (nativeErrorCode === DECODE_ERROR_CODE) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.MediaDecodeError,
|
||||
source: PlaybackDiagnosticSource.Native,
|
||||
code: DiagnosticCode.MediaDecodeError,
|
||||
source: DiagnosticSource.Native,
|
||||
metadata,
|
||||
nativeErrorCode,
|
||||
nativeErrorMessage,
|
||||
@@ -171,9 +69,9 @@ export function classifyNativePlaybackIssue(
|
||||
if (nativeErrorCode === SOURCE_NOT_SUPPORTED_CODE) {
|
||||
return createDiagnostic({
|
||||
code: isLikelyContainerIssue(metadata)
|
||||
? PlaybackDiagnosticCode.UnsupportedContainer
|
||||
: PlaybackDiagnosticCode.UnsupportedCodec,
|
||||
source: PlaybackDiagnosticSource.Native,
|
||||
? DiagnosticCode.UnsupportedContainer
|
||||
: DiagnosticCode.UnsupportedCodec,
|
||||
source: DiagnosticSource.Native,
|
||||
metadata,
|
||||
nativeErrorCode,
|
||||
nativeErrorMessage,
|
||||
@@ -181,8 +79,8 @@ export function classifyNativePlaybackIssue(
|
||||
}
|
||||
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnknownPlaybackError,
|
||||
source: PlaybackDiagnosticSource.Native,
|
||||
code: DiagnosticCode.UnknownPlaybackError,
|
||||
source: DiagnosticSource.Native,
|
||||
metadata,
|
||||
nativeErrorCode,
|
||||
nativeErrorMessage,
|
||||
@@ -203,8 +101,10 @@ export function classifyHlsPlaybackIssue(
|
||||
|
||||
if (isNetworkFailure(lowerType, lowerDetails)) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.NetworkError,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
code: isBrowserAccessFailure(lowerDetails)
|
||||
? DiagnosticCode.BrowserAccessError
|
||||
: DiagnosticCode.NetworkError,
|
||||
source: DiagnosticSource.Hls,
|
||||
metadata: mergedMetadata,
|
||||
details,
|
||||
});
|
||||
@@ -212,8 +112,8 @@ export function classifyHlsPlaybackIssue(
|
||||
|
||||
if (isDrmOrEncryptionFailure(lowerDetails)) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.DrmOrEncryption,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
code: DiagnosticCode.DrmOrEncryption,
|
||||
source: DiagnosticSource.Hls,
|
||||
metadata: mergedMetadata,
|
||||
details,
|
||||
});
|
||||
@@ -221,8 +121,8 @@ export function classifyHlsPlaybackIssue(
|
||||
|
||||
if (isCodecFailure(lowerDetails)) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnsupportedCodec,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
code: DiagnosticCode.UnsupportedCodec,
|
||||
source: DiagnosticSource.Hls,
|
||||
metadata: mergedMetadata,
|
||||
details,
|
||||
});
|
||||
@@ -230,16 +130,16 @@ export function classifyHlsPlaybackIssue(
|
||||
|
||||
if (lowerType.includes('media') || lowerType.includes('mux')) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.MediaDecodeError,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
code: DiagnosticCode.MediaDecodeError,
|
||||
source: DiagnosticSource.Hls,
|
||||
metadata: mergedMetadata,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnknownPlaybackError,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
code: DiagnosticCode.UnknownPlaybackError,
|
||||
source: DiagnosticSource.Hls,
|
||||
metadata: mergedMetadata,
|
||||
details,
|
||||
});
|
||||
@@ -253,10 +153,21 @@ export function classifyMpegTsPlaybackIssue(
|
||||
const lowerDetails = details.toLowerCase();
|
||||
const lowerType = (error.type ?? '').toLowerCase();
|
||||
|
||||
if (isEarlyEofFailure(lowerDetails)) {
|
||||
return createDiagnostic({
|
||||
code: DiagnosticCode.MediaDecodeError,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
if (isNetworkFailure(lowerType, lowerDetails)) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.NetworkError,
|
||||
source: PlaybackDiagnosticSource.MpegTs,
|
||||
code: isBrowserAccessFailure(lowerDetails)
|
||||
? DiagnosticCode.BrowserAccessError
|
||||
: DiagnosticCode.NetworkError,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
@@ -264,8 +175,8 @@ export function classifyMpegTsPlaybackIssue(
|
||||
|
||||
if (lowerDetails.includes('codec')) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnsupportedCodec,
|
||||
source: PlaybackDiagnosticSource.MpegTs,
|
||||
code: DiagnosticCode.UnsupportedCodec,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
@@ -273,8 +184,8 @@ export function classifyMpegTsPlaybackIssue(
|
||||
|
||||
if (lowerDetails.includes('format') || lowerDetails.includes('mse')) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnsupportedContainer,
|
||||
source: PlaybackDiagnosticSource.MpegTs,
|
||||
code: DiagnosticCode.UnsupportedContainer,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
@@ -282,16 +193,16 @@ export function classifyMpegTsPlaybackIssue(
|
||||
|
||||
if (lowerType.includes('media')) {
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.MediaDecodeError,
|
||||
source: PlaybackDiagnosticSource.MpegTs,
|
||||
code: DiagnosticCode.MediaDecodeError,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnknownPlaybackError,
|
||||
source: PlaybackDiagnosticSource.MpegTs,
|
||||
code: DiagnosticCode.UnknownPlaybackError,
|
||||
source: DiagnosticSource.MpegTs,
|
||||
metadata,
|
||||
details,
|
||||
});
|
||||
@@ -312,8 +223,8 @@ export function classifyUnsupportedHlsManifestCodecs(
|
||||
}
|
||||
|
||||
return createDiagnostic({
|
||||
code: PlaybackDiagnosticCode.UnsupportedCodec,
|
||||
source: PlaybackDiagnosticSource.Source,
|
||||
code: DiagnosticCode.UnsupportedCodec,
|
||||
source: DiagnosticSource.Source,
|
||||
metadata,
|
||||
details: mimeType,
|
||||
});
|
||||
@@ -354,148 +265,11 @@ function createDiagnostic(options: {
|
||||
|
||||
function isExternalFallbackRecommended(code: PlaybackDiagnosticCode): boolean {
|
||||
return (
|
||||
code === PlaybackDiagnosticCode.UnsupportedContainer ||
|
||||
code === PlaybackDiagnosticCode.UnsupportedCodec ||
|
||||
code === PlaybackDiagnosticCode.MediaDecodeError ||
|
||||
code === PlaybackDiagnosticCode.DrmOrEncryption
|
||||
);
|
||||
}
|
||||
|
||||
function mergeCodecMetadata(
|
||||
metadata: PlaybackSourceMetadata,
|
||||
codecs: Pick<PlaybackSourceMetadataInput, 'audioCodecs' | 'videoCodecs'>
|
||||
): PlaybackSourceMetadata {
|
||||
const audioCodecs = normalizeCodecs([
|
||||
...metadata.audioCodecs,
|
||||
...(codecs.audioCodecs ?? []),
|
||||
]);
|
||||
const videoCodecs = normalizeCodecs([
|
||||
...metadata.videoCodecs,
|
||||
...(codecs.videoCodecs ?? []),
|
||||
]);
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
audioCodecs,
|
||||
videoCodecs,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCodecs(codecs: readonly string[] | undefined): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(codecs ?? [])
|
||||
.map((codec) => codec.trim())
|
||||
.filter((codec) => codec.length > 0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeToken(value: string | undefined): string {
|
||||
return value?.trim().toLowerCase() ?? '';
|
||||
}
|
||||
|
||||
function normalizeExtensionToken(value: string | undefined): string {
|
||||
return normalizeToken(value).replace(/^\.+/, '');
|
||||
}
|
||||
|
||||
function getMediaExtensionFromUrl(url: string): string {
|
||||
const queryExtension = getMediaExtensionFromQuery(url);
|
||||
if (queryExtension) {
|
||||
return queryExtension;
|
||||
}
|
||||
|
||||
const pathExtension = normalizeExtensionToken(getExtensionFromUrl(url));
|
||||
if (NON_MEDIA_URL_EXTENSIONS.has(pathExtension)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return pathExtension;
|
||||
}
|
||||
|
||||
function getMediaExtensionFromQuery(url: string): string {
|
||||
try {
|
||||
const parsedUrl = new URL(url, 'http://iptvnator.local');
|
||||
const declaredExtension = normalizeExtensionToken(
|
||||
parsedUrl.searchParams.get('extension') ?? undefined
|
||||
);
|
||||
|
||||
if (!declaredExtension) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return NON_MEDIA_URL_EXTENSIONS.has(declaredExtension)
|
||||
? ''
|
||||
: declaredExtension;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function inferContainerFromMimeType(mimeType: string | undefined): string {
|
||||
if (!mimeType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const subtype = mimeType.split(';')[0]?.split('/')[1] ?? '';
|
||||
return subtype.toLowerCase();
|
||||
}
|
||||
|
||||
function isLikelyContainerIssue(metadata: PlaybackSourceMetadata): boolean {
|
||||
return (
|
||||
UNSUPPORTED_CONTAINER_EXTENSIONS.has(metadata.extension) ||
|
||||
metadata.mimeType === 'video/matroska'
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeErrorDetails(
|
||||
error: HlsPlaybackErrorInput | MpegTsPlaybackErrorInput
|
||||
): string {
|
||||
const hlsError = 'error' in error ? error.error : undefined;
|
||||
const mpegTsInfo = 'info' in error ? error.info : undefined;
|
||||
const errorMessage =
|
||||
hlsError instanceof Error
|
||||
? hlsError.message
|
||||
: typeof hlsError === 'string'
|
||||
? hlsError
|
||||
: '';
|
||||
const info =
|
||||
typeof mpegTsInfo === 'string'
|
||||
? mpegTsInfo
|
||||
: mpegTsInfo
|
||||
? JSON.stringify(mpegTsInfo)
|
||||
: '';
|
||||
|
||||
return [error.details, error.message, errorMessage, info]
|
||||
.filter((part): part is string => Boolean(part))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function isNetworkFailure(type: string, details: string): boolean {
|
||||
return (
|
||||
type.includes('network') ||
|
||||
details.includes('network') ||
|
||||
details.includes('loaderror') ||
|
||||
details.includes('timeout') ||
|
||||
details.includes('status')
|
||||
);
|
||||
}
|
||||
|
||||
function isCodecFailure(details: string): boolean {
|
||||
return (
|
||||
details.includes('codec') ||
|
||||
details.includes('incompatiblecodecs') ||
|
||||
details.includes('addcodec')
|
||||
);
|
||||
}
|
||||
|
||||
function isDrmOrEncryptionFailure(details: string): boolean {
|
||||
return (
|
||||
details.includes('decrypt') ||
|
||||
details.includes('keysystem') ||
|
||||
details.includes('keyload') ||
|
||||
details.includes('license') ||
|
||||
details.includes('drm')
|
||||
code === DiagnosticCode.UnsupportedContainer ||
|
||||
code === DiagnosticCode.UnsupportedCodec ||
|
||||
code === DiagnosticCode.MediaDecodeError ||
|
||||
code === DiagnosticCode.BrowserAccessError ||
|
||||
code === DiagnosticCode.DrmOrEncryption
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import type {
|
||||
HlsPlaybackErrorInput,
|
||||
MpegTsPlaybackErrorInput,
|
||||
} from './playback-diagnostics.model';
|
||||
|
||||
export function normalizeErrorDetails(
|
||||
error: HlsPlaybackErrorInput | MpegTsPlaybackErrorInput
|
||||
): string {
|
||||
const hlsError = 'error' in error ? error.error : undefined;
|
||||
const mpegTsInfo = 'info' in error ? error.info : undefined;
|
||||
const errorMessage = normalizeErrorPayload(hlsError);
|
||||
const info = normalizeErrorPayload(mpegTsInfo);
|
||||
|
||||
return [error.details, error.message, errorMessage, info]
|
||||
.filter((part): part is string => Boolean(part))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function isNetworkFailure(type: string, details: string): boolean {
|
||||
return (
|
||||
type.includes('network') ||
|
||||
details.includes('network') ||
|
||||
details.includes('loaderror') ||
|
||||
details.includes('timeout') ||
|
||||
details.includes('status')
|
||||
);
|
||||
}
|
||||
|
||||
export function isBrowserAccessFailure(details: string): boolean {
|
||||
return (
|
||||
details.includes('cors') ||
|
||||
details.includes('cross-origin') ||
|
||||
details.includes('cross origin') ||
|
||||
details.includes('access-control') ||
|
||||
details.includes('access control') ||
|
||||
details.includes('content security policy') ||
|
||||
details.includes('mixed content') ||
|
||||
details.includes('private network access') ||
|
||||
details.includes('blocked by content security') ||
|
||||
details.includes('blocked by cors') ||
|
||||
details.includes('blocked by mixed content') ||
|
||||
details.includes('not allowed to load local resource') ||
|
||||
details.includes('err_blocked') ||
|
||||
details.includes('err_cleartext')
|
||||
);
|
||||
}
|
||||
|
||||
export function isEarlyEofFailure(details: string): boolean {
|
||||
const compactDetails = details.replace(/[^a-z0-9]/g, '');
|
||||
return compactDetails.includes('earlyeof');
|
||||
}
|
||||
|
||||
export function isCodecFailure(details: string): boolean {
|
||||
return (
|
||||
details.includes('codec') ||
|
||||
details.includes('incompatiblecodecs') ||
|
||||
details.includes('addcodec')
|
||||
);
|
||||
}
|
||||
|
||||
export function isDrmOrEncryptionFailure(details: string): boolean {
|
||||
return (
|
||||
details.includes('decrypt') ||
|
||||
details.includes('keysystem') ||
|
||||
details.includes('keyload') ||
|
||||
details.includes('license') ||
|
||||
details.includes('drm')
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeErrorPayload(payload: unknown): string {
|
||||
if (!payload) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof payload === 'string') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (payload instanceof Error) {
|
||||
const extraDetails = stringifyUnknown(payload);
|
||||
return [payload.message, extraDetails === '{}' ? '' : extraDetails]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
return stringifyUnknown(payload);
|
||||
}
|
||||
|
||||
function stringifyUnknown(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value) || '';
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import { getExtensionFromUrl } from 'm3u-utils';
|
||||
|
||||
import type {
|
||||
PlaybackSourceMetadata,
|
||||
PlaybackSourceMetadataInput,
|
||||
} from './playback-diagnostics.model';
|
||||
|
||||
const UNSUPPORTED_CONTAINER_EXTENSIONS = new Set([
|
||||
'avi',
|
||||
'asf',
|
||||
'divx',
|
||||
'flv',
|
||||
'm2ts',
|
||||
'm4v',
|
||||
'mkv',
|
||||
'mov',
|
||||
'mpeg',
|
||||
'mpg',
|
||||
'rm',
|
||||
'rmvb',
|
||||
'ts',
|
||||
'vob',
|
||||
'wmv',
|
||||
]);
|
||||
|
||||
const UNSUPPORTED_CONTAINER_NAMES = new Set([
|
||||
...UNSUPPORTED_CONTAINER_EXTENSIONS,
|
||||
'matroska',
|
||||
'quicktime',
|
||||
'x-matroska',
|
||||
'x-msvideo',
|
||||
]);
|
||||
|
||||
const EXPLICIT_MEDIA_EXTENSION_QUERY_KEYS = ['extension', 'ext'];
|
||||
|
||||
const DECLARED_MEDIA_FORMAT_QUERY_KEYS = ['format', 'container'];
|
||||
|
||||
const DECLARED_MEDIA_EXTENSION_ALIASES = new Map([
|
||||
['hls', 'm3u8'],
|
||||
['mpegts', 'ts'],
|
||||
['mpeg-ts', 'ts'],
|
||||
]);
|
||||
|
||||
const DECLARED_MEDIA_EXTENSIONS = new Set([
|
||||
...UNSUPPORTED_CONTAINER_EXTENSIONS,
|
||||
'aac',
|
||||
'flac',
|
||||
'm3u',
|
||||
'm3u8',
|
||||
'm4s',
|
||||
'mp3',
|
||||
'mp4',
|
||||
'mpd',
|
||||
'oga',
|
||||
'ogg',
|
||||
'ogv',
|
||||
'webm',
|
||||
]);
|
||||
|
||||
const NON_MEDIA_URL_EXTENSIONS = new Set([
|
||||
'asp',
|
||||
'aspx',
|
||||
'cgi',
|
||||
'jsp',
|
||||
'mpv',
|
||||
'php',
|
||||
'pl',
|
||||
]);
|
||||
|
||||
const BROWSER_LIMITED_CODEC_PATTERNS: ReadonlyArray<{
|
||||
readonly label: string;
|
||||
readonly pattern: RegExp;
|
||||
}> = [
|
||||
{ label: 'HEVC', pattern: /^(hev1|hvc1|hevc|h265)/ },
|
||||
{ label: 'AC-3', pattern: /^(ac-?3|dac3)$/ },
|
||||
{ label: 'E-AC-3', pattern: /^(ec-?3|eac-?3|dec3)$/ },
|
||||
{ label: 'DTS', pattern: /^(dts|dtsc|dtse|dtsh|dtsl)/ },
|
||||
{ label: 'MPEG-2 Video', pattern: /^(mp2v|mpeg2video)/ },
|
||||
];
|
||||
|
||||
export function createPlaybackSourceMetadata(
|
||||
input: PlaybackSourceMetadataInput
|
||||
): PlaybackSourceMetadata {
|
||||
const extension = getPlaybackMediaExtensionFromUrl(input.url);
|
||||
const mimeType = input.mimeType?.trim() || undefined;
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
extension,
|
||||
container: extension || inferContainerFromMimeType(mimeType),
|
||||
mimeType,
|
||||
player: input.player,
|
||||
audioCodecs: normalizeCodecs(input.audioCodecs),
|
||||
videoCodecs: normalizeCodecs(input.videoCodecs),
|
||||
};
|
||||
}
|
||||
|
||||
export function getPlaybackMediaExtensionFromUrl(url: string): string {
|
||||
const explicitQueryExtension = getMediaExtensionFromQuery(
|
||||
url,
|
||||
EXPLICIT_MEDIA_EXTENSION_QUERY_KEYS
|
||||
);
|
||||
if (explicitQueryExtension) {
|
||||
return explicitQueryExtension;
|
||||
}
|
||||
|
||||
const pathExtension = normalizeExtensionToken(getExtensionFromUrl(url));
|
||||
if (DECLARED_MEDIA_EXTENSIONS.has(pathExtension)) {
|
||||
return pathExtension;
|
||||
}
|
||||
|
||||
const formatQueryExtension = getMediaExtensionFromQuery(
|
||||
url,
|
||||
DECLARED_MEDIA_FORMAT_QUERY_KEYS
|
||||
);
|
||||
if (formatQueryExtension) {
|
||||
return formatQueryExtension;
|
||||
}
|
||||
|
||||
if (NON_MEDIA_URL_EXTENSIONS.has(pathExtension)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return pathExtension;
|
||||
}
|
||||
|
||||
export function getLikelyBrowserUnsupportedCodecLabels(
|
||||
metadata: Pick<PlaybackSourceMetadata, 'audioCodecs' | 'videoCodecs'>
|
||||
): string[] {
|
||||
const codecs = [...metadata.videoCodecs, ...metadata.audioCodecs].map(
|
||||
normalizeToken
|
||||
);
|
||||
|
||||
return BROWSER_LIMITED_CODEC_PATTERNS.filter(({ pattern }) =>
|
||||
codecs.some((codec) => pattern.test(codec))
|
||||
).map(({ label }) => label);
|
||||
}
|
||||
|
||||
export function isLikelyContainerIssue(
|
||||
metadata: PlaybackSourceMetadata
|
||||
): boolean {
|
||||
return (
|
||||
UNSUPPORTED_CONTAINER_NAMES.has(metadata.extension) ||
|
||||
UNSUPPORTED_CONTAINER_NAMES.has(metadata.container) ||
|
||||
metadata.mimeType === 'video/matroska'
|
||||
);
|
||||
}
|
||||
|
||||
export function mergeCodecMetadata(
|
||||
metadata: PlaybackSourceMetadata,
|
||||
codecs: Pick<PlaybackSourceMetadataInput, 'audioCodecs' | 'videoCodecs'>
|
||||
): PlaybackSourceMetadata {
|
||||
const audioCodecs = normalizeCodecs([
|
||||
...metadata.audioCodecs,
|
||||
...(codecs.audioCodecs ?? []),
|
||||
]);
|
||||
const videoCodecs = normalizeCodecs([
|
||||
...metadata.videoCodecs,
|
||||
...(codecs.videoCodecs ?? []),
|
||||
]);
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
audioCodecs,
|
||||
videoCodecs,
|
||||
};
|
||||
}
|
||||
|
||||
function inferContainerFromMimeType(mimeType: string | undefined): string {
|
||||
if (!mimeType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const subtype = mimeType.split(';')[0]?.split('/')[1] ?? '';
|
||||
return subtype.toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeCodecs(codecs: readonly string[] | undefined): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(codecs ?? [])
|
||||
.map((codec) => codec.trim())
|
||||
.filter((codec) => codec.length > 0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeToken(value: string | undefined): string {
|
||||
return value?.trim().toLowerCase() ?? '';
|
||||
}
|
||||
|
||||
function normalizeExtensionToken(value: string | undefined): string {
|
||||
return normalizeToken(value).replace(/^\.+/, '');
|
||||
}
|
||||
|
||||
function getMediaExtensionFromQuery(
|
||||
url: string,
|
||||
queryKeys: readonly string[]
|
||||
): string {
|
||||
try {
|
||||
const parsedUrl = new URL(url, 'http://iptvnator.local');
|
||||
for (const key of queryKeys) {
|
||||
const declaredExtension = normalizeDeclaredMediaExtension(
|
||||
parsedUrl.searchParams.get(key) ?? undefined
|
||||
);
|
||||
if (declaredExtension) {
|
||||
return declaredExtension;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDeclaredMediaExtension(value: string | undefined): string {
|
||||
const extension = normalizeExtensionToken(value);
|
||||
const normalized =
|
||||
DECLARED_MEDIA_EXTENSION_ALIASES.get(extension) ?? extension;
|
||||
|
||||
if (!normalized || NON_MEDIA_URL_EXTENSIONS.has(normalized)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return DECLARED_MEDIA_EXTENSIONS.has(normalized) ? normalized : '';
|
||||
}
|
||||
@@ -163,6 +163,9 @@ describe('VjsPlayerComponent', () => {
|
||||
expect(
|
||||
isMpegTsSource('https://example.com/live.php?extension=ts')
|
||||
).toBe(true);
|
||||
expect(isMpegTsSource('https://example.com/live.php?stream=123')).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('emits a playback issue when VideoJS reports an unsupported source', () => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import '@yangkghjh/videojs-aspect-ratio-panel';
|
||||
import { getStreamExtensionFromUrl } from 'm3u-utils';
|
||||
import mpegts from 'mpegts.js';
|
||||
import videoJs from 'video.js';
|
||||
import 'videojs-contrib-quality-levels';
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
classifyMpegTsPlaybackIssue,
|
||||
classifyNativePlaybackIssue,
|
||||
createPlaybackSourceMetadata,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
|
||||
/**
|
||||
@@ -254,7 +254,7 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
private isMpegTsSource(url?: string): boolean {
|
||||
if (!url) return false;
|
||||
const extension = getStreamExtensionFromUrl(url);
|
||||
const extension = getPlaybackMediaExtensionFromUrl(url);
|
||||
return (extension === 'ts' || !extension) && mpegts.isSupported();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,37 @@
|
||||
{{ meta }}
|
||||
</div>
|
||||
}
|
||||
@if (getDiagnosticCodecHint(issue); as codecHint) {
|
||||
<div
|
||||
class="web-player-diagnostic__codec-hint"
|
||||
data-test-id="playback-codec-hint"
|
||||
>
|
||||
{{
|
||||
'PLAYBACK_DIAGNOSTICS.CODEC_HINT'
|
||||
| translate: { codecs: codecHint }
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
<details
|
||||
class="web-player-diagnostic__details"
|
||||
data-test-id="playback-diagnostic-details"
|
||||
>
|
||||
<summary>
|
||||
<mat-icon aria-hidden="true">info</mat-icon>
|
||||
{{ 'PLAYBACK_DIAGNOSTICS.DETAILS_SUMMARY' | translate }}
|
||||
</summary>
|
||||
<dl>
|
||||
@for (
|
||||
detail of getDiagnosticDetails(issue);
|
||||
track detail.labelKey
|
||||
) {
|
||||
<div class="web-player-diagnostic__detail-row">
|
||||
<dt>{{ detail.labelKey | translate }}</dt>
|
||||
<dd>{{ detail.value }}</dd>
|
||||
</div>
|
||||
}
|
||||
</dl>
|
||||
</details>
|
||||
</div>
|
||||
<div class="web-player-diagnostic__actions">
|
||||
@if (canShowExternalFallbackActions()) {
|
||||
|
||||
@@ -59,7 +59,8 @@ app-html-video-player {
|
||||
}
|
||||
|
||||
.web-player-diagnostic__description,
|
||||
.web-player-diagnostic__meta {
|
||||
.web-player-diagnostic__meta,
|
||||
.web-player-diagnostic__codec-hint {
|
||||
margin-top: 3px;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.3;
|
||||
@@ -70,6 +71,80 @@ app-html-video-player {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__codec-hint {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-on-error-container) 18%, transparent);
|
||||
border-radius: 6px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-error-container) 7%,
|
||||
transparent
|
||||
);
|
||||
font-weight: 550;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details {
|
||||
margin-top: 10px;
|
||||
border-top: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-on-error-container) 16%, transparent);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details summary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
opacity: 0.84;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details summary mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details dl {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
gap: 5px 12px;
|
||||
margin: 8px 0 0;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__detail-row {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__detail-row dt {
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-on-error-container) 76%,
|
||||
transparent
|
||||
);
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__detail-row dd {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__actions {
|
||||
grid-area: actions;
|
||||
display: flex;
|
||||
@@ -95,4 +170,17 @@ app-html-video-player {
|
||||
.web-player-diagnostic__actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__details dl {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.web-player-diagnostic__detail-row {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.web-player-diagnostic__detail-row dt {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,73 @@ describe('WebPlayerViewComponent', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats web script playback URLs without declared media extension as MPEG-TS', () => {
|
||||
const streamUrl = 'https://example.com/live.php?stream=123&token=x';
|
||||
|
||||
component.setVjsOptions(streamUrl);
|
||||
|
||||
expect(component.vjsOptions.sources).toEqual([
|
||||
{
|
||||
src: streamUrl,
|
||||
type: 'video/mp2t',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses browser access diagnostic translation keys', () => {
|
||||
const issue = createBrowserAccessDiagnostic();
|
||||
|
||||
expect(component.getDiagnosticTitleKey(issue)).toBe(
|
||||
'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.TITLE'
|
||||
);
|
||||
expect(component.getDiagnosticDescriptionKey(issue)).toBe(
|
||||
'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.DESCRIPTION'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders technical details and codec-specific hints in the diagnostic banner', () => {
|
||||
fixture.detectChanges();
|
||||
const issue = createUnsupportedCodecDiagnostic();
|
||||
|
||||
component.handlePlaybackIssue(issue);
|
||||
fixture.detectChanges();
|
||||
|
||||
const details = fixture.debugElement.query(
|
||||
By.css('[data-test-id="playback-diagnostic-details"]')
|
||||
);
|
||||
const codecHint = fixture.debugElement.query(
|
||||
By.css('[data-test-id="playback-codec-hint"]')
|
||||
);
|
||||
|
||||
expect(details.nativeElement.textContent).toContain(
|
||||
'PLAYBACK_DIAGNOSTICS.DETAILS_SUMMARY'
|
||||
);
|
||||
expect(component.getDiagnosticCodecHint(issue)).toBe('HEVC, AC-3');
|
||||
expect(component.getDiagnosticDetails(issue)).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_CODE',
|
||||
value: 'unsupported-codec',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_PLAYER',
|
||||
value: 'Video.js',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_VIDEO_CODECS',
|
||||
value: 'hvc1.1.6.L93.B0',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_AUDIO_CODECS',
|
||||
value: 'ac-3',
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(codecHint.nativeElement.textContent).toContain(
|
||||
'PLAYBACK_DIAGNOSTICS.CODEC_HINT'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function createUnsupportedContainerDiagnostic(): PlaybackDiagnostic {
|
||||
@@ -202,3 +269,33 @@ function createUnsupportedContainerDiagnostic(): PlaybackDiagnostic {
|
||||
externalFallbackRecommended: true,
|
||||
};
|
||||
}
|
||||
|
||||
function createBrowserAccessDiagnostic(): PlaybackDiagnostic {
|
||||
return {
|
||||
code: PlaybackDiagnosticCode.BrowserAccessError,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
sourceUrl: 'https://provider.example/live.m3u8',
|
||||
container: 'm3u8',
|
||||
mimeType: 'application/x-mpegURL',
|
||||
player: 'videojs',
|
||||
audioCodecs: [],
|
||||
videoCodecs: [],
|
||||
details: 'blocked by CORS policy',
|
||||
externalFallbackRecommended: true,
|
||||
};
|
||||
}
|
||||
|
||||
function createUnsupportedCodecDiagnostic(): PlaybackDiagnostic {
|
||||
return {
|
||||
code: PlaybackDiagnosticCode.UnsupportedCodec,
|
||||
source: PlaybackDiagnosticSource.Hls,
|
||||
sourceUrl: 'https://example.com/live/index.m3u8',
|
||||
container: 'm3u8',
|
||||
mimeType: 'application/x-mpegURL',
|
||||
player: 'videojs',
|
||||
audioCodecs: ['ac-3'],
|
||||
videoCodecs: ['hvc1.1.6.L93.B0'],
|
||||
details: 'manifestIncompatibleCodecsError',
|
||||
externalFallbackRecommended: true,
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
Signal,
|
||||
ViewEncapsulation,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
@@ -17,7 +16,6 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { getStreamExtensionFromUrl } from 'm3u-utils';
|
||||
import {
|
||||
ResolvedPortalPlayback,
|
||||
Settings,
|
||||
@@ -32,9 +30,16 @@ import {
|
||||
type PlaybackDiagnostic,
|
||||
PlaybackDiagnosticCode,
|
||||
type PlaybackFallbackRequest,
|
||||
getLikelyBrowserUnsupportedCodecLabels,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
import { VjsPlayerComponent } from '../vjs-player/vjs-player.component';
|
||||
|
||||
type PlaybackDiagnosticDetail = {
|
||||
readonly labelKey: string;
|
||||
readonly value: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-web-player-view',
|
||||
templateUrl: './web-player-view.component.html',
|
||||
@@ -65,12 +70,11 @@ export class WebPlayerViewComponent {
|
||||
volume = input<number>(1);
|
||||
showCaptions = input<boolean>(false);
|
||||
playerOverride = input<VideoPlayer | null>(null);
|
||||
@Output() timeUpdate = new EventEmitter<{
|
||||
readonly timeUpdate = output<{
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
}>();
|
||||
@Output() externalFallbackRequested =
|
||||
new EventEmitter<PlaybackFallbackRequest>();
|
||||
readonly externalFallbackRequested = output<PlaybackFallbackRequest>();
|
||||
|
||||
settings = toSignal(
|
||||
this.storage.get(STORE_KEY.Settings)
|
||||
@@ -118,7 +122,7 @@ export class WebPlayerViewComponent {
|
||||
}
|
||||
|
||||
setVjsOptions(streamUrl: string) {
|
||||
const extension = getStreamExtensionFromUrl(streamUrl);
|
||||
const extension = getPlaybackMediaExtensionFromUrl(streamUrl);
|
||||
const mimeType =
|
||||
extension === 'm3u' || extension === 'm3u8'
|
||||
? 'application/x-mpegURL'
|
||||
@@ -168,7 +172,58 @@ export class WebPlayerViewComponent {
|
||||
return codecs;
|
||||
}
|
||||
|
||||
return issue.container || issue.mimeType || issue.details || '';
|
||||
return issue.container || issue.mimeType || '';
|
||||
}
|
||||
|
||||
getDiagnosticCodecHint(issue: PlaybackDiagnostic): string {
|
||||
return getLikelyBrowserUnsupportedCodecLabels(issue).join(', ');
|
||||
}
|
||||
|
||||
getDiagnosticDetails(
|
||||
issue: PlaybackDiagnostic
|
||||
): readonly PlaybackDiagnosticDetail[] {
|
||||
return [
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_CODE',
|
||||
value: issue.code,
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_PLAYER',
|
||||
value: this.formatPlayer(issue.player),
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_SOURCE',
|
||||
value: this.formatDiagnosticSource(issue.source),
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_CONTAINER',
|
||||
value: issue.container,
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_MIME_TYPE',
|
||||
value: issue.mimeType ?? '',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_VIDEO_CODECS',
|
||||
value: issue.videoCodecs.join(', '),
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_AUDIO_CODECS',
|
||||
value: issue.audioCodecs.join(', '),
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_NATIVE_ERROR_CODE',
|
||||
value: issue.nativeErrorCode?.toString() ?? '',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_NATIVE_ERROR_MESSAGE',
|
||||
value: issue.nativeErrorMessage ?? '',
|
||||
},
|
||||
{
|
||||
labelKey: 'PLAYBACK_DIAGNOSTICS.DETAIL_ERROR_DETAILS',
|
||||
value: issue.details ?? '',
|
||||
},
|
||||
].filter(({ value }) => value.trim().length > 0);
|
||||
}
|
||||
|
||||
private getDiagnosticTranslationBase(issue: PlaybackDiagnostic): string {
|
||||
@@ -181,6 +236,8 @@ export class WebPlayerViewComponent {
|
||||
return 'PLAYBACK_DIAGNOSTICS.MEDIA_DECODE_ERROR';
|
||||
case PlaybackDiagnosticCode.NetworkError:
|
||||
return 'PLAYBACK_DIAGNOSTICS.NETWORK_ERROR';
|
||||
case PlaybackDiagnosticCode.BrowserAccessError:
|
||||
return 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR';
|
||||
case PlaybackDiagnosticCode.DrmOrEncryption:
|
||||
return 'PLAYBACK_DIAGNOSTICS.DRM_OR_ENCRYPTION';
|
||||
case PlaybackDiagnosticCode.UnknownPlaybackError:
|
||||
@@ -192,4 +249,34 @@ export class WebPlayerViewComponent {
|
||||
private detectDesktop(): boolean {
|
||||
return typeof window !== 'undefined' && !!window.electron;
|
||||
}
|
||||
|
||||
private formatPlayer(player: PlaybackDiagnostic['player']): string {
|
||||
switch (player) {
|
||||
case 'videojs':
|
||||
return 'Video.js';
|
||||
case 'html5':
|
||||
return 'HTML5';
|
||||
case 'artplayer':
|
||||
return 'ArtPlayer';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private formatDiagnosticSource(
|
||||
source: PlaybackDiagnostic['source']
|
||||
): string {
|
||||
switch (source) {
|
||||
case 'hls':
|
||||
return 'HLS.js';
|
||||
case 'mpegts':
|
||||
return 'mpegts.js';
|
||||
case 'native':
|
||||
return 'Native media element';
|
||||
case 'source':
|
||||
return 'Stream metadata';
|
||||
default:
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user