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.browser,
// Chromecast globals
'chrome': 'readonly',
// Tizen globals
'tizen': false,
'webapis': false,
'tizen': 'readonly',
'webapis': 'readonly',
// WebOS globals
'webOS': false,
// Dependency globals
'$': false,
'jQuery': false,
'webOS': 'readonly',
// Jellyfin globals
'ApiClient': true,
'Events': true,
'chrome': true,
'Emby': false,
'Hls': true,
'LibraryMenu': true,
'Windows': false,
'ApiClient': 'readonly',
// Build time definitions
__COMMIT_SHA__: false,
__JF_BUILD_VERSION__: false,
__PACKAGE_JSON_NAME__: false,
__PACKAGE_JSON_VERSION__: false,
__USE_SYSTEM_FONTS__: false,
__WEBPACK_SERVE__: false
__COMMIT_SHA__: 'readonly',
__JF_BUILD_VERSION__: 'readonly',
__PACKAGE_JSON_NAME__: 'readonly',
__PACKAGE_JSON_VERSION__: 'readonly',
__USE_SYSTEM_FONTS__: 'readonly',
__WEBPACK_SERVE__: 'readonly'
}
},
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 Events from '../utils/events.ts';
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) {
elem.src = '';
elem.innerHTML = '';
@@ -260,7 +244,7 @@ export function destroyFlvPlayer(instance) {
}
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 () {
if (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));
// 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
) {
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) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
case HlsErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code === 0) {
// 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;
case Hls.ErrorTypes.MEDIA_ERROR:
case HlsErrorTypes.MEDIA_ERROR:
console.debug('fatal media error encountered, try to recover');
handleHlsJsMediaError(instance, reject);
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 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 '../../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 'elements/emby-button/emby-button';
import 'elements/emby-button/paper-icon-button-light';
import './recordingfields.scss';
import 'styles/flexstyles.scss';
function loadData(parent, program) {
if (program.IsSeries) {
parent.querySelector('.recordSeriesContainer').classList.remove('hide');
@@ -1,4 +1,5 @@
import 'jquery';
import $ from 'jquery';
import loading from '../loading/loading';
import globalize from '../../lib/globalize';
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 globalize from '../../lib/globalize';
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 Dashboard from 'utils/dashboard';
import './viewManager/viewContainer.scss';
import Dashboard from '../utils/dashboard';
const getMainAnimatedPages = () => {
return document.querySelector('.mainAnimatedPages');
@@ -65,13 +68,13 @@ export function loadView(options) {
}
if (currentPage) {
if (newViewInfo.hasScript && window.$) {
if (newViewInfo.hasScript) {
mainAnimatedPages.removeChild(currentPage);
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.replaceChild(view, currentPage);
}
} else if (newViewInfo.hasScript && window.$) {
} else if (newViewInfo.hasScript) {
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.appendChild(view);
@@ -109,11 +112,6 @@ export function loadView(options) {
afterAnimate(allPages, pageIndex);
}
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view;
});
}
@@ -213,11 +211,6 @@ export function tryRestoreView(options) {
afterAnimate(allPages, index);
}
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view;
});
}
@@ -1,17 +1,18 @@
import { OutboundWebSocketMessageType } from '@jellyfin/sdk/lib/websocket';
import 'webcomponents.js/webcomponents-lite';
import Sortable from 'sortablejs';
import itemShortcuts from '../../components/shortcuts';
import inputManager from '../../scripts/inputManager';
import { playbackManager } from '../../components/playback/playbackmanager';
import imageLoader from '../../components/images/imageLoader';
import layoutManager from '../../components/layoutManager';
import browser from '../../scripts/browser';
import dom from '../../utils/dom';
import loading from '../../components/loading/loading';
import focusManager from '../../components/focusManager';
import focusManager from 'components/focusManager';
import imageLoader from 'components/images/imageLoader';
import layoutManager from 'components/layoutManager';
import loading from 'components/loading/loading';
import { playbackManager } from 'components/playback/playbackmanager';
import itemShortcuts from 'components/shortcuts';
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);
+23 -27
View File
@@ -50,13 +50,12 @@ function supportsFade() {
return !browser.tv;
}
function requireHlsPlayer(callback) {
import('hls.js/dist/hls.js').then(({ default: hls }) => {
function requireHlsPlayer() {
return import('hls.js/dist/hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
return hls;
});
}
@@ -174,26 +173,25 @@ class HtmlAudioPlayer {
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) {
requireHlsPlayer(async () => {
const includeCorsCredentials = await getIncludeCorsCredentials();
const hls = new Hls({
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;
const hls = new Hls({
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;
});
}, async () => {
elem.autoplay = true;
@@ -204,11 +202,9 @@ class HtmlAudioPlayer {
elem.crossOrigin = 'use-credentials';
}
return htmlMediaHelper.applySrc(elem, val, options).then(function () {
self._currentSrc = val;
return htmlMediaHelper.playWithPromise(elem, onError);
});
elem.src = val;
self._currentSrc = val;
return htmlMediaHelper.playWithPromise(elem, onError);
});
}
+36 -67
View File
@@ -24,7 +24,6 @@ import {
destroyCastPlayer,
getCrossOriginValue,
enableHlsJsPlayerForCodecs,
applySrc,
resetSrc,
playWithPromise,
onEndedInternal,
@@ -119,13 +118,12 @@ function enableNativeTrackSupport(mediaSource, track) {
return true;
}
function requireHlsPlayer(callback) {
import('hls.js/dist/hls.js').then(({ default: hls }) => {
function requireHlsPlayer() {
return import('hls.js/dist/hls.js').then(({ default: hls }) => {
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
return hls;
});
}
@@ -549,41 +547,40 @@ export class HtmlVideoPlayer {
/**
* @private
*/
setSrcWithHlsJs(elem, options, url) {
async setSrcWithHlsJs(elem, options, url) {
const Hls = await requireHlsPlayer();
const includeCorsCredentials = await getIncludeCorsCredentials();
return new Promise((resolve, reject) => {
requireHlsPlayer(async () => {
let maxBufferLength = 30;
let maxBufferLength = 30;
// Some browsers cannot handle huge fragments in high bitrate.
// 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.
// https://github.com/video-dev/hls.js/issues/876
if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) {
maxBufferLength = 6;
// Some browsers cannot handle huge fragments in high bitrate.
// 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.
// https://github.com/video-dev/hls.js/issues/876
if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) {
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';
}
return applySrc(elem, val, options).then(() => {
this.#currentSrc = val;
return playWithPromise(elem, this.onError);
});
elem.src = val;
this.#currentSrc = val;
return playWithPromise(elem, this.onError);
}
}
@@ -1328,25 +1323,10 @@ export class HtmlVideoPlayer {
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
*/
async fetchSubtitles(track, item) {
if (window.Windows && itemHelper.isLocalItem(item)) {
return this.fetchSubtitlesUwp(track, item);
}
this.incrementFetchQueue();
try {
const response = await fetch(getTextTrackUrl(track, item, '.js'));
@@ -1956,10 +1936,6 @@ export class HtmlVideoPlayer {
if (
// Check non-standard Safari PiP support
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
|| document.pictureInPictureEnabled
) {
@@ -2043,13 +2019,6 @@ export class HtmlVideoPlayer {
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') {
video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline');
}
+2 -2
View File
@@ -1,5 +1,5 @@
import escapeHtml from 'escape-html';
import 'jquery';
import $ from 'jquery';
import 'material-design-icons-iconfont';
import globalize from 'lib/globalize';
@@ -281,7 +281,7 @@ function updateEditorNode(page, item) {
$('.editorNode', elem).remove();
$(elem).append(getNodeInnerHtml(item));
if (item.IsFolder) {
const tree = jQuery.jstree._reference('.libraryTree');
const tree = $.jstree._reference('.libraryTree');
const currentNode = tree._get_node(null, false);
tree.refresh(currentNode);
}
+3 -1
View File
@@ -1,3 +1,5 @@
import $ from 'jquery';
import globalize from 'lib/globalize';
export function showLayoutMenu (button, currentLayout, views) {
@@ -30,7 +32,7 @@ export function showLayoutMenu (button, currentLayout, views) {
cancelable: false
}));
if (!dispatchEvent && window.$) {
if (!dispatchEvent) {
$(button).trigger('layoutchange', [id]);
}
}