diff --git a/frontend/package.json b/frontend/package.json
index 49fbacfb..c0a82d11 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -40,6 +40,7 @@
"radix-vue": "1.7.4",
"sortablejs": "1.15.2",
"swiper": "11.1.1",
+ "unique-names-generator": "4.7.1",
"uuid": "9.0.1",
"vue": "3.4.27",
"vue-i18n": "9.13.1",
diff --git a/frontend/src/pages/settings/index.vue b/frontend/src/pages/settings/index.vue
index cdfffa6e..fde839b2 100644
--- a/frontend/src/pages/settings/index.vue
+++ b/frontend/src/pages/settings/index.vue
@@ -33,6 +33,10 @@
{{ clientVersion }}
+
+ | {{ $t('deviceName') }} |
+ {{ remote.sdk.deviceInfo.name }} |
+
diff --git a/frontend/src/plugins/remote/sdk/sdk-utils.ts b/frontend/src/plugins/remote/sdk/sdk-utils.ts
index 01aeefd7..efe7a9cd 100644
--- a/frontend/src/plugins/remote/sdk/sdk-utils.ts
+++ b/frontend/src/plugins/remote/sdk/sdk-utils.ts
@@ -1,75 +1,55 @@
+/**
+ * We generate an unique name for this device instead of guessing the user's browser based on the user agent:
+ * - Browser user agent can be spoofed and is not reliable.
+ * - There's a major push to deprecate it: https://developers.google.com/privacy-sandbox/blog/user-agent-reduction-deprecation-trial?hl=es-419
+ * - Browsers are so ubiquitous that there are multiple form factors and devices that can run them. We can't easily track
+ * which device the user is using, so the best thing is to not rely on any platform-specific support and provide generic support
+ * for everything, using browser APIs like MediaCapabilities for playback capability detection.
+ */
import { type Api, Jellyfin } from '@jellyfin/sdk';
import { v4 } from 'uuid';
-import {
- isAndroid,
- isApple,
- isChrome,
- isChromiumBased,
- isEdge,
- isFirefox,
- isMobile,
- isTizen,
- isWebOS
-} from '@/utils/browser-detection';
import { version } from '@/../package.json';
+import { adjectives, animals, colors, countries, languages, names, starWars, uniqueNamesGenerator } from 'unique-names-generator';
+import { destr } from 'destr';
+
+interface DeviceInfo {
+ id: string;
+ name: string;
+}
/**
- * Returns the device ID, creating it in case it does not exist
+ * Returns the device ID and name, creating it in case it does not exist
*/
-function ensureDeviceId(): string {
- const storageKey = 'deviceId';
- const val = window.localStorage.getItem(storageKey);
+function ensureDeviceInfo(): DeviceInfo {
+ const storageKey = 'device';
+ const storeData = destr(window.localStorage.getItem(storageKey));
+
+ if (!storeData) {
+ const payload = {
+ id: v4(),
+ name: uniqueNamesGenerator({
+ dictionaries: [adjectives, animals, colors, countries, names, languages, starWars],
+ separator: '-',
+ length: 3
+ })
+ };
- if (!val) {
- const id = v4();
+ window.localStorage.setItem(storageKey, JSON.stringify(payload));
- window.localStorage.setItem(storageKey, id);
- return id;
+ return payload;
}
- return val;
+ return storeData;
}
const SDK = new Jellyfin({
clientInfo: {
- name: 'Jellyfin Web (Vue)',
+ name: 'Jellyfin Vue',
version: version
},
- deviceInfo: {
- name: getDeviceName(),
- id: ensureDeviceId()
- }
+ deviceInfo: ensureDeviceInfo()
});
-/**
- * Gets the device's name based on the browser's user agent.
- */
-function getDeviceName(): string {
- let deviceName = 'Unknown';
-
- if (isChrome()) {
- deviceName = 'Chrome';
- } else if (isEdge() && !isChromiumBased()) {
- deviceName = 'Edge (EdgeHTML)';
- } else if (isEdge()) {
- deviceName = 'Edge (Chromium)';
- } else if (isFirefox()) {
- deviceName = 'Firefox';
- } else if (isApple() && !isMobile()) {
- deviceName = 'Safari';
- } else if (isWebOS()) {
- deviceName = 'LG Smart TV';
- } else if (isTizen()) {
- deviceName = 'Samsung Smart TV';
- } else if (isApple() && isMobile()) {
- deviceName = 'iPhone';
- } else if (isAndroid()) {
- deviceName = 'Android';
- }
-
- return deviceName;
-}
-
/**
* Connects to the given server with the given credentials without
* altering the app's API/SDK or axios instance.
diff --git a/frontend/src/store/playback-manager.ts b/frontend/src/store/playback-manager.ts
index 6b3ebd53..441bb55b 100644
--- a/frontend/src/store/playback-manager.ts
+++ b/frontend/src/store/playback-manager.ts
@@ -31,7 +31,6 @@ import { remote } from '@/plugins/remote';
import { apiStore } from '@/store/api';
import { getImageInfo } from '@/utils/images';
import { getItemRuntime } from '@/utils/items';
-import playbackProfile from '@/utils/playback-profiles';
import { msToTicks } from '@/utils/time';
import { mediaControls, mediaElementRef } from '@/store';
import { CommonStore } from '@/store/super/common-store';
diff --git a/package-lock.json b/package-lock.json
index 8ea0942b..9fe7d768 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,6 +40,7 @@
"radix-vue": "1.7.4",
"sortablejs": "1.15.2",
"swiper": "11.1.1",
+ "unique-names-generator": "4.7.1",
"uuid": "9.0.1",
"vue": "3.4.27",
"vue-i18n": "9.13.1",
@@ -10399,6 +10400,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/unique-names-generator": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz",
+ "integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",