From c11d630e42b89866674d1fe864a8b77ead14c162 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 26 Oct 2025 14:50:27 +0100 Subject: [PATCH 1/3] Add Titan OS detection --- src/assets/img/devices/titanos.svg | 12 ++++++++++++ src/components/apphost.js | 1 + src/scripts/browser.js | 10 ++++++++++ src/utils/image.ts | 2 ++ 4 files changed, 25 insertions(+) create mode 100644 src/assets/img/devices/titanos.svg diff --git a/src/assets/img/devices/titanos.svg b/src/assets/img/devices/titanos.svg new file mode 100644 index 0000000000..c40bb3addd --- /dev/null +++ b/src/assets/img/devices/titanos.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/components/apphost.js b/src/components/apphost.js index addc352c15..ce88435a50 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -12,6 +12,7 @@ const appName = 'Jellyfin Web'; const BrowserName = { tizen: 'Samsung Smart TV', web0s: 'LG Smart TV', + titanos: 'Titan OS', operaTv: 'Opera TV', xboxOne: 'Xbox One', ps4: 'Sony PS4', diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 5cd287fbc1..e6cda97f96 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -19,6 +19,10 @@ function isTv() { return true; } + if (userAgent.indexOf('titanos') !== -1) { + return true; + } + return isWeb0s(); } @@ -195,6 +199,7 @@ const uaMatch = function (ua) { || /(edga)[ /]([\w.]+)/.exec(ua) || /(edgios)[ /]([\w.]+)/.exec(ua) || /(edge)[ /]([\w.]+)/.exec(ua) + || /(titanos)[ /]([\w.]+)/.exec(ua) || /(opera)[ /]([\w.]+)/.exec(ua) || /(opr)[ /]([\w.]+)/.exec(ua) || /(chrome)[ /]([\w.]+)/.exec(ua) @@ -209,6 +214,7 @@ const uaMatch = function (ua) { || /(iphone)/.exec(ua) || /(windows)/.exec(ua) || /(android)/.exec(ua) + || /(titanos)/.exec(ua) || []; let browser = match[1] || ''; @@ -305,6 +311,10 @@ if (browser.web0s) { // UserAgent string contains 'Chrome' and 'Safari', but we only want 'tizen' to be true delete browser.chrome; delete browser.safari; +} else if (browser.titanos) { + // UserAgent string contains 'Opr' and 'Safari', but we only want 'titanos' to be true + delete browser.operaTv; + delete browser.safari; } else { browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1; } diff --git a/src/utils/image.ts b/src/utils/image.ts index 6193bf4208..60d10798e2 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -31,6 +31,8 @@ function getWebDeviceIcon(browser: string | null | undefined) { return BASE_DEVICE_IMAGE_URL + 'edge.svg'; case 'Internet Explorer': return BASE_DEVICE_IMAGE_URL + 'msie.svg'; + case 'Titan OS': + return BASE_DEVICE_IMAGE_URL + 'titanos.svg'; default: return BASE_DEVICE_IMAGE_URL + 'html5.svg'; } From 6ce3e579c200d95fe299c736da664d8823c412c8 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sun, 26 Oct 2025 15:09:37 +0100 Subject: [PATCH 2/3] Update browser declaration --- src/scripts/browser.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/browser.d.ts b/src/scripts/browser.d.ts index 512e2899ca..bb9ea131b8 100644 --- a/src/scripts/browser.d.ts +++ b/src/scripts/browser.d.ts @@ -23,6 +23,7 @@ declare namespace browser { export let tizen: boolean; export let vidaa: boolean; export let web0s: boolean; + export let titanos: boolean; export let edgeUwp: boolean; export let web0sVersion: number | undefined; export let tizenVersion: number | undefined; From f3d7994b2a02aa8ffca7b7a9bb736dc94085d211 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Mon, 27 Oct 2025 19:24:35 +0100 Subject: [PATCH 3/3] Replace indexOf usages with includes --- src/scripts/browser.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/scripts/browser.js b/src/scripts/browser.js index e6cda97f96..dc72f73ecf 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -3,23 +3,23 @@ function isTv() { const userAgent = navigator.userAgent.toLowerCase(); // The OculusBrowsers userAgent also has the samsungbrowser defined but is not a tv. - if (userAgent.indexOf('oculusbrowser') !== -1) { + if (userAgent.includes('oculusbrowser')) { return false; } - if (userAgent.indexOf('tv') !== -1) { + if (userAgent.includes('tv')) { return true; } - if (userAgent.indexOf('samsungbrowser') !== -1) { + if (userAgent.includes('samsungbrowser')) { return true; } - if (userAgent.indexOf('viera') !== -1) { + if (userAgent.includes('viera')) { return true; } - if (userAgent.indexOf('titanos') !== -1) { + if (userAgent.includes('titanos')) { return true; } @@ -29,8 +29,8 @@ function isTv() { function isWeb0s() { const userAgent = navigator.userAgent.toLowerCase(); - return userAgent.indexOf('netcast') !== -1 - || userAgent.indexOf('web0s') !== -1; + return userAgent.includes('netcast') + || userAgent.includes('web0s'); } function isMobile(userAgent) { @@ -49,7 +49,7 @@ function isMobile(userAgent) { const lower = userAgent.toLowerCase(); for (let i = 0, length = terms.length; i < length; i++) { - if (lower.indexOf(terms[i]) !== -1) { + if (lower.includes(terms[i])) { return true; } } @@ -109,7 +109,7 @@ function web0sVersion(browser) { if (browser.chrome) { const userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.indexOf('netcast') !== -1) { + if (userAgent.includes('netcast')) { // The built-in browser (NetCast) may have a version that doesn't correspond to the actual web engine // Since there is no reliable way to detect webOS version, we return an undefined version @@ -205,7 +205,7 @@ const uaMatch = function (ua) { || /(chrome)[ /]([\w.]+)/.exec(ua) || /(safari)[ /]([\w.]+)/.exec(ua) || /(firefox)[ /]([\w.]+)/.exec(ua) - || ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) + || !ua.includes('compatible') && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; const versionMatch = /(version)[ /]([\w.]+)/.exec(ua); @@ -265,11 +265,11 @@ if (matched.platform) { browser.edgeChromium = browser.edg || browser.edga || browser.edgios; -if (!browser.chrome && !browser.edgeChromium && !browser.edge && !browser.opera && userAgent.toLowerCase().indexOf('webkit') !== -1) { +if (!browser.chrome && !browser.edgeChromium && !browser.edge && !browser.opera && userAgent.toLowerCase().includes('webkit')) { browser.safari = true; } -browser.osx = userAgent.toLowerCase().indexOf('mac os x') !== -1; +browser.osx = userAgent.toLowerCase().includes('mac os x'); // This is a workaround to detect iPads on iOS 13+ that report as desktop Safari // This may break in the future if Apple releases a touchscreen Mac @@ -278,7 +278,7 @@ if (browser.osx && !browser.iphone && !browser.ipod && !browser.ipad && navigato browser.ipad = true; } -if (userAgent.toLowerCase().indexOf('playstation 4') !== -1) { +if (userAgent.toLowerCase().includes('playstation 4')) { browser.ps4 = true; browser.tv = true; } @@ -287,16 +287,16 @@ if (isMobile(userAgent)) { browser.mobile = true; } -if (userAgent.toLowerCase().indexOf('xbox') !== -1) { +if (userAgent.toLowerCase().includes('xbox')) { browser.xboxOne = true; browser.tv = true; } browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null; browser.hisense = userAgent.toLowerCase().includes('hisense'); -browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || window.tizen != null; +browser.tizen = userAgent.toLowerCase().includes('tizen') || window.tizen != null; browser.vidaa = userAgent.toLowerCase().includes('vidaa'); browser.web0s = isWeb0s(); -browser.edgeUwp = (browser.edge || browser.edgeChromium) && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1); +browser.edgeUwp = (browser.edge || browser.edgeChromium) && (userAgent.toLowerCase().includes('msapphost') || userAgent.toLowerCase().includes('webview')); if (browser.web0s) { browser.web0sVersion = web0sVersion(browser); @@ -316,11 +316,11 @@ if (browser.web0s) { delete browser.operaTv; delete browser.safari; } else { - browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1; + browser.orsay = userAgent.toLowerCase().includes('smarthub'); } browser.tv = isTv(); -browser.operaTv = browser.tv && userAgent.toLowerCase().indexOf('opr/') !== -1; +browser.operaTv = browser.tv && userAgent.toLowerCase().includes('opr/'); if (browser.mobile || browser.tv) { browser.slow = true;