Cleanup global variables (#8544)

* Mark all globals readonly

* Remove legacy Windows UWP app global

* Remove unused globals

* Remove Events global

* Remove jQuery global

* Remove Hls global

* Fix player source removal

* Fix es5 compat import

* Remove jQuery ($) global
This commit is contained in:
Bill Thornton authored and GitHub committed 2026-10-20 12:33:23 -04:00
commit d071252ef0
11 files changed
+118 -174

No files matched your search

+12 -19
View File
@@ -207,29 +207,22 @@ export default tseslint.config(
}, },
globals: { globals: {
...globals.browser, ...globals.browser,
// Chromecast globals
'chrome': 'readonly',
// Tizen globals // Tizen globals
'tizen': false, 'tizen': 'readonly',
'webapis': false, 'webapis': 'readonly',
// WebOS globals // WebOS globals
'webOS': false, 'webOS': 'readonly',
// Dependency globals
'$': false,
'jQuery': false,
// Jellyfin globals // Jellyfin globals
'ApiClient': true, 'ApiClient': 'readonly',
'Events': true,
'chrome': true,
'Emby': false,
'Hls': true,
'LibraryMenu': true,
'Windows': false,
// Build time definitions // Build time definitions
__COMMIT_SHA__: false, __COMMIT_SHA__: 'readonly',
__JF_BUILD_VERSION__: false, __JF_BUILD_VERSION__: 'readonly',
__PACKAGE_JSON_NAME__: false, __PACKAGE_JSON_NAME__: 'readonly',
__PACKAGE_JSON_VERSION__: false, __PACKAGE_JSON_VERSION__: 'readonly',
__USE_SYSTEM_FONTS__: false, __USE_SYSTEM_FONTS__: 'readonly',
__WEBPACK_SERVE__: false __WEBPACK_SERVE__: 'readonly'
} }
}, },
settings: { settings: {
+8 -24
View File
@@ -1,4 +1,6 @@
import appSettings from '../scripts/settings/appSettings' ; import { Events as HlsEvents, ErrorTypes as HlsErrorTypes } from 'hls.js/dist/hls.js';
import appSettings from '../scripts/settings/appSettings';
import browser from '../scripts/browser'; import browser from '../scripts/browser';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
import { MediaError } from 'types/mediaError'; import { MediaError } from 'types/mediaError';
@@ -167,24 +169,6 @@ export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) {
} }
} }
export function applySrc(elem, src, options) {
if (window.Windows && options.mediaSource?.IsLocal) {
return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) {
const playlist = new Windows.Media.Playback.MediaPlaybackList();
const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file);
const startTime = (options.playerStartPositionTicks || 0) / 10000;
playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime));
elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true });
return Promise.resolve();
});
} else {
elem.src = src;
}
return Promise.resolve();
}
export function resetSrc(elem) { export function resetSrc(elem) {
elem.src = ''; elem.src = '';
elem.innerHTML = ''; elem.innerHTML = '';
@@ -260,7 +244,7 @@ export function destroyFlvPlayer(instance) {
} }
export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) { export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
hls.on(Hls.Events.MANIFEST_PARSED, function () { hls.on(HlsEvents.MANIFEST_PARSED, function () {
playWithPromise(elem, onErrorFn).then(resolve, function () { playWithPromise(elem, onErrorFn).then(resolve, function () {
if (reject) { if (reject) {
reject(); reject();
@@ -269,11 +253,11 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
}); });
}); });
hls.on(Hls.Events.ERROR, function (event, data) { hls.on(HlsEvents.ERROR, function (event, data) {
console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false));
// try to recover network error // try to recover network error
if (data.type === Hls.ErrorTypes.NETWORK_ERROR if (data.type === HlsErrorTypes.NETWORK_ERROR
&& data.response?.code && data.response.code >= 400 && data.response?.code && data.response.code >= 400
) { ) {
console.debug('hls.js response error code: ' + data.response.code); console.debug('hls.js response error code: ' + data.response.code);
@@ -293,7 +277,7 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
if (data.fatal) { if (data.fatal) {
switch (data.type) { switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR: case HlsErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code === 0) { if (data.response && data.response.code === 0) {
// This could be a CORS error related to access control response headers // This could be a CORS error related to access control response headers
@@ -315,7 +299,7 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
} }
break; break;
case Hls.ErrorTypes.MEDIA_ERROR: case HlsErrorTypes.MEDIA_ERROR:
console.debug('fatal media error encountered, try to recover'); console.debug('fatal media error encountered, try to recover');
handleHlsJsMediaError(instance, reject); handleHlsJsMediaError(instance, reject);
reject = null; reject = null;
@@ -1,17 +1,21 @@
import globalize from '../../lib/globalize';
import { ServerConnections } from 'lib/jellyfin-apiclient';
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket'; import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
import loading from '../loading/loading';
import dom from '../../utils/dom'; import loading from 'components/loading/loading';
import toast from 'components/toast/toast';
import globalize from 'lib/globalize';
import { ServerConnections } from 'lib/jellyfin-apiclient';
import dom from 'utils/dom';
import Events from 'utils/events';
import recordingHelper from './recordinghelper'; import recordingHelper from './recordinghelper';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import './recordingfields.scss';
import '../../styles/flexstyles.scss';
import toast from '../toast/toast';
import template from './recordingfields.template.html'; import template from './recordingfields.template.html';
import 'elements/emby-button/emby-button';
import 'elements/emby-button/paper-icon-button-light';
import './recordingfields.scss';
import 'styles/flexstyles.scss';
function loadData(parent, program) { function loadData(parent, program) {
if (program.IsSeries) { if (program.IsSeries) {
parent.querySelector('.recordSeriesContainer').classList.remove('hide'); parent.querySelector('.recordSeriesContainer').classList.remove('hide');
@@ -1,4 +1,5 @@
import 'jquery'; import $ from 'jquery';
import loading from '../loading/loading'; import loading from '../loading/loading';
import globalize from '../../lib/globalize'; import globalize from '../../lib/globalize';
import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-checkbox/emby-checkbox';
+2 -1
View File
@@ -1,4 +1,5 @@
import 'jquery'; import $ from 'jquery';
import loading from '../loading/loading'; import loading from '../loading/loading';
import globalize from '../../lib/globalize'; import globalize from '../../lib/globalize';
import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-checkbox/emby-checkbox';
+6 -13
View File
@@ -1,6 +1,9 @@
import $ from 'jquery';
import { importModule } from '@uupaa/dynamic-import-polyfill'; import { importModule } from '@uupaa/dynamic-import-polyfill';
import Dashboard from 'utils/dashboard';
import './viewManager/viewContainer.scss'; import './viewManager/viewContainer.scss';
import Dashboard from '../utils/dashboard';
const getMainAnimatedPages = () => { const getMainAnimatedPages = () => {
return document.querySelector('.mainAnimatedPages'); return document.querySelector('.mainAnimatedPages');
@@ -65,13 +68,13 @@ export function loadView(options) {
} }
if (currentPage) { if (currentPage) {
if (newViewInfo.hasScript && window.$) { if (newViewInfo.hasScript) {
mainAnimatedPages.removeChild(currentPage); mainAnimatedPages.removeChild(currentPage);
view = $(view).appendTo(mainAnimatedPages)[0]; view = $(view).appendTo(mainAnimatedPages)[0];
} else { } else {
mainAnimatedPages.replaceChild(view, currentPage); mainAnimatedPages.replaceChild(view, currentPage);
} }
} else if (newViewInfo.hasScript && window.$) { } else if (newViewInfo.hasScript) {
view = $(view).appendTo(mainAnimatedPages)[0]; view = $(view).appendTo(mainAnimatedPages)[0];
} else { } else {
mainAnimatedPages.appendChild(view); mainAnimatedPages.appendChild(view);
@@ -109,11 +112,6 @@ export function loadView(options) {
afterAnimate(allPages, pageIndex); afterAnimate(allPages, pageIndex);
} }
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view; return view;
}); });
} }
@@ -213,11 +211,6 @@ export function tryRestoreView(options) {
afterAnimate(allPages, index); afterAnimate(allPages, index);
} }
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view; return view;
}); });
} }
@@ -1,17 +1,18 @@
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
import 'webcomponents.js/webcomponents-lite'; import 'webcomponents.js/webcomponents-lite';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import itemShortcuts from '../../components/shortcuts'; import focusManager from 'components/focusManager';
import inputManager from '../../scripts/inputManager'; import imageLoader from 'components/images/imageLoader';
import { playbackManager } from '../../components/playback/playbackmanager'; import layoutManager from 'components/layoutManager';
import imageLoader from '../../components/images/imageLoader'; import loading from 'components/loading/loading';
import layoutManager from '../../components/layoutManager'; import { playbackManager } from 'components/playback/playbackmanager';
import browser from '../../scripts/browser'; import itemShortcuts from 'components/shortcuts';
import dom from '../../utils/dom';
import loading from '../../components/loading/loading';
import focusManager from '../../components/focusManager';
import { ServerConnections } from 'lib/jellyfin-apiclient'; import { ServerConnections } from 'lib/jellyfin-apiclient';
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket'; import browser from 'scripts/browser';
import inputManager from 'scripts/inputManager';
import dom from 'utils/dom';
import Events from 'utils/events';
const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype);
+23 -27
View File
@@ -50,13 +50,12 @@ function supportsFade() {
return !browser.tv; return !browser.tv;
} }
function requireHlsPlayer(callback) { function requireHlsPlayer() {
import('hls.js/dist/hls.js').then(({ default: hls }) => { return import('hls.js/dist/hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false; hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity; hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90; hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls; return hls;
callback();
}); });
} }
@@ -174,26 +173,25 @@ class HtmlAudioPlayer {
navigator.audioSession.type = 'playback'; navigator.audioSession.type = 'playback';
} }
return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () { return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(async function () {
const Hls = await requireHlsPlayer();
const includeCorsCredentials = await getIncludeCorsCredentials();
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
requireHlsPlayer(async () => { const hls = new Hls({
const includeCorsCredentials = await getIncludeCorsCredentials(); manifestLoadingTimeOut: 20000,
xhrSetup: function (xhr) {
const hls = new Hls({ xhr.withCredentials = includeCorsCredentials;
manifestLoadingTimeOut: 20000, }
xhrSetup: function (xhr) {
xhr.withCredentials = includeCorsCredentials;
}
});
hls.loadSource(val);
hls.attachMedia(elem);
htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject);
self._hlsPlayer = hls;
self._currentSrc = val;
}); });
hls.loadSource(val);
hls.attachMedia(elem);
htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject);
self._hlsPlayer = hls;
self._currentSrc = val;
}); });
}, async () => { }, async () => {
elem.autoplay = true; elem.autoplay = true;
@@ -204,11 +202,9 @@ class HtmlAudioPlayer {
elem.crossOrigin = 'use-credentials'; elem.crossOrigin = 'use-credentials';
} }
return htmlMediaHelper.applySrc(elem, val, options).then(function () { elem.src = val;
self._currentSrc = val; self._currentSrc = val;
return htmlMediaHelper.playWithPromise(elem, onError);
return htmlMediaHelper.playWithPromise(elem, onError);
});
}); });
} }
+36 -67
View File
@@ -24,7 +24,6 @@ import {
destroyCastPlayer, destroyCastPlayer,
getCrossOriginValue, getCrossOriginValue,
enableHlsJsPlayerForCodecs, enableHlsJsPlayerForCodecs,
applySrc,
resetSrc, resetSrc,
playWithPromise, playWithPromise,
onEndedInternal, onEndedInternal,
@@ -119,13 +118,12 @@ function enableNativeTrackSupport(mediaSource, track) {
return true; return true;
} }
function requireHlsPlayer(callback) { function requireHlsPlayer() {
import('hls.js/dist/hls.js').then(({ default: hls }) => { return import('hls.js/dist/hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false; hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity; hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90; hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls; return hls;
callback();
}); });
} }
@@ -549,41 +547,40 @@ export class HtmlVideoPlayer {
/** /**
* @private * @private
*/ */
setSrcWithHlsJs(elem, options, url) { async setSrcWithHlsJs(elem, options, url) {
const Hls = await requireHlsPlayer();
const includeCorsCredentials = await getIncludeCorsCredentials();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
requireHlsPlayer(async () => { let maxBufferLength = 30;
let maxBufferLength = 30;
// Some browsers cannot handle huge fragments in high bitrate. // Some browsers cannot handle huge fragments in high bitrate.
// This issue usually happens when using HWA encoders with a high bitrate setting. // This issue usually happens when using HWA encoders with a high bitrate setting.
// Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome. // Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome.
// https://github.com/video-dev/hls.js/issues/876 // https://github.com/video-dev/hls.js/issues/876
if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) { if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) {
maxBufferLength = 6; maxBufferLength = 6;
}
const hls = new Hls({
startPosition: options.playerStartPositionTicks / 10000000,
manifestLoadingTimeOut: 20000,
maxBufferLength,
maxMaxBufferLength: maxBufferLength,
videoPreference: { preferHDR: true },
xhrSetup(xhr) {
xhr.withCredentials = includeCorsCredentials;
} }
const includeCorsCredentials = await getIncludeCorsCredentials();
const hls = new Hls({
startPosition: options.playerStartPositionTicks / 10000000,
manifestLoadingTimeOut: 20000,
maxBufferLength: maxBufferLength,
maxMaxBufferLength: maxBufferLength,
videoPreference: { preferHDR: true },
xhrSetup(xhr) {
xhr.withCredentials = includeCorsCredentials;
}
});
hls.loadSource(url);
hls.attachMedia(elem);
bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject);
this._hlsPlayer = hls;
// This is needed in setCurrentTrackElement
this.#currentSrc = url;
}); });
hls.loadSource(url);
hls.attachMedia(elem);
bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject);
this._hlsPlayer = hls;
// This is needed in setCurrentTrackElement
this.#currentSrc = url;
}); });
} }
@@ -657,11 +654,9 @@ export class HtmlVideoPlayer {
elem.crossOrigin = 'use-credentials'; elem.crossOrigin = 'use-credentials';
} }
return applySrc(elem, val, options).then(() => { elem.src = val;
this.#currentSrc = val; this.#currentSrc = val;
return playWithPromise(elem, this.onError);
return playWithPromise(elem, this.onError);
});
} }
} }
@@ -1328,25 +1323,10 @@ export class HtmlVideoPlayer {
this.#currentBitmapSubRenderer = null; this.#currentBitmapSubRenderer = null;
} }
/**
* @private
*/
fetchSubtitlesUwp(track) {
return Windows.Storage.StorageFile.getFileFromPathAsync(track.Path).then(function (storageFile) {
return Windows.Storage.FileIO.readTextAsync(storageFile);
}).then(function (text) {
return JSON.parse(text);
});
}
/** /**
* @private * @private
*/ */
async fetchSubtitles(track, item) { async fetchSubtitles(track, item) {
if (window.Windows && itemHelper.isLocalItem(item)) {
return this.fetchSubtitlesUwp(track, item);
}
this.incrementFetchQueue(); this.incrementFetchQueue();
try { try {
const response = await fetch(getTextTrackUrl(track, item, '.js')); const response = await fetch(getTextTrackUrl(track, item, '.js'));
@@ -1956,10 +1936,6 @@ export class HtmlVideoPlayer {
if ( if (
// Check non-standard Safari PiP support // Check non-standard Safari PiP support
typeof video.webkitSupportsPresentationMode === 'function' && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === 'function' typeof video.webkitSupportsPresentationMode === 'function' && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === 'function'
// Check non-standard Windows PiP support
|| (window.Windows
&& Windows.UI.ViewManagement.ApplicationView.getForCurrentView()
.isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay))
// Check standard PiP support // Check standard PiP support
|| document.pictureInPictureEnabled || document.pictureInPictureEnabled
) { ) {
@@ -2043,13 +2019,6 @@ export class HtmlVideoPlayer {
document.exitPictureInPicture().catch(HtmlVideoPlayer.onPictureInPictureError); document.exitPictureInPicture().catch(HtmlVideoPlayer.onPictureInPictureError);
} }
} }
} else if (window.Windows) {
this.isPip = isEnabled;
if (isEnabled) {
Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay);
} else {
Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.default);
}
} else if (video?.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { } else if (video?.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') {
video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline'); video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline');
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import escapeHtml from 'escape-html'; import escapeHtml from 'escape-html';
import 'jquery'; import $ from 'jquery';
import 'material-design-icons-iconfont'; import 'material-design-icons-iconfont';
import globalize from 'lib/globalize'; import globalize from 'lib/globalize';
@@ -281,7 +281,7 @@ function updateEditorNode(page, item) {
$('.editorNode', elem).remove(); $('.editorNode', elem).remove();
$(elem).append(getNodeInnerHtml(item)); $(elem).append(getNodeInnerHtml(item));
if (item.IsFolder) { if (item.IsFolder) {
const tree = jQuery.jstree._reference('.libraryTree'); const tree = $.jstree._reference('.libraryTree');
const currentNode = tree._get_node(null, false); const currentNode = tree._get_node(null, false);
tree.refresh(currentNode); tree.refresh(currentNode);
} }
+3 -1
View File
@@ -1,3 +1,5 @@
import $ from 'jquery';
import globalize from 'lib/globalize'; import globalize from 'lib/globalize';
export function showLayoutMenu (button, currentLayout, views) { export function showLayoutMenu (button, currentLayout, views) {
@@ -30,7 +32,7 @@ export function showLayoutMenu (button, currentLayout, views) {
cancelable: false cancelable: false
})); }));
if (!dispatchEvent && window.$) { if (!dispatchEvent) {
$(button).trigger('layoutchange', [id]); $(button).trigger('layoutchange', [id]);
} }
} }