/* global define */ /* jshint module: true */ // closure start const appMode = globalThis.appMode; const isNativeTizen = globalThis.appMode === 'tizen'; const isNativeLG = globalThis.appMode === 'webos'; let customPaths; function returnFirstDependency(obj) { return obj; } function returnFirstDependencyDefault(obj) { if (Array.isArray(obj)) { obj = obj[0]; } // this ? check is needed for importing modules without exports and babel-transpiled code to requirejs return obj?.default || obj; } function loadSharedComponentsDictionary(globalize) { const baseUrl = 'modules/common/strings/'; const languages = [ 'ar', 'bg-BG', 'ca', 'cs', 'da', 'de', 'el', 'en-GB', 'en-US', 'es', 'es-AR', 'es-MX', 'et-EE', 'fi', 'fr', 'fr-CA', 'gsw', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-LT', 'ms', 'nb', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl-SI', 'sv', 'tr', 'uk', 'vi', 'zh-CN', 'zh-HK', 'zh-TW' ]; const translations = languages.map(function (i) { return { lang: i, path: baseUrl + i + '.json' }; }); return globalize.loadStrings({ name: 'sharedcomponents', translations: translations }); } function isGamepadSupported() { return 'ongamepadconnected' in window || navigator.getGamepads || navigator.webkitGetGamepads; } function enableNativeGamepadKeyMapping() { // On Windows UWP, this will tell the platform to make the gamepad emit normal keyboard events if (window.navigator && typeof window.navigator.gamepadInputEmulation === "string") { // We want the gamepad to provide gamepad VK keyboard events rather than moving a // mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events // and provide input to the DOM navigator.getGamepads API. window.navigator.gamepadInputEmulation = "keyboard"; return true; } return false; } function loadPlugin(url) { return Promise.all([ importFromPath('./modules/common/pluginmanager.js') ]).then(function (responses) { const pluginManager = responses[0]; if (url.startsWith('./') && url.endsWith('.js')) { console.log('Loading plugin module: ' + url); return getDynamicImport(url)().then(function (f) { return pluginManager.loadPlugin(f, url); }); } return pluginManager.loadPluginFromUrl(url); }); } function getAvailablePluginsAppStore() { const promises = [ this._getAvailablePlugins(), importFromPath('./modules/common/pluginmanager.js') ]; return Promise.all(promises).then(function (responses) { const plugins = responses[0]; const pluginManager = responses[1]; return plugins.filter(function (p) { return pluginManager.allowPluginPages(p.guid); }); }); } function returnFalse() { return false; } function onApiClientCreated(e, apiClient) { if (appMode === 'ios' || appMode === 'android') { apiClient._getAvailablePlugins = apiClient.getAvailablePlugins; apiClient.getAvailablePlugins = getAvailablePluginsAppStore.bind(apiClient); } Promise.all([ importFromPath('./modules/browser.js') ]).then(function (responses) { const browser = responses[0]; // replace this, not reliable if (browser.operaTv) { apiClient.isWebSocketSupported = returnFalse; } }); } function getServerAddressFromWindowUrl() { // Try to get the server address from the browser url // This will preserve protocol, hostname, port and subdirectory const urlLower = window.location.href.toLowerCase(); const index = urlLower.lastIndexOf('/web'); if (index !== -1) { return urlLower.substring(0, index); } // If the above failed, just piece it together manually const loc = window.location; let address = loc.protocol + '//' + loc.hostname; if (loc.port) { address += ':' + loc.port; } return address; } function createConnectionManager() { return Promise.all([ importFromPath('./modules/emby-apiclient/connectionmanager.js'), importFromPath('./modules/emby-apiclient/events.js'), importFromPath('./modules/common/servicelocator.js') ]).then(function (outerResponses) { const connectionManager = outerResponses[0]; const events = outerResponses[1]; const serviceLocator = outerResponses[2]; const appHost = serviceLocator.appHost; // TODO: This needs to go. Shouldn't be needed globally anymore globalThis.Events = events; connectionManager.globalScopeApiClient = true; connectionManager.devicePixelRatio = globalThis.devicePixelRatio; // This is solely for the native apps to call connectionManager methods from native code // client-side code in js files should not use the global scope object globalThis.ConnectionManager = connectionManager; events.on(connectionManager, 'apiclientcreated', onApiClientCreated); if (!appHost.supports('multiserver')) { connectionManager.enableServerAddressValidation = false; let accessToken; let userId; const windowSearch = window.location.search; if (windowSearch) { const params = new URLSearchParams(window.location.search); accessToken = params.get('accessToken'); userId = params.get('userId'); if (!accessToken || !userId || params.get('e') !== '1') { accessToken = null; userId = null; } } console.log('creating ApiClient singleton'); connectionManager.validateServerIds = false; const serverAddress = getServerAddressFromWindowUrl(); const apiClient = connectionManager.getApiClientFromServerInfo({ ManualAddress: serverAddress, ManualAddressOnly: true, IsLocalServer: true, AccessToken: accessToken, UserId: userId }, serverAddress); if (accessToken && userId) { window.location = 'index.html'; } apiClient.enableAutomaticNetworking = false; console.log('loaded ApiClient singleton'); } }); } function getPluginPageContentPath() { return globalThis.ApiClient ? globalThis.ApiClient.getUrl('web/ConfigurationPage') : null; } function defineCoreRoutes(appRouter, appHost, browser) { console.log('Defining core routes'); appRouter.addRoute({ path: '/startup/connectlogin.html', controller: 'startup/connectlogin.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, autoFocus: false, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/connectsignup.html', controller: 'startup/connectsignup.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/welcome.html', controller: 'startup/welcome.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/forgotpassword.html', controller: 'startup/forgotpassword.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/forgotpasswordpin.html', controller: 'startup/forgotpasswordpin.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/home', contentPath: '/home/home.html', type: 'home', defaultTitle: true, controller: 'home/home.js', controllerType: 'module', autoFocus: false, homeButton: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true, enableMediaControlTV: true }); // legacy appRouter.addRoute({ path: '/home/home.html', contentPath: '/home/home.html', type: 'home', defaultTitle: true, controller: 'home/home.js', controllerType: 'module', autoFocus: false, homeButton: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true, enableMediaControlTV: true }); // legacy appRouter.addRoute({ path: '/home.html', contentPath: '/home/home.html', type: 'home', defaultTitle: true, controller: 'home/home.js', controllerType: 'module', autoFocus: false, homeButton: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true, enableMediaControlTV: true }); appRouter.addRoute({ path: '/home_horiz/home.html', type: 'home', defaultTitle: true, controller: 'home_horiz/home.js', controllerType: 'module', headerTabs: true, autoFocus: false, homeButton: false, headerBackground: false, clearBackdrop: true, enableMediaControlTV: true }); appRouter.addRoute({ path: '/list/list.html', controller: 'list/list.js', controllerType: 'module', autoFocus: false, canRefresh: true, adjustHeaderForEmbeddedScroll: true, supportsThemeMedia: true }); appRouter.addRoute({ contentPath: '/item/item.html', path: '/item', autoFocus: false, controller: 'item/item.js', controllerType: 'module', transparentHeader: true, adjustHeaderForEmbeddedScroll: true, supportsThemeMedia: true, transition: true }); // legacy appRouter.addRoute({ contentPath: '/item/item.html', path: '/item/item.html', controller: 'item/item.js', controllerType: 'module', autoFocus: false, transparentHeader: true, adjustHeaderForEmbeddedScroll: true, supportsThemeMedia: true, transition: true }); appRouter.addRoute({ contentPath: '/livetv/livetv.html', path: '/livetv', controller: 'livetv/livetv.js', title: 'LiveTV', controllerType: 'module', autoFocus: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true, enableMediaControlTV: true }); appRouter.addRoute({ path: '/startup/login.html', contentPath: '/list/list.html', controller: 'startup/login.js', controllerType: 'module', anonymous: true, autoFocus: false, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/manuallogin.html', controller: 'startup/manuallogin.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, autoFocus: false, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ contentPath: '/games/games.html', path: '/games', controller: 'games/games.js', controllerType: 'module', autoFocus: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ contentPath: '/videos/videos.html', path: '/videos', controller: 'videos/videos.js', controllerType: 'module', autoFocus: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ contentPath: '/music/music.html', path: '/music', controller: 'music/music.js', controllerType: 'module', autoFocus: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true, enableMediaControlTV: true }); appRouter.addRoute({ path: '/videoosd/videoosd.html', controller: 'videoosd/videoosd.js', controllerType: 'module', type: 'video-osd', supportsThemeMedia: true, // the page has it's own enableMediaControl: false, autoFocus: false, headerBackground: false, homeButton: false, drawer: false, dockedTabs: false, backButton: true, transparentHeader: true, // for offline playback anonymous: true }); appRouter.addRoute({ contentPath: '/settings/settings.html', path: '/settings', controller: 'settings/settings.js', controllerType: 'module', title: 'Settings', autoFocus: false, clearBackdrop: true, settingsTheme: true, drawer: false, adjustHeaderForEmbeddedScroll: true }); if (appHost.supports('keyboardsettings')) { appRouter.addRoute({ path: '/settings/keyboard.html', controller: 'settings/keyboard.js', controllerType: 'module', type: 'settings', title: 'HeaderKeyboardAndRemote', thumbImage: '', order: 2, icon: '', clearBackdrop: true, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, autoFocus: false }); } if (appHost.supports('applogger')) { appRouter.addRoute({ path: '/applogs', contentPath: '/logs/logs.html', controller: 'logs/logs.js', autoFocus: false, controllerType: 'module', type: 'settings', icon: 'folder_open', clearBackdrop: true, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, navMenuId: 'applogs', title: 'Logs' }); appRouter.addRoute({ path: '/applog', contentPath: '/list/list.html', autoFocus: false, controller: 'logs/log.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, canRefresh: true, navMenuId: 'applogs', title: 'Logs' }); } appRouter.addRoute({ path: '/settings/notifications.html', contentPath: '/list/list.html', controller: 'settings/notifications.js', controllerType: 'module', type: 'settings', title: 'Notifications', category: 'Playback', thumbImage: '', order: 1001, icon: '', clearBackdrop: true, settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true, featureId: 'notifications', minServerVersion: '4.8.0.20' }); appRouter.addRoute({ path: '/settings/playback.html', controller: 'settings/playback.js', controllerType: 'module', type: 'settings', title: 'Playback', category: 'Playback', thumbImage: '', order: 2, icon: '', clearBackdrop: true, settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true }); appRouter.addRoute({ path: '/settings/subtitles.html', controller: 'settings/subtitles.js', controllerType: 'module', type: 'settings', title: 'Subtitles', category: 'Playback', thumbImage: '', order: 3, icon: '', clearBackdrop: true, settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true }); appRouter.addRoute({ path: '/settings/display.html', controller: 'settings/display.js', controllerType: 'module', type: 'settings', title: 'Display', category: 'General', thumbImage: '', order: 0, icon: '', clearBackdrop: true, settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true }); appRouter.addRoute({ path: '/settings/homescreen.html', controller: 'settings/homescreen.js', controllerType: 'module', type: 'settings', title: 'HeaderHomeScreen', category: 'General', thumbImage: '', order: 1, icon: '', clearBackdrop: true, settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true }); appRouter.addRoute({ path: '/settings/profile.html', controller: 'settings/profile.js', controllerType: 'module', type: 'settings', title: 'Profile', icon: 'person', clearBackdrop: true, roles: 'EnableUserPreferenceAccess', settingsTheme: true, settingsType: 'user', adjustHeaderForEmbeddedScroll: true, hideDrawerWithOtherUserIdParam: true }); if (appHost.supports('cameraupload')) { appRouter.addRoute({ path: '/settings/cameraupload.html', autoFocus: false, controller: 'settings/cameraupload.js', controllerType: 'module', type: 'settings', icon: 'photo', title: 'HeaderCameraUpload', clearBackdrop: true, settingsTheme: true, adjustHeaderForEmbeddedScroll: true }); } if (appHost.supports('sync')) { appRouter.addRoute({ path: '/settings/download', contentPath: '/settings/download/download.html', controller: 'settings/download/download.js', controllerType: 'module', type: 'settings', icon: '', title: 'Downloads', clearBackdrop: true, settingsTheme: true, headerTabs: true, adjustHeaderForEmbeddedScroll: true }); } appRouter.addRoute({ path: '/search', contentPath: '/search/search.html', controller: 'search/search.js', controllerType: 'module', title: '', autoFocus: false, clearBackdrop: true, searchButton: false, adjustHeaderForEmbeddedScroll: true, navMenuId: 'search' }); appRouter.addRoute({ path: '/startup/manualserver.html', controller: 'startup/manualserver.js', controllerType: 'module', anonymous: true, startup: true, defaultTitle: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false }); appRouter.addRoute({ path: '/startup/selectserver.html', contentPath: '/list/list.html', autoFocus: false, anonymous: true, startup: true, controller: 'startup/selectserver.js', controllerType: 'module', title: 'HeaderSelectServer', clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, drawer: false, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160340-emby-connect' }); appRouter.addRoute({ contentPath: '/tv/tv.html', path: '/tv', controller: 'tv/tv.js', controllerType: 'module', autoFocus: false, headerTabs: true, adjustHeaderForEmbeddedScroll: true }); if (appHost.supports('serversetup')) { appRouter.addRoute({ contentPath: '/plugins/addplugin.html', path: '/plugins/install', autoFocus: false, roles: 'admin', controller: 'plugins/addpluginpage.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159720-plugins', title: 'Plugins' }); appRouter.addRoute({ path: '/database', contentPath: '/server/database/database.html', roles: 'admin', controller: 'server/database/database.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, title: 'Database', autoFocus: false }); appRouter.addRoute({ path: '/dashboard', contentPath: '/dashboard/dashboard.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dashboard.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, title: 'Dashboard' }); // legacy appRouter.addRoute({ path: '/dashboard.html', contentPath: '/dashboard/dashboard.html', autoFocus: false, roles: 'admin', controller: 'dashboard/dashboard.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, title: 'Dashboard' }); appRouter.addRoute({ path: '/dashboard/settings', contentPath: '/dashboard/settings.html', controller: 'dashboard/settings.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159322-server-settings', title: 'Settings', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ contentPath: '/list/list.html', path: '/devices', autoFocus: false, roles: 'admin', controller: 'devices/devices.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159497-devices', title: 'Devices', adjustHeaderForEmbeddedScroll: true, canRefresh: true }); appRouter.addRoute({ contentPath: '/network/network.html', path: '/network', autoFocus: false, roles: 'admin', controller: 'network/network.js', controllerType: 'module', title: 'Network', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/articles/Hosting-Settings.html', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/devices/device.html', autoFocus: false, roles: 'admin', controller: 'devices/device.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159497-devices', title: 'Devices', navMenuId: 'devices', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/devices/cameraupload.html', autoFocus: false, roles: 'admin', controller: 'devices/cameraupload.js', controllerType: 'module', settingsTheme: true, title: 'HeaderCameraUpload', clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159382-camera-upload', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ contentPath: '/metadatamanager/metadatamanager.html', path: '/metadatamanager', controller: 'metadatamanager/metadatamanager.js', controllerType: 'module', autoFocus: false, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, title: 'MetadataManager' }); appRouter.addRoute({ contentPath: '/transcoding/transcoding.html', path: '/transcoding', autoFocus: false, roles: 'admin', controller: 'transcoding/transcoding.js', controllerType: 'module', title: 'Transcoding', settingsTheme: true, headerTabs: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159897-transcoding', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/librarysetup', contentPath: '/librarysetup/librarysetup.html', controller: 'librarysetup/librarysetup.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, headerTabs: true, title: 'Library', helpUrl: 'https://support.emby.media/support/solutions/articles/44001159319-library-setup', navMenuId: 'librarysetup' }); appRouter.addRoute({ path: '/livetvsetup', contentPath: '/livetvsetup/livetvsetup.html', controller: 'livetvsetup/livetvsetup.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, headerTabs: true, title: 'LiveTV', helpUrl: 'https://support.emby.media/support/solutions/articles/44001160415-live-tv-setup', navMenuId: 'livetvsetup' }); appRouter.addRoute({ path: '/livetvsetup/guideprovider.html', autoFocus: false, roles: 'admin', controller: 'livetvsetup/guideprovider.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160415-live-tv-setup', title: 'LiveTV', navMenuId: 'livetvsetup' }); appRouter.addRoute({ path: '/livetvsetup/livetvtuner.html', autoFocus: false, roles: 'admin', controller: 'livetvsetup/livetvtuner.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160415-live-tv-setup', title: 'HeaderTVSourceSetup', navMenuId: 'livetvsetup' }); appRouter.addRoute({ path: '/logs', contentPath: '/logs/logs.html', controller: 'logs/logs.js', autoFocus: false, roles: 'admin', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, canRefresh: true, title: 'Logs' }); appRouter.addRoute({ path: '/log', contentPath: '/list/list.html', autoFocus: false, roles: 'admin', controller: 'logs/log.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, canRefresh: true, navMenuId: 'logs', title: 'Logs' }); appRouter.addRoute({ path: '/plugins', contentPath: '/plugins/plugins.html', controller: 'plugins/plugins.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, headerTabs: true, title: 'Plugins', helpUrl: 'https://support.emby.media/support/solutions/articles/44001159720-plugins', navMenuId: 'plugins' }); appRouter.addRoute({ path: '/dashboard/releasenotes.html', autoFocus: false, roles: 'admin', controller: 'dashboard/releasenotes.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/scheduledtasks', contentPath: '/scheduledtasks/scheduledtasks.html', roles: 'admin', autoFocus: false, controller: 'scheduledtasks/scheduledtasks.js', controllerType: 'module', title: 'HeaderScheduledTasks', clearBackdrop: true, settingsTheme: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159751-scheduled-tasks', navMenuId: 'scheduledtasks', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/scheduledtask', contentPath: '/list/list.html', autoFocus: false, roles: 'admin', controller: 'scheduledtasks/scheduledtask.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159751-scheduled-tasks', title: 'HeaderScheduledTasks', navMenuId: 'scheduledtasks', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/serveractivity', contentPath: '/list/list.html', autoFocus: false, roles: 'admin', controller: 'dashboard/serveractivity.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, canRefresh: true, navMenuId: 'dashboard' }); appRouter.addRoute({ path: '/apikeys', contentPath: '/list/list.html', autoFocus: false, roles: 'admin', controller: 'apikeys/apikeys.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, adjustHeaderForEmbeddedScroll: true, canRefresh: true, title: 'HeaderApiKeys' }); appRouter.addRoute({ contentPath: '/embypremiere/embypremiere.html', path: '/embypremiere', controller: 'embypremiere/embypremiere.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://emby.media/premiere', title: 'Emby Premiere', adjustHeaderForEmbeddedScroll: true }); appRouter.addRoute({ path: '/serverdownloads', contentPath: 'server/sync/sync.html', autoFocus: false, controller: 'server/sync/sync.js', controllerType: 'module', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, headerTabs: true, clearBackdrop: true, title: 'Downloads' }); appRouter.addRoute({ path: '/conversions', contentPath: 'server/sync/sync.html', autoFocus: false, controller: 'server/sync/sync.js', controllerType: 'module', settingsTheme: true, headerTabs: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, title: 'Conversions' }); appRouter.addRoute({ path: '/users/user', contentPath: '/users/user.html', controller: 'users/user.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, headerTabs: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160237-users', navMenuId: 'users' }); appRouter.addRoute({ path: '/users/new', contentPath: '/users/usernew.html', controller: 'users/usernew.js', controllerType: 'module', autoFocus: false, roles: 'admin', settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160237-users', navMenuId: 'users', title: 'HeaderNewUser' }); appRouter.addRoute({ path: '/users', contentPath: '/list/list.html', autoFocus: false, roles: 'admin', controller: 'users/users.js', controllerType: 'module', settingsTheme: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001160237-users', adjustHeaderForEmbeddedScroll: true, canRefresh: true, title: 'Users' }); appRouter.addRoute({ path: '/wizard/wizardagreement.html', anonymous: true, controller: 'wizard/wizardagreement.js', controllerType: 'module', homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true }); appRouter.addRoute({ path: '/wizard/wizardremoteaccess.html', anonymous: true, controller: 'wizard/wizardremoteaccess.js', controllerType: 'module', homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true }); appRouter.addRoute({ path: '/wizard/wizardfinish.html', anonymous: true, controller: 'wizard/wizardfinishpage.js', controllerType: 'module', homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true }); appRouter.addRoute({ path: '/wizard/wizardlibrary.html', controller: 'wizard/wizardlibrary.js', controllerType: 'module', anonymous: true, homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, helpUrl: 'https://support.emby.media/support/solutions/articles/44001159319-library-setup' }); appRouter.addRoute({ path: '/wizard/wizardstart.html', anonymous: true, controller: 'wizard/wizardstart.js', controllerType: 'module', homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true }); appRouter.addRoute({ path: '/wizard/wizarduser.html', controller: 'wizard/wizarduserpage.js', controllerType: 'module', autoFocus: false, anonymous: true, homeButton: false, secondaryHeaderFeatures: false, defaultTitle: true, drawer: false, dockedTabs: false, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true }); appRouter.addRoute({ path: '/configurationpage', autoFocus: false, enableCache: false, enableContentQueryString: true, roles: 'admin', contentPath: appHost.supports('multiserver') ? getPluginPageContentPath : null, settingsTheme: true, windowScroll: 3, // used by navdrawercontent, but this should be cleaned up and removed requiresDynamicTitle: true }); appRouter.addRoute({ path: '/genericui', contentPath: 'modules/genericui/genericui.html', autoFocus: false, controller: 'modules/genericui/genericui.js', controllerType: 'module', enableContentQueryString: true, settingsTheme: true, adjustHeaderForEmbeddedScroll: true, clearBackdrop: true, }); } appRouter.addRoute({ path: '/index.html', isDefaultRoute: true, clearBackdrop: true, autoFocus: false }); appRouter.addRoute({ path: '/', isDefaultRoute: true, clearBackdrop: true }); } function triggerWorkerTask() { require(['bgtaskregister'], function (bgtaskregister) { try { // Trigger background task bgtaskregister.triggerTask(); } catch (err) { console.error("Error firing ApplicationTrigger", err); } }); } function getWindowsLocalSync() { return { sync: triggerWorkerTask, setProgressUpdatesEnabled: function (enabled) { } }; } function getDynamicImportWithoutExport(path) { return function () { return import(path); }; } function getDynamicImport(path) { return function () { return import(path).then(returnFirstDependencyDefault); }; } function importFromPath(path) { return getDynamicImport(path)(); } function importFromPathWithoutExport(path) { return getDynamicImportWithoutExport(path)(); } function getNativeImport(objectName) { const nativeObject = globalThis[objectName]; return Promise.resolve(nativeObject); } function loadAppStorage() { let promise; if (appMode === 'winjs') { promise = getRequirePromise("native/windows/appstorage"); } else { try { localStorage.setItem('_test', '0'); localStorage.removeItem('_test'); promise = importFromPath('./modules/emby-apiclient/appstorage-localstorage.js'); } catch (e) { promise = importFromPath('./modules/emby-apiclient/appstorage-memory.js'); } } return promise.then(function (appStorage) { return (appStorage.init ? appStorage.init() : Promise.resolve()).then(function () { return appStorage; }); }); } function loadApiClientInternal() { return loadAppHost().then(function (appHost) { if (appHost.supports('sync')) { return getDynamicImport('./modules/emby-apiclient/apiclientex.js')(); } return getDynamicImport('./modules/emby-apiclient/apiclient.js')(); }); } function loadApiClient() { console.log('loadApiClient'); return getDynamicImport('./modules/common/servicelocator.js')().then(function (serviceLocator) { return loadApiClientInternal().then(function (apiClientFactory) { serviceLocator.initialize({ apiClientFactory: apiClientFactory }); return apiClientFactory; }); }); } function supportsTizenNaclSockets() { if (globalThis.tizen && globalThis.tizen.systeminfo) { const v = globalThis.tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version'); // Don't load sockets for 2015 models, wasm supported from 2020 models // wasm is supported in 2020, but udp socket() creation fails in the 5.5 emulator so stick with nacl return (!supportsTizenWasmSockets() && v && parseFloat(v) >= parseFloat('2.4')); } return false; } function supportsTizenWasmSockets() { if (globalThis.tizen && globalThis.tizen.systeminfo) { const v = globalThis.tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version'); // Don't load sockets for 2015 models, wasm supported from 2020 models // wasm is supported in 2020, but udp socket() creation fails in the 5.5 emulator so stick with nacl return (v && parseFloat(v) >= parseFloat('6.0')); } return false; } function loadServerDiscovery() { if (customPaths.serverdiscovery) { return getRequirePromise(addJsExtIfNeeded(customPaths.serverdiscovery)); } if (appMode === 'winjs') { return getRequirePromise("native/windows/serverdiscovery"); } if (isNativeTizen && (supportsTizenNaclSockets() || supportsTizenWasmSockets())) { return getRequirePromise("native/tizen/serverdiscovery"); } if (appMode === 'android') { return getRequirePromise("native/android/serverdiscovery"); } if (appMode === 'ios') { return getRequirePromise("native/ios/serverdiscovery"); } return getDynamicImport('./modules/emby-apiclient/serverdiscovery.js')(); } function loadShell() { if (customPaths.shell) { return getRequirePromise(addJsExtIfNeeded(customPaths.shell)); } if (appMode === 'android') { return getRequirePromise("native/android/shell"); } return getDynamicImport('./modules/shell.js')(); } function loadWakeOnLan() { if (customPaths.wakeonlan) { return getRequirePromise(addJsExtIfNeeded(customPaths.wakeonlan)); } if (appMode === 'winjs') { return getRequirePromise("native/windows/wakeonlan"); } if (isNativeTizen && (supportsTizenNaclSockets() || supportsTizenWasmSockets())) { return getRequirePromise("native/tizen/wakeonlan"); } if (appMode === 'ios') { return getRequirePromise("native/ios/wakeonlan"); } if (appMode === 'android') { return getRequirePromise("native/android/wakeonlan"); } return getDynamicImport('./modules/emby-apiclient/wakeonlan.js')(); } function loadFullscreenManager() { return getDynamicImport('./modules/common/servicelocator.js')().then(function (serviceLocator) { let promise; if (customPaths.fullscreenmanager) { promise = getRequirePromise(customPaths.fullscreenmanager); } else { promise = getDynamicImport('./modules/fullscreen/fullscreenmanager.js')(); } return promise.then(function (fullscreenManager) { serviceLocator.initialize({ fullscreenManager: fullscreenManager }); return fullscreenManager; }); }); } function bindOnBeforeWindowUnload() { getDynamicImport('./modules/common/playback/playbackmanager.js')().then(function (playbackManager) { window.addEventListener("beforeunload", function (e) { try { playbackManager.onAppClose(); } catch (err) { console.log('error in onAppClose: ' + err); } }); }); } function loadIap() { console.log('loadIap'); let promise; if (appMode === 'android') { promise = getRequirePromise("native/android/iap"); } else if (appMode === 'ios') { promise = getRequirePromise("native/ios/iap"); } else { promise = getDynamicImport('./modules/iap.js')(); } return promise.then(function (iapManager) { return getDynamicImport('./modules/common/servicelocator.js')().then(function (serviceLocator) { serviceLocator.initialize({ iapManager: iapManager }); }); }); } function loadServiceLocator() { console.log('loadServiceLocator'); return Promise.all([ loadAppStorage(), loadAppHost(), loadShell(), // this needs to be in place before anything tries to load playbackManager loadFullscreenManager(), loadWakeOnLan(), loadServerDiscovery() ]).then(function (responses) { console.log('loadServiceLocator - inner load 1'); const appStorage = responses[0]; const appHost = responses[1]; const shell = responses[2]; const wakeOnLan = responses[4]; const serverDiscovery = responses[5]; const promises = [ getDynamicImport('./modules/common/servicelocator.js')() ]; if (appHost.supports('sync')) { promises.push(require(['filerepository'])); promises.push(require(['itemrepository'])); promises.push(require(['transfermanager'])); promises.push(require(['useractionrepository'])); promises.push(require(['localsync'])); } if (appHost.supports('cameraupload')) { promises.push(loadCameraUpload()); } if (appHost.supports('applogger')) { promises.push(loadAppLogger()); } return Promise.all(promises).then(function (responsesInner) { console.log('loadServiceLocator - inner load 2'); let index = 0; const serviceLocator = responsesInner[index]; index++; let fileRepository; let itemRepository; let transferManager; let userActionRepository; let localSync; let cameraUpload; let appLogger; if (appHost.supports('sync')) { fileRepository = responsesInner[index][0]; index++; itemRepository = responsesInner[index][0]; index++; transferManager = responsesInner[index][0]; index++; userActionRepository = responsesInner[index][0]; index++; localSync = responsesInner[index][0]; index++; } if (appHost.supports('cameraupload')) { cameraUpload = responsesInner[index]; index++; } if (appHost.supports('applogger')) { appLogger = responsesInner[index]; index++; } console.log('loadServiceLocator - calling serviceLocator.initialize'); serviceLocator.initialize({ appStorage: appStorage, appHost: appHost, shell: shell, wakeOnLan: wakeOnLan, serverDiscovery: serverDiscovery, fileRepository: fileRepository, itemRepository: itemRepository, transferManager: transferManager, userActionRepository: userActionRepository, cameraUpload: cameraUpload, appLogger: appLogger, localSync: localSync }); console.log('loadServiceLocator - calling appHost.init'); return appHost.init().then(loadApiClient).then(loadIap); }); }); } function addJsExtIfNeeded(path) { if (!path.endsWith('.js')) { path += '.js'; } return path; } function getRequirePromise(dep) { return new Promise(function (resolve, reject) { require([dep], resolve); }); } function loadAppLogger() { if (appMode === 'android') { return getRequirePromise("native/android/applogger"); } return Promise.resolve(getDummyAppLogger()); } function getDummyAppLogger() { return { getLogFiles: function (query) { let items = []; items.push({ Name: 'currentlog.txt', Id: 'currentlog.txt', DateCreated: new Date().toISOString(), DateModified: new Date().toISOString(), Type: 'Log', CanDownload: true, CanShare: true }); const total = items.length; if (query.StartIndex) { items = items.slice(query.StartIndex); } if (query.Limit) { items.length = Math.min(query.Limit, items.length); } return Promise.resolve({ Items: items, TotalRecordCount: total }); }, getLogLines: function (query) { let items = []; for (let i = 0, length = 10000; i < length; i++) { items.push('line ' + i); } const total = items.length; if (query.StartIndex) { items = items.slice(query.StartIndex); } if (query.Limit) { items.length = Math.min(query.Limit, items.length); } return Promise.resolve({ Items: items, TotalRecordCount: total }); }, downloadLog: function (name) { console.log('downloading dummy log file: ' + name); return Promise.resolve(); }, shareLog: function (name) { console.log('sharing dummy log file: ' + name); return Promise.resolve(); } }; } function loadCameraUpload() { if (appMode === 'ios') { return getRequirePromise("native/ios/cameraupload"); } if (appMode === 'android') { return getRequirePromise("native/android/cameraupload"); } return Promise.resolve(getDummyCameraUpload()); } function getDummyCameraUpload() { return { start: function () { }, setProgressUpdatesEnabled: function () { }, getAvailableFolders: function () { return Promise.resolve([{ Id: '541C6607-9C45-4875-A292-5F89F742B2B3/L0/040', Name: 'TestFolder1' }, { Id: '773DFE72-F38F-4220-8F2F-C4A472DBBA75/L0/040', Name: 'TestFolder2' }]); } }; } function loadAppHost() { if (customPaths.apphost) { return getRequirePromise(addJsExtIfNeeded(customPaths.apphost)); } if (appMode === 'ios') { return getRequirePromise("native/ios/apphost"); } if (appMode === 'android') { return getRequirePromise("native/android/apphost"); } return importFromPath('./modules/apphost.js'); } function getImportMap() { const elem = document.querySelector('script[type="importmap"]'); if (elem) { const html = elem.innerHTML; if (html) { try { const obj = JSON.parse(html); if (obj) { const imports = obj.imports; if (imports) { return imports; } } } catch (err) { console.log('error parsing import map: ' + err); } } } return {}; } /** * Object.entries() polyfill * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries */ // this has to be here due to initRequire using it before polyfills start getting loaded if (!Object.entries) { Object.entries = function (obj) { const ownProps = Object.keys(obj); let i = ownProps.length; const resArray = new Array(i); // preallocate the Array while (i--) { resArray[i] = [ownProps[i], obj[ownProps[i]]]; } return resArray; }; } function initRequire() { const importMap = getImportMap(); const entries = Object.entries(importMap); for (let i = 0, length = entries.length; i < length; i++) { const entry = entries[i]; const key = entry[0]; const url = entry[1]; //console.log('defining from importMap: ' + key + ': ' + url); define(key, [], getDynamicImport(url)); } // HTML DONE define("embyProgressBarStyle", [], returnFirstDependency); // alias define("inputmanager", ['inputManager'], returnFirstDependency); define("fullscreenManager", loadFullscreenManager); define("shell", [], loadShell); if (customPaths.filesystem) { define("filesystem", [addJsExtIfNeeded(customPaths.filesystem)], returnFirstDependency); } else { define("filesystem", [], getDynamicImport('./modules/common/filesystem.js')); } define("hlsjs", ["modules/hlsjs/hls.min"], returnFirstDependency); // alias define('connectionManagerResolver', ['connectionManager'], returnFirstDependency); define("cardStyle", ['css!modules/cardbuilder/card.css'], returnFirstDependency); define("flexStyles", ["css!modules/flexstyles.css"], returnFirstDependency); define("programStyles", ['css!modules/emby-elements/guide/programs.css'], returnFirstDependency); define("apphost", [], loadAppHost); define("cameraUpload", [], loadCameraUpload); define("appLogger", [], loadAppLogger); define("serverdiscovery", [], loadServerDiscovery); define("wakeOnLan", [], loadWakeOnLan); define("appStorage", [], loadAppStorage); define("clearButtonStyle", [], returnFirstDependency); define("listViewStyle", ['css!' + "modules/listview/listview.css"], returnFirstDependency); define("formDialogStyle", ["css!modules/formdialog.css"], returnFirstDependency); define("sectionsStyle", ["css!modules/sections.css"], returnFirstDependency); define("mediasync", ["modules/sync/mediasync"], returnFirstDependency); define("scrollStyles", ['css!modules/scrollstyles.css'], returnFirstDependency); // alias define("appsettings", ['appSettings'], returnFirstDependency); define("material-icons", ['css!modules/fonts/material-icons/style.css'], returnFirstDependency); define("systemFontsCss", ['css!modules/fonts/fonts.css'], returnFirstDependency); define("dialogTemplateHtml", ["text!modules/dialog/dialog.template.html"], returnFirstDependency); define("jQuery", ['https://code.jquery.com/jquery-3.7.0.slim.min.js'], function () { if (globalThis.ApiClient) { globalThis.jQuery.ajax = globalThis.ApiClient.ajax; } return globalThis.jQuery; }); define("apiInput", ['serverNotifications'], returnFirstDependency); define('apiClientResolver', ['connectionManager'], function (connectionManager) { return function () { return connectionManager.currentApiClient(); }; }); define("embyRouter", ['appRouter'], returnFirstDependency); define("webActionSheet", ["actionsheet"], returnFirstDependency); if (isNativeTizen && supportsTizenNaclSockets()) { define('sockets', ['native/tizen/naclSockets/sockets'], returnFirstDependency); } if (isNativeTizen && supportsTizenWasmSockets()) { define('sockets', ['native/tizen/wasmSockets/sockets'], returnFirstDependency); } define("iapManager", [], loadIap); define("detailtablecss", [], returnFirstDependency); define("apiclient", [], loadApiClient); } function loadCoreDictionary(globalize) { const baseUrl = 'strings/'; const languages = [ 'ar', 'be-BY', 'bg-BG', 'ca', 'cs', 'da', 'de', 'el', 'en-GB', 'en-US', 'es', 'es-AR', 'es-MX', 'fa', 'fi', 'fr', 'fr-CA', 'gsw', 'he', 'hi-IN', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-LT', 'ms', 'nb', 'nl', 'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl-SI', 'sv', 'tr', 'uk', 'vi', 'zh-CN', 'zh-HK', 'zh-TW' ]; const translations = languages.map(function (i) { return { lang: i, path: baseUrl + i + '.json' }; }); return globalize.loadStrings({ name: 'core', translations: translations }); } function replaceConsoleLogger() { // We should probably have a logging abstraction to avoid replacing console.log console.log = function () { }; } function loadWindowsComponents(appHost) { if (!navigator.mediaSession) { require(['native/windows/mediasession']); } if (!navigator.connection || !navigator.connection.type) { require(['native/windows/networkpolyfill']); } if (!appHost.supports('sync')) { return; } require(['bgtaskregister'], function (bgtaskregister) { bgtaskregister.registerTask(); //bgtaskregister.unregisterTask(); }); } function loadHeader() { console.log('loadHeader'); return Promise.all([ importFromPath('./modules/appheader/appheader.js') ]).then(function (responses) { return responses[0].init(); }); } function onAppReady() { if ("virtualKeyboard" in navigator) { navigator.virtualKeyboard.overlaysContent = true; } console.log('onAppReady'); let promises = [ importFromPath('./modules/common/servicelocator.js'), importFromPath('./modules/approuter.js'), importFromPath('./modules/browser.js'), importFromPath('./modules/common/pluginmanager.js') ]; if (appMode === 'ios' || appMode === 'android') { promises.push(importFromPath('./modules/registrationservices/registrationservices.js')); } if (appMode === 'android') { promises.push(getRequirePromise('native/android/appshortcuts')); promises.push(getRequirePromise('native/android/nativecredentials')); promises.push(getRequirePromise('native/android/nativesettings')); } else if (appMode === 'ios') { promises.push(getRequirePromise('native/ios/appshortcuts')); promises.push(getRequirePromise('native/ios/nativecredentials')); promises.push(getRequirePromise('native/ios/nativesettings')); } return Promise.all(promises).then(function (responses) { const serviceLocator = responses[0]; const appHost = serviceLocator.appHost; const appRouter = responses[1]; const browser = responses[2]; const pluginManager = responses[3]; promises = []; console.log('Loaded dependencies in onAppReady'); defineCoreRoutes(appRouter, appHost, browser); appRouter.start({ click: false, // this will need to be true to support pages in subfolders hashbang: true }); document.dispatchEvent(new CustomEvent("appready", {})); if (appHost.supports('soundeffects')) { importFromPath('./modules/soundeffects/soundeffectsmanager.js'); } importFromPathWithoutExport('./modules/thememediaplayer.js'); importFromPathWithoutExport('./modules/transparencymanagement.js'); if (!appHost.supports('nativegamepadkeymapping') && !enableNativeGamepadKeyMapping() && isGamepadSupported()) { importFromPathWithoutExport('./modules/input/gamepadtokey.js'); } if (appHost.supports('webhidautoauth')) { importFromPath('./modules/input/hidinput.js').then(function(hidInput) { if (hidInput.shouldTryConnect()) { window.addEventListener('keydown', function() { hidInput.tryConnect(); }, { capture: true, passive: false, once: true }); } }); } if (appHost.supports('windowstate')) { importFromPathWithoutExport('./modules/controlbox.js'); } if (appMode === 'android') { require([ 'native/android/mediasession', 'native/android/chromecast' ]); } else if (appMode === 'ios') { require([ 'native/ios/mediasession', 'native/ios/backgroundfetch', 'native/ios/nativeplayerbridge' ]); } // We need these but it's ok to let the UI load first else if (isNativeTizen) { require(['native/tizen/input']); require(['native/tizen/networkerror']); if (browser.sdkVersion && browser.sdkVersion >= 2.4) { require(['native/tizen/preview']); } require(['native/tizen/screensavermanager']); // itemContextMenu is also checking this to allow all options when sideloading browser.tizenSideload = false; if (browser.tizenSideload) { require(['native/tizen/expiration']); } } if (!browser.tv) { importFromPathWithoutExport('./modules/notifications.js'); importFromPath('./modules/dockedtabs/dockedtabs.js'); } importFromPathWithoutExport('./modules/nowplayingbar/nowplayingbar.js'); if (appHost.supports('remotecontrol')) { // For now this is needed because it also performs the mirroring function importFromPathWithoutExport('./modules/playback/remotecontrolautoplay.js'); } if (navigator.mediaSession && !appHost.supports('nativemediasession')) { importFromPathWithoutExport('./modules/playback/mediasession.js'); } importFromPath('./modules/input/mouse.js'); importFromPath('./modules/input/keyboard.js'); importFromPath('./modules/common/input/api.js'); if (appHost.supports('screensaver')) { importFromPath('./modules/screensavermanager.js'); } if (appHost.supports('fullscreenchange')) { importFromPathWithoutExport('./modules/fullscreen/fullscreen-dc.js'); } if (!appHost.supports('multiserver')) { if (globalThis.ApiClient) { require(['css!' + globalThis.ApiClient.getUrl('Branding/Css')]); } } if (appMode === 'winjs') { loadWindowsComponents(appHost); } // load this early so that css is already available and the first dialog isn't loaded awkwardly // this will no longer be needed once we start using import assertions importFromPath('./modules/actionsheet/actionsheet.js'); require(['formDialogStyle']); bindOnBeforeWindowUnload(); return Promise.all(promises); }); } function loadExternalScripts() { console.log('loadExternalScripts'); const startInfo = this; const scripts = startInfo.scripts; if (scripts) { return require(scripts); } return Promise.resolve(); } function loadPlugins() { console.log('loadPlugins'); const startInfo = this; return Promise.all([ importFromPath('./modules/common/servicelocator.js'), importFromPath('./modules/browser.js'), importFromPath('./modules/approuter.js') ]).then(function (responses) { const appHost = responses[0].appHost; const browser = responses[1]; // Doesn't really belong here but this is the earliest apphost is loaded if (appHost.supports('windowstate')) { document.querySelector('.skinHeader').insertAdjacentHTML('beforeend', '
'); require(['css!modules/windowdrag.css']); } const externalPlugins = startInfo.plugins || []; console.log('Loading installed plugins'); // Load installed plugins if (customPaths.pluginloader) { const forcedPlugins = [ './modules/common/playback/playbackvalidation.js', './modules/common/playback/playaccessvalidation.js', './modules/common/playback/experimentalwarnings.js', './modules/htmlaudioplayer/plugin.js', './modules/photoplayer/plugin.js', './modules/confirmstillplaying/plugin.js', //'./modules/common/playback/playqueueconfirmation.js' ]; return getRequirePromise(addJsExtIfNeeded(customPaths.pluginloader)).then(function (pluginloader) { return pluginloader.loadPlugins(forcedPlugins); }); } const list = [ './modules/common/playback/playbackvalidation.js', './modules/common/playback/playaccessvalidation.js', './modules/common/playback/experimentalwarnings.js' ]; //list.push('./modules/common/playback/playqueueconfirmation.js'); if (appHost.supports('soundeffects')) { list.push('./modules/soundeffects/defaultsoundeffects/plugin.js'); } if (appHost.supports('screensaver')) { list.push('./modules/logoscreensaver/plugin.js'); list.push('./modules/backdropscreensaver/plugin.js'); list.push('./modules/photoscreensaver/plugin.js'); } if (appMode === 'android') { list.push('native/android/mpvvideoplayer'); list.push('native/android/mpvaudioplayer'); } else if (appMode === 'ios') { list.push('native/ios/mpvaudioplayer'); list.push('native/ios/mpvvideoplayer'); } if (appMode !== 'android' && appMode !== 'ios') { list.push('./modules/htmlaudioplayer/plugin.js'); } if (appMode === 'ios') { list.push('native/ios/chromecast'); } if (appMode === 'android') { // intent player list.push('native/android/externalplayer'); list.push('native/android/chromecast'); } if (globalThis.webapis && webapis.avplay) { list.push('native/tizen/tizenavplayer/plugin'); } else { if (appMode !== 'android' && appMode !== 'ios') { list.push('./modules/htmlvideoplayer/plugin.js'); } } list.push('./modules/photoplayer/plugin.js'); if (appHost.supports('remotecontrol')) { list.push('./modules/sessionplayer.js'); // test for globalThis.chrome to detect other chromium based browsers as well as shims such as this: https://github.com/hensm/fx_cast // exclude old edge due to seeing the chrome global in uwp if (globalThis.chrome && !browser.edge && !browser.electron && appMode !== 'android') { list.push('./modules/chromecast/chromecastplayer.js'); } } if (appHost.supports('youtube') || browser.electron) { if (appMode === 'winjs') { list.push('native/windows/youtubeplayer/plugin'); } else { list.push('./modules/youtubeplayer/plugin.js'); } } for (let i = 0, length = externalPlugins.length; i < length; i++) { list.push(externalPlugins[i]); } if (browser.electron) { list.push('./modules/externalplayer/plugin.js'); } list.push('./modules/confirmstillplaying/plugin.js'); return Promise.all(list.map(loadPlugin)); }); } function loadFirstLevelPresentationDependencies() { console.log('loadFirstLevelPresentationDependencies'); return Promise.all([importFromPath('./modules/browser.js')]).then(function (responses) { const browser = responses[0]; // This is a tizen performance guideline to remove all logging from released applications // do this as late as possible to make it easier to debug startup problems if (appMode || browser.tv) { replaceConsoleLogger(); } // do early to prevent flash of content if (browser.osx) { document.documentElement.classList.add('html-osx'); } else if (appMode === 'winjs') { document.documentElement.classList.add('html-winjs'); } const promises = []; promises.push(require(['flexStyles'])); promises.push(require(['css!modules/layout.css'])); const supportsCssVariables = CSS.supports('color', 'var(--fake-var)'); if (!supportsCssVariables) { promises.push(require(['css!modules/layout_nocssvars.css'])); } promises.push(require(['sectionsStyle'])); promises.push(require(['systemFontsCss'])); return Promise.all(promises); }); } function loadGlobalization() { return Promise.all([ importFromPath('./modules/common/globalize.js'), importFromPath('./modules/common/servicelocator.js') ]).then(function (responses) { const globalize = responses[0]; const serviceLocator = responses[1]; const appHost = serviceLocator.appHost; if (globalThis.urlCacheParam) { globalize.setCacheParam(globalThis.urlCacheParam); } const stringPromises = []; if (appHost.supports('serversetup')) { stringPromises.push(loadCoreDictionary(globalize)); } stringPromises.push(loadSharedComponentsDictionary(globalize)); return Promise.all(stringPromises); }); } function loadThirdLevelPolyfills() { console.log('loadThirdLevelPolyfills'); return Promise.all([ importFromPath('./modules/browser.js'), ]).then(function (responses) { const browser = responses[0]; if (appMode === 'embyclient' && globalThis.NativeAppHost?.supports('sync')) { define("transfermanager", [], getNativeImport('NativeTransferManager')); define("filerepository", [], getNativeImport('NativeFileRepository')); define("localsync", [], getNativeImport('NativeLocalSync')); define('itemrepository', [], getNativeImport('NativeItemRepository')); define('useractionrepository', [], getNativeImport('NativeUserActionRepository')); } else if (appMode === 'winjs' && !browser.xboxOne) { define('bgtaskregister', ['native/windows/bgtaskregister'], returnFirstDependency); define('transfermanager', ['native/windows/transfermanager'], returnFirstDependency); define('filerepository', ['native/windows/filerepository'], returnFirstDependency); define("localsync", [], getWindowsLocalSync); define("itemrepository", [], getDynamicImport('./modules/localdatabase/itemrepository.js')); define("useractionrepository", [], getDynamicImport('./modules/localdatabase/useractionrepository.js')); } else if (appMode === 'ios') { define("filerepository", ["native/ios/filerepository"], returnFirstDependency); define("transfermanager", ["filerepository"], returnFirstDependency); define("localsync", ["native/ios/localsync"], returnFirstDependency); define('itemrepository', ['native/ios/itemrepository'], returnFirstDependency); define('useractionrepository', ['native/ios/useractionrepository'], returnFirstDependency); } else if (appMode === 'android' && AndroidAppHost.supportsSync()) { define("transfermanager", [], getDynamicImport('./modules/sync/transfermanager.js')); define("filerepository", ["native/android/filerepository"], returnFirstDependency); define("localsync", ["native/android/localsync"], returnFirstDependency); define('itemrepository', ['native/android/itemrepository'], returnFirstDependency); define('useractionrepository', ['native/android/useractionrepository'], returnFirstDependency); } else { define("transfermanager", [], getDynamicImport('./modules/sync/transfermanager.js')); define("filerepository", [], getDynamicImport('./modules/sync/filerepository.js')); define("localsync", [], getDynamicImport('./modules/sync/localsync.js')); define("itemrepository", [], getDynamicImport('./modules/localdatabase/itemrepository.js')); define("useractionrepository", [], getDynamicImport('./modules/localdatabase/useractionrepository.js')); } const promises = []; // these two have to come last because they depend on others such as Map, Set, WeakMap and MutationObserver if (!('customElements' in globalThis)) { // tizen 2015 fails with the newer polyfill if (globalThis.MutationObserver && globalThis.Reflect) { promises.push(require(['modules/polyfills/custom-elements'])); } else { promises.push(require(['modules/polyfills/document-register-element'])); } } else if (!(('customElements' in globalThis) && !browser.iOS && !browser.safari && customElements.upgrade)) { promises.push(importFromPathWithoutExport('./modules/polyfills/custom-elements-builtin.js')); } if (isNativeTizen) { promises.push(require(['native/tizen/tizeninfo'])); } if (isNativeLG) { promises.push(require(['native/webos/webosinfo'])); } return Promise.all(promises); }); } function loadSecondLevelPolyfills() { console.log('loadSecondLevelPolyfills'); const promises = []; if (typeof SpeechRecognition === 'undefined') { globalThis.SpeechRecognition = globalThis.webkitSpeechRecognition; } // Note that the "unfetch" minimal fetch polyfill defines fetch() without // defining window.Request, and this polyfill need to work on top of unfetch // so the below feature detection needs the !globalThis.AbortController part. // The Request.prototype check is also needed because Safari versions 11.1.2 // up to and including 12.1.x has a window.AbortController present but still // does NOT correctly implement abortable fetch: // https://bugs.webkit.org/show_bug.cgi?id=174980#c2 const isMissingRequestSignalSupport = typeof globalThis.Request === 'function' && !Object.prototype.hasOwnProperty.call(globalThis.Request.prototype, 'signal'); if (typeof AbortSignal === 'undefined' || isMissingRequestSignalSupport || !AbortSignal.timeout || !AbortSignal.prototype?.throwIfAborted) { promises.push(importFromPathWithoutExport('./modules/polyfills/abortsignal.js')); } if (typeof AbortController === 'undefined' || isMissingRequestSignalSupport) { promises.push(importFromPathWithoutExport('./modules/polyfills/abortcontroller.js')); } if (typeof AbortController !== 'undefined' && isMissingRequestSignalSupport) { promises.push(importFromPathWithoutExport('./modules/polyfills/abortablefetch.js')); } if (!Number.isInteger) { promises.push(importFromPathWithoutExport('./modules/polyfills/number.js')); } if (typeof Intl === 'undefined' || !Intl.NumberFormat) { promises.push(importFromPathWithoutExport('./modules/polyfills/numberformat.js')); } if (typeof Intl === 'undefined' || !Intl.RelativeTimeFormat) { promises.push(importFromPathWithoutExport('./modules/polyfills/relativetimeformat.js')); } if (typeof Object.assign !== 'function' || typeof Object.create !== 'function' || typeof Object.hasOwn !== 'function') { promises.push(importFromPathWithoutExport('./modules/polyfills/object.js')); } if (typeof Promise.any !== 'function' || typeof Promise.allSettled !== 'function') { promises.push(importFromPathWithoutExport('./modules/polyfills/promise.js')); } if (!String.prototype.includes || !String.prototype.startsWith || !String.prototype.endsWith || !String.prototype.replaceAll) { promises.push(importFromPathWithoutExport('./modules/polyfills/string.js')); } if (!Array.prototype.filter || !Array.prototype.includes || !Array.prototype.some || !Array.isArray || !Array.from) { promises.push(importFromPathWithoutExport('./modules/polyfills/array.js')); } if (!Element.prototype.matches || !Element.prototype.closest || !Element.prototype.remove || !Element.prototype.replaceChildren || !Element.prototype.append) { promises.push(importFromPathWithoutExport('./modules/polyfills/element.js')); } if (!HTMLFormElement.prototype.requestSubmit) { promises.push(importFromPathWithoutExport('./modules/polyfills/form.js')); } if (!Function.prototype.bind) { promises.push(importFromPathWithoutExport('./modules/polyfills/bind.js')); } if (typeof Map === 'undefined') { promises.push(importFromPathWithoutExport('./modules/polyfills/map.js')); } if (typeof WeakMap === 'undefined') { promises.push(importFromPathWithoutExport('./modules/polyfills/weakmap.js')); } if (typeof Set === 'undefined' || !Set.prototype.entries) { promises.push(importFromPathWithoutExport('./modules/polyfills/set.js')); } if (typeof crypto === 'undefined' || !crypto.randomUUID) { promises.push(importFromPathWithoutExport('./modules/polyfills/crypto.js')); } if (typeof CSS === 'undefined' || !CSS.supports) { promises.push(importFromPathWithoutExport('./modules/polyfills/css.js')); } return Promise.all(promises); } function requiresUrlSearchParamsPolyfill() { if (typeof URLSearchParams === 'undefined') { return true; } try { const plus = '+'; // test for broken support in iOS 10.3 // https://github.com/ungap/url-search-params/blob/master/index.js // https://github.com/WebReflection/url-search-params#ios-10--other-platforms-bug if ( new URLSearchParams('q=%2B').get('q') !== plus || new URLSearchParams({ q: plus }).get('q') !== plus || new URLSearchParams([['q', plus]]).get('q') !== plus || new URLSearchParams('q=\n').toString() !== 'q=%0A' || new URLSearchParams({ q: ' &' }).toString() !== 'q=+%26' || new URLSearchParams({ q: '%zx' }).toString() !== 'q=%25zx' ) { return true; } return false; } catch (error) { return true; } } function requiresDataTransferPolyfill() { if (typeof DataTransfer === 'undefined') { return true; } try { new DataTransfer(); return false; } catch (error) { return true; } } function loadFirstLevelPolyfills() { const promises = []; if (globalThis.Emby.requiresClassesPolyfill) { promises.push(importFromPathWithoutExport('./modules/babelhelpers.js')); } if (typeof ResizeObserver === 'undefined') { promises.push(importFromPathWithoutExport('./modules/polyfills/resizeobserver.js')); } if (typeof IntersectionObserver === 'undefined') { promises.push(importFromPathWithoutExport('./modules/polyfills/intersection-observer.js')); } // technically this should be loaded before the fetch polyfill since fetch.js checks for support for URLSearchParams right at the top // but that's only used when assigning a URLSearchParams instance to a request body which we shouldn't use anyway due to inconsistent support for it if (requiresUrlSearchParamsPolyfill()) { promises.push(importFromPathWithoutExport('./modules/polyfills/urlsearchparams.js')); } if (requiresDataTransferPolyfill()) { promises.push(importFromPathWithoutExport('./modules/polyfills/datatransfer.js')); } if (typeof fetch === 'undefined') { promises.push(importFromPathWithoutExport('./modules/polyfills/fetch.js')); } return Promise.all(promises); } function start(startInfo) { enableNativeGamepadKeyMapping(); if (typeof Windows !== 'undefined' && Windows.UI) { try { Windows.UI.ViewManagement.ApplicationViewScaling.trySetDisableLayoutScaling(true); } catch (err) { } try { Windows.UI.ViewManagement.ApplicationView.getForCurrentView().setDesiredBoundsMode(Windows.UI.ViewManagement.ApplicationViewBoundsMode.useCoreWindow); } catch (err) { } } startInfo = startInfo || globalThis.appStartInfo || {}; customPaths = startInfo.paths || {}; initRequire(); return loadFirstLevelPolyfills() .then(loadSecondLevelPolyfills, loadSecondLevelPolyfills) .then(loadThirdLevelPolyfills, loadThirdLevelPolyfills) .then(loadServiceLocator) .then(createConnectionManager) .then(loadGlobalization) .then(loadFirstLevelPresentationDependencies) .then(loadPlugins.bind(startInfo)) .then(loadExternalScripts.bind(startInfo)) .then(loadHeader) .then(onAppReady); } if (!globalThis.Emby) { globalThis.Emby = {}; } Emby.importModule = importFromPath; Emby.App = { start: start }; if (globalThis.location.href.toString().toLowerCase().indexOf('autostart=false') === -1) { start(); } // closure end