diff --git a/.editorconfig b/.editorconfig index 92cf9dc590..84ba694073 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,5 @@ trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf -[json] +[*.json] indent_size = 2 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 73f40aaca1..c51cd6b31f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -36,6 +36,7 @@ - [MrTimscampi](https://github.com/MrTimscampi) - [Sarab Singh](https://github.com/sarab97) - [Andrei Oanca](https://github.com/OancaAndrei) + - [Cromefire_](https://github.com/cromefire) # Emby Contributors diff --git a/package.json b/package.json index 60a0ef92d4..3b966e3cdd 100644 --- a/package.json +++ b/package.json @@ -5,18 +5,18 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.10.5", + "@babel/core": "^7.11.0", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.10.5", "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.10.3", - "autoprefixer": "^9.8.5", + "@babel/preset-env": "^7.11.0", + "autoprefixer": "^9.8.6", "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", "browser-sync": "^2.26.12", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^4.0.0", + "css-loader": "^4.1.1", "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", @@ -48,7 +48,7 @@ "stylelint-config-rational-order": "^0.1.2", "stylelint-no-browser-hacks": "^1.2.1", "stylelint-order": "^4.1.0", - "webpack": "^4.44.0", + "webpack": "^4.44.1", "webpack-merge": "^4.2.2", "webpack-stream": "^5.2.1" }, @@ -62,7 +62,7 @@ "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", - "hls.js": "^0.14.6", + "hls.js": "^0.14.7", "howler": "^2.2.0", "intersection-observer": "^0.11.0", "jellyfin-apiclient": "^1.4.1", @@ -141,6 +141,7 @@ "src/components/playback/playerSelectionMenu.js", "src/components/playback/playersettingsmenu.js", "src/components/playback/playmethodhelper.js", + "src/components/playback/playqueuemanager.js", "src/components/playback/remotecontrolautoplay.js", "src/components/playback/volumeosd.js", "src/components/playbackSettings/playbackSettings.js", @@ -148,8 +149,10 @@ "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", "src/components/prompt/prompt.js", + "src/components/refreshdialog/refreshdialog.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/plugins/htmlVideoPlayer/plugin.js", "src/components/search/searchfields.js", "src/components/search/searchresults.js", "src/components/settingshelper.js", @@ -176,6 +179,12 @@ "src/controllers/dashboard/general.js", "src/controllers/dashboard/librarydisplay.js", "src/controllers/dashboard/logs.js", + "src/controllers/music/musicalbums.js", + "src/controllers/music/musicartists.js", + "src/controllers/music/musicgenres.js", + "src/controllers/music/musicplaylists.js", + "src/controllers/music/musicrecommended.js", + "src/controllers/music/songs.js", "src/controllers/dashboard/mediaLibrary.js", "src/controllers/dashboard/metadataImages.js", "src/controllers/dashboard/metadatanfo.js", @@ -195,6 +204,7 @@ "src/controllers/playback/queue/index.js", "src/controllers/playback/video/index.js", "src/controllers/searchpage.js", + "src/controllers/livetvtuner.js", "src/controllers/shows/episodes.js", "src/controllers/shows/tvgenres.js", "src/controllers/shows/tvlatest.js", @@ -208,7 +218,6 @@ "src/controllers/user/playback/index.js", "src/controllers/user/profile/index.js", "src/controllers/user/subtitles/index.js", - "src/controllers/user/subtitles/index.js", "src/controllers/wizard/finish/index.js", "src/controllers/wizard/remote/index.js", "src/controllers/wizard/settings/index.js", diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 61815a590f..047ae0a1c6 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -646,7 +646,7 @@ .layout-desktop .detailRibbon, .layout-tv .detailRibbon { margin-top: -7.2em; - height: 7.18em; + height: 7.2em; } .layout-desktop .noBackdrop .detailRibbon, diff --git a/src/components/appRouter.js b/src/components/appRouter.js index e4c53fbbb8..7f4976424c 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -231,8 +231,7 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdro max /= 8; max *= 1000000; max *= 0.7; - max = parseInt(max); - return max; + return parseInt(max, 10); } } /* eslint-enable compat/compat */ diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 0215f8d8e5..1521650ed0 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -104,9 +104,7 @@ import browser from 'browser'; const cardBoxCssClass = 'cardBox'; const cardScalableClass = 'cardScalable'; - const html = ``; - - return html; + return ``; } export function buildChapterCards(item, chapters, options) { diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 85d5954d18..3c6394db79 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2,6 +2,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla 'use strict'; loading = loading.default || loading; + PlayQueueManager = PlayQueueManager.default || PlayQueueManager; function enableLocalPlaylistManagement(player) { if (player.getPlaylist) { @@ -20,6 +21,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla screenfull.on('change', function () { events.trigger(player, 'fullscreenchange'); }); + } else { + // iOS Safari + document.addEventListener('webkitfullscreenchange', function () { + events.trigger(player, 'fullscreenchange'); + }, false); } } @@ -884,9 +890,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla } } - targets = targets.sort(sortPlayerTargets); - - return targets; + return targets.sort(sortPlayerTargets); }); }); }; @@ -1401,6 +1405,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return player.isFullscreen(); } + if (!screenfull.isEnabled) { + // iOS Safari + return document.webkitIsFullScreen; + } + return screenfull.isFullscreen; }; @@ -1412,6 +1421,16 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla if (screenfull.isEnabled) { screenfull.toggle(); + } else { + // iOS Safari + if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { + document.webkitCancelFullscreen(); + } else { + const elem = document.querySelector('video'); + if (elem && elem.webkitEnterFullscreen) { + elem.webkitEnterFullscreen(); + } + } } }; @@ -3372,8 +3391,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla PlaybackManager.prototype.getSubtitleUrl = function (textStream, serverId) { var apiClient = connectionManager.getApiClient(serverId); - var textStreamUrl = !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl; - return textStreamUrl; + + return !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl; }; PlaybackManager.prototype.stop = function (player) { diff --git a/src/components/playback/playqueuemanager.js b/src/components/playback/playqueuemanager.js index 696075b344..fbe1ceabfe 100644 --- a/src/components/playback/playqueuemanager.js +++ b/src/components/playback/playqueuemanager.js @@ -1,56 +1,56 @@ -define([], function () { - 'use strict'; +/*eslint prefer-const: "error"*/ - var currentId = 0; - function addUniquePlaylistItemId(item) { - if (!item.PlaylistItemId) { - item.PlaylistItemId = 'playlistItem' + currentId; - currentId++; +let currentId = 0; +function addUniquePlaylistItemId(item) { + if (!item.PlaylistItemId) { + item.PlaylistItemId = 'playlistItem' + currentId; + currentId++; + } +} + +function findPlaylistIndex(playlistItemId, list) { + for (let i = 0, length = list.length; i < length; i++) { + if (list[i].PlaylistItemId === playlistItemId) { + return i; } } - function findPlaylistIndex(playlistItemId, list) { - for (var i = 0, length = list.length; i < length; i++) { - if (list[i].PlaylistItemId === playlistItemId) { - return i; - } - } + return -1; +} - return -1; - } - - function PlayQueueManager() { +class PlayQueueManager { + constructor() { this._sortedPlaylist = []; this._playlist = []; this._repeatMode = 'RepeatNone'; this._shuffleMode = 'Sorted'; } - PlayQueueManager.prototype.getPlaylist = function () { + getPlaylist() { return this._playlist.slice(0); - }; + } - PlayQueueManager.prototype.setPlaylist = function (items) { + setPlaylist(items) { items = items.slice(0); - for (var i = 0, length = items.length; i < length; i++) { + for (let i = 0, length = items.length; i < length; i++) { addUniquePlaylistItemId(items[i]); } this._currentPlaylistItemId = null; this._playlist = items; this._repeatMode = 'RepeatNone'; - }; + } - PlayQueueManager.prototype.queue = function (items) { - for (var i = 0, length = items.length; i < length; i++) { + queue(items) { + for (let i = 0, length = items.length; i < length; i++) { addUniquePlaylistItemId(items[i]); this._playlist.push(items[i]); } - }; + } - PlayQueueManager.prototype.shufflePlaylist = function () { + shufflePlaylist() { this._sortedPlaylist = []; for (const item of this._playlist) { this._sortedPlaylist.push(item); @@ -65,42 +65,31 @@ define([], function () { } this._playlist.unshift(currentPlaylistItem); this._shuffleMode = 'Shuffle'; - }; + } - PlayQueueManager.prototype.sortShuffledPlaylist = function () { + sortShuffledPlaylist() { this._playlist = []; - for (let item of this._sortedPlaylist) { + for (const item of this._sortedPlaylist) { this._playlist.push(item); } this._sortedPlaylist = []; this._shuffleMode = 'Sorted'; - }; + } - PlayQueueManager.prototype.clearPlaylist = function (clearCurrentItem = false) { + clearPlaylist(clearCurrentItem = false) { const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0]; this._playlist = []; if (!clearCurrentItem) { this._playlist.push(currentPlaylistItem); } - }; - - function arrayInsertAt(destArray, pos, arrayToInsert) { - var args = []; - args.push(pos); // where to insert - args.push(0); // nothing to remove - args = args.concat(arrayToInsert); // add on array to insert - destArray.splice.apply(destArray, args); // splice it in } - PlayQueueManager.prototype.queueNext = function (items) { - var i; - var length; - - for (i = 0, length = items.length; i < length; i++) { + queueNext(items) { + for (let i = 0, length = items.length; i < length; i++) { addUniquePlaylistItemId(items[i]); } - var currentIndex = this.getCurrentPlaylistIndex(); + let currentIndex = this.getCurrentPlaylistIndex(); if (currentIndex === -1) { currentIndex = this._playlist.length; @@ -109,43 +98,43 @@ define([], function () { } arrayInsertAt(this._playlist, currentIndex, items); - }; + } - PlayQueueManager.prototype.getCurrentPlaylistIndex = function () { + getCurrentPlaylistIndex() { return findPlaylistIndex(this.getCurrentPlaylistItemId(), this._playlist); - }; + } - PlayQueueManager.prototype.getCurrentItem = function () { - var index = findPlaylistIndex(this.getCurrentPlaylistItemId(), this._playlist); + getCurrentItem() { + const index = findPlaylistIndex(this.getCurrentPlaylistItemId(), this._playlist); return index === -1 ? null : this._playlist[index]; - }; + } - PlayQueueManager.prototype.getCurrentPlaylistItemId = function () { + getCurrentPlaylistItemId() { return this._currentPlaylistItemId; - }; + } - PlayQueueManager.prototype.setPlaylistState = function (playlistItemId, playlistIndex) { + setPlaylistState(playlistItemId, playlistIndex) { this._currentPlaylistItemId = playlistItemId; - }; + } - PlayQueueManager.prototype.setPlaylistIndex = function (playlistIndex) { + setPlaylistIndex(playlistIndex) { if (playlistIndex < 0) { this.setPlaylistState(null); } else { this.setPlaylistState(this._playlist[playlistIndex].PlaylistItemId); } - }; + } - PlayQueueManager.prototype.removeFromPlaylist = function (playlistItemIds) { + removeFromPlaylist(playlistItemIds) { if (this._playlist.length <= playlistItemIds.length) { return { result: 'empty' }; } - var currentPlaylistItemId = this.getCurrentPlaylistItemId(); - var isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1; + const currentPlaylistItemId = this.getCurrentPlaylistItemId(); + const isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1; this._sortedPlaylist = this._sortedPlaylist.filter(function (item) { return !playlistItemIds.includes(item.PlaylistItemId); @@ -159,17 +148,13 @@ define([], function () { result: 'removed', isCurrentIndex: isCurrentIndex }; - }; - - function moveInArray(array, from, to) { - array.splice(to, 0, array.splice(from, 1)[0]); } - PlayQueueManager.prototype.movePlaylistItem = function (playlistItemId, newIndex) { - var playlist = this.getPlaylist(); + movePlaylistItem(playlistItemId, newIndex) { + const playlist = this.getPlaylist(); - var oldIndex; - for (var i = 0, length = playlist.length; i < length; i++) { + let oldIndex; + for (let i = 0, length = playlist.length; i < length; i++) { if (playlist[i].PlaylistItemId === playlistItemId) { oldIndex = i; break; @@ -195,30 +180,30 @@ define([], function () { playlistItemId: playlistItemId, newIndex: newIndex }; - }; + } - PlayQueueManager.prototype.reset = function () { + reset() { this._sortedPlaylist = []; this._playlist = []; this._currentPlaylistItemId = null; this._repeatMode = 'RepeatNone'; this._shuffleMode = 'Sorted'; - }; + } - PlayQueueManager.prototype.setRepeatMode = function (value) { + setRepeatMode(value) { const repeatModes = ['RepeatOne', 'RepeatAll', 'RepeatNone']; if (repeatModes.includes(value)) { this._repeatMode = value; } else { throw new TypeError('invalid value provided for setRepeatMode'); } - }; + } - PlayQueueManager.prototype.getRepeatMode = function () { + getRepeatMode() { return this._repeatMode; - }; + } - PlayQueueManager.prototype.setShuffleMode = function (value) { + setShuffleMode(value) { switch (value) { case 'Shuffle': this.shufflePlaylist(); @@ -229,9 +214,9 @@ define([], function () { default: throw new TypeError('invalid value provided to setShuffleMode'); } - }; + } - PlayQueueManager.prototype.toggleShuffleMode = function () { + toggleShuffleMode() { switch (this._shuffleMode) { case 'Shuffle': this.setShuffleMode('Sorted'); @@ -242,16 +227,16 @@ define([], function () { default: throw new TypeError('current value for shufflequeue is invalid'); } - }; + } - PlayQueueManager.prototype.getShuffleMode = function () { + getShuffleMode() { return this._shuffleMode; - }; + } - PlayQueueManager.prototype.getNextItemInfo = function () { - var newIndex; - var playlist = this.getPlaylist(); - var playlistLength = playlist.length; + getNextItemInfo() { + let newIndex; + const playlist = this.getPlaylist(); + const playlistLength = playlist.length; switch (this.getRepeatMode()) { case 'RepeatOne': @@ -272,7 +257,7 @@ define([], function () { return null; } - var item = playlist[newIndex]; + const item = playlist[newIndex]; if (!item) { return null; @@ -282,7 +267,19 @@ define([], function () { item: item, index: newIndex }; - }; + } +} - return PlayQueueManager; -}); +function arrayInsertAt(destArray, pos, arrayToInsert) { + let args = []; + args.push(pos); // where to insert + args.push(0); // nothing to remove + args = args.concat(arrayToInsert); // add on array to insert + destArray.splice.apply(destArray, args); // splice it in +} + +function moveInArray(array, from, to) { + array.splice(to, 0, array.splice(from, 1)[0]); +} + +export default PlayQueueManager; diff --git a/src/components/refreshdialog/refreshdialog.js b/src/components/refreshdialog/refreshdialog.js index 57b2ee3189..1ec0517448 100644 --- a/src/components/refreshdialog/refreshdialog.js +++ b/src/components/refreshdialog/refreshdialog.js @@ -1,96 +1,108 @@ -define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-input', 'emby-checkbox', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, connectionManager, appRouter, globalize) { - 'use strict'; +import dom from 'dom'; +import dialogHelper from 'dialogHelper'; +import loading from 'loading'; +import layoutManager from 'layoutManager'; +import connectionManager from 'connectionManager'; +import globalize from 'globalize'; +import 'emby-input'; +import 'emby-checkbox'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; - loading = loading.default || loading; +/*eslint prefer-const: "error"*/ - function getEditorHtml() { - var html = ''; +function getEditorHtml() { + let html = ''; - html += '
'; - html += '
'; - html += '
'; + html += '
'; + html += '
'; + html += ''; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += ''; + html += '
'; - html += ''; + html += ''; - html += '
'; - html += globalize.translate('RefreshDialogHelp'); - html += '
'; + html += '
'; + html += globalize.translate('RefreshDialogHelp'); + html += '
'; - html += ''; + html += ''; - html += '
'; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; - html += ''; - html += '
'; - html += '
'; + html += ''; + html += '
'; + html += '
'; - return html; - } + return html; +} - function centerFocus(elem, horiz, on) { - require(['scrollHelper'], function (scrollHelper) { - var fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); +function centerFocus(elem, horiz, on) { + import('scrollHelper').then(({default: scrollHelper}) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +function onSubmit(e) { + loading.show(); + + const instance = this; + const dlg = dom.parentWithClass(e.target, 'dialog'); + const options = instance.options; + + const apiClient = connectionManager.getApiClient(options.serverId); + + const replaceAllMetadata = dlg.querySelector('#selectMetadataRefreshMode').value === 'all'; + + const mode = dlg.querySelector('#selectMetadataRefreshMode').value === 'scan' ? 'Default' : 'FullRefresh'; + const replaceAllImages = mode === 'FullRefresh' && dlg.querySelector('.chkReplaceImages').checked; + + options.itemIds.forEach(function (itemId) { + apiClient.refreshItem(itemId, { + + Recursive: true, + ImageRefreshMode: mode, + MetadataRefreshMode: mode, + ReplaceAllImages: replaceAllImages, + ReplaceAllMetadata: replaceAllMetadata }); - } + }); - function onSubmit(e) { - loading.show(); + dialogHelper.close(dlg); - var instance = this; - var dlg = dom.parentWithClass(e.target, 'dialog'); - var options = instance.options; + import('toast').then(({default: toast}) => { + toast(globalize.translate('RefreshQueued')); + }); - var apiClient = connectionManager.getApiClient(options.serverId); + loading.hide(); - var replaceAllMetadata = dlg.querySelector('#selectMetadataRefreshMode').value === 'all'; + e.preventDefault(); + return false; +} - var mode = dlg.querySelector('#selectMetadataRefreshMode').value === 'scan' ? 'Default' : 'FullRefresh'; - var replaceAllImages = mode === 'FullRefresh' && dlg.querySelector('.chkReplaceImages').checked; - - options.itemIds.forEach(function (itemId) { - apiClient.refreshItem(itemId, { - - Recursive: true, - ImageRefreshMode: mode, - MetadataRefreshMode: mode, - ReplaceAllImages: replaceAllImages, - ReplaceAllMetadata: replaceAllMetadata - }); - }); - - dialogHelper.close(dlg); - - require(['toast'], function (toast) { - toast(globalize.translate('RefreshQueued')); - }); - - loading.hide(); - - e.preventDefault(); - return false; - } - - function RefreshDialog(options) { +class RefreshDialog { + constructor(options) { this.options = options; } - RefreshDialog.prototype.show = function () { - var dialogOptions = { + show() { + const dialogOptions = { removeOnClose: true, scrollY: false }; @@ -101,12 +113,12 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dialogOptions.size = 'small'; } - var dlg = dialogHelper.createDialog(dialogOptions); + const dlg = dialogHelper.createDialog(dialogOptions); dlg.classList.add('formDialog'); - var html = ''; - var title = globalize.translate('RefreshMetadata'); + let html = ''; + const title = globalize.translate('RefreshMetadata'); html += '
'; html += ''; @@ -152,7 +164,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'connectionM dlg.addEventListener('close', resolve); dialogHelper.open(dlg); }); - }; + } +} - return RefreshDialog; -}); +export default RefreshDialog; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 02dc66d35a..1b4df3e4b2 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -40,7 +40,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } function getContextMenuOptions(item, user, button) { - var options = { + return { item: item, open: false, play: false, @@ -55,8 +55,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti user: user, share: true }; - - return options; } function getProgramScheduleHtml(items) { @@ -359,9 +357,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti }); html.push('' + artist.Name + ''); } - html = html.join(' / '); - return html; + return html.join(' / '); } /** @@ -525,6 +522,14 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti }); imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; + } else if (item.ImageTags && item.ImageTags.Primary) { + imgUrl = apiClient.getScaledImageUrl(item.Id, { + type: 'Primary', + maxWidth: dom.getScreenWidth(), + tag: item.ImageTags.Primary + }); + imageLoader.lazyImage(itemBackdropElement, imgUrl); + hasbackdrop = true; } else { itemBackdropElement.style.backgroundImage = ''; } @@ -1731,7 +1736,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } function getVideosHtml(items) { - var html = cardBuilder.getCardsHtml({ + return cardBuilder.getCardsHtml({ items: items, shape: 'autooverflow', showTitle: true, @@ -1740,8 +1745,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti centerText: true, showRuntime: true }); - - return html; } function renderSpecials(page, item, user) { diff --git a/src/controllers/livetvtuner.js b/src/controllers/livetvtuner.js index 7f5b70483d..9250c18432 100644 --- a/src/controllers/livetvtuner.js +++ b/src/controllers/livetvtuner.js @@ -1,234 +1,228 @@ -define(['globalize', 'loading', 'libraryMenu', 'dom', 'emby-input', 'emby-button', 'emby-checkbox', 'emby-select'], function (globalize, loading, libraryMenu, dom) { - 'use strict'; +import globalize from 'globalize'; +import loading from 'loading'; +import dom from 'dom'; +import 'emby-input'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; - loading = loading.default || loading; +function isM3uVariant(type) { + return ['nextpvr'].indexOf(type || '') !== -1; +} - function isM3uVariant(type) { - return ['nextpvr'].indexOf(type || '') !== -1; - } - - function fillTypes(view, currentId) { - return ApiClient.getJSON(ApiClient.getUrl('LiveTv/TunerHosts/Types')).then(function (types) { - var selectType = view.querySelector('.selectType'); - var html = ''; - html += types.map(function (tuner) { - return ''; - }).join(''); - html += ''; - selectType.innerHTML = html; - selectType.disabled = null != currentId; - selectType.value = ''; - onTypeChange.call(selectType); - }); - } - - function reload(view, providerId) { - view.querySelector('.txtDevicePath').value = ''; - view.querySelector('.chkFavorite').checked = false; - view.querySelector('.txtDevicePath').value = ''; - - if (providerId) { - ApiClient.getNamedConfiguration('livetv').then(function (config) { - var info = config.TunerHosts.filter(function (i) { - return i.Id === providerId; - })[0]; - fillTunerHostInfo(view, info); - }); - } - } - - function fillTunerHostInfo(view, info) { - var selectType = view.querySelector('.selectType'); - var type = info.Type || ''; - - if (info.Source && isM3uVariant(info.Source)) { - type = info.Source; - } - - selectType.value = type; +function fillTypes(view, currentId) { + return ApiClient.getJSON(ApiClient.getUrl('LiveTv/TunerHosts/Types')).then(function (types) { + const selectType = view.querySelector('.selectType'); + let html = ''; + html += types.map(function (tuner) { + return ''; + }).join(''); + html += ''; + selectType.innerHTML = html; + selectType.disabled = null != currentId; + selectType.value = ''; onTypeChange.call(selectType); - view.querySelector('.txtDevicePath').value = info.Url || ''; - view.querySelector('.txtFriendlyName').value = info.FriendlyName || ''; - view.querySelector('.txtUserAgent').value = info.UserAgent || ''; - view.querySelector('.fldDeviceId').value = info.DeviceId || ''; - view.querySelector('.chkFavorite').checked = info.ImportFavoritesOnly; - view.querySelector('.chkTranscode').checked = info.AllowHWTranscoding; - view.querySelector('.chkStreamLoop').checked = info.EnableStreamLooping; - view.querySelector('.txtTunerCount').value = info.TunerCount || '0'; - } + }); +} - function submitForm(page) { - loading.show(); - var info = { - Type: page.querySelector('.selectType').value, - Url: page.querySelector('.txtDevicePath').value || null, - UserAgent: page.querySelector('.txtUserAgent').value || null, - FriendlyName: page.querySelector('.txtFriendlyName').value || null, - DeviceId: page.querySelector('.fldDeviceId').value || null, - TunerCount: page.querySelector('.txtTunerCount').value || 0, - ImportFavoritesOnly: page.querySelector('.chkFavorite').checked, - AllowHWTranscoding: page.querySelector('.chkTranscode').checked, - EnableStreamLooping: page.querySelector('.chkStreamLoop').checked - }; +function reload(view, providerId) { + view.querySelector('.txtDevicePath').value = ''; + view.querySelector('.chkFavorite').checked = false; + view.querySelector('.txtDevicePath').value = ''; - if (isM3uVariant(info.Type)) { - info.Source = info.Type; - info.Type = 'm3u'; - } - - var id = getParameterByName('id'); - - if (id) { - info.Id = id; - } - - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('LiveTv/TunerHosts'), - data: JSON.stringify(info), - contentType: 'application/json' - }).then(function (result) { - Dashboard.processServerConfigurationUpdateResult(); - Dashboard.navigate('livetvstatus.html'); - }, function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('ErrorSavingTvProvider') - }); + if (providerId) { + ApiClient.getNamedConfiguration('livetv').then(function (config) { + const info = config.TunerHosts.filter(function (i) { + return i.Id === providerId; + })[0]; + fillTunerHostInfo(view, info); }); } +} - function getRequirePromise(deps) { - return new Promise(function (resolve, reject) { - require(deps, resolve); - }); +function fillTunerHostInfo(view, info) { + const selectType = view.querySelector('.selectType'); + let type = info.Type || ''; + + if (info.Source && isM3uVariant(info.Source)) { + type = info.Source; } - function getDetectedDevice() { - return getRequirePromise(['tunerPicker']).then(function (tunerPicker) { - return new tunerPicker().show({ - serverId: ApiClient.serverId() - }); - }); - } + selectType.value = type; + onTypeChange.call(selectType); + view.querySelector('.txtDevicePath').value = info.Url || ''; + view.querySelector('.txtFriendlyName').value = info.FriendlyName || ''; + view.querySelector('.txtUserAgent').value = info.UserAgent || ''; + view.querySelector('.fldDeviceId').value = info.DeviceId || ''; + view.querySelector('.chkFavorite').checked = info.ImportFavoritesOnly; + view.querySelector('.chkTranscode').checked = info.AllowHWTranscoding; + view.querySelector('.chkStreamLoop').checked = info.EnableStreamLooping; + view.querySelector('.txtTunerCount').value = info.TunerCount || '0'; +} - function onTypeChange() { - var value = this.value; - var view = dom.parentWithClass(this, 'page'); - var mayIncludeUnsupportedDrmChannels = 'hdhomerun' === value; - var supportsTranscoding = 'hdhomerun' === value; - var supportsFavorites = 'hdhomerun' === value; - var supportsTunerIpAddress = 'hdhomerun' === value; - var supportsTunerFileOrUrl = 'm3u' === value; - var supportsStreamLooping = 'm3u' === value; - var supportsTunerCount = 'm3u' === value; - var supportsUserAgent = 'm3u' === value; - var suppportsSubmit = 'other' !== value; - var supportsSelectablePath = supportsTunerFileOrUrl; - var txtDevicePath = view.querySelector('.txtDevicePath'); - - if (supportsTunerIpAddress) { - txtDevicePath.label(globalize.translate('LabelTunerIpAddress')); - view.querySelector('.fldPath').classList.remove('hide'); - } else if (supportsTunerFileOrUrl) { - txtDevicePath.label(globalize.translate('LabelFileOrUrl')); - view.querySelector('.fldPath').classList.remove('hide'); - } else { - view.querySelector('.fldPath').classList.add('hide'); - } - - if (supportsSelectablePath) { - view.querySelector('.btnSelectPath').classList.remove('hide'); - view.querySelector('.txtDevicePath').setAttribute('required', 'required'); - } else { - view.querySelector('.btnSelectPath').classList.add('hide'); - view.querySelector('.txtDevicePath').removeAttribute('required'); - } - - if (supportsUserAgent) { - view.querySelector('.fldUserAgent').classList.remove('hide'); - } else { - view.querySelector('.fldUserAgent').classList.add('hide'); - } - - if (supportsFavorites) { - view.querySelector('.fldFavorites').classList.remove('hide'); - } else { - view.querySelector('.fldFavorites').classList.add('hide'); - } - - if (supportsTranscoding) { - view.querySelector('.fldTranscode').classList.remove('hide'); - } else { - view.querySelector('.fldTranscode').classList.add('hide'); - } - - if (supportsStreamLooping) { - view.querySelector('.fldStreamLoop').classList.remove('hide'); - } else { - view.querySelector('.fldStreamLoop').classList.add('hide'); - } - - if (supportsTunerCount) { - view.querySelector('.fldTunerCount').classList.remove('hide'); - view.querySelector('.txtTunerCount').setAttribute('required', 'required'); - } else { - view.querySelector('.fldTunerCount').classList.add('hide'); - view.querySelector('.txtTunerCount').removeAttribute('required'); - } - - if (mayIncludeUnsupportedDrmChannels) { - view.querySelector('.drmMessage').classList.remove('hide'); - } else { - view.querySelector('.drmMessage').classList.add('hide'); - } - - if (suppportsSubmit) { - view.querySelector('.button-submit').classList.remove('hide'); - } else { - view.querySelector('.button-submit').classList.add('hide'); - } - } - - return function (view, params) { - if (!params.id) { - view.querySelector('.btnDetect').classList.remove('hide'); - } - - view.addEventListener('viewshow', function () { - var currentId = params.id; - fillTypes(view, currentId).then(function () { - reload(view, currentId); - }); - }); - view.querySelector('form').addEventListener('submit', function (e) { - submitForm(view); - e.preventDefault(); - e.stopPropagation(); - return false; - }); - view.querySelector('.selectType').addEventListener('change', onTypeChange); - view.querySelector('.btnDetect').addEventListener('click', function () { - getDetectedDevice().then(function (info) { - fillTunerHostInfo(view, info); - }); - }); - view.querySelector('.btnSelectPath').addEventListener('click', function () { - require(['directorybrowser'], function (directoryBrowser) { - var picker = new directoryBrowser.default(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - view.querySelector('.txtDevicePath').value = path; - } - - picker.close(); - } - }); - }); - }); +function submitForm(page) { + loading.show(); + const info = { + Type: page.querySelector('.selectType').value, + Url: page.querySelector('.txtDevicePath').value || null, + UserAgent: page.querySelector('.txtUserAgent').value || null, + FriendlyName: page.querySelector('.txtFriendlyName').value || null, + DeviceId: page.querySelector('.fldDeviceId').value || null, + TunerCount: page.querySelector('.txtTunerCount').value || 0, + ImportFavoritesOnly: page.querySelector('.chkFavorite').checked, + AllowHWTranscoding: page.querySelector('.chkTranscode').checked, + EnableStreamLooping: page.querySelector('.chkStreamLoop').checked }; -}); + + if (isM3uVariant(info.Type)) { + info.Source = info.Type; + info.Type = 'm3u'; + } + + if (getParameterByName('id')) { + info.Id = getParameterByName('id'); + } + + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('LiveTv/TunerHosts'), + data: JSON.stringify(info), + contentType: 'application/json' + }).then(function (result) { + Dashboard.processServerConfigurationUpdateResult(); + Dashboard.navigate('livetvstatus.html'); + }, function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('ErrorSavingTvProvider') + }); + }); +} + +function getDetectedDevice() { + return import('tunerPicker').then(({default: tunerPicker}) => { + return new tunerPicker().show({ + serverId: ApiClient.serverId() + }); + }); +} + +function onTypeChange() { + const value = this.value; + const view = dom.parentWithClass(this, 'page'); + const mayIncludeUnsupportedDrmChannels = 'hdhomerun' === value; + const supportsTranscoding = 'hdhomerun' === value; + const supportsFavorites = 'hdhomerun' === value; + const supportsTunerIpAddress = 'hdhomerun' === value; + const supportsTunerFileOrUrl = 'm3u' === value; + const supportsStreamLooping = 'm3u' === value; + const supportsTunerCount = 'm3u' === value; + const supportsUserAgent = 'm3u' === value; + const suppportsSubmit = 'other' !== value; + const supportsSelectablePath = supportsTunerFileOrUrl; + const txtDevicePath = view.querySelector('.txtDevicePath'); + + if (supportsTunerIpAddress) { + txtDevicePath.label(globalize.translate('LabelTunerIpAddress')); + view.querySelector('.fldPath').classList.remove('hide'); + } else if (supportsTunerFileOrUrl) { + txtDevicePath.label(globalize.translate('LabelFileOrUrl')); + view.querySelector('.fldPath').classList.remove('hide'); + } else { + view.querySelector('.fldPath').classList.add('hide'); + } + + if (supportsSelectablePath) { + view.querySelector('.btnSelectPath').classList.remove('hide'); + view.querySelector('.txtDevicePath').setAttribute('required', 'required'); + } else { + view.querySelector('.btnSelectPath').classList.add('hide'); + view.querySelector('.txtDevicePath').removeAttribute('required'); + } + + if (supportsUserAgent) { + view.querySelector('.fldUserAgent').classList.remove('hide'); + } else { + view.querySelector('.fldUserAgent').classList.add('hide'); + } + + if (supportsFavorites) { + view.querySelector('.fldFavorites').classList.remove('hide'); + } else { + view.querySelector('.fldFavorites').classList.add('hide'); + } + + if (supportsTranscoding) { + view.querySelector('.fldTranscode').classList.remove('hide'); + } else { + view.querySelector('.fldTranscode').classList.add('hide'); + } + + if (supportsStreamLooping) { + view.querySelector('.fldStreamLoop').classList.remove('hide'); + } else { + view.querySelector('.fldStreamLoop').classList.add('hide'); + } + + if (supportsTunerCount) { + view.querySelector('.fldTunerCount').classList.remove('hide'); + view.querySelector('.txtTunerCount').setAttribute('required', 'required'); + } else { + view.querySelector('.fldTunerCount').classList.add('hide'); + view.querySelector('.txtTunerCount').removeAttribute('required'); + } + + if (mayIncludeUnsupportedDrmChannels) { + view.querySelector('.drmMessage').classList.remove('hide'); + } else { + view.querySelector('.drmMessage').classList.add('hide'); + } + + if (suppportsSubmit) { + view.querySelector('.button-submit').classList.remove('hide'); + } else { + view.querySelector('.button-submit').classList.add('hide'); + } +} + +export default function (view, params) { + if (!params.id) { + view.querySelector('.btnDetect').classList.remove('hide'); + } + + view.addEventListener('viewshow', function () { + const currentId = params.id; + fillTypes(view, currentId).then(function () { + reload(view, currentId); + }); + }); + view.querySelector('form').addEventListener('submit', function (e) { + submitForm(view); + e.preventDefault(); + e.stopPropagation(); + return false; + }); + view.querySelector('.selectType').addEventListener('change', onTypeChange); + view.querySelector('.btnDetect').addEventListener('click', function () { + getDetectedDevice().then(function (info) { + fillTunerHostInfo(view, info); + }); + }); + view.querySelector('.btnSelectPath').addEventListener('click', function () { + import('directorybrowser').then(({default: directorybrowser}) => { + const picker = new directorybrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + view.querySelector('.txtDevicePath').value = path; + } + + picker.close(); + } + }); + }); + }); +} diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index c95621b431..bc6d2c4e37 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -1,10 +1,18 @@ -define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'userSettings', 'globalize', 'emby-itemscontainer'], function (layoutManager, playbackManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, userSettings, globalize) { - 'use strict'; +import playbackManager from 'playbackManager'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function playAll() { ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { playbackManager.play({ @@ -21,7 +29,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser } function getPageData() { - var key = getSavedQueryKey(); + const key = getSavedQueryKey(); if (!pageData) { pageData = { @@ -62,8 +70,8 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser } function onViewStyleChange() { - var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); if ('List' == viewStyle) { itemsContainer.classList.add('vertical-list'); @@ -79,7 +87,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser function reloadItems(page) { loading.show(); isLoading = true; - var query = getQuery(); + const query = getQuery(); ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -105,8 +113,8 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser window.scrollTo(0, 0); updateFilterControls(page); - var html; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -116,7 +124,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser sortButton: false, filterButton: false }); - var viewStyle = self.getCurrentViewStyle(); + const viewStyle = self.getCurrentViewStyle(); if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, @@ -147,50 +155,48 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser overlayPlayButton: true }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); }); } function updateFilterControls(tabContent) { - var query = getQuery(); + const query = getQuery(); self.alphaPicker.value(query.NameStartsWithOrGreater); } - var savedQueryKey; - var pageData; - var self = this; - var isLoading = false; + let savedQueryKey; + let pageData; + const self = this; + let isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(), mode: 'albums', serverId: ApiClient.serverId() @@ -208,17 +214,17 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(); + const newValue = e.detail.value; + const query = getQuery(); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(tabContent); }); - self.alphaPicker = new AlphaPicker.default({ + self.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -262,12 +268,12 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser button: e.target }); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData().view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); getQuery().StartIndex = 0; @@ -287,5 +293,6 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 150f908170..985c629103 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -1,16 +1,22 @@ -define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', 'alphaPicker', 'listView', 'cardBuilder', 'apphost', 'userSettings', 'emby-itemscontainer'], function (layoutManager, loading, events, libraryBrowser, imageLoader, AlphaPicker, listView, cardBuilder, appHost, userSettings) { - 'use strict'; +import loading from 'loading'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import AlphaPicker from 'alphaPicker'; +import listView from 'listView'; +import cardBuilder from 'cardBuilder'; +import * as userSettings from 'userSettings'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { - var queryValues = { + const queryValues = { SortBy: 'SortName', SortOrder: 'Ascending', Recursive: true, @@ -48,8 +54,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' } function onViewStyleChange() { - var viewStyle = self.getCurrentViewStyle(); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); if ('List' == viewStyle) { itemsContainer.classList.add('vertical-list'); @@ -65,8 +71,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' function reloadItems(page) { loading.show(); isLoading = true; - var query = getQuery(page); - var promise = self.mode == 'albumartists' ? + const query = getQuery(page); + const promise = self.mode == 'albumartists' ? ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : ApiClient.getArtists(ApiClient.getCurrentUserId(), query); promise.then(function (result) { @@ -94,8 +100,8 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' window.scrollTo(0, 0); updateFilterControls(page); - var html; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -105,7 +111,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' sortButton: false, filterButton: false }); - var viewStyle = self.getCurrentViewStyle(); + const viewStyle = self.getCurrentViewStyle(); if (viewStyle == 'List') { html = listView.getListViewHtml({ items: result.Items, @@ -132,49 +138,47 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' overlayPlayButton: true }); } - var i; - var length; - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(tabContent); }); }); } function updateFilterControls(tabContent) { - var query = getQuery(tabContent); + const query = getQuery(tabContent); self.alphaPicker.value(query.NameStartsWithOrGreater); } - var self = this; - var data = {}; - var isLoading = false; + const self = this; + const data = {}; + let isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: self.mode, serverId: ApiClient.serverId() @@ -192,17 +196,17 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }; function initPage(tabContent) { - var alphaPickerElement = tabContent.querySelector('.alphaPicker'); - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const alphaPickerElement = tabContent.querySelector('.alphaPicker'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - var newValue = e.detail.value; - var query = getQuery(tabContent); + const newValue = e.detail.value; + const query = getQuery(tabContent); query.NameStartsWithOrGreater = newValue; query.StartIndex = 0; reloadItems(tabContent); }); - self.alphaPicker = new AlphaPicker.default({ + self.alphaPicker = new AlphaPicker({ element: alphaPickerElement, valueChangeEvent: 'click' }); @@ -214,12 +218,12 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' tabContent.querySelector('.btnFilter').addEventListener('click', function () { self.showFilterMenu(); }); - var btnSelectView = tabContent.querySelector('.btnSelectView'); + const btnSelectView = tabContent.querySelector('.btnSelectView'); btnSelectView.addEventListener('click', function (e) { libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); }); btnSelectView.addEventListener('layoutchange', function (e) { - var viewStyle = e.detail.viewStyle; + const viewStyle = e.detail.viewStyle; getPageData(tabContent).view = viewStyle; libraryBrowser.saveViewSetting(getSavedQueryKey(tabContent), viewStyle); getQuery(tabContent).StartIndex = 0; @@ -237,5 +241,6 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index 30064b3b64..29e0d888e4 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -1,13 +1,14 @@ -define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -37,15 +38,15 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); } function reloadItems(context, promise) { - var query = getQuery(); + const query = getQuery(); promise.then(function (result) { - var html = ''; - var viewStyle = self.getCurrentViewStyle(); + let html = ''; + const viewStyle = self.getCurrentViewStyle(); if (viewStyle == 'Thumb') { html = cardBuilder.getCardsHtml({ @@ -85,13 +86,13 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f }); } - var elem = context.querySelector('#items'); + const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(context); }); }); @@ -102,8 +103,8 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f self.renderTab(); } - var self = this; - var data = {}; + const self = this; + const data = {}; self.getViewStyles = function () { return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); @@ -120,7 +121,7 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f }; self.enableViewSelection = true; - var promise; + let promise; self.preRender = function () { promise = getPromise(); @@ -129,5 +130,6 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f self.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index 6f11501daa..befeafcf4c 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -1,13 +1,14 @@ -define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], function (libraryBrowser, cardBuilder, appHost, imageLoader, loading) { - 'use strict'; +import libraryBrowser from 'libraryBrowser'; +import cardBuilder from 'cardBuilder'; +import imageLoader from 'imageLoader'; +import loading from 'loading'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData() { - var key = getSavedQueryKey(); - var pageData = data[key]; + const key = getSavedQueryKey(); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -38,14 +39,14 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f function getPromise() { loading.show(); - var query = getQuery(); + const query = getQuery(); return ApiClient.getItems(ApiClient.getCurrentUserId(), query); } function reloadItems(context, promise) { - var query = getQuery(); + const query = getQuery(); promise.then(function (result) { - var html = ''; + let html = ''; html = cardBuilder.getCardsHtml({ items: result.Items, shape: 'square', @@ -56,26 +57,26 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f allowBottomPadding: true, cardLayout: false }); - var elem = context.querySelector('#items'); + const elem = context.querySelector('#items'); elem.innerHTML = html; imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(context); }); }); } - var self = this; - var data = {}; + const self = this; + const data = {}; self.getCurrentViewStyle = function () { return getPageData().view; }; - var promise; + let promise; self.preRender = function () { promise = getPromise(); @@ -84,5 +85,6 @@ define(['libraryBrowser', 'cardBuilder', 'apphost', 'imageLoader', 'loading'], f self.renderTab = function () { reloadItems(tabContent, promise); }; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index f1809623d4..e06f865a27 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -1,10 +1,26 @@ -define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'libraryMenu', 'playbackManager', 'mainTabsManager', 'globalize', 'scrollStyles', 'emby-itemscontainer', 'emby-tabs', 'emby-button', 'flexStyles'], function (browser, layoutManager, userSettings, inputManager, loading, cardBuilder, dom, appHost, imageLoader, libraryMenu, playbackManager, mainTabsManager, globalize) { - 'use strict'; +import browser from 'browser'; +import layoutManager from 'layoutManager'; +import * as userSettings from 'userSettings'; +import inputManager from 'inputManager'; +import loading from 'loading'; +import cardBuilder from 'cardBuilder'; +import dom from 'dom'; +import imageLoader from 'imageLoader'; +import libraryMenu from 'libraryMenu'; +import * as mainTabsManager from 'mainTabsManager'; +import globalize from 'globalize'; +import 'scrollStyles'; +import 'emby-itemscontainer'; +import 'emby-tabs'; +import 'emby-button'; +import 'flexStyles'; + +/* eslint-disable indent */ loading = loading.default || loading; function itemsPerRow() { - var screenWidth = dom.getWindowSize().innerWidth; + const screenWidth = dom.getWindowSize().innerWidth; if (screenWidth >= 1920) { return 9; @@ -31,8 +47,8 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' function loadLatest(page, parentId) { loading.show(); - var userId = ApiClient.getCurrentUserId(); - var options = { + const userId = ApiClient.getCurrentUserId(); + const options = { IncludeItemTypes: 'Audio', Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', @@ -43,7 +59,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' }; ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { var elem = page.querySelector('#recentlyAddedSongs'); - elem.innerHTML = cardBuilder.getCardsHtml({ items: items, showUnplayedIndicator: false, @@ -61,14 +76,14 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' imageLoader.lazyChildren(elem); loading.hide(); - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); } function loadRecentlyPlayed(page, parentId) { - var options = { + const options = { SortBy: 'DatePlayed', SortOrder: 'Descending', IncludeItemTypes: 'Audio', @@ -82,7 +97,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector('#recentlyPlayed'); + const elem = page.querySelector('#recentlyPlayed'); if (result.Items.length) { elem.classList.remove('hide'); @@ -91,6 +106,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } var itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ items: result.Items, showUnplayedIndicator: false, @@ -110,7 +126,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } function loadFrequentlyPlayed(page, parentId) { - var options = { + const options = { SortBy: 'PlayCount', SortOrder: 'Descending', IncludeItemTypes: 'Audio', @@ -124,7 +140,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' EnableTotalRecordCount: false }; ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - var elem = page.querySelector('#topPlayed'); + const elem = page.querySelector('#topPlayed'); if (result.Items.length) { elem.classList.remove('hide'); @@ -157,7 +173,7 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' loadRecentlyPlayed(tabContent, parentId); loadFrequentlyPlayed(tabContent, parentId); - require(['components/favoriteitems'], function (favoriteItems) { + import('components/favoriteitems').then(({default: favoriteItems}) => { favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); }); } @@ -208,10 +224,10 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } } - return function (view, params) { + export default function (view, params) { function reload() { loading.show(); - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); + const tabContent = view.querySelector(".pageTabContent[data-index='0']"); loadSuggestionsTab(view, tabContent, params.topParentId); } @@ -254,46 +270,48 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } function getTabController(page, index, callback) { - var depends = []; + let depends; switch (index) { case 0: + depends = 'controllers/music/musicrecommended'; break; case 1: - depends.push('controllers/music/musicalbums'); + depends = 'controllers/music/musicalbums'; break; case 2: case 3: - depends.push('controllers/music/musicartists'); + depends = 'controllers/music/musicartists'; break; case 4: - depends.push('controllers/music/musicplaylists'); + depends = 'controllers/music/musicplaylists'; break; case 5: - depends.push('controllers/music/songs'); + depends = 'controllers/music/songs'; break; case 6: - depends.push('controllers/music/musicgenres'); + depends = 'controllers/music/musicgenres'; break; case 7: - depends.push('scripts/searchtab'); + depends = 'scripts/searchtab'; + break; } - require(depends, function (controllerFactory) { - var tabContent; + import(depends).then(({default: controllerFactory}) => { + let tabContent; if (0 == index) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); self.tabContent = tabContent; } - var controller = tabControllers[index]; + let controller = tabControllers[index]; if (!controller) { tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); @@ -355,10 +373,10 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' var currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); self.initTab = function () { - var tabContent = view.querySelector(".pageTabContent[data-index='0']"); - var containers = tabContent.querySelectorAll('.itemsContainer'); + const tabContent = view.querySelector(".pageTabContent[data-index='0']"); + const containers = tabContent.querySelectorAll('.itemsContainer'); - for (var i = 0, length = containers.length; i < length; i++) { + for (let i = 0, length = containers.length; i < length; i++) { setScrollClasses(containers[i], enableScrollX()); } }; @@ -367,12 +385,12 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' reload(); }; - var tabControllers = []; - var renderedTabs = []; + const tabControllers = []; + const renderedTabs = []; view.addEventListener('viewshow', function (e) { initTabs(); if (!view.getAttribute('data-title')) { - var parentId = params.topParentId; + const parentId = params.topParentId; if (parentId) { ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { @@ -397,5 +415,6 @@ define(['browser', 'layoutManager', 'userSettings', 'inputManager', 'loading', ' } }); }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 0d34987a74..4c4f229e58 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -1,13 +1,18 @@ -define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userSettings', 'globalize', 'emby-itemscontainer'], function (events, libraryBrowser, imageLoader, listView, loading, userSettings, globalize) { - 'use strict'; +import events from 'events'; +import libraryBrowser from 'libraryBrowser'; +import imageLoader from 'imageLoader'; +import listView from 'listView'; +import loading from 'loading'; +import * as userSettings from 'userSettings'; +import globalize from 'globalize'; +import 'emby-itemscontainer'; - loading = loading.default || loading; - libraryBrowser = libraryBrowser.default || libraryBrowser; +/* eslint-disable indent */ - return function (view, params, tabContent) { + export default function (view, params, tabContent) { function getPageData(context) { - var key = getSavedQueryKey(context); - var pageData = data[key]; + const key = getSavedQueryKey(context); + let pageData = data[key]; if (!pageData) { pageData = data[key] = { @@ -49,7 +54,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS function reloadItems(page) { loading.show(); isLoading = true; - var query = getQuery(page); + const query = getQuery(page); ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { function onNextPageClick() { if (isLoading) { @@ -74,9 +79,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS } window.scrollTo(0, 0); - var i; - var length; - var pagingHtml = libraryBrowser.getQueryPagingHtml({ + const pagingHtml = libraryBrowser.getQueryPagingHtml({ startIndex: query.StartIndex, limit: query.Limit, totalRecordCount: result.TotalRecordCount, @@ -86,49 +89,49 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS sortButton: false, filterButton: false }); - var html = listView.getListViewHtml({ + const html = listView.getListViewHtml({ items: result.Items, action: 'playallfromhere', smallIcon: true, artist: true, addToListButton: true }); - var elems = tabContent.querySelectorAll('.paging'); + let elems = tabContent.querySelectorAll('.paging'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].innerHTML = pagingHtml; } elems = tabContent.querySelectorAll('.btnNextPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onNextPageClick); } elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (i = 0, length = elems.length; i < length; i++) { + for (let i = 0, length = elems.length; i < length; i++) { elems[i].addEventListener('click', onPreviousPageClick); } - var itemsContainer = tabContent.querySelector('.itemsContainer'); + const itemsContainer = tabContent.querySelector('.itemsContainer'); itemsContainer.innerHTML = html; imageLoader.lazyChildren(itemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; - require(['autoFocuser'], function (autoFocuser) { + import('autoFocuser').then(({default: autoFocuser}) => { autoFocuser.autoFocus(page); }); }); } - var self = this; - var data = {}; - var isLoading = false; + const self = this; + const data = {}; + let isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { - var filterDialog = new filterDialogFactory({ + import('components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => { + const filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'songs', serverId: ApiClient.serverId() @@ -196,5 +199,6 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS }; self.destroy = function () {}; - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index d2bddfc136..5a22ebf285 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,6 +1,7 @@ import playbackManager from 'playbackManager'; import dom from 'dom'; import inputManager from 'inputManager'; +import mouseManager from 'mouseManager'; import datetime from 'datetime'; import itemHelper from 'itemHelper'; import mediaInfo from 'mediaInfo'; @@ -367,6 +368,7 @@ import 'css!assets/css/videoosd'; function hideOsd() { slideUpToHide(headerElement); hideMainOsdControls(); + mouseManager.hideCursor(); } function toggleOsd() { @@ -435,6 +437,7 @@ import 'css!assets/css/videoosd'; const elem = osdBottomElement; clearHideAnimationEventListeners(elem); elem.classList.add('videoOsdBottom-hidden'); + dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, { once: true }); @@ -1131,6 +1134,7 @@ import 'css!assets/css/videoosd'; clickedElement = e.target; const key = keyboardnavigation.getKeyName(e); + const isKeyModified = e.ctrlKey || e.altKey; if (!currentVisibleMenu && 32 === e.keyCode) { playbackManager.playPause(currentPlayer); @@ -1235,8 +1239,10 @@ import 'css!assets/css/videoosd'; case '7': case '8': case '9': { - const percent = parseInt(key, 10) * 10; - playbackManager.seekPercent(percent, currentPlayer); + if (!isKeyModified) { + const percent = parseInt(key, 10) * 10; + playbackManager.seekPercent(percent, currentPlayer); + } break; } } diff --git a/src/index.html b/src/index.html index f8d867cb1a..a135079a7d 100644 --- a/src/index.html +++ b/src/index.html @@ -129,7 +129,7 @@ animation: fadein 0.5s; width: 30%; height: 30%; - background-image: url(assets/img/banner-light.png); + background-image: url(assets/img/icon-transparent.png); background-position: center center; background-repeat: no-repeat; background-size: contain; @@ -139,6 +139,14 @@ -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } + + @media screen + and (min-device-width: 992px) + and (-webkit-min-device-pixel-ratio: 1) { + .splashLogo { + background-image: url(assets/img/banner-light.png); + } + } diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 9e91723449..083a61670f 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,17 +1,43 @@ +import browser from 'browser'; +import events from 'events'; +import appHost from 'apphost'; +import loading from 'loading'; +import dom from 'dom'; +import playbackManager from 'playbackManager'; +import appRouter from 'appRouter'; +import connectionManager from 'connectionManager'; +import { + bindEventsToHlsPlayer, + destroyHlsPlayer, + destroyFlvPlayer, + destroyCastPlayer, + getCrossOriginValue, + enableHlsJsPlayer, + applySrc, + playWithPromise, + onEndedInternal, + saveVolume, + seekOnPlaybackStart, + onErrorInternal, + handleHlsJsMediaError, + getSavedVolume, + isValidDuration, + getBufferedRanges +} from 'htmlMediaHelper'; +import itemHelper from 'itemHelper'; +import screenfull from 'screenfull'; +import globalize from 'globalize'; -define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackManager', 'appRouter', 'appSettings', 'connectionManager', 'htmlMediaHelper', 'itemHelper', 'screenfull', 'globalize'], function (browser, require, events, appHost, loading, dom, playbackManager, appRouter, appSettings, connectionManager, htmlMediaHelper, itemHelper, screenfull, globalize) { - 'use strict'; +/* eslint-disable indent */ - loading = loading.default || loading; - - function tryRemoveElement(elem) { - var parentNode = elem.parentNode; +function tryRemoveElement(elem) { + const parentNode = elem.parentNode; if (parentNode) { // Seeing crashes in edge webview try { parentNode.removeChild(elem); } catch (err) { - console.error('error removing dialog element: ' + err); + console.error(`error removing dialog element: ${err}`); } } } @@ -24,7 +50,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (browser.firefox) { - if ((currentSrc || '').toLowerCase().indexOf('.m3u8') !== -1) { + if ((currentSrc || '').toLowerCase().includes('.m3u8')) { return false; } } @@ -50,7 +76,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } if (track) { - var format = (track.Codec || '').toLowerCase(); + const format = (track.Codec || '').toLowerCase(); if (format === 'ssa' || format === 'ass') { return false; } @@ -60,7 +86,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function requireHlsPlayer(callback) { - require(['hlsjs'], function (hls) { + import('hlsjs').then(({default: hls}) => { window.Hls = hls; callback(); }); @@ -86,9 +112,9 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function zoomIn(elem) { - return new Promise(function (resolve, reject) { - var duration = 240; - elem.style.animation = 'htmlvideoplayer-zoomin ' + duration + 'ms ease-in normal'; + return new Promise(resolve => { + const duration = 240; + elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; hidePrePlaybackPage(); dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { once: true @@ -97,7 +123,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function normalizeTrackEventText(text, useHtml) { - var result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); return useHtml ? result.replace(/\n/gi, '
') : result; } @@ -106,7 +132,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return track.Path; } - var url = playbackManager.getSubtitleUrl(track, item.ServerId); + let url = playbackManager.getSubtitleUrl(track, item.ServerId); if (format) { url = url.replace('.vtt', format); } @@ -115,84 +141,189 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } function getDefaultProfile() { - return new Promise(function (resolve, reject) { - require(['browserdeviceprofile'], function (profileBuilder) { - resolve(profileBuilder({})); - }); + return import('browserdeviceprofile').then(({default: profileBuilder}) => { + return profileBuilder({}); }); } - function HtmlVideoPlayer() { - if (browser.edgeUwp) { - this.name = 'Windows Video Player'; - } else { - this.name = 'Html Video Player'; - } + export class HtmlVideoPlayer { + /** + * @type {string} + */ + name + /** + * @type {string} + */ + type = 'mediaplayer'; + /** + * @type {string} + */ + id = 'htmlvideoplayer'; + /** + * Let any players created by plugins take priority + * + * @type {number} + */ + priority = 1; + /** + * @type {boolean} + */ + isFetching = false; - this.type = 'mediaplayer'; - this.id = 'htmlvideoplayer'; + /** + * @type {HTMLDivElement | null | undefined} + */ + #videoDialog; + /** + * @type {number | undefined} + */ + #subtitleTrackIndexToSetOnPlaying; + /** + * @type {number | null} + */ + #audioTrackIndexToSetOnPlaying; + /** + * @type {null | undefined} + */ + #currentClock; + /** + * @type {any | null | undefined} + */ + #currentSubtitlesOctopus; + /** + * @type {null | undefined} + */ + #currentAssRenderer; + /** + * @type {number | undefined} + */ + #customTrackIndex; + /** + * @type {boolean | undefined} + */ + #showTrackOffset; + /** + * @type {number | undefined} + */ + #currentTrackOffset; + /** + * @type {HTMLElement | null | undefined} + */ + #videoSubtitlesElem; + /** + * @type {any | null | undefined} + */ + #currentTrackEvents; + /** + * @type {string[] | undefined} + */ + #supportedFeatures; + /** + * @type {HTMLVideoElement | null | undefined} + */ + #mediaElement; + /** + * @type {number} + */ + #fetchQueue = 0; + /** + * @type {string | undefined} + */ + #currentSrc; + /** + * @type {boolean | undefined} + */ + #started; + /** + * @type {boolean | undefined} + */ + #timeUpdated; + /** + * @type {number | null | undefined} + */ + #currentTime; + /** + * @type {any | undefined} + */ + #flvPlayer; + /** + * @private (used in other files) + * @type {any | undefined} + */ + _hlsPlayer; + /** + * @private (used in other files) + * @type {any | null | undefined} + */ + _castPlayer; + /** + * @private (used in other files) + * @type {any | undefined} + */ + _currentPlayOptions; + /** + * @type {any | undefined} + */ + #lastProfile; + /** + * @type {MutationObserver | IntersectionObserver | undefined} (Unclear observer typing) + */ + #resizeObserver; - // Let any players created by plugins take priority - this.priority = 1; - - var videoDialog; - - var subtitleTrackIndexToSetOnPlaying; - var audioTrackIndexToSetOnPlaying; - - var currentClock; - var currentSubtitlesOctopus; - var currentAssRenderer; - var customTrackIndex = -1; - - var showTrackOffset; - var currentTrackOffset; - - var videoSubtitlesElem; - var currentTrackEvents; - - var self = this; - - self.currentSrc = function () { - return self._currentSrc; - }; - - self._fetchQueue = 0; - self.isFetching = false; - - function incrementFetchQueue() { - if (self._fetchQueue <= 0) { - self.isFetching = true; - events.trigger(self, 'beginFetch'); - } - - self._fetchQueue++; - } - - function decrementFetchQueue() { - self._fetchQueue--; - - if (self._fetchQueue <= 0) { - self.isFetching = false; - events.trigger(self, 'endFetch'); + constructor() { + if (browser.edgeUwp) { + this.name = 'Windows Video Player'; + } else { + this.name = 'Html Video Player'; } } - function updateVideoUrl(streamInfo) { - var isHls = streamInfo.url.toLowerCase().indexOf('.m3u8') !== -1; + currentSrc() { + return this.#currentSrc; + } - var mediaSource = streamInfo.mediaSource; - var item = streamInfo.item; + /** + * @private + */ + incrementFetchQueue() { + if (this.#fetchQueue <= 0) { + this.isFetching = true; + events.trigger(this, 'beginFetch'); + } + + this.#fetchQueue++; + } + + /** + * @private + */ + decrementFetchQueue() { + this.#fetchQueue--; + + if (this.#fetchQueue <= 0) { + this.isFetching = false; + events.trigger(this, 'endFetch'); + } + } + + /** + * @private + */ + updateVideoUrl(streamInfo) { + const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); + + const mediaSource = streamInfo.mediaSource; + const item = streamInfo.item; // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts // This will start the transcoding process before actually feeding the video url into the player // Edit: Also seeing stalls from hls.js if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { - var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); + const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); loading.show(); - console.debug('prefetching hls playlist: ' + hlsPlaylistUrl); + console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); return connectionManager.getApiClient(item.ServerId).ajax({ @@ -200,42 +331,41 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa url: hlsPlaylistUrl }).then(function () { - console.debug('completed prefetching hls playlist: ' + hlsPlaylistUrl); + console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); loading.hide(); streamInfo.url = hlsPlaylistUrl; - - return Promise.resolve(); }, function () { - console.error('error prefetching hls playlist: ' + hlsPlaylistUrl); + console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); loading.hide(); - return Promise.resolve(); }); } else { return Promise.resolve(); } } - self.play = function (options) { - self._started = false; - self._timeUpdated = false; + play(options) { + this.#started = false; + this.#timeUpdated = false; - self._currentTime = null; + this.#currentTime = null; - self.resetSubtitleOffset(); + this.resetSubtitleOffset(); - return createMediaElement(options).then(function (elem) { - return updateVideoUrl(options).then(function () { - return setCurrentSrc(elem, options); + return this.createMediaElement(options).then(elem => { + return this.updateVideoUrl(options).then(() => { + return this.setCurrentSrc(elem, options); }); }); - }; + } - function setSrcWithFlvJs(instance, elem, options, url) { - return new Promise(function (resolve, reject) { - require(['flvjs'], function (flvjs) { - var flvPlayer = flvjs.createPlayer({ + /** + * @private + */ + setSrcWithFlvJs(elem, options, url) { + return import('flvjs').then(({default: flvjs}) => { + const flvPlayer = flvjs.createPlayer({ type: 'flv', url: url }, @@ -244,117 +374,126 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa lazyLoad: false }); - flvPlayer.attachMediaElement(elem); - flvPlayer.load(); + flvPlayer.attachMediaElement(elem); + flvPlayer.load(); - flvPlayer.play().then(resolve, reject); - instance._flvPlayer = flvPlayer; + this.#flvPlayer = flvPlayer; - // This is needed in setCurrentTrackElement - self._currentSrc = url; - }); + // This is needed in setCurrentTrackElement + this.#currentSrc = url; + + return flvPlayer.play(); }); } - function setSrcWithHlsJs(instance, elem, options, url) { - return new Promise(function (resolve, reject) { - requireHlsPlayer(function () { - var hls = new Hls({ + /** + * @private + */ + setSrcWithHlsJs(elem, options, url) { + return new Promise((resolve, reject) => { + requireHlsPlayer(() => { + const hls = new Hls({ manifestLoadingTimeOut: 20000, - xhrSetup: function(xhr, xhr_url) { + xhrSetup(xhr) { xhr.withCredentials = true; } }); hls.loadSource(url); hls.attachMedia(elem); - htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject); + bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); - self._hlsPlayer = hls; + this._hlsPlayer = hls; // This is needed in setCurrentTrackElement - self._currentSrc = url; + this.#currentSrc = url; }); }); } - function setCurrentSrc(elem, options) { - elem.removeEventListener('error', onError); + /** + * @private + */ + setCurrentSrc(elem, options) { + elem.removeEventListener('error', this.onError); - var val = options.url; - console.debug('playing url: ' + val); + let val = options.url; + console.debug(`playing url: ${val}`); // Convert to seconds - var seconds = (options.playerStartPositionTicks || 0) / 10000000; + const seconds = (options.playerStartPositionTicks || 0) / 10000000; if (seconds) { - val += '#t=' + seconds; + val += `#t=${seconds}`; } - htmlMediaHelper.destroyHlsPlayer(self); - htmlMediaHelper.destroyFlvPlayer(self); - htmlMediaHelper.destroyCastPlayer(self); + destroyHlsPlayer(this); + destroyFlvPlayer(this); + destroyCastPlayer(this); - subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; - if (subtitleTrackIndexToSetOnPlaying != null && subtitleTrackIndexToSetOnPlaying >= 0) { - var initialSubtitleStream = options.mediaSource.MediaStreams[subtitleTrackIndexToSetOnPlaying]; + this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; + if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { + const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { - subtitleTrackIndexToSetOnPlaying = -1; + this.#subtitleTrackIndexToSetOnPlaying = -1; } } - audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; + this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - self._currentPlayOptions = options; + this._currentPlayOptions = options; - var crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource); + const crossOrigin = getCrossOriginValue(options.mediaSource); if (crossOrigin) { elem.crossOrigin = crossOrigin; } - if (htmlMediaHelper.enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.indexOf('.m3u8') !== -1) { - return setSrcWithHlsJs(self, elem, options, val); + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { + return this.setSrcWithHlsJs(elem, options, val); } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { - return setSrcWithFlvJs(self, elem, options, val); + return this.setSrcWithFlvJs(elem, options, val); } else { elem.autoplay = true; // Safari will not send cookies without this elem.crossOrigin = 'use-credentials'; - return htmlMediaHelper.applySrc(elem, val, options).then(function () { - self._currentSrc = val; + return applySrc(elem, val, options).then(() => { + this.#currentSrc = val; - return htmlMediaHelper.playWithPromise(elem, onError); + return playWithPromise(elem, this.onError); }); } } - self.setSubtitleStreamIndex = function (index) { - setCurrentTrackElement(index); - }; + setSubtitleStreamIndex(index) { + this.setCurrentTrackElement(index); + } - self.resetSubtitleOffset = function() { - currentTrackOffset = 0; - showTrackOffset = false; - }; + resetSubtitleOffset() { + this.#currentTrackOffset = 0; + this.#showTrackOffset = false; + } - self.enableShowingSubtitleOffset = function() { - showTrackOffset = true; - }; + enableShowingSubtitleOffset() { + this.#showTrackOffset = true; + } - self.disableShowingSubtitleOffset = function() { - showTrackOffset = false; - }; + disableShowingSubtitleOffset() { + this.#showTrackOffset = false; + } - self.isShowingSubtitleOffsetEnabled = function() { - return showTrackOffset; - }; + isShowingSubtitleOffsetEnabled() { + return this.#showTrackOffset; + } - function getTextTrack() { - var videoElement = self._mediaElement; + /** + * @private + */ + getTextTrack() { + const videoElement = this.#mediaElement; if (videoElement) { return Array.from(videoElement.textTracks) - .find(function(trackElement) { + .find(function (trackElement) { // get showing .vtt textTack return trackElement.mode === 'showing'; }); @@ -363,64 +502,79 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa } } - self.setSubtitleOffset = function(offset) { - var offsetValue = parseFloat(offset); + /** + * @private + */ + setSubtitleOffset(offset) { + const offsetValue = parseFloat(offset); // if .ass currently rendering - if (currentSubtitlesOctopus) { - updateCurrentTrackOffset(offsetValue); - currentSubtitlesOctopus.timeOffset = (self._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; + if (this.#currentSubtitlesOctopus) { + this.updateCurrentTrackOffset(offsetValue); + this.#currentSubtitlesOctopus.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; } else { - var trackElement = getTextTrack(); + const trackElement = this.getTextTrack(); // if .vtt currently rendering if (trackElement) { - setTextTrackSubtitleOffset(trackElement, offsetValue); - } else if (currentTrackEvents) { - setTrackEventsSubtitleOffset(currentTrackEvents, offsetValue); + this.setTextTrackSubtitleOffset(trackElement, offsetValue); + } else if (this.#currentTrackEvents) { + this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue); } else { console.debug('No available track, cannot apply offset: ', offsetValue); } } - }; + } - function updateCurrentTrackOffset(offsetValue) { - var relativeOffset = offsetValue; - var newTrackOffset = offsetValue; - if (currentTrackOffset) { - relativeOffset -= currentTrackOffset; + /** + * @private + */ + updateCurrentTrackOffset(offsetValue) { + let relativeOffset = offsetValue; + const newTrackOffset = offsetValue; + if (this.#currentTrackOffset) { + relativeOffset -= this.#currentTrackOffset; } - currentTrackOffset = newTrackOffset; + this.#currentTrackOffset = newTrackOffset; // relative to currentTrackOffset return relativeOffset; } - function setTextTrackSubtitleOffset(currentTrack, offsetValue) { + /** + * @private + */ + setTextTrackSubtitleOffset(currentTrack, offsetValue) { if (currentTrack.cues) { - offsetValue = updateCurrentTrackOffset(offsetValue); + offsetValue = this.updateCurrentTrackOffset(offsetValue); Array.from(currentTrack.cues) - .forEach(function(cue) { + .forEach(function (cue) { cue.startTime -= offsetValue; cue.endTime -= offsetValue; }); } } - function setTrackEventsSubtitleOffset(trackEvents, offsetValue) { + /** + * @private + */ + setTrackEventsSubtitleOffset(trackEvents, offsetValue) { if (Array.isArray(trackEvents)) { - offsetValue = updateCurrentTrackOffset(offsetValue) * 1e7; // ticks - trackEvents.forEach(function(trackEvent) { + offsetValue = this.updateCurrentTrackOffset(offsetValue) * 1e7; // ticks + trackEvents.forEach(function (trackEvent) { trackEvent.StartPositionTicks -= offsetValue; trackEvent.EndPositionTicks -= offsetValue; }); } } - self.getSubtitleOffset = function() { - return currentTrackOffset; - }; + getSubtitleOffset() { + return this.#currentTrackOffset; + } - function isAudioStreamSupported(stream, deviceProfile) { - var codec = (stream.Codec || '').toLowerCase(); + /** + * @private + */ + isAudioStreamSupported(stream, deviceProfile) { + const codec = (stream.Codec || '').toLowerCase(); if (!codec) { return true; @@ -431,7 +585,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return true; } - var profiles = deviceProfile.DirectPlayProfiles || []; + const profiles = deviceProfile.DirectPlayProfiles || []; return profiles.filter(function (p) { if (p.Type === 'Video') { @@ -439,37 +593,35 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return true; } - return p.AudioCodec.toLowerCase().indexOf(codec) !== -1; + return p.AudioCodec.toLowerCase().includes(codec); } return false; }).length > 0; } - function getSupportedAudioStreams() { - var profile = self._lastProfile; + /** + * @private + */ + getSupportedAudioStreams() { + const profile = this.#lastProfile; - return getMediaStreamAudioTracks(self._currentPlayOptions.mediaSource).filter(function (stream) { - return isAudioStreamSupported(stream, profile); + return getMediaStreamAudioTracks(this._currentPlayOptions.mediaSource).filter((stream) => { + return this.isAudioStreamSupported(stream, profile); }); } - self.setAudioStreamIndex = function (index) { - var streams = getSupportedAudioStreams(); + setAudioStreamIndex(index) { + const streams = this.getSupportedAudioStreams(); if (streams.length < 2) { // If there's only one supported stream then trust that the player will handle it on it's own return; } - var audioIndex = -1; - var i; - var length; - var stream; - - for (i = 0, length = streams.length; i < length; i++) { - stream = streams[i]; + let audioIndex = -1; + for (const stream of streams) { audioIndex++; if (stream.Index === index) { @@ -481,204 +633,272 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa return; } - var elem = self._mediaElement; + const elem = this.#mediaElement; if (!elem) { return; } - // https://msdn.microsoft.com/en-us/library/hh772507(v=vs.85).aspx + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - var elemAudioTracks = elem.audioTracks || []; - console.debug('found ' + elemAudioTracks.length + ' audio tracks'); + /** + * @type {ArrayLike|any[]} + */ + const elemAudioTracks = elem.audioTracks || []; + console.debug(`found ${elemAudioTracks.length} audio tracks`); - for (i = 0, length = elemAudioTracks.length; i < length; i++) { + for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { if (audioIndex === i) { - console.debug('setting audio track ' + i + ' to enabled'); - elemAudioTracks[i].enabled = true; + console.debug(`setting audio track ${i} to enabled`); + audioTrack.enabled = true; } else { - console.debug('setting audio track ' + i + ' to disabled'); - elemAudioTracks[i].enabled = false; + console.debug(`setting audio track ${i} to disabled`); + audioTrack.enabled = false; } } - }; + } - self.stop = function (destroyPlayer) { - var elem = self._mediaElement; - var src = self._currentSrc; + stop(destroyPlayer) { + const elem = this.#mediaElement; + const src = this.#currentSrc; if (elem) { if (src) { elem.pause(); } - htmlMediaHelper.onEndedInternal(self, elem, onError); + onEndedInternal(this, elem, this.onError); if (destroyPlayer) { - self.destroy(); + this.destroy(); } } - destroyCustomTrack(elem); + this.destroyCustomTrack(elem); return Promise.resolve(); - }; + } - self.destroy = function () { - htmlMediaHelper.destroyHlsPlayer(self); - htmlMediaHelper.destroyFlvPlayer(self); + destroy() { + destroyHlsPlayer(this); + destroyFlvPlayer(this); appRouter.setTransparency('none'); - var videoElement = self._mediaElement; + const videoElement = this.#mediaElement; if (videoElement) { - self._mediaElement = null; + this.#mediaElement = null; - destroyCustomTrack(videoElement); - videoElement.removeEventListener('timeupdate', onTimeUpdate); - videoElement.removeEventListener('ended', onEnded); - videoElement.removeEventListener('volumechange', onVolumeChange); - videoElement.removeEventListener('pause', onPause); - videoElement.removeEventListener('playing', onPlaying); - videoElement.removeEventListener('play', onPlay); - videoElement.removeEventListener('click', onClick); - videoElement.removeEventListener('dblclick', onDblClick); - videoElement.removeEventListener('waiting', onWaiting); + this.destroyCustomTrack(videoElement); + videoElement.removeEventListener('timeupdate', this.onTimeUpdate); + videoElement.removeEventListener('ended', this.onEnded); + videoElement.removeEventListener('volumechange', this.onVolumeChange); + videoElement.removeEventListener('pause', this.onPause); + videoElement.removeEventListener('playing', this.onPlaying); + videoElement.removeEventListener('play', this.onPlay); + videoElement.removeEventListener('click', this.onClick); + videoElement.removeEventListener('dblclick', this.onDblClick); + videoElement.removeEventListener('waiting', this.onWaiting); videoElement.parentNode.removeChild(videoElement); } - var dlg = videoDialog; + const dlg = this.#videoDialog; if (dlg) { - videoDialog = null; + this.#videoDialog = null; dlg.parentNode.removeChild(dlg); } if (screenfull.isEnabled) { screenfull.exit(); + } else { + // iOS Safari + if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { + document.webkitCancelFullscreen(); + } } - }; - - function onEnded() { - destroyCustomTrack(this); - htmlMediaHelper.onEndedInternal(self, this, onError); } - function onTimeUpdate(e) { - // get the player position and the transcoding offset - var time = this.currentTime; + /** + * @private + * @param e {Event} The event received from the `