From f16df9788a6b773b2a309345f11d18c589d4aee3 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 16 Jul 2020 22:19:09 +0200 Subject: [PATCH] Migrate bundle, qualityOptions, appHost and appLoader --- package.json | 2 + src/bundle.js | 60 +- src/components/actionSheet/actionSheet.js | 14 +- src/components/appRouter.js | 4 +- src/components/apphost.js | 776 +++++++++--------- src/components/cardbuilder/cardBuilder.js | 28 +- .../displaySettings/displaySettings.js | 6 +- .../imageDownloader/imageDownloader.js | 2 +- src/components/images/imageLoader.js | 14 +- src/components/indicators/indicators.js | 2 +- src/components/itemContextMenu.js | 2 +- src/components/multiSelect/multiSelect.js | 2 +- src/components/nowPlayingBar/nowPlayingBar.js | 2 +- src/components/playback/mediasession.js | 8 +- src/components/playback/playbackmanager.js | 2 +- .../playback/playerSelectionMenu.js | 2 +- .../playbackSettings/playbackSettings.js | 6 +- src/components/qualityOptions.js | 305 +++---- src/components/remotecontrol/remotecontrol.js | 2 +- src/components/scrollManager.js | 2 +- src/components/skinManager.js | 184 +++++ src/components/slideshow/slideshow.js | 10 +- .../dashboard/plugins/repositories/index.js | 2 +- src/controllers/music/musicrecommended.js | 1 - src/controllers/playback/video/index.js | 2 +- src/controllers/session/login/index.js | 2 +- src/controllers/user/menu.js | 58 ++ src/controllers/user/profile.js | 106 +++ src/plugins/bookPlayer/plugin.js | 36 +- src/plugins/bookPlayer/tableOfContents.js | 18 +- src/plugins/experimentalWarnings/plugin.js | 2 +- src/plugins/htmlAudioPlayer/plugin.js | 4 +- src/plugins/htmlVideoPlayer/plugin.js | 4 +- src/scripts/apploader.js | 92 +-- src/scripts/deleteHelper.js | 4 +- src/scripts/gamepadtokey.js | 2 +- src/scripts/inputManager.js | 4 +- src/scripts/libraryMenu.js | 4 +- src/scripts/site.js | 22 +- 39 files changed, 1075 insertions(+), 723 deletions(-) create mode 100644 src/components/skinManager.js create mode 100644 src/controllers/user/menu.js create mode 100644 src/controllers/user/profile.js diff --git a/package.json b/package.json index 6e2480ca05..ce29b55eae 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "src/components/alert.js", "src/components/alphaPicker/alphaPicker.js", "src/components/appFooter/appFooter.js", + "src/components/apphost.js", "src/components/autoFocuser.js", "src/components/backdrop/backdrop.js", "src/components/cardbuilder/cardBuilder.js", @@ -156,6 +157,7 @@ "src/components/recordingcreator/seriesrecordingeditor.js", "src/components/recordingcreator/recordinghelper.js", "src/components/refreshdialog/refreshdialog.js", + "src/components/qualityOptions.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", "src/plugins/htmlVideoPlayer/plugin.js", diff --git a/src/bundle.js b/src/bundle.js index ae2a59f0d5..86ebb1ccdf 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -2,159 +2,165 @@ * require.js module definitions bundled by webpack */ // Use define from require.js not webpack's define -var _define = window.define; +const _define = window.define; // fetch -var fetch = require('whatwg-fetch'); +const fetch = require('whatwg-fetch'); _define('fetch', function() { return fetch; }); // Blurhash -var blurhash = require('blurhash'); +const blurhash = require('blurhash'); _define('blurhash', function() { return blurhash; }); // query-string -var query = require('query-string'); +const query = require('query-string'); _define('queryString', function() { return query; }); // flvjs -var flvjs = require('flv.js/dist/flv').default; +const flvjs = require('flv.js/dist/flv').default; _define('flvjs', function() { return flvjs; }); // jstree -var jstree = require('jstree'); +const jstree = require('jstree'); require('jstree/dist/themes/default/style.css'); _define('jstree', function() { return jstree; }); // jquery -var jquery = require('jquery'); +const jquery = require('jquery'); _define('jQuery', function() { return jquery; }); // hlsjs -var hlsjs = require('hls.js'); +const hlsjs = require('hls.js'); _define('hlsjs', function() { return hlsjs; }); // howler -var howler = require('howler'); +const howler = require('howler'); _define('howler', function() { return howler; }); // resize-observer-polyfill -var resize = require('resize-observer-polyfill').default; +const resize = require('resize-observer-polyfill').default; _define('resize-observer-polyfill', function() { return resize; }); // swiper -var swiper = require('swiper/js/swiper'); +const swiper = require('swiper/js/swiper'); require('swiper/css/swiper.min.css'); _define('swiper', function() { return swiper; }); // sortable -var sortable = require('sortablejs').default; +const sortable = require('sortablejs').default; _define('sortable', function() { return sortable; }); // webcomponents -var webcomponents = require('webcomponents.js/webcomponents-lite'); +const webcomponents = require('webcomponents.js/webcomponents-lite'); _define('webcomponents', function() { return webcomponents; }); +// shaka +const shaka = require('shaka-player'); +_define('shaka', function() { + return shaka; +}); + // libass-wasm -var libassWasm = require('libass-wasm'); +const libassWasm = require('libass-wasm'); _define('JavascriptSubtitlesOctopus', function() { return libassWasm; }); // material-icons -var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); +const materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); _define('material-icons', function() { return materialIcons; }); // noto font -var noto = require('jellyfin-noto'); +const noto = require('jellyfin-noto'); _define('jellyfin-noto', function () { return noto; }); -var epubjs = require('epubjs'); +const epubjs = require('epubjs'); _define('epubjs', function () { return epubjs; }); // page.js -var page = require('page'); +const page = require('page'); _define('page', function() { return page; }); // core-js -var polyfill = require('@babel/polyfill/dist/polyfill'); +const polyfill = require('@babel/polyfill/dist/polyfill'); _define('polyfill', function () { return polyfill; }); // domtokenlist-shim -var classlist = require('classlist.js'); +const classlist = require('classlist.js'); _define('classlist-polyfill', function () { return classlist; }); // Date-FNS -var dateFns = require('date-fns'); +const dateFns = require('date-fns'); _define('date-fns', function () { return dateFns; }); -var dateFnsLocale = require('date-fns/locale'); +const dateFnsLocale = require('date-fns/locale'); _define('date-fns/locale', function () { return dateFnsLocale; }); -var fast_text_encoding = require('fast-text-encoding'); +const fast_text_encoding = require('fast-text-encoding'); _define('fast-text-encoding', function () { return fast_text_encoding; }); // intersection-observer -var intersection_observer = require('intersection-observer'); +const intersection_observer = require('intersection-observer'); _define('intersection-observer', function () { return intersection_observer; }); // screenfull -var screenfull = require('screenfull'); +const screenfull = require('screenfull'); _define('screenfull', function () { return screenfull; }); // headroom.js -var headroom = require('headroom.js/dist/headroom'); +const headroom = require('headroom.js/dist/headroom'); _define('headroom', function () { return headroom; }); // apiclient -var apiclient = require('jellyfin-apiclient'); +const apiclient = require('jellyfin-apiclient'); _define('apiclient', function () { return apiclient.ApiClient; diff --git a/src/components/actionSheet/actionSheet.js b/src/components/actionSheet/actionSheet.js index 937cd2afe5..be84cf0a06 100644 --- a/src/components/actionSheet/actionSheet.js +++ b/src/components/actionSheet/actionSheet.js @@ -9,14 +9,14 @@ import 'scrollStyles'; import 'listViewStyle'; function getOffsets(elems) { - let results = []; + const results = []; if (!document) { return results; } for (const elem of elems) { - let box = elem.getBoundingClientRect(); + const box = elem.getBoundingClientRect(); results.push({ top: box.top, @@ -34,7 +34,7 @@ function getPosition(options, dlg) { const windowHeight = windowSize.innerHeight; const windowWidth = windowSize.innerWidth; - let pos = getOffsets([options.positionTo])[0]; + const pos = getOffsets([options.positionTo])[0]; if (options.positionY !== 'top') { pos.top += (pos.height || 0) / 2; @@ -82,7 +82,7 @@ export function show(options) { // positionTo // showCancel // title - let dialogOptions = { + const dialogOptions = { removeOnClose: true, enableHistory: options.enableHistory, scrollY: false @@ -103,7 +103,7 @@ export function show(options) { dialogOptions.autoFocus = false; } - let dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); if (isFullscreen) { dlg.classList.add('actionsheet-fullscreen'); @@ -129,7 +129,7 @@ export function show(options) { } let renderIcon = false; - let icons = []; + const icons = []; let itemIcon; for (const item of options.items) { itemIcon = item.icon || (item.selected ? 'check' : null); @@ -241,7 +241,7 @@ export function show(options) { centerFocus(dlg.querySelector('.actionSheetScroller'), false, true); } - let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); + const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); if (btnCloseActionSheet) { btnCloseActionSheet.addEventListener('click', function () { dialogHelper.close(dlg); diff --git a/src/components/appRouter.js b/src/components/appRouter.js index da3b08317c..efabdab1cb 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -329,8 +329,8 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdro } if (shouldExitApp) { - if (appHost.supports('exit')) { - appHost.exit(); + if (appHost.default.supports('exit')) { + appHost.default.exit(); return; } return; diff --git a/src/components/apphost.js b/src/components/apphost.js index 3ed590b546..90ae418de6 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -1,447 +1,445 @@ -define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) { - 'use strict'; +import appSettings from 'appSettings'; +import browser from 'browser'; +import events from 'events'; +import htmlMediaHelper from 'htmlMediaHelper'; +import * as webSettings from 'webSettings'; +import globalize from 'globalize'; - browser = browser.default || browser; +function getBaseProfileOptions(item) { + const disableHlsVideoAudioCodecs = []; - function getBaseProfileOptions(item) { - var disableHlsVideoAudioCodecs = []; + if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { + if (browser.edge) { + disableHlsVideoAudioCodecs.push('mp3'); + } - if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { - if (browser.edge) { - disableHlsVideoAudioCodecs.push('mp3'); + disableHlsVideoAudioCodecs.push('ac3'); + disableHlsVideoAudioCodecs.push('eac3'); + disableHlsVideoAudioCodecs.push('opus'); + } + + return { + enableMkvProgressive: false, + disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs + }; +} + +function getDeviceProfile(item, options = {}) { + return new Promise(function (resolve) { + import(['browserdeviceprofile']).then(({default: profileBuilder}) => { + let profile; + + if (window.NativeShell) { + profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); + } else { + const builderOpts = getBaseProfileOptions(item); + builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats'); + profile = profileBuilder(builderOpts); } - disableHlsVideoAudioCodecs.push('ac3'); - disableHlsVideoAudioCodecs.push('eac3'); - disableHlsVideoAudioCodecs.push('opus'); - } - - return { - enableMkvProgressive: false, - disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs - }; - } - - function getDeviceProfileForWindowsUwp(item) { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) { - var profileOptions = getBaseProfileOptions(item); - profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); - profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(); - profileOptions.audioChannels = uwpMediaCaps.getAudioChannels(); - resolve(profileBuilder(profileOptions)); - }); + resolve(profile); }); + }); +} + +function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'); +} + +function replaceAll(originalString, strReplace, strWith) { + const strReplace2 = escapeRegExp(strReplace); + const reg = new RegExp(strReplace2, 'ig'); + return originalString.replace(reg, strWith); +} + +function generateDeviceId() { + const keys = []; + + if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { + const result = replaceAll(btoa(keys.join('|')), '=', '1'); + return Promise.resolve(result); } - function getDeviceProfile(item, options) { - options = options || {}; + return Promise.resolve(new Date().getTime()); +} - if (self.Windows) { - return getDeviceProfileForWindowsUwp(item); - } +function getDeviceId() { + const key = '_deviceId2'; + const deviceId = appSettings.get(key); - return new Promise(function (resolve) { - require(['browserdeviceprofile'], function (profileBuilder) { - var profile; - - if (window.NativeShell) { - profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); - } else { - var builderOpts = getBaseProfileOptions(item); - builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats'); - profile = profileBuilder(builderOpts); - } - - resolve(profile); - }); - }); + if (deviceId) { + return Promise.resolve(deviceId); } - function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + return generateDeviceId().then(function (deviceId) { + appSettings.set(key, deviceId); + return deviceId; + }); +} + +function getDeviceName() { + var deviceName; + if (browser.tizen) { + deviceName = 'Samsung Smart TV'; + } else if (browser.web0s) { + deviceName = 'LG Smart TV'; + } else if (browser.operaTv) { + deviceName = 'Opera TV'; + } else if (browser.xboxOne) { + deviceName = 'Xbox One'; + } else if (browser.ps4) { + deviceName = 'Sony PS4'; + } else if (browser.chrome) { + deviceName = 'Chrome'; + } else if (browser.edgeChromium) { + deviceName = 'Edge Chromium'; + } else if (browser.edge) { + deviceName = 'Edge'; + } else if (browser.firefox) { + deviceName = 'Firefox'; + } else if (browser.opera) { + deviceName = 'Opera'; + } else if (browser.safari) { + deviceName = 'Safari'; + } else { + deviceName = 'Web Browser'; } - function replaceAll(originalString, strReplace, strWith) { - var strReplace2 = escapeRegExp(strReplace); - var reg = new RegExp(strReplace2, 'ig'); - return originalString.replace(reg, strWith); + if (browser.ipad) { + deviceName += ' iPad'; + } else if (browser.iphone) { + deviceName += ' iPhone'; + } else if (browser.android) { + deviceName += ' Android'; } - function generateDeviceId() { - var keys = []; - - if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { - var result = replaceAll(btoa(keys.join('|')), '=', '1'); - return Promise.resolve(result); - } - - return Promise.resolve(new Date().getTime()); + if (browser.ipad) { + deviceName += ' iPad'; + } else if (browser.iphone) { + deviceName += ' iPhone'; + } else if (browser.android) { + deviceName += ' Android'; } - function getDeviceId() { - var key = '_deviceId2'; - var deviceId = appSettings.get(key); + return deviceName; +} - if (deviceId) { - return Promise.resolve(deviceId); - } - - return generateDeviceId().then(function (deviceId) { - appSettings.set(key, deviceId); - return deviceId; - }); +function supportsVoiceInput() { + if (!browser.tv) { + return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition; } - function getDeviceName() { - var deviceName; - if (browser.tizen) { - deviceName = 'Samsung Smart TV'; - } else if (browser.web0s) { - deviceName = 'LG Smart TV'; - } else if (browser.operaTv) { - deviceName = 'Opera TV'; - } else if (browser.xboxOne) { - deviceName = 'Xbox One'; - } else if (browser.ps4) { - deviceName = 'Sony PS4'; - } else if (browser.chrome) { - deviceName = 'Chrome'; - } else if (browser.edgeChromium) { - deviceName = 'Edge Chromium'; - } else if (browser.edge) { - deviceName = 'Edge'; - } else if (browser.firefox) { - deviceName = 'Firefox'; - } else if (browser.opera) { - deviceName = 'Opera'; - } else if (browser.safari) { - deviceName = 'Safari'; - } else { - deviceName = 'Web Browser'; - } - - if (browser.ipad) { - deviceName += ' iPad'; - } else if (browser.iphone) { - deviceName += ' iPhone'; - } else if (browser.android) { - deviceName += ' Android'; - } - - return deviceName; - } - - function supportsVoiceInput() { - if (!browser.tv) { - return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition; - } + return false; +} +function supportsFullscreen() { + if (browser.tv) { return false; } - function supportsFullscreen() { - if (browser.tv) { - return false; - } + const element = document.documentElement; + return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; +} - var element = document.documentElement; - return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; - } +function getSyncProfile() { + return new Promise(function (resolve) { + require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) { + let profile; - function getSyncProfile() { - return new Promise(function (resolve) { - require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) { - var profile; + if (window.NativeShell) { + profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings); + } else { + profile = profileBuilder(); + profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate(); + } - if (window.NativeShell) { - profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings); - } else { - profile = profileBuilder(); - profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate(); - } - - resolve(profile); - }); + resolve(profile); }); - } + }); +} - function getDefaultLayout() { - return 'desktop'; - } - - function supportsHtmlMediaAutoplay() { - if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) { - return true; - } - - if (browser.mobile) { - return false; - } +function getDefaultLayout() { + return 'desktop'; +} +function supportsHtmlMediaAutoplay() { + if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) { return true; } - function supportsCue() { - try { - var video = document.createElement('video'); - var style = document.createElement('style'); - - style.textContent = 'video::cue {background: inherit}'; - document.body.appendChild(style); - document.body.appendChild(video); - - var cue = window.getComputedStyle(video, '::cue').background; - document.body.removeChild(style); - document.body.removeChild(video); - - return !!cue.length; - } catch (err) { - console.error('error detecting cue support: ' + err); - return false; - } + if (browser.mobile) { + return false; } - function onAppVisible() { - if (isHidden) { - isHidden = false; - console.debug('triggering app resume event'); - events.trigger(appHost, 'resume'); - } + return true; +} + +function supportsCue() { + try { + const video = document.createElement('video'); + const style = document.createElement('style'); + + style.textContent = 'video::cue {background: inherit}'; + document.body.appendChild(style); + document.body.appendChild(video); + + const cue = window.getComputedStyle(video, '::cue').background; + document.body.removeChild(style); + document.body.removeChild(video); + + return !!cue.length; + } catch (err) { + console.error('error detecting cue support: ' + err); + return false; + } +} + +function onAppVisible() { + if (isHidden) { + isHidden = false; + console.debug('triggering app resume event'); + events.trigger(appHost, 'resume'); + } +} + +function onAppHidden() { + if (!isHidden) { + isHidden = true; + console.debug('app is hidden'); + } +} + +const supportedFeatures = function () { + const features = []; + + if (navigator.share) { + features.push('sharing'); } - function onAppHidden() { - if (!isHidden) { - isHidden = true; - console.debug('app is hidden'); - } + if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { + features.push('filedownload'); } - var supportedFeatures = function () { - var features = []; + if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { + features.push('exit'); + } else { + features.push('exitmenu'); + features.push('plugins'); + } - if (navigator.share) { - features.push('sharing'); - } + if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { + features.push('externallinks'); + features.push('externalpremium'); + } - if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { - features.push('filedownload'); - } + if (!browser.operaTv) { + features.push('externallinkdisplay'); + } - if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { - features.push('exit'); + if (supportsVoiceInput()) { + features.push('voiceinput'); + } + + if (supportsHtmlMediaAutoplay()) { + features.push('htmlaudioautoplay'); + features.push('htmlvideoautoplay'); + } + + if (browser.edgeUwp) { + features.push('sync'); + } + + if (supportsFullscreen()) { + features.push('fullscreenchange'); + } + + if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { + features.push('physicalvolumecontrol'); + } + + if (!browser.tv && !browser.xboxOne && !browser.ps4) { + features.push('remotecontrol'); + } + + if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { + features.push('remotevideo'); + } + + features.push('displaylanguage'); + features.push('otherapppromotions'); + features.push('displaymode'); + features.push('targetblank'); + features.push('screensaver'); + + webSettings.enableMultiServer().then(enabled => { + if (enabled) features.push('multiserver'); + }); + + if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { + features.push('subtitleappearancesettings'); + } + + if (!browser.orsay) { + features.push('subtitleburnsettings'); + } + + if (!browser.tv && !browser.ps4 && !browser.xboxOne) { + features.push('fileinput'); + } + + if (browser.chrome) { + features.push('chromecast'); + } + + return features; +}(); + +/** + * Do exit according to platform + */ +function doExit() { + try { + if (window.NativeShell) { + window.NativeShell.AppHost.exit(); + } else if (browser.tizen) { + tizen.application.getCurrentApplication().exit(); + } else if (browser.web0s) { + webOS.platformBack(); } else { - features.push('exitmenu'); - features.push('plugins'); + window.close(); } + } catch (err) { + console.error('error closing application: ' + err); + } +} - if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { - features.push('externallinks'); - features.push('externalpremium'); - } +let exitPromise; - if (!browser.operaTv) { - features.push('externallinkdisplay'); - } - - if (supportsVoiceInput()) { - features.push('voiceinput'); - } - - if (supportsHtmlMediaAutoplay()) { - features.push('htmlaudioautoplay'); - features.push('htmlvideoautoplay'); - } - - if (browser.edgeUwp) { - features.push('sync'); - } - - if (supportsFullscreen()) { - features.push('fullscreenchange'); - } - - if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) { - features.push('physicalvolumecontrol'); - } - - if (!browser.tv && !browser.xboxOne && !browser.ps4) { - features.push('remotecontrol'); - } - - if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) { - features.push('remotevideo'); - } - - features.push('displaylanguage'); - features.push('otherapppromotions'); - features.push('displaymode'); - features.push('targetblank'); - features.push('screensaver'); - - webSettings.getMultiServer().then(enabled => { - if (enabled) features.push('multiserver'); - }); - - if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) { - features.push('subtitleappearancesettings'); - } - - if (!browser.orsay) { - features.push('subtitleburnsettings'); - } - - if (!browser.tv && !browser.ps4 && !browser.xboxOne) { - features.push('fileinput'); - } - - if (browser.chrome || browser.edgeChromium) { - features.push('chromecast'); - } - - return features; - }(); - - /** - * Do exit according to platform - */ - function doExit() { - try { - if (window.NativeShell) { - window.NativeShell.AppHost.exit(); - } else if (browser.tizen) { - tizen.application.getCurrentApplication().exit(); - } else if (browser.web0s) { - webOS.platformBack(); - } else { - window.close(); - } - } catch (err) { - console.error('error closing application: ' + err); - } +/** + * Ask user for exit + */ +function askForExit() { + if (exitPromise) { + return; } - var exitPromise; - - /** - * Ask user for exit - */ - function askForExit() { - if (exitPromise) { - return; - } - - require(['actionsheet'], function (actionsheet) { - exitPromise = actionsheet.show({ - title: globalize.translate('MessageConfirmAppExit'), - items: [ - {id: 'yes', name: globalize.translate('Yes')}, - {id: 'no', name: globalize.translate('No')} - ] - }).then(function (value) { - if (value === 'yes') { - doExit(); - } - }).finally(function () { - exitPromise = null; - }); - }); - } - - var deviceId; - var deviceName; - var appName = 'Jellyfin Web'; - var appVersion = '10.7.0'; - - var appHost = { - getWindowState: function () { - return document.windowState || 'Normal'; - }, - setWindowState: function (state) { - alert('setWindowState is not supported and should not be called'); - }, - exit: function () { - if (!!window.appMode && browser.tizen) { - askForExit(); - } else { + require(['actionsheet'], function (actionsheet) { + exitPromise = actionsheet.show({ + title: globalize.translate('MessageConfirmAppExit'), + items: [ + {id: 'yes', name: globalize.translate('Yes')}, + {id: 'no', name: globalize.translate('No')} + ] + }).then(function (value) { + if (value === 'yes') { doExit(); } - }, - supports: function (command) { - if (window.NativeShell) { - return window.NativeShell.AppHost.supports(command); - } + }).finally(function () { + exitPromise = null; + }); + }); +} - return supportedFeatures.indexOf(command.toLowerCase()) !== -1; - }, - preferVisualCards: browser.android || browser.chrome, - getSyncProfile: getSyncProfile, - getDefaultLayout: function () { - if (window.NativeShell) { - return window.NativeShell.AppHost.getDefaultLayout(); - } +let deviceId; +let deviceName; +const appName = 'Jellyfin Web'; +const appVersion = '10.6.0'; - return getDefaultLayout(); - }, - getDeviceProfile: getDeviceProfile, - init: function () { - if (window.NativeShell) { - return window.NativeShell.AppHost.init(); - } - - deviceName = getDeviceName(); - getDeviceId().then(function (id) { - deviceId = id; - }); - }, - deviceName: function () { - return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName; - }, - deviceId: function () { - return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId; - }, - appName: function () { - return window.NativeShell ? window.NativeShell.AppHost.appName() : appName; - }, - appVersion: function () { - return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion; - }, - getPushTokenInfo: function () { - return {}; - }, - setUserScalable: function (scalable) { - if (!browser.tv) { - var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; - document.querySelector('meta[name=viewport]').setAttribute('content', att); - } - } - }; - - var isHidden = false; - var hidden; - var visibilityChange; - - if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */ - hidden = 'hidden'; - visibilityChange = 'visibilitychange'; - } else if (typeof document.webkitHidden !== 'undefined') { - hidden = 'webkitHidden'; - visibilityChange = 'webkitvisibilitychange'; - } - - document.addEventListener(visibilityChange, function () { - /* eslint-disable-next-line compat/compat */ - if (document[hidden]) { - onAppHidden(); +const appHost = { + getWindowState: function () { + return document.windowState || 'Normal'; + }, + setWindowState: function () { + alert('setWindowState is not supported and should not be called'); + }, + exit: function () { + if (!!window.appMode && browser.tizen) { + askForExit(); } else { - onAppVisible(); + doExit(); + } + }, + supports: function (command) { + if (window.NativeShell) { + return window.NativeShell.AppHost.supports(command); } - }, false); - if (self.addEventListener) { - self.addEventListener('focus', onAppVisible); - self.addEventListener('blur', onAppHidden); + return supportedFeatures.indexOf(command.toLowerCase()) !== -1; + }, + preferVisualCards: browser.android || browser.chrome, + getSyncProfile: getSyncProfile, + getDefaultLayout: function () { + if (window.NativeShell) { + return window.NativeShell.AppHost.getDefaultLayout(); + } + + return getDefaultLayout(); + }, + getDeviceProfile: getDeviceProfile, + init: function () { + if (window.NativeShell) { + return window.NativeShell.AppHost.init(); + } + + deviceName = getDeviceName(); + getDeviceId().then(function (id) { + deviceId = id; + }); + }, + deviceName: function () { + return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName; + }, + deviceId: function () { + return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId; + }, + appName: function () { + return window.NativeShell ? window.NativeShell.AppHost.appName() : appName; + }, + appVersion: function () { + return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion; + }, + getPushTokenInfo: function () { + return {}; + }, + setThemeColor: function (color) { + const metaThemeColor = document.querySelector('meta[name=theme-color]'); + + if (metaThemeColor) { + metaThemeColor.setAttribute('content', color); + } + }, + setUserScalable: function (scalable) { + if (!browser.tv) { + const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; + document.querySelector('meta[name=viewport]').setAttribute('content', att); + } } +}; - return appHost; -}); +let isHidden = false; +let hidden; +let visibilityChange; + +if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */ + hidden = 'hidden'; + visibilityChange = 'visibilitychange'; +} else if (typeof document.webkitHidden !== 'undefined') { + hidden = 'webkitHidden'; + visibilityChange = 'webkitvisibilitychange'; +} + +document.addEventListener(visibilityChange, function () { + /* eslint-disable-next-line compat/compat */ + if (document[hidden]) { + onAppHidden(); + } else { + onAppVisible(); + } +}, false); + +if (self.addEventListener) { + self.addEventListener('focus', onAppVisible); + self.addEventListener('blur', onAppHidden); +} + +export default appHost; diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 4a37331ef4..e644365906 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -362,12 +362,12 @@ import 'programStyles'; let hasOpenRow; let hasOpenSection; - let sectionTitleTagName = options.sectionTitleTagName || 'div'; + const sectionTitleTagName = options.sectionTitleTagName || 'div'; let apiClient; let lastServerId; for (const [i, item] of items.entries()) { - let serverId = item.ServerId || options.serverId; + const serverId = item.ServerId || options.serverId; if (serverId !== lastServerId) { lastServerId = serverId; @@ -621,7 +621,7 @@ import 'programStyles'; }); } - let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; + const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; return { imgUrl: imgUrl, @@ -656,7 +656,7 @@ import 'programStyles'; for (let i = 0; i < character.length; i++) { sum += parseInt(character.charAt(i)); } - let index = String(sum).substr(-1); + const index = String(sum).substr(-1); return (index % numRandomColors) + 1; } else { @@ -682,7 +682,7 @@ import 'programStyles'; for (let i = 0; i < lines.length; i++) { let currentCssClass = cssClass; - let text = lines[i]; + const text = lines[i]; if (valid > 0 && isOuterFooter) { currentCssClass += ' cardText-secondary'; @@ -707,7 +707,7 @@ import 'programStyles'; } if (forceLines) { - let linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); + const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); while (valid < linesLength) { html += "
 
"; @@ -1036,7 +1036,7 @@ import 'programStyles'; * @returns {string} HTML markup for the item count indicator. */ function getItemCountsHtml(options, item) { - let counts = []; + const counts = []; let childText; if (item.Type === 'Playlist') { @@ -1318,7 +1318,7 @@ import 'programStyles'; let cardBoxClose = ''; let cardScalableClose = ''; - let cardContentClass = 'cardContent'; + const cardContentClass = 'cardContent'; let blurhashAttrib = ''; if (blurhash && blurhash.length > 0) { @@ -1337,7 +1337,7 @@ import 'programStyles'; cardImageContainerClose = ''; } - let cardScalableClass = 'cardScalable'; + const cardScalableClass = 'cardScalable'; cardImageContainerOpen = '
' + cardImageContainerOpen; cardBoxClose = '
'; @@ -1681,7 +1681,7 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); for (let i = 0, length = cells.length; i < length; i++) { - let cell = cells[i]; + const cell = cells[i]; const icon = cell.querySelector('.timerIndicator'); if (!icon) { const indicatorsElem = ensureIndicators(cell); @@ -1700,8 +1700,8 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); for (let i = 0; i < cells.length; i++) { - let cell = cells[i]; - let icon = cell.querySelector('.timerIndicator'); + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); if (icon) { icon.parentNode.removeChild(icon); } @@ -1718,8 +1718,8 @@ import 'programStyles'; const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); for (let i = 0; i < cells.length; i++) { - let cell = cells[i]; - let icon = cell.querySelector('.timerIndicator'); + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); if (icon) { icon.parentNode.removeChild(icon); } diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index ae7647f98b..641faa7f3b 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -75,13 +75,13 @@ import 'emby-button'; context.querySelector('.languageSection').classList.add('hide'); } - if (appHost.supports('displaymode')) { + if (appHost.default.supports('displaymode')) { context.querySelector('.fldDisplayMode').classList.remove('hide'); } else { context.querySelector('.fldDisplayMode').classList.add('hide'); } - if (appHost.supports('externallinks')) { + if (appHost.default.supports('externallinks')) { context.querySelector('.learnHowToContributeContainer').classList.remove('hide'); } else { context.querySelector('.learnHowToContributeContainer').classList.add('hide'); @@ -136,7 +136,7 @@ import 'emby-button'; function saveUser(context, user, userSettingsInstance, apiClient) { user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; - if (appHost.supports('displaylanguage')) { + if (appHost.default.supports('displaylanguage')) { userSettingsInstance.language(context.querySelector('#selectLanguage').value); } diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index 2be2ef09b2..5141ff73e5 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -208,7 +208,7 @@ import 'cardStyle'; html += '
'; html += '
'; - if (layoutManager.tv || !appHost.supports('externallinks')) { + if (layoutManager.tv || !appHost.default.supports('externallinks')) { html += '
'; } else { html += ''; diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index 0effcc7a57..39dc6c2622 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -17,8 +17,8 @@ import 'css!./style'; // Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us, // improving the performance and reducing the memory usage, while retaining almost full blur quality. // Lower values had more visible pixelation - let width = 18; - let height = 18; + const width = 18; + const height = 18; let pixels; try { pixels = blurhash.decode(blurhashstr, width, height); @@ -27,11 +27,11 @@ import 'css!./style'; target.classList.add('non-blurhashable'); return; } - let canvas = document.createElement('canvas'); + const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; - let ctx = canvas.getContext('2d'); - let imgData = ctx.createImageData(width, height); + const ctx = canvas.getContext('2d'); + const imgData = ctx.createImageData(width, height); imgData.data.set(pixels); ctx.putImageData(imgData, 0, 0); @@ -55,7 +55,7 @@ import 'css!./style'; if (!entry) { throw new Error('entry cannot be null'); } - let target = entry.target; + const target = entry.target; var source = undefined; if (target) { @@ -78,7 +78,7 @@ import 'css!./style'; throw new TypeError('url cannot be undefined'); } - let preloaderImg = new Image(); + const preloaderImg = new Image(); preloaderImg.src = url; elem.classList.add('lazy-hidden'); diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index 604f480f1d..bbd672ef72 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -82,7 +82,7 @@ export function enablePlayedIndicator(item) { export function getPlayedIndicatorHtml(item) { if (enablePlayedIndicator(item)) { - let userData = item.UserData || {}; + const userData = item.UserData || {}; if (userData.UnplayedItemCount) { return '
' + userData.UnplayedItemCount + '
'; } diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 173383d064..3471057139 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -158,7 +158,7 @@ import actionsheet from 'actionsheet'; } // Books are promoted to major download Button and therefor excluded in the context menu - if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { + if ((item.CanDownload && appHost.default.supports('filedownload')) && item.Type !== 'Book') { commands.push({ name: globalize.translate('Download'), id: 'download', diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index d922aee84a..feba0ade5d 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -196,7 +196,7 @@ import 'css!./multiSelect'; }); } - if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { + if (user.Policy.EnableContentDownloading && appHost.default.supports('filedownload')) { menuItems.push({ name: globalize.translate('ButtonDownload'), id: 'download', diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index d411dcc620..aeba8fedf9 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -415,7 +415,7 @@ import 'emby-ratingbutton'; showVolumeSlider = false; } - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (currentPlayer.isLocalPlayer && appHost.default.supports('physicalvolumecontrol')) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 0f275c88f7..5c7ddf45dd 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -127,7 +127,7 @@ import connectionManager from 'connectionManager'; artwork: getImageUrls(item) }); } else { - let itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); + const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); window.NativeShell.updateMediaSession({ action: eventName, @@ -244,10 +244,10 @@ import connectionManager from 'connectionManager'; /* eslint-disable-next-line compat/compat */ navigator.mediaSession.setActionHandler('seekto', function (object) { - let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; // Convert to ms - let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); - let wantedTime = object.seekTime * 1000; + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + const wantedTime = object.seekTime * 1000; playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); }); } diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 437127be18..ed06e3eef3 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -3520,7 +3520,7 @@ class PlaybackManager { 'PlayTrailers' ]; - if (apphost.supports('fullscreenchange')) { + if (appHost.default.supports('fullscreenchange')) { list.push('ToggleFullscreen'); } diff --git a/src/components/playback/playerSelectionMenu.js b/src/components/playback/playerSelectionMenu.js index 7799613400..878fccb6da 100644 --- a/src/components/playback/playerSelectionMenu.js +++ b/src/components/playback/playerSelectionMenu.js @@ -121,7 +121,7 @@ export function show(button) { // Unfortunately we can't allow the url to change or chromecast will throw a security error // Might be able to solve this in the future by moving the dialogs to hashbangs - if (!(!browser.chrome && !browser.edgeChromium || appHost.supports('castmenuhashchange'))) { + if (!(!browser.chrome && !browser.edgeChromium || appHost.default.supports('castmenuhashchange'))) { menuOptions.enableHistory = false; } diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 66e1ae777f..14dc742dfd 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -98,7 +98,7 @@ import 'emby-checkbox'; context.querySelector('.videoQualitySection').classList.add('hide'); } - if (appHost.supports('multiserver')) { + if (appHost.default.supports('multiserver')) { context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); @@ -162,7 +162,7 @@ import 'emby-checkbox'; } }); - if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { + if (appHost.default.supports('externalplayerintent') && userId === loggedInUserId) { context.querySelector('.fldExternalPlayer').classList.remove('hide'); } else { context.querySelector('.fldExternalPlayer').classList.add('hide'); @@ -171,7 +171,7 @@ import 'emby-checkbox'; if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { context.querySelector('.qualitySections').classList.remove('hide'); - if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { + if (appHost.default.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { context.querySelector('.fldChromecastQuality').classList.remove('hide'); } else { context.querySelector('.fldChromecastQuality').classList.add('hide'); diff --git a/src/components/qualityOptions.js b/src/components/qualityOptions.js index 10a5df39ba..d6bced83f7 100644 --- a/src/components/qualityOptions.js +++ b/src/components/qualityOptions.js @@ -1,160 +1,165 @@ -define(['globalize'], function (globalize) { - 'use strict'; +import globalize from 'globalize'; - function getVideoQualityOptions(options) { - var maxStreamingBitrate = options.currentMaxBitrate; - var videoWidth = options.videoWidth; - var videoHeight = options.videoHeight; +export function getVideoQualityOptions(options) { - // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. - // 4:3 1440x1080 -> 1920x1080 - if (videoWidth / videoHeight < 16 / 9) { - videoWidth = videoHeight * (16 / 9); - } + var maxStreamingBitrate = options.currentMaxBitrate; + var videoWidth = options.videoWidth; + var videoHeight = options.videoHeight; - var maxAllowedWidth = videoWidth || 4096; - - var qualityOptions = []; - - if (maxAllowedWidth >= 3800) { - qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); - qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); - qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); - } - - // Some 1080- videos are reported as 1912? - if (maxAllowedWidth >= 1900) { - qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); - qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); - qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); - qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); - qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); - qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); - qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); - qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); - qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); - qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); - qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); - qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); - } else if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); - qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); - qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); - qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); - } else if (maxAllowedWidth >= 620) { - qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); - qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); - qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); - qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); - qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); - } - - if (maxAllowedWidth >= 1260) { - qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); - qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); - qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); - - // The extra 1 is because they're keyed off the bitrate value - qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); - qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); - } - - qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); - qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); - qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); - qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); - qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); - qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + // If the aspect ratio is less than 16/9 (1.77), set the width as if it were pillarboxed. + // 4:3 1440x1080 -> 1920x1080 + if (videoWidth / videoHeight < 16 / 9) { + videoWidth = videoHeight * (16 / 9); } - function getAudioQualityOptions(options) { - var maxStreamingBitrate = options.currentMaxBitrate; + var maxAllowedWidth = videoWidth || 4096; + //var maxAllowedHeight = videoHeight || 2304; - var qualityOptions = []; + var qualityOptions = []; - qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); - qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); - qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); - qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); - qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); - qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); - qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); - qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); - qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); - - var autoQualityOption = { - name: globalize.translate('Auto'), - bitrate: 0, - selected: options.isAutomaticBitrateEnabled - }; - - if (options.enableAuto) { - qualityOptions.push(autoQualityOption); - } - - if (maxStreamingBitrate) { - var selectedIndex = -1; - for (var i = 0, length = qualityOptions.length; i < length; i++) { - var option = qualityOptions[i]; - - if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { - selectedIndex = i; - } - } - - if (selectedIndex === -1) { - selectedIndex = qualityOptions.length - 1; - } - - var currentQualityOption = qualityOptions[selectedIndex]; - - if (!options.isAutomaticBitrateEnabled) { - currentQualityOption.selected = true; - } else { - autoQualityOption.autoText = currentQualityOption.name; - } - } - - return qualityOptions; + if (maxAllowedWidth >= 3800) { + qualityOptions.push({ name: '4K - 120 Mbps', maxHeight: 2160, bitrate: 120000000 }); + qualityOptions.push({ name: '4K - 100 Mbps', maxHeight: 2160, bitrate: 100000000 }); + qualityOptions.push({ name: '4K - 80 Mbps', maxHeight: 2160, bitrate: 80000000 }); } - return { - getVideoQualityOptions: getVideoQualityOptions, - getAudioQualityOptions: getAudioQualityOptions + // Some 1080- videos are reported as 1912? + if (maxAllowedWidth >= 1900) { + + qualityOptions.push({ name: '1080p - 60 Mbps', maxHeight: 1080, bitrate: 60000000 }); + qualityOptions.push({ name: '1080p - 50 Mbps', maxHeight: 1080, bitrate: 50000000 }); + qualityOptions.push({ name: '1080p - 40 Mbps', maxHeight: 1080, bitrate: 40000000 }); + qualityOptions.push({ name: '1080p - 30 Mbps', maxHeight: 1080, bitrate: 30000000 }); + qualityOptions.push({ name: '1080p - 25 Mbps', maxHeight: 1080, bitrate: 25000000 }); + qualityOptions.push({ name: '1080p - 20 Mbps', maxHeight: 1080, bitrate: 20000000 }); + qualityOptions.push({ name: '1080p - 15 Mbps', maxHeight: 1080, bitrate: 15000000 }); + qualityOptions.push({ name: '1080p - 10 Mbps', maxHeight: 1080, bitrate: 10000001 }); + qualityOptions.push({ name: '1080p - 8 Mbps', maxHeight: 1080, bitrate: 8000001 }); + qualityOptions.push({ name: '1080p - 6 Mbps', maxHeight: 1080, bitrate: 6000001 }); + qualityOptions.push({ name: '1080p - 5 Mbps', maxHeight: 1080, bitrate: 5000001 }); + qualityOptions.push({ name: '1080p - 4 Mbps', maxHeight: 1080, bitrate: 4000002 }); + + } else if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 10 Mbps', maxHeight: 720, bitrate: 10000000 }); + qualityOptions.push({ name: '720p - 8 Mbps', maxHeight: 720, bitrate: 8000000 }); + qualityOptions.push({ name: '720p - 6 Mbps', maxHeight: 720, bitrate: 6000000 }); + qualityOptions.push({ name: '720p - 5 Mbps', maxHeight: 720, bitrate: 5000000 }); + + } else if (maxAllowedWidth >= 620) { + qualityOptions.push({ name: '480p - 4 Mbps', maxHeight: 480, bitrate: 4000001 }); + qualityOptions.push({ name: '480p - 3 Mbps', maxHeight: 480, bitrate: 3000001 }); + qualityOptions.push({ name: '480p - 2.5 Mbps', maxHeight: 480, bitrate: 2500000 }); + qualityOptions.push({ name: '480p - 2 Mbps', maxHeight: 480, bitrate: 2000001 }); + qualityOptions.push({ name: '480p - 1.5 Mbps', maxHeight: 480, bitrate: 1500001 }); + } + + if (maxAllowedWidth >= 1260) { + qualityOptions.push({ name: '720p - 4 Mbps', maxHeight: 720, bitrate: 4000000 }); + qualityOptions.push({ name: '720p - 3 Mbps', maxHeight: 720, bitrate: 3000000 }); + qualityOptions.push({ name: '720p - 2 Mbps', maxHeight: 720, bitrate: 2000000 }); + + // The extra 1 is because they're keyed off the bitrate value + qualityOptions.push({ name: '720p - 1.5 Mbps', maxHeight: 720, bitrate: 1500000 }); + qualityOptions.push({ name: '720p - 1 Mbps', maxHeight: 720, bitrate: 1000001 }); + } + + qualityOptions.push({ name: '480p - 1 Mbps', maxHeight: 480, bitrate: 1000000 }); + qualityOptions.push({ name: '480p - 720 kbps', maxHeight: 480, bitrate: 720000 }); + qualityOptions.push({ name: '480p - 420 kbps', maxHeight: 480, bitrate: 420000 }); + qualityOptions.push({ name: '360p', maxHeight: 360, bitrate: 400000 }); + qualityOptions.push({ name: '240p', maxHeight: 240, bitrate: 320000 }); + qualityOptions.push({ name: '144p', maxHeight: 144, bitrate: 192000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled }; -}); + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export function getAudioQualityOptions(options) { + var maxStreamingBitrate = options.currentMaxBitrate; + + var qualityOptions = []; + + qualityOptions.push({ name: '2 Mbps', bitrate: 2000000 }); + qualityOptions.push({ name: '1.5 Mbps', bitrate: 1500000 }); + qualityOptions.push({ name: '1 Mbps', bitrate: 1000000 }); + qualityOptions.push({ name: '320 kbps', bitrate: 320000 }); + qualityOptions.push({ name: '256 kbps', bitrate: 256000 }); + qualityOptions.push({ name: '192 kbps', bitrate: 192000 }); + qualityOptions.push({ name: '128 kbps', bitrate: 128000 }); + qualityOptions.push({ name: '96 kbps', bitrate: 96000 }); + qualityOptions.push({ name: '64 kbps', bitrate: 64000 }); + + var autoQualityOption = { + name: globalize.translate('Auto'), + bitrate: 0, + selected: options.isAutomaticBitrateEnabled + }; + + if (options.enableAuto) { + qualityOptions.push(autoQualityOption); + } + + if (maxStreamingBitrate) { + var selectedIndex = -1; + for (var i = 0, length = qualityOptions.length; i < length; i++) { + var option = qualityOptions[i]; + + if (selectedIndex === -1 && option.bitrate <= maxStreamingBitrate) { + selectedIndex = i; + } + } + + if (selectedIndex === -1) { + + selectedIndex = qualityOptions.length - 1; + } + + var currentQualityOption = qualityOptions[selectedIndex]; + + if (!options.isAutomaticBitrateEnabled) { + currentQualityOption.selected = true; + } else { + autoQualityOption.autoText = currentQualityOption.name; + } + } + + return qualityOptions; +} + +export default { + getVideoQualityOptions, + getAudioQualityOptions +}; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index b5ac4c9a8b..8668202424 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -397,7 +397,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL showVolumeSlider = false; } - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (currentPlayer.isLocalPlayer && appHost.default.supports('physicalvolumecontrol')) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 4ee31a6275..549cb9445c 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -251,7 +251,7 @@ import layoutManager from 'layoutManager'; * @return {ScrollerData} Scroller data. */ function getScrollerData(scroller, vertical) { - let data = {}; + const data = {}; if (!vertical) { data.scrollPos = scroller.scrollLeft; diff --git a/src/components/skinManager.js b/src/components/skinManager.js new file mode 100644 index 0000000000..e157854850 --- /dev/null +++ b/src/components/skinManager.js @@ -0,0 +1,184 @@ +define(['apphost', 'userSettings', 'browser', 'events', 'backdrop', 'globalize', 'require', 'appSettings'], function (appHost, userSettings, browser, events, backdrop, globalize, require, appSettings) { + 'use strict'; + + var themeStyleElement; + var currentThemeId; + + function unloadTheme() { + var elem = themeStyleElement; + if (elem) { + elem.parentNode.removeChild(elem); + themeStyleElement = null; + currentThemeId = null; + } + } + + function loadUserSkin(options) { + options = options || {}; + if (options.start) { + Emby.Page.invokeShortcut(options.start); + } else { + Emby.Page.goHome(); + } + } + + function getThemes() { + return [{ + name: 'Apple TV', + id: 'appletv' + }, { + name: 'Blue Radiance', + id: 'blueradiance' + }, { + name: 'Dark', + id: 'dark', + isDefault: true, + isDefaultServerDashboard: true + }, { + name: 'Light', + id: 'light' + }, { + name: 'Purple Haze', + id: 'purplehaze' + }, { + name: 'Windows Media Center', + id: 'wmc' + }]; + } + + var skinManager = { + getThemes: getThemes, + loadUserSkin: loadUserSkin + }; + + function getThemeStylesheetInfo(id, isDefaultProperty) { + var themes = skinManager.getThemes(); + var defaultTheme; + var selectedTheme; + + for (var i = 0, length = themes.length; i < length; i++) { + var theme = themes[i]; + if (theme[isDefaultProperty]) { + defaultTheme = theme; + } + if (id === theme.id) { + selectedTheme = theme; + } + } + + selectedTheme = selectedTheme || defaultTheme; + return { + stylesheetPath: require.toUrl('themes/' + selectedTheme.id + '/theme.css'), + themeId: selectedTheme.id + }; + } + + var themeResources = {}; + var lastSound = 0; + var currentSound; + + function loadThemeResources(id) { + lastSound = 0; + if (currentSound) { + currentSound.stop(); + currentSound = null; + } + + backdrop.clearBackdrop(); + } + + function onThemeLoaded() { + document.documentElement.classList.remove('preload'); + try { + var color = getComputedStyle(document.querySelector('.skinHeader')).getPropertyValue('background-color'); + if (color) { + appHost.default.setThemeColor(color); + } + } catch (err) { + console.error('error setting theme color: ' + err); + } + } + + skinManager.setTheme = function (id, context) { + return new Promise(function (resolve, reject) { + if (currentThemeId && currentThemeId === id) { + resolve(); + return; + } + + var isDefaultProperty = context === 'serverdashboard' ? 'isDefaultServerDashboard' : 'isDefault'; + var info = getThemeStylesheetInfo(id, isDefaultProperty); + if (currentThemeId && currentThemeId === info.themeId) { + resolve(); + return; + } + + var linkUrl = info.stylesheetPath; + unloadTheme(); + + var link = document.createElement('link'); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('type', 'text/css'); + link.onload = function () { + onThemeLoaded(); + resolve(); + }; + + link.setAttribute('href', linkUrl); + document.head.appendChild(link); + themeStyleElement = link; + currentThemeId = info.themeId; + loadThemeResources(info.themeId); + + onViewBeforeShow({}); + }); + }; + + function onViewBeforeShow(e) { + if (e.detail && e.detail.type === 'video-osd') { + // This removes the space that the scrollbar takes while playing a video + document.body.classList.remove('force-scroll'); + return; + } + + if (themeResources.backdrop) { + backdrop.setBackdrop(themeResources.backdrop); + } + + if (!browser.mobile && userSettings.enableThemeSongs()) { + if (lastSound === 0) { + if (themeResources.themeSong) { + playSound(themeResources.themeSong); + } + } else if ((new Date().getTime() - lastSound) > 30000) { + if (themeResources.effect) { + playSound(themeResources.effect); + } + } + } + // This keeps the scrollbar always present in all pages, so we avoid clipping while switching between pages + // that need the scrollbar and pages that don't. + document.body.classList.add('force-scroll'); + } + + document.addEventListener('viewshow', onViewBeforeShow); + + function playSound(path, volume) { + lastSound = new Date().getTime(); + require(['howler'], function (howler) { + /* globals Howl */ + try { + var sound = new Howl({ + src: [path], + volume: volume || 0.1 + }); + sound.play(); + currentSound = sound; + } catch (err) { + console.error('error playing sound: ' + err); + } + }); + } + + return skinManager; +}); diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 38728ec6c6..f76adbd6c5 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -113,7 +113,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f */ function setUserScalable(scalable) { try { - appHost.setUserScalable(scalable); + appHost.default.setUserScalable(scalable); } catch (err) { console.error('error in appHost.setUserScalable: ' + err); } @@ -162,10 +162,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f html += '
'; if (actionButtonsOnTop) { - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + if (appHost.default.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } - if (appHost.supports('sharing')) { + if (appHost.default.supports('sharing')) { html += getIcon('share', 'btnShare slideshowButton', true); } } @@ -176,10 +176,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f html += '
'; html += getIcon('play_arrow', 'btnSlideshowPause slideshowButton', true, true); - if (appHost.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { + if (appHost.default.supports('filedownload') && options.user && options.user.Policy.EnableContentDownloading) { html += getIcon('file_download', 'btnDownload slideshowButton', true); } - if (appHost.supports('sharing')) { + if (appHost.default.supports('sharing')) { html += getIcon('share', 'btnShare slideshowButton', true); } diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 3087cdd927..25cbbac38a 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -105,7 +105,7 @@ export default function(view, params) { }); view.querySelector('.btnNewRepository').addEventListener('click', () => { - let dialog = dialogHelper.createDialog({ + const dialog = dialogHelper.createDialog({ scrollY: false, size: 'large', modal: false, diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index db7dac9547..34aee4e6a0 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -104,7 +104,6 @@ import 'flexStyles'; } var itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index a8bd0e01f3..2129061a35 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -898,7 +898,7 @@ import 'css!assets/css/videoosd'; showVolumeSlider = false; } - if (player.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + if (player.isLocalPlayer && appHost.default.supports('physicalvolumecontrol')) { showMuteButton = false; showVolumeSlider = false; } diff --git a/src/controllers/session/login/index.js b/src/controllers/session/login/index.js index 8bac557a20..c53c59f3b2 100644 --- a/src/controllers/session/login/index.js +++ b/src/controllers/session/login/index.js @@ -199,7 +199,7 @@ import 'emby-checkbox'; loading.show(); libraryMenu.setTransparentMenu(true); - if (!appHost.supports('multiserver')) { + if (!appHost.default.supports('multiserver')) { view.querySelector('.btnSelectServer').classList.add('hide'); } diff --git a/src/controllers/user/menu.js b/src/controllers/user/menu.js new file mode 100644 index 0000000000..bd6f4de8a2 --- /dev/null +++ b/src/controllers/user/menu.js @@ -0,0 +1,58 @@ +define(['apphost', 'connectionManager', 'layoutManager', 'listViewStyle', 'emby-button'], function(appHost, connectionManager, layoutManager) { + 'use strict'; + + return function(view, params) { + view.querySelector('.btnLogout').addEventListener('click', function() { + Dashboard.logout(); + }); + + view.querySelector('.selectServer').addEventListener('click', function () { + Dashboard.selectServer(); + }); + + view.querySelector('.clientSettings').addEventListener('click', function () { + window.NativeShell.openClientSettings(); + }); + + view.addEventListener('viewshow', function() { + // this page can also be used by admins to change user preferences from the user edit page + var userId = params.userId || Dashboard.getCurrentUserId(); + var page = this; + + page.querySelector('.lnkMyProfile').setAttribute('href', 'myprofile.html?userId=' + userId); + page.querySelector('.lnkDisplayPreferences').setAttribute('href', 'mypreferencesdisplay.html?userId=' + userId); + page.querySelector('.lnkHomePreferences').setAttribute('href', 'mypreferenceshome.html?userId=' + userId); + page.querySelector('.lnkPlaybackPreferences').setAttribute('href', 'mypreferencesplayback.html?userId=' + userId); + page.querySelector('.lnkSubtitlePreferences').setAttribute('href', 'mypreferencessubtitles.html?userId=' + userId); + + if (window.NativeShell && window.NativeShell.AppHost.supports('clientsettings')) { + page.querySelector('.clientSettings').classList.remove('hide'); + } else { + page.querySelector('.clientSettings').classList.add('hide'); + } + + if (appHost.default.supports('multiserver')) { + page.querySelector('.selectServer').classList.remove('hide'); + } else { + page.querySelector('.selectServer').classList.add('hide'); + } + + // hide the actions if user preferences are being edited for a different user + if (params.userId && params.userId !== Dashboard.getCurrentUserId) { + page.querySelector('.userSection').classList.add('hide'); + page.querySelector('.adminSection').classList.add('hide'); + } + + ApiClient.getUser(userId).then(function(user) { + page.querySelector('.headerUsername').innerHTML = user.Name; + if (!user.Policy.IsAdministrator) { + page.querySelector('.adminSection').classList.add('hide'); + } + }); + + require(['autoFocuser'], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); + }); + }; +}); diff --git a/src/controllers/user/profile.js b/src/controllers/user/profile.js new file mode 100644 index 0000000000..eb912418fd --- /dev/null +++ b/src/controllers/user/profile.js @@ -0,0 +1,106 @@ +define(['controllers/dashboard/users/userpasswordpage', 'loading', 'libraryMenu', 'apphost', 'globalize', 'emby-button'], function (UserPasswordPage, loading, libraryMenu, appHost, globalize) { + 'use strict'; + + function reloadUser(page) { + var userId = getParameterByName('userId'); + loading.show(); + ApiClient.getUser(userId).then(function (user) { + page.querySelector('.username').innerHTML = user.Name; + libraryMenu.setTitle(user.Name); + + var imageUrl = 'assets/img/avatar.png'; + if (user.PrimaryImageTag) { + imageUrl = ApiClient.getUserImageUrl(user.Id, { + tag: user.PrimaryImageTag, + type: 'Primary' + }); + } + + var userImage = page.querySelector('#image'); + userImage.style.backgroundImage = 'url(' + imageUrl + ')'; + + Dashboard.getCurrentUser().then(function (loggedInUser) { + if (user.PrimaryImageTag) { + page.querySelector('#btnAddImage').classList.add('hide'); + page.querySelector('#btnDeleteImage').classList.remove('hide'); + } else if (appHost.default.supports('fileinput') && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { + page.querySelector('#btnDeleteImage').classList.add('hide'); + page.querySelector('#btnAddImage').classList.remove('hide'); + } + }); + loading.hide(); + }); + } + + function onFileReaderError(evt) { + loading.hide(); + switch (evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + require(['toast'], function (toast) { + toast(globalize.translate('FileNotFound')); + }); + break; + case evt.target.error.ABORT_ERR: + onFileReaderAbort(); + break; + case evt.target.error.NOT_READABLE_ERR: + default: + require(['toast'], function (toast) { + toast(globalize.translate('FileReadError')); + }); + } + } + + function onFileReaderAbort(evt) { + loading.hide(); + require(['toast'], function (toast) { + toast(globalize.translate('FileReadCancelled')); + }); + } + + function setFiles(page, files) { + var userImage = page.querySelector('#image'); + var file = files[0]; + + if (!file || !file.type.match('image.*')) { + return false; + } + + var reader = new FileReader(); + reader.onerror = onFileReaderError; + reader.onabort = onFileReaderAbort; + reader.onload = function (evt) { + userImage.style.backgroundImage = 'url(' + evt.target.result + ')'; + var userId = getParameterByName('userId'); + ApiClient.uploadUserImage(userId, 'Primary', file).then(function () { + loading.hide(); + reloadUser(page); + }); + }; + + reader.readAsDataURL(file); + } + + return function (view, params) { + reloadUser(view); + new UserPasswordPage(view, params); + view.querySelector('#btnDeleteImage').addEventListener('click', function () { + require(['confirm'], function (confirm) { + confirm(globalize.translate('DeleteImageConfirmation'), globalize.translate('DeleteImage')).then(function () { + loading.show(); + var userId = getParameterByName('userId'); + ApiClient.deleteUserImage(userId, 'primary').then(function () { + loading.hide(); + reloadUser(view); + }); + }); + }); + }); + view.querySelector('#btnAddImage').addEventListener('click', function (evt) { + view.querySelector('#uploadImage').click(); + }); + view.querySelector('#uploadImage').addEventListener('change', function (evt) { + setFiles(view, evt.target.files); + }); + }; +}); diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index c167046cb9..afd7052e1d 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -27,16 +27,16 @@ export class BookPlayer { this._loaded = false; loading.show(); - let elem = this.createMediaElement(); + const elem = this.createMediaElement(); return this.setCurrentSrc(elem, options); } stop() { this.unbindEvents(); - let elem = this._mediaElement; - let tocElement = this._tocElement; - let rendition = this._rendition; + const elem = this._mediaElement; + const tocElement = this._tocElement; + const rendition = this._rendition; if (elem) { dialogHelper.close(elem); @@ -93,11 +93,9 @@ export class BookPlayer { } onWindowKeyUp(e) { - let key = keyboardnavigation.getKeyName(e); - - // TODO: depending on the event this can be the document or the rendition itself - let rendition = this._rendition || this; - let book = rendition.book; + const key = keyboardnavigation.getKeyName(e); + const rendition = this._rendition; + const book = rendition.book; if (this._loaded === false) return; switch (key) { @@ -147,7 +145,7 @@ export class BookPlayer { } bindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerExit').addEventListener('click', this.onDialogClosed, {once: true}); @@ -166,7 +164,7 @@ export class BookPlayer { } unbindMediaElementEvents() { - let elem = this._mediaElement; + const elem = this._mediaElement; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerExit').removeEventListener('click', this.onDialogClosed); @@ -231,7 +229,7 @@ export class BookPlayer { } setCurrentSrc(elem, options) { - let item = options.items[0]; + const item = options.items[0]; this._currentItem = item; this.streamInfo = { started: true, @@ -241,25 +239,25 @@ export class BookPlayer { } }; - let serverId = item.ServerId; - let apiClient = connectionManager.getApiClient(serverId); + const serverId = item.ServerId; + const apiClient = connectionManager.getApiClient(serverId); return new Promise((resolve, reject) => { import('epubjs').then(({default: epubjs}) => { - let downloadHref = apiClient.getItemDownloadUrl(item.Id); - let book = epubjs(downloadHref, {openAs: 'epub'}); - let rendition = book.renderTo(elem, {width: '100%', height: '97%'}); + const downloadHref = apiClient.getItemDownloadUrl(item.Id); + const book = epubjs(downloadHref, {openAs: 'epub'}); + const rendition = book.renderTo(elem, {width: '100%', height: '97%'}); this._currentSrc = downloadHref; this._rendition = rendition; - let cancellationToken = { + const cancellationToken = { shouldCancel: false }; this._cancellationToken = cancellationToken; return rendition.display().then(() => { - let epubElem = document.querySelector('.epub-container'); + const epubElem = document.querySelector('.epub-container'); epubElem.style.display = 'none'; this.bindEvents(); diff --git a/src/plugins/bookPlayer/tableOfContents.js b/src/plugins/bookPlayer/tableOfContents.js index 23e288aff5..a1c5d8f220 100644 --- a/src/plugins/bookPlayer/tableOfContents.js +++ b/src/plugins/bookPlayer/tableOfContents.js @@ -11,7 +11,7 @@ export default class TableOfContents { } destroy() { - let elem = this._elem; + const elem = this._elem; if (elem) { this.unbindEvents(); dialogHelper.close(elem); @@ -21,14 +21,14 @@ export default class TableOfContents { } bindEvents() { - let elem = this._elem; + const elem = this._elem; elem.addEventListener('close', this.onDialogClosed, {once: true}); elem.querySelector('.btnBookplayerTocClose').addEventListener('click', this.onDialogClosed, {once: true}); } unbindEvents() { - let elem = this._elem; + const elem = this._elem; elem.removeEventListener('close', this.onDialogClosed); elem.querySelector('.btnBookplayerTocClose').removeEventListener('click', this.onDialogClosed); @@ -39,10 +39,10 @@ export default class TableOfContents { } replaceLinks(contents, f) { - let links = contents.querySelectorAll('a[href]'); + const links = contents.querySelectorAll('a[href]'); links.forEach((link) => { - let href = link.getAttribute('href'); + const href = link.getAttribute('href'); link.onclick = () => { f(href); @@ -52,9 +52,9 @@ export default class TableOfContents { } createMediaElement() { - let rendition = this._rendition; + const rendition = this._rendition; - let elem = dialogHelper.createDialog({ + const elem = dialogHelper.createDialog({ size: 'small', autoFocus: false, removeOnClose: true @@ -69,7 +69,7 @@ export default class TableOfContents { rendition.book.navigation.forEach((chapter) => { tocHtml += '
  • '; // Remove '../' from href - let link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; + const link = chapter.href.startsWith('../') ? chapter.href.substr(3) : chapter.href; tocHtml += `${chapter.label}`; tocHtml += '
  • '; }); @@ -78,7 +78,7 @@ export default class TableOfContents { elem.innerHTML = tocHtml; this.replaceLinks(elem, (href) => { - let relative = rendition.book.path.relative(href); + const relative = rendition.book.path.relative(href); rendition.display(relative); this.destroy(); }); diff --git a/src/plugins/experimentalWarnings/plugin.js b/src/plugins/experimentalWarnings/plugin.js index c39612d45b..d97bd9a1a7 100644 --- a/src/plugins/experimentalWarnings/plugin.js +++ b/src/plugins/experimentalWarnings/plugin.js @@ -12,7 +12,7 @@ define(['connectionManager', 'globalize', 'userSettings', 'apphost'], function ( } function showMessage(text, userSettingsKey, appHostFeature) { - if (appHost.supports(appHostFeature)) { + if (appHost.default.supports(appHostFeature)) { return Promise.resolve(); } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 16fce8c9d1..f550e3dd0f 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -334,8 +334,8 @@ define(['events', 'browser', 'require', 'apphost', 'appSettings', 'htmlMediaHelp }; HtmlAudioPlayer.prototype.getDeviceProfile = function (item) { - if (appHost.getDeviceProfile) { - return appHost.getDeviceProfile(item); + if (appHost.default.getDeviceProfile) { + return appHost.default.getDeviceProfile(item); } return getDefaultProfile(); diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index d52f0eb5b3..40d8e57c45 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -105,7 +105,7 @@ function tryRemoveElement(elem) { } function hidePrePlaybackPage() { - let animatedPage = document.querySelector('.page:not(.hide)'); + const animatedPage = document.querySelector('.page:not(.hide)'); animatedPage.classList.add('hide'); // At this point, we must hide the scrollbar placeholder, so it's not being displayed while the item is being loaded document.body.classList.remove('force-scroll'); @@ -1288,7 +1288,7 @@ function tryRemoveElement(elem) { } let html = ''; - let cssClass = 'htmlvideoplayer'; + const cssClass = 'htmlvideoplayer'; // Can't autoplay in these browsers so we need to use the full controls, at least until playback starts if (!appHost.supports('htmlvideoautoplay')) { diff --git a/src/scripts/apploader.js b/src/scripts/apploader.js index 183b765d16..e5f85e1119 100644 --- a/src/scripts/apploader.js +++ b/src/scripts/apploader.js @@ -1,52 +1,48 @@ -(function() { - 'use strict'; +function injectScriptElement(src, onload) { + if (!src) { + return; + } - function injectScriptElement(src, onload) { - if (!src) { - return; + const script = document.createElement('script'); + if (self.dashboardVersion) { + src += `?v=${self.dashboardVersion}`; + } + script.src = src; + script.setAttribute('async', ''); + + if (onload) { + script.onload = onload; + } + + document.head.appendChild(script); +} + +function loadSite() { + injectScriptElement( + './libraries/alameda.js', + function() { + // onload of require library + injectScriptElement('./scripts/site.js'); } + ); +} - var script = document.createElement('script'); - if (self.dashboardVersion) { - src += `?v=${self.dashboardVersion}`; - } - script.src = src; - script.setAttribute('async', ''); +try { + Promise.resolve(); +} catch (ex) { + // this checks for several cases actually, typical is + // Promise() being missing on some legacy browser, and a funky one + // is Promise() present but buggy on WebOS 2 + window.Promise = undefined; + self.Promise = undefined; +} - if (onload) { - script.onload = onload; - } - - document.head.appendChild(script); - } - - function loadSite() { - injectScriptElement( - './libraries/alameda.js', - function() { - // onload of require library - injectScriptElement('./scripts/site.js'); - } - ); - } - - try { - Promise.resolve(); - } catch (ex) { - // this checks for several cases actually, typical is - // Promise() being missing on some legacy browser, and a funky one - // is Promise() present but buggy on WebOS 2 - window.Promise = undefined; - self.Promise = undefined; - } - - if (!self.Promise) { - // Load Promise polyfill if they are not natively supported - injectScriptElement( - './libraries/npo.js', - loadSite - ); - } else { - loadSite(); - } -})(); +if (!self.Promise) { + // Load Promise polyfill if they are not natively supported + injectScriptElement( + './libraries/npo.js', + loadSite + ); +} else { + loadSite(); +} diff --git a/src/scripts/deleteHelper.js b/src/scripts/deleteHelper.js index e10a1568c7..44876c75b3 100644 --- a/src/scripts/deleteHelper.js +++ b/src/scripts/deleteHelper.js @@ -15,7 +15,7 @@ export function deleteItem(options) { const item = options.item; const parentId = item.SeasonId || item.SeriesId || item.ParentId; - let apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); return confirm({ @@ -34,7 +34,7 @@ export function deleteItem(options) { } } }, function (err) { - let result = function () { + const result = function () { return Promise.reject(err); }; diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js index 870429ee09..20c01bdcba 100644 --- a/src/scripts/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -187,7 +187,7 @@ require(['apphost'], function (appHost) { return false; } - if (appHost.getWindowState() === 'Minimized') { + if (appHost.default.getWindowState() === 'Minimized') { return false; } diff --git a/src/scripts/inputManager.js b/src/scripts/inputManager.js index 3432c9e351..9225d07f0a 100644 --- a/src/scripts/inputManager.js +++ b/src/scripts/inputManager.js @@ -39,7 +39,7 @@ import appHost from 'apphost'; dom.removeEventListener(scope, 'command', fn, {}); } - let commandTimes = {}; + const commandTimes = {}; function checkCommandTime(command) { const last = commandTimes[command] || 0; @@ -113,7 +113,7 @@ import appHost from 'apphost'; 'back': () => { if (appRouter.canGoBack()) { appRouter.back(); - } else if (appHost.supports('exit')) { + } else if (appHost.default.supports('exit')) { appHost.exit(); } }, diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index bbe01276ba..e7ce344301 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -277,7 +277,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' html += globalize.translate('HeaderUser'); html += ''; - if (appHost.supports('multiserver')) { + if (appHost.default.supports('multiserver')) { html += '' + globalize.translate('ButtonSelectServer') + ''; } @@ -589,7 +589,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', ' showBySelector('.lnkSyncToOtherDevices', false); } - if (user.Policy.EnableContentDownloading && appHost.supports('sync')) { + if (user.Policy.EnableContentDownloading && appHost.default.supports('sync')) { showBySelector('.libraryMenuDownloads', true); } else { showBySelector('.libraryMenuDownloads', false); diff --git a/src/scripts/site.js b/src/scripts/site.js index cd85c35e83..0879c11344 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -200,8 +200,8 @@ var Dashboard = { SupportsPersistentIdentifier: self.appMode === 'cordova' || self.appMode === 'android', SupportsMediaControl: true }; - appHost.getPushTokenInfo(); - return capabilities = Object.assign(capabilities, appHost.getPushTokenInfo()); + appHost.default.getPushTokenInfo(); + return capabilities = Object.assign(capabilities, appHost.default.getPushTokenInfo()); }, selectServer: function () { if (window.NativeShell && typeof window.NativeShell.selectServer === 'function') { @@ -271,17 +271,17 @@ function initClient() { } function createConnectionManager() { - return require(['connectionManagerFactory', 'apphost', 'credentialprovider', 'events', 'userSettings'], function (ConnectionManager, apphost, credentialProvider, events, userSettings) { + return require(['connectionManagerFactory', 'apphost', 'credentialprovider', 'events', 'userSettings'], function (ConnectionManager, appHost, credentialProvider, events, userSettings) { var credentialProviderInstance = new credentialProvider(); - var promises = [apphost.getSyncProfile(), apphost.init()]; + var promises = [appHost.default.getSyncProfile(), appHost.default.init()]; return Promise.all(promises).then(function (responses) { var deviceProfile = responses[0]; - var capabilities = Dashboard.capabilities(apphost); + var capabilities = Dashboard.capabilities(appHost); capabilities.DeviceProfile = deviceProfile; - var connectionManager = new ConnectionManager(credentialProviderInstance, apphost.appName(), apphost.appVersion(), apphost.deviceName(), apphost.deviceId(), capabilities); + var connectionManager = new ConnectionManager(credentialProviderInstance, appHost.default.appName(), appHost.default.appVersion(), appHost.default.deviceName(), appHost.default.deviceId(), capabilities); defineConnectionManager(connectionManager); bindConnectionManagerEvents(connectionManager, events, userSettings); @@ -292,7 +292,7 @@ function initClient() { return require(['apiclient'], function (apiClientFactory) { console.debug('creating ApiClient singleton'); - var apiClient = new apiClientFactory(Dashboard.serverAddress(), apphost.appName(), apphost.appVersion(), apphost.deviceName(), apphost.deviceId()); + var apiClient = new apiClientFactory(Dashboard.serverAddress(), appHost.default.appName(), appHost.default.appVersion(), appHost.default.deviceName(), appHost.default.deviceId()); apiClient.enableAutomaticNetworking = false; apiClient.manualAddressOnly = true; @@ -350,8 +350,8 @@ function initClient() { } function getLayoutManager(layoutManager, appHost) { - if (appHost.getDefaultLayout) { - layoutManager.defaultLayout = appHost.getDefaultLayout(); + if (appHost.default.getDefaultLayout) { + layoutManager.defaultLayout = appHost.default.getDefaultLayout(); } layoutManager.init(); @@ -538,13 +538,13 @@ function initClient() { require(['components/nowPlayingBar/nowPlayingBar']); } - if (appHost.supports('remotecontrol')) { + if (appHost.default.supports('remotecontrol')) { require(['playerSelectionMenu', 'components/playback/remotecontrolautoplay']); } require(['libraries/screensavermanager']); - if (!appHost.supports('physicalvolumecontrol') || browser.touch) { + if (!appHost.default.supports('physicalvolumecontrol') || browser.touch) { require(['components/playback/volumeosd']); }