From d22b75076c80c84be5e471bac48a9ab42841a387 Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Sat, 11 Nov 2023 11:51:25 +0000 Subject: [PATCH 001/238] Add option to enable replaygain scanning --- .../libraryoptionseditor/libraryoptionseditor.js | 4 ++++ .../libraryoptionseditor.template.html | 8 ++++++++ src/strings/en-us.json | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 11c0806104..0b9ce0b967 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -416,6 +416,8 @@ export function setContentType(parent, contentType) { } } + parent.querySelector('.chkUseReplayGainTagsContainer').classList.toggle('hide', contentType !== 'music'); + parent.querySelector('.chkEnableLUFSScanContainer').classList.toggle('hide', contentType !== 'music'); if (contentType === 'tvshows') { @@ -515,6 +517,7 @@ export function getLibraryOptions(parent) { EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked, + UseReplayGainTags: parent.querySelector('.chkUseReplayGainTags').checked, ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, EnableInternetProviders: true, @@ -577,6 +580,7 @@ export function setLibraryOptions(parent, options) { parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan; + parent.querySelector('.chkUseReplayGainTags').checked = options.UseReplayGainTags; parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index a221f53bbf..bf3bd7aaa8 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -55,6 +55,14 @@
${LabelEnableRealtimeMonitorHelp}
+
+ +
${LabelUseReplayGainTagsHelp}
+
+
+
+

${Trickplay}

+
+ +
${ExtractTrickplayImagesHelp}
+
+ +
+ +
${LabelExtractTrickplayDuringLibraryScanHelp}
+
+
+

${HeaderChapterImages}

diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index cf8cf41c29..503b844859 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -125,7 +125,7 @@ function getItemsForPlayback(serverId, query) { } else { query.Limit = query.Limit || 300; } - query.Fields = 'Chapters'; + query.Fields = ['Chapters', 'Trickplay']; query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; @@ -1858,7 +1858,7 @@ class PlaybackManager { IsVirtualUnaired: false, IsMissing: false, UserId: apiClient.getCurrentUserId(), - Fields: 'Chapters' + Fields: ['Chapters', 'Trickplay'] }).then(function (episodesResult) { const originalResults = episodesResult.Items; const isSeries = firstItem.Type === 'Series'; @@ -1940,7 +1940,7 @@ class PlaybackManager { IsVirtualUnaired: false, IsMissing: false, UserId: apiClient.getCurrentUserId(), - Fields: 'Chapters' + Fields: ['Chapters', 'Trickplay'] }).then(function (episodesResult) { let foundItem = false; episodesResult.Items = episodesResult.Items.filter(function (e) { diff --git a/src/controllers/dashboard/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html index 330dac8a1a..82e2c3d6f7 100644 --- a/src/controllers/dashboard/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -135,6 +135,12 @@ ${AllowAv1Encoding}
+
+ +
diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index 3d434df7f6..86152cb9e9 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -19,6 +19,7 @@ function loadPage(page, config, systemInfo) { page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; page.querySelector('#chkAllowAv1Encoding').checked = config.AllowAv1Encoding; + page.querySelector('#chkAllowMjpegEncoding').checked = config.AllowMjpegEncoding; $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); $('#selectThreadCount', page).val(config.EncodingThreadCount); page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; @@ -125,6 +126,7 @@ function onSubmit() { config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; config.AllowAv1Encoding = form.querySelector('#chkAllowAv1Encoding').checked; + config.AllowMjpegEncoding = form.querySelector('#chkAllowMjpegEncoding').checked; ApiClient.updateNamedConfiguration('encoding', config).then(function () { updateEncoder(form); }, function () { @@ -175,6 +177,9 @@ function getTabs() { }, { href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') + }, { + href: '#/dashboard/playback/trickplay', + name: globalize.translate('Trickplay') }]; } diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index 6b4985df34..f0d35f02ee 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -39,6 +39,9 @@ function getTabs() { }, { href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') + }, { + href: '#/dashboard/playback/trickplay', + name: globalize.translate('Trickplay') }]; } @@ -52,4 +55,3 @@ $(document).on('pageinit', '#playbackConfigurationPage', function () { loadPage(page, config); }); }); - diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index c02a5cdbde..d6ca058743 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -30,6 +30,9 @@ function getTabs() { }, { href: '#/dashboard/playback/streaming', name: globalize.translate('TabStreaming') + }, { + href: '#/dashboard/playback/trickplay', + name: globalize.translate('Trickplay') }]; } diff --git a/src/controllers/dashboard/trickplay.html b/src/controllers/dashboard/trickplay.html new file mode 100644 index 0000000000..fb9028a010 --- /dev/null +++ b/src/controllers/dashboard/trickplay.html @@ -0,0 +1,65 @@ +
+
+
+
+
+

${Trickplay}

+
+
+
+ +
+
+
+ +
${LabelScanBehaviorHelp}
+
+
+ +
${LabelProcessPriorityHelp}
+
+
+ +
${LabelImageIntervalHelp}
+
+
+ +
${LabelWidthResolutionsHelp}
+
+
+ +
${LabelTileWidthHelp}
+
+
+ +
${LabelTileHeightHelp}
+
+
+ +
${LabelJpegQualityHelp}
+
+
+ +
${LabelQscaleHelp}
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/controllers/dashboard/trickplay.js b/src/controllers/dashboard/trickplay.js new file mode 100644 index 0000000000..674434274a --- /dev/null +++ b/src/controllers/dashboard/trickplay.js @@ -0,0 +1,71 @@ +import 'jquery'; +import loading from '../../components/loading/loading'; +import libraryMenu from '../../scripts/libraryMenu'; +import globalize from '../../scripts/globalize'; +import Dashboard from '../../utils/dashboard'; + +function loadPage(page, config) { + const trickplayOptions = config.TrickplayOptions; + + page.querySelector('#chkEnableHwAcceleration').checked = trickplayOptions.EnableHwAcceleration; + $('#selectScanBehavior', page).val(trickplayOptions.ScanBehavior); + $('#selectProcessPriority', page).val(trickplayOptions.ProcessPriority); + $('#txtInterval', page).val(trickplayOptions.Interval); + $('#txtWidthResolutions', page).val(trickplayOptions.WidthResolutions.join(',')); + $('#txtTileWidth', page).val(trickplayOptions.TileWidth); + $('#txtTileHeight', page).val(trickplayOptions.TileHeight); + $('#txtQscale', page).val(trickplayOptions.Qscale); + $('#txtJpegQuality', page).val(trickplayOptions.JpegQuality); + $('#txtProcessThreads', page).val(trickplayOptions.ProcessThreads); + loading.hide(); +} + +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + const trickplayOptions = config.TrickplayOptions; + + trickplayOptions.EnableHwAcceleration = form.querySelector('#chkEnableHwAcceleration').checked; + trickplayOptions.ScanBehavior = $('#selectScanBehavior', form).val(); + trickplayOptions.ProcessPriority = $('#selectProcessPriority', form).val(); + trickplayOptions.Interval = Math.max(0, parseInt($('#txtInterval', form).val() || '10000', 10)); + trickplayOptions.WidthResolutions = $('#txtWidthResolutions', form).val().replace(' ', '').split(',').map(Number); + trickplayOptions.TileWidth = Math.max(1, parseInt($('#txtTileWidth', form).val() || '10', 10)); + trickplayOptions.TileHeight = Math.max(1, parseInt($('#txtTileHeight', form).val() || '10', 10)); + trickplayOptions.Qscale = Math.min(31, parseInt($('#txtQscale', form).val() || '10', 10)); + trickplayOptions.JpegQuality = Math.min(100, parseInt($('#txtJpegQuality', form).val() || '80', 10)); + trickplayOptions.ProcessThreads = parseInt($('#txtProcessThreads', form).val() || '0', 10); + + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + + return false; +} + +function getTabs() { + return [{ + href: '#/dashboard/playback/transcoding', + name: globalize.translate('Transcoding') + }, { + href: '#/dashboard/playback/resume', + name: globalize.translate('ButtonResume') + }, { + href: '#/dashboard/playback/streaming', + name: globalize.translate('TabStreaming') + }, { + href: '#/dashboard/playback/trickplay', + name: globalize.translate('Trickplay') + }]; +} + +$(document).on('pageinit', '#trickplayConfigurationPage', function () { + $('.trickplayConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#trickplayConfigurationPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 3, getTabs); + const page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); +}); diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 2cb099856b..5f212bfc3e 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1356,6 +1356,81 @@ export default function (view) { resetIdle(); } + function updateTrickplayBubbleHtml(apiClient, trickplayInfo, item, mediaSourceId, bubble, positionTicks) { + let doFullUpdate = false; + let chapterThumbContainer = bubble.querySelector('.chapterThumbContainer'); + let chapterThumb; + let chapterThumbText; + + // Create bubble elements if they don't already exist + if (chapterThumbContainer) { + chapterThumb = chapterThumbContainer.querySelector('.chapterThumb'); + chapterThumbText = chapterThumbContainer.querySelector('.chapterThumbText'); + } else { + doFullUpdate = true; + + chapterThumbContainer = document.createElement('div'); + chapterThumbContainer.classList.add('chapterThumbContainer'); + chapterThumbContainer.style.overflow = 'hidden'; + + const chapterThumbWrapper = document.createElement('div'); + chapterThumbWrapper.classList.add('chapterThumbWrapper'); + chapterThumbWrapper.style.overflow = 'hidden'; + chapterThumbWrapper.style.position = 'relative'; + chapterThumbWrapper.style.width = trickplayInfo.Width + 'px'; + chapterThumbWrapper.style.height = trickplayInfo.Height + 'px'; + chapterThumbContainer.appendChild(chapterThumbWrapper); + + chapterThumb = document.createElement('img'); + chapterThumb.classList.add('chapterThumb'); + chapterThumb.style.position = 'absolute'; + chapterThumb.style.width = 'unset'; + chapterThumb.style.minWidth = 'unset'; + chapterThumb.style.height = 'unset'; + chapterThumb.style.minHeight = 'unset'; + chapterThumbWrapper.appendChild(chapterThumb); + + const chapterThumbTextContainer = document.createElement('div'); + chapterThumbTextContainer.classList.add('chapterThumbTextContainer'); + chapterThumbContainer.appendChild(chapterThumbTextContainer); + + chapterThumbText = document.createElement('h2'); + chapterThumbText.classList.add('chapterThumbText'); + chapterThumbTextContainer.appendChild(chapterThumbText); + } + + // Update trickplay values + const currentTimeMs = positionTicks / 10_000; + const currentTile = Math.floor(currentTimeMs / trickplayInfo.Interval); + + const tileSize = trickplayInfo.TileWidth * trickplayInfo.TileHeight; + const tileOffset = currentTile % tileSize; + const index = Math.floor(currentTile / tileSize); + + const tileOffsetX = tileOffset % trickplayInfo.TileWidth; + const tileOffsetY = Math.floor(tileOffset / trickplayInfo.TileWidth); + const offsetX = -(tileOffsetX * trickplayInfo.Width); + const offsetY = -(tileOffsetY * trickplayInfo.Height); + + const imgSrc = apiClient.getUrl('Videos/' + item.Id + '/Trickplay/' + trickplayInfo.Width + '/' + index + '.jpg', { + api_key: apiClient.accessToken(), + MediaSourceId: mediaSourceId + }); + + if (chapterThumb.src != imgSrc) chapterThumb.src = imgSrc; + chapterThumb.style.left = offsetX + 'px'; + chapterThumb.style.top = offsetY + 'px'; + + chapterThumbText.textContent = datetime.getDisplayRunningTime(positionTicks); + + // Set bubble innerHTML if container isn't part of DOM + if (doFullUpdate) { + bubble.innerHTML = chapterThumbContainer.outerHTML; + } + + return true; + } + function getImgUrl(item, chapter, index, maxWidth, apiClient) { if (chapter.ImageTag) { return apiClient.getScaledImageUrl(item.Id, { @@ -1681,6 +1756,33 @@ export default function (view) { } }); + nowPlayingPositionSlider.updateBubbleHtml = function(bubble, value) { + showOsd(); + + const item = currentItem; + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; + + if (item?.Trickplay) { + const mediaSourceId = currentPlayer?.streamInfo?.mediaSource?.Id; + const trickplayResolutions = item.Trickplay[mediaSourceId]; + + if (!trickplayResolutions) return false; + + // TODO: just to test. must pick proper resolution and/or check above that trickplay resolutions has at least one key. + return updateTrickplayBubbleHtml( + ServerConnections.getApiClient(item.ServerId), + trickplayResolutions[Object.keys(trickplayResolutions)[0]], + item, + mediaSourceId, + bubble, + ticks); + } + + return false; + }; + nowPlayingPositionSlider.getBubbleHtml = function (value) { showOsd(); if (enableProgressByTimeOfDay) { diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index ddbe9c596c..86a52c0787 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -161,6 +161,10 @@ function updateBubble(range, percent, value, bubble) { let html; + if (range.updateBubbleHtml?.(bubble, value)) { + return; + } + if (range.getBubbleHtml) { html = range.getBubbleHtml(percent, value); } else { diff --git a/src/plugins/syncPlay/core/Helper.js b/src/plugins/syncPlay/core/Helper.js index 47f25578a3..1489f1e156 100644 --- a/src/plugins/syncPlay/core/Helper.js +++ b/src/plugins/syncPlay/core/Helper.js @@ -84,7 +84,7 @@ export function getItemsForPlayback(apiClient, query) { }); } else { query.Limit = query.Limit || 300; - query.Fields = 'Chapters'; + query.Fields = ['Chapters', 'Trickplay']; query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; @@ -200,7 +200,7 @@ export function translateItemsForPlayback(apiClient, items, options) { IsVirtualUnaired: false, IsMissing: false, UserId: apiClient.getCurrentUserId(), - Fields: 'Chapters' + Fields: ['Chapters', 'Trickplay'] }).then(function (episodesResult) { let foundItem = false; episodesResult.Items = episodesResult.Items.filter(function (e) { diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 1f282a2112..4daf77243c 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1,4 +1,37 @@ { + "Trickplay": "Trickplay", + "TrickplayHardwareAccel": "Enable hardware acceleration", + "NonBlockingScan": "Non Blocking - queues generation, then returns", + "BlockingScan": "Blocking - queues generation, blocks scan until complete", + "LabelScanBehavior": "Scan Behavior", + "LabelScanBehaviorHelp": "The default behavior is non blocking, which will add media to the library before trickplay generation is done. Blocking will ensure trickplay files are generated before media is added to the library, but will make scans significantly longer.", + "PriorityHigh": "High", + "PriorityAboveNormal": "Above Normal", + "PriorityNormal": "Normal", + "PriorityBelowNormal": "Below Normal", + "PriorityIdle": "Idle", + "LabelProcessPriority": "Process Priority", + "LabelProcessPriorityHelp": "Setting this lower or higher will determine how the CPU prioritizes the ffmpeg preview images generation process in relation to other processes. If you notice slowdown while generating preview images but don't want to fully stop their generation, try lowering this as well as the thread count.", + "LabelImageInterval": "ImageInterval", + "LabelImageIntervalHelp": "Interval of time between each new trickplay image. A shorter interval and more images will take up more space.", + "LabelWidthResolutions": "Width Resolutions", + "LabelWidthResolutionsHelp": "Comma separated list of the width (px) that trickplay images will be generated at. All images should generate proportionally to the source, so a width of 320 on a 16:9 video ends up around 320x180.", + "LabelTileWidth": "Tile Width", + "LabelTileWidthHelp": "Maximum number of images to tile in X direction.", + "LabelTileHeight": "Tile Height", + "LabelTileHeightHelp": "Maximum number of images to tile in Y direction.", + "LabelJpegQuality": "JPEG Quality", + "LabelJpegQualityHelp": "The image quality for preview image tiles.", + "LabelQscale": "Qscale", + "LabelQscaleHelp": "The quality scale of images output by ffmpeg, with 2 being the highest quality and 31 being the lowest.", + "LabelTrickplayThreads": "FFmpeg Threads", + "LabelTrickplayThreadsHelp": "The number of threads to pass to the \"-threads\" argument of ffmpeg.", + "OptionExtractTrickplayImage": "Enable trickplay image extraction", + "ExtractTrickplayImagesHelp": "Trickplay images are similar to chapter images, except they span the entire length of the content and are used to show a preview when scrubbing through videos.", + "LabelExtractTrickplayDuringLibraryScan": "Extract trickplay images during the library scan", + "LabelExtractTrickplayDuringLibraryScanHelp": "Generate trickplay images when videos are imported during the library scan. Otherwise, they will be extracted during the chapter images scheduled task. If generation is set to non-blocking this will not affect the time a library scan takes to complete.", + + "Absolute": "Absolute", "AccessRestrictedTryAgainLater": "Access is currently restricted. Please try again later.", "Actor": "Actor", From 7877a43482ad2e9b5f3c6a3e62df84e63db6c947 Mon Sep 17 00:00:00 2001 From: Nick <20588554+nicknsy@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:26:49 -0800 Subject: [PATCH 003/238] Handle different trickplay resolutions --- src/controllers/dashboard/trickplay.html | 5 ++++- src/controllers/playback/video/index.js | 14 ++++++++++++-- src/strings/en-us.json | 6 ++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/controllers/dashboard/trickplay.html b/src/controllers/dashboard/trickplay.html index fb9028a010..cf0e2d44d9 100644 --- a/src/controllers/dashboard/trickplay.html +++ b/src/controllers/dashboard/trickplay.html @@ -9,9 +9,12 @@
+
+ ${EnableLibrary} + +
${EnableLibraryHelp}
+
+
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index bf71f50583..bbd2d98a3c 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -237,6 +237,8 @@ "EnableFasterAnimations": "Faster animations", "EnableFasterAnimationsHelp": "Use faster animations and transitions.", "EnableHardwareEncoding": "Enable hardware encoding", + "EnableLibrary": "Enable the library", + "EnableLibraryHelp": "Allows the library to be enabled/disabled", "EnableNextVideoInfoOverlay": "Show next video info during playback", "EnableNextVideoInfoOverlayHelp": "At the end of a video, display info about the next video coming up in the current playlist.", "EnablePhotos": "Display the photos", From dad1da94d35c594f0ac9be4bcf6b59792cc91c7e Mon Sep 17 00:00:00 2001 From: Nick <20588554+nicknsy@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:43:19 -0800 Subject: [PATCH 006/238] Remove trickplay from redirects --- src/apps/dashboard/routes/_redirects.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/apps/dashboard/routes/_redirects.ts b/src/apps/dashboard/routes/_redirects.ts index 32b6554325..94211c79c2 100644 --- a/src/apps/dashboard/routes/_redirects.ts +++ b/src/apps/dashboard/routes/_redirects.ts @@ -36,6 +36,5 @@ export const REDIRECTS: Redirect[] = [ { from: 'usernew.html', to: '/dashboard/users/add' }, { from: 'userparentalcontrol.html', to: '/dashboard/users/parentalcontrol' }, { from: 'userpassword.html', to: '/dashboard/users/password' }, - { from: 'userprofiles.html', to: '/dashboard/users' }, - { from: 'trickplayconfiguration.html', to: '/dashboard/playback/trickplay' } + { from: 'userprofiles.html', to: '/dashboard/users' } ]; From 44602d453ecd9ac8d50784be27cf3d52cf9169c7 Mon Sep 17 00:00:00 2001 From: Nick <20588554+nicknsy@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:34:18 -0800 Subject: [PATCH 007/238] Only set trickplay data on new item info --- src/controllers/playback/video/index.js | 41 +++++++++++++------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 0b0da69815..7345313f8d 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -146,6 +146,24 @@ export default function (view) { btnUserRating.classList.add('hide'); btnUserRating.setItem(null); } + + // Update trickplay data + trickplayResolution = null; + + const mediaSourceId = currentPlayer.streamInfo.mediaSource.Id; + let trickplayResolutions; + if (item.Trickplay && (trickplayResolutions = item.Trickplay[mediaSourceId])) { + // Prefer highest resolution <= 20% of total screen resolution width + let bestWidth; + const maxWidth = window.screen.width * window.devicePixelRatio * 0.2; + for (const [, info] of Object.entries(trickplayResolutions)) { + if (!bestWidth + || (info.Width < bestWidth && bestWidth > maxWidth) // Objects not guaranteed to be sorted in any order, first width might be > maxWidth. + || (info.Width > bestWidth && info.Width <= maxWidth)) bestWidth = info.Width; + } + + if (bestWidth) trickplayResolution = trickplayResolutions[bestWidth]; + } } function getDisplayTimeWithoutAmPm(date, showSeconds) { @@ -1530,6 +1548,7 @@ export default function (view) { let programEndDateMs = 0; let playbackStartTimeTicks = 0; let subtitleSyncOverlay; + let trickplayResolution = null; const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider'); @@ -1764,28 +1783,12 @@ export default function (view) { ticks /= 100; ticks *= value; - if (item?.Trickplay) { - const mediaSourceId = currentPlayer?.streamInfo?.mediaSource?.Id; - const trickplayResolutions = item.Trickplay[mediaSourceId]; - - if (!trickplayResolutions) return false; - - // Prefer highest resolution <= 20% of total screen resolution width - let bestWidth; - const maxWidth = window.screen.width * window.devicePixelRatio * 0.2; - for (const [, info] of Object.entries(trickplayResolutions)) { - if (!bestWidth - || (info.Width < bestWidth && bestWidth > maxWidth) // Objects not guaranteed to be sorted in any order, first width might be > maxWidth. - || (info.Width > bestWidth && info.Width <= maxWidth)) bestWidth = info.Width; - } - - if (!bestWidth) return false; - + if (trickplayResolution && item?.Trickplay) { return updateTrickplayBubbleHtml( ServerConnections.getApiClient(item.ServerId), - trickplayResolutions[bestWidth.toString()], + trickplayResolution, item, - mediaSourceId, + currentPlayer.streamInfo.mediaSource.Id, bubble, ticks); } From 9ab45c43ad9ce6207302c9995dfed578cbcf109b Mon Sep 17 00:00:00 2001 From: Topher Johnson Date: Sun, 25 Feb 2024 13:35:36 -0800 Subject: [PATCH 008/238] Change LibraryOption to match backend Changed "disable" to "enable" to match backend. It makes more sense to have it be "enable" because that's what the checkbox says. Now no weird "not" logic is needed to send options to the backend. --- src/components/libraryoptionseditor/libraryoptionseditor.js | 4 ++-- .../libraryoptionseditor/libraryoptionseditor.template.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index c8f1a1ddcf..036dbf948b 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -511,7 +511,7 @@ function setImageOptionsIntoOptions(options) { export function getLibraryOptions(parent) { const options = { - Disabled: !parent.querySelector('.chkEnabled').checked, + Enabled: parent.querySelector('.chkEnabled').checked, EnableArchiveMediaFiles: false, EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, @@ -575,7 +575,7 @@ export function setLibraryOptions(parent, options) { parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; - parent.querySelector('.chkEnabled').checked = !options.Disabled; + parent.querySelector('.chkEnabled').checked = options.Enabled; parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan; diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.template.html b/src/components/libraryoptionseditor/libraryoptionseditor.template.html index ea0f3069f7..38ebe99310 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.template.html +++ b/src/components/libraryoptionseditor/libraryoptionseditor.template.html @@ -1,5 +1,5 @@

${HeaderLibrarySettings}

-
+
-
+
+
+
+ +
${AllowVideoToolboxTonemappingHelp}
+
+
+