diff --git a/src/assets/css/fonts.css b/src/assets/css/fonts.css index f125bc6a0..cb0da0f80 100644 --- a/src/assets/css/fonts.css +++ b/src/assets/css/fonts.css @@ -29,7 +29,7 @@ h3 { } .layout-tv { - font-size: 2.5vh; + font-size: 130%; } .layout-mobile { diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 7a3e0ada6..d268ca87e 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -439,10 +439,46 @@ background-size: cover; background-repeat: no-repeat; background-position: center; + background-attachment: fixed; height: 50vh; position: relative; } +.layout-mobile .itemBackdrop { + background-attachment: scroll; +} + +.layout-desktop .itemBackdrop::after, +.layout-tv .itemBackdrop::after { + content: ""; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.65); + display: block; +} + +.layout-desktop .noBackdrop .itemBackdrop, +.layout-tv .noBackdrop .itemBackdrop { + display: none; +} + +.detailPageContent { + display: flex; + flex-direction: column; + padding-left: 2%; + padding-right: 2%; +} + +.layout-desktop .noBackdrop .detailPageContent, +.layout-tv .noBackdrop .detailPageContent { + margin-top: 2.5em; +} + +.layout-desktop .noBackdrop .detailImageContainer img, +.layout-tv .noBackdrop .detailImageContainer img { + margin-top: 0; +} + .personBackdrop { background-size: contain; } @@ -503,14 +539,13 @@ display: flex; align-items: center; justify-content: center; + text-align: center; } .detailPagePrimaryContainer { display: flex; align-items: center; align-content: center; - position: sticky; - top: 3.85em; z-index: 2; } @@ -520,13 +555,21 @@ top: 0; } -.layout-tv .detailPagePrimaryContainer { +.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, +.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer { position: relative; top: 0; + padding-left: 32.45vw; } -.detailSticky { - background-color: #101010; +.layout-desktop .detailSticky, +.layout-tv .detailSticky { + margin-top: -7.2em; +} + +.layout-desktop .noBackdrop .detailSticky, +.layout-tv .noBackdrop .detailSticky { + margin-top: 0; } .infoWrapper { @@ -548,23 +591,17 @@ margin: 1.25em 0; } -.detailPageContent { - display: flex; - flex-direction: column; - padding-left: 2%; - padding-right: 2%; -} - .detailImageContainer { - position: sticky; - top: 25%; + position: relative; + margin-top: -25vh; float: left; - width: 22.786458333333332vw; + width: 25vw; + z-index: 3; } -.layout-mobile .detailImageContainer, -.layout-tv .detailImageContainer { - position: relative; +.layout-desktop .noBackdrop .detailImageContainer, +.layout-tv .noBackdrop .detailImageContainer { + margin-top: 0; } .detailPagePrimaryContent { @@ -572,15 +609,19 @@ } .detailLogo { - width: 25em; - height: 9.375em; + width: 67.25vw; + height: 14.5vh; position: absolute; - top: 14.5%; - right: 10.5%; + top: 15vh; + right: 0; -webkit-background-size: contain; background-size: contain; } +.noBackdrop .detailLogo { + display: none; +} + @media all and (max-width: 87.5em) { .detailLogo { right: 5%; @@ -607,8 +648,8 @@ .itemDetailImage { width: 100% !important; - box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); - -webkit-box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); + -webkit-box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75); + box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.75); } div.itemDetailGalleryLink.defaultCardBackground { @@ -635,6 +676,16 @@ div.itemDetailGalleryLink.defaultCardBackground { position: relative; } + .layout-desktop .detailPageWrapperContainer, + .layout-tv .detailPageWrapperContainer { + margin-top: 7.2em; + } + + .layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, + .layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer { + padding-left: 3.3%; + } + .btnPlaySimple { display: none !important; } @@ -849,6 +900,11 @@ div.itemDetailGalleryLink.defaultCardBackground { border-collapse: collapse; } +.layout-desktop .noBackdrop .detailPageWrapperContainer, +.layout-tv .noBackdrop .detailPageWrapperContainer { + margin-top: 3.8em; +} + .mediaInfoStream { margin: 0 3em 0 0; display: inline-block; diff --git a/src/controllers/encodingsettings.js b/src/controllers/encodingsettings.js index e16450885..ff140ed53 100644 --- a/src/controllers/encodingsettings.js +++ b/src/controllers/encodingsettings.js @@ -15,6 +15,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo page.querySelector("#selectEncoderPreset").value = config.EncoderPreset || ""; page.querySelector("#txtH264Crf").value = config.H264Crf || ""; page.querySelector("#chkEnableSubtitleExtraction").checked = config.EnableSubtitleExtraction || false; + page.querySelector("#chkEnableThrottling").checked = config.EnableThrottling || false; page.querySelector("#selectVideoDecoder").dispatchEvent(new CustomEvent("change", { bubbles: true })); @@ -58,6 +59,7 @@ define(["jQuery", "loading", "globalize", "dom", "libraryMenu"], function ($, lo config.EncoderPreset = form.querySelector("#selectEncoderPreset").value; config.H264Crf = parseInt(form.querySelector("#txtH264Crf").value || "0"); config.EnableSubtitleExtraction = form.querySelector("#chkEnableSubtitleExtraction").checked; + config.EnableThrottling = form.querySelector("#chkEnableThrottling").checked; config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll(".chkDecodeCodec"), function (c) { return c.checked; }), function (c) { diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemdetailpage.js index ad8c0286b..e18311048 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemdetailpage.js @@ -1,4 +1,4 @@ -define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "apphost", "imageLoader", "libraryMenu", "globalize", "browser", "events", "scrollHelper", "playbackManager", "libraryBrowser", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-button", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function (loading, appRouter, layoutManager, connectionManager, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, appHost, imageLoader, libraryMenu, globalize, browser, events, scrollHelper, playbackManager, libraryBrowser) { +define(["loading", "appRouter", "layoutManager", "connectionManager", "userSettings", "cardBuilder", "datetime", "mediaInfo", "backdrop", "listView", "itemContextMenu", "itemHelper", "dom", "indicators", "imageLoader", "libraryMenu", "globalize", "browser", "events", "playbackManager", "scrollStyles", "emby-itemscontainer", "emby-checkbox", "emby-button", "emby-playstatebutton", "emby-ratingbutton", "emby-scroller", "emby-select"], function (loading, appRouter, layoutManager, connectionManager, userSettings, cardBuilder, datetime, mediaInfo, backdrop, listView, itemContextMenu, itemHelper, dom, indicators, imageLoader, libraryMenu, globalize, browser, events, playbackManager) { "use strict"; function getPromise(apiClient, params) { @@ -60,8 +60,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild return options; } - function getProgramScheduleHtml(items, options) { - options = options || {}; + function getProgramScheduleHtml(items) { var html = ""; html += '
'; html += listView.getListViewHtml({ @@ -445,7 +444,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } } - function renderBackdrop(page, item, apiClient) { + function renderBackdrop(item) { if (dom.getWindowSize().innerWidth >= 1000) { backdrop.setBackdrops([item]); } else { @@ -455,7 +454,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild function renderDetailPageBackdrop(page, item, apiClient) { var imgUrl; - var screenWidth = screen.availWidth; var hasbackdrop = false; var itemBackdropElement = page.querySelector("#itemBackdrop"); var usePrimaryImage = item.MediaType === "Video" && item.Type !== "Movie" && item.Type !== "Trailer" || @@ -464,13 +462,17 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild item.Type === "MusicArtist" || item.Type === "Person"; + if (!layoutManager.mobile && !userSettings.enableBackdrops()) { + return false; + } + if ("Program" === item.Type && item.ImageTags && item.ImageTags.Thumb) { imgUrl = apiClient.getScaledImageUrl(item.Id, { type: "Thumb", index: 0, tag: item.ImageTags.Thumb }); - itemBackdropElement.classList.remove("noBackdrop"); + page.classList.remove("noBackdrop"); imageLoader.lazyImage(itemBackdropElement, imgUrl, false); hasbackdrop = true; } else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) { @@ -479,7 +481,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild index: 0, tag: item.ImageTags.Primary }); - itemBackdropElement.classList.remove("noBackdrop"); + page.classList.remove("noBackdrop"); imageLoader.lazyImage(itemBackdropElement, imgUrl, false); hasbackdrop = true; } else if (item.BackdropImageTags && item.BackdropImageTags.length) { @@ -488,7 +490,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild index: 0, tag: item.BackdropImageTags[0] }); - itemBackdropElement.classList.remove("noBackdrop"); + page.classList.remove("noBackdrop"); imageLoader.lazyImage(itemBackdropElement, imgUrl, false); hasbackdrop = true; } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { @@ -497,7 +499,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild index: 0, tag: item.ParentBackdropImageTags[0] }); - itemBackdropElement.classList.remove("noBackdrop"); + page.classList.remove("noBackdrop"); imageLoader.lazyImage(itemBackdropElement, imgUrl, false); hasbackdrop = true; } else if (item.ImageTags && item.ImageTags.Thumb) { @@ -506,15 +508,16 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild index: 0, tag: item.ImageTags.Thumb }); - itemBackdropElement.classList.remove("noBackdrop"); + page.classList.remove("noBackdrop"); imageLoader.lazyImage(itemBackdropElement, imgUrl, false); hasbackdrop = true; } else { - itemBackdropElement.classList.add("noBackdrop"); itemBackdropElement.style.backgroundImage = ""; } if ("Person" === item.Type) { + // FIXME: This hides the backdrop on all persons to fix a margin issue. Ideally, a proper fix should be made. + page.classList.add('noBackdrop'); itemBackdropElement.classList.add("personBackdrop"); } else { itemBackdropElement.classList.remove("personBackdrop"); @@ -526,6 +529,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild function reloadFromItem(instance, page, params, item, user) { var context = params.context; page.querySelector(".detailPagePrimaryContainer").classList.add("detailSticky"); + renderName(item, page.querySelector(".nameContainer"), false, context); var apiClient = connectionManager.getApiClient(item.ServerId); renderSeriesTimerEditor(page, item, apiClient, user); @@ -536,7 +540,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild setInitialCollapsibleState(page, item, apiClient, context, user); renderDetails(page, item, apiClient, context); renderTrackSelections(page, instance, item); - renderBackdrop(page, item, apiClient); + renderBackdrop(item); renderDetailPageBackdrop(page, item, apiClient); var canPlay = reloadPlayButtons(page, item); @@ -663,7 +667,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild }); var detailLogo = page.querySelector(".detailLogo"); - if (url) { + if (!layoutManager.mobile && !userSettings.enableBackdrops()) { + detailLogo.classList.add("hide"); + } else if (url) { detailLogo.classList.remove("hide"); detailLogo.classList.add("lazy"); detailLogo.setAttribute("data-src", url); @@ -817,7 +823,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild if (editable) { html += ""; } else if (!editable && url === undefined) { - html += "
" + html += ""; } var progressHtml = item.IsFolder || !item.UserData ? "" : indicators.getProgressBarHtml(item); @@ -873,7 +879,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild elem.querySelector(".detailImageProgressContainer").innerHTML = indicators.getProgressBarHtml(item); } - function refreshImage(page, item, user) { + function refreshImage(page, item) { refreshDetailImageUserData(page.querySelector(".detailImageContainer"), item); } @@ -922,10 +928,10 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild if ("Playlist" == item.Type) { page.querySelector("#childrenCollapsible").classList.remove("hide"); - renderPlaylistItems(page, item, user); + renderPlaylistItems(page, item); } else if ("Studio" == item.Type || "Person" == item.Type || "Genre" == item.Type || "MusicGenre" == item.Type || "MusicArtist" == item.Type) { page.querySelector("#childrenCollapsible").classList.remove("hide"); - renderItemsByName(page, item, user); + renderItemsByName(page, item); } else if (item.IsFolder) { if ("BoxSet" == item.Type) { page.querySelector("#childrenCollapsible").classList.add("hide"); @@ -937,7 +943,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } if ("Series" == item.Type) { - renderSeriesSchedule(page, item, user); + renderSeriesSchedule(page, item); renderNextUp(page, item, user); } else { page.querySelector(".nextUpSection").classList.add("hide"); @@ -952,7 +958,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild page.querySelector("#specialsCollapsible").classList.add("hide"); } - renderCast(page, item, context, enableScrollX() ? null : 12); + renderCast(page, item); if (item.PartCount && item.PartCount > 1) { page.querySelector("#additionalPartsCollapsible").classList.remove("hide"); @@ -988,7 +994,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } } - function renderGenres(page, item, apiClient, context, isStatic) { + function renderGenres(page, item, context) { context = context || inferContext(item); var type; var genres = item.GenreItems || []; @@ -1022,7 +1028,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } } - function renderDirector(page, item, apiClient, context, isStatic) { + function renderDirector(page, item, context) { var directors = (item.People || []).filter(function (p) { return "Director" === p.Type; }); @@ -1050,8 +1056,8 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild renderSimilarItems(page, item, context); renderMoreFromSeason(page, item, apiClient); renderMoreFromArtist(page, item, apiClient); - renderDirector(page, item, apiClient, context, isStatic); - renderGenres(page, item, apiClient, context, isStatic); + renderDirector(page, item, context); + renderGenres(page, item, context); renderChannelGuide(page, apiClient, item); var taglineElement = page.querySelector(".tagline"); @@ -1125,14 +1131,6 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild return scrollX ? "overflowSquare" : "square"; } - function getThumbShape(scrollX) { - if (null == scrollX) { - scrollX = enableScrollX(); - } - - return scrollX ? "overflowBackdrop" : "backdrop"; - } - function renderMoreFromSeason(view, item, apiClient) { var section = view.querySelector(".moreFromSeasonSection"); @@ -1508,13 +1506,13 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } } - function renderItemsByName(page, item, user) { + function renderItemsByName(page, item) { require("scripts/itembynamedetailpage".split(","), function () { window.ItemsByName.renderItems(page, item); }); } - function renderPlaylistItems(page, item, user) { + function renderPlaylistItems(page, item) { require("scripts/playlistedit".split(","), function () { PlaylistViewer.render(page, item); }); @@ -1594,7 +1592,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } } - function renderSeriesSchedule(page, item, user) { + function renderSeriesSchedule(page, item) { var apiClient = connectionManager.getApiClient(item.ServerId); apiClient.getLiveTvPrograms({ UserId: apiClient.getCurrentUserId(), @@ -1844,7 +1842,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild }); } - function renderCast(page, item, context, limit, isStatic) { + function renderCast(page, item) { var people = (item.People || []).filter(function (p) { return "Director" !== p.Type; }); @@ -1934,7 +1932,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild playbackManager.play(playOptions); } - function playTrailer(page) { + function playTrailer() { playbackManager.playTrailers(currentItem); } @@ -1991,11 +1989,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild } function onPlayTrailerClick() { - playTrailer(view); - } - - function onDownloadChange() { - reload(self, view, params); + playTrailer(); } function onDownloadClick() { @@ -2050,9 +2044,7 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild if (userData) { currentItem.UserData = userData; reloadPlayButtons(view, currentItem); - apiClient.getCurrentUser().then(function (user) { - refreshImage(view, currentItem, user); - }); + refreshImage(view, currentItem); } } } @@ -2083,11 +2075,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild }); view.addEventListener("click", function (e) { if (dom.parentWithClass(e.target, "moreScenes")) { - apiClient.getCurrentUser().then(function (user) { - renderScenes(view, currentItem); - }); + renderScenes(view, currentItem); } else if (dom.parentWithClass(e.target, "morePeople")) { - renderCast(view, currentItem, params.context); + renderCast(view, currentItem); } else if (dom.parentWithClass(e.target, "moreSpecials")) { apiClient.getCurrentUser().then(function (user) { renderSpecials(view, currentItem, user); diff --git a/src/encodingsettings.html b/src/encodingsettings.html index 7d7c3a8c6..bcdd86544 100644 --- a/src/encodingsettings.html +++ b/src/encodingsettings.html @@ -144,6 +144,14 @@
${AllowOnTheFlySubtitleExtractionHelp}
+
+ +
${AllowFfmpegThrottlingHelp}
+
+
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 2e370836f..72894707d 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -24,6 +24,8 @@ "AllowMediaConversionHelp": "Grant or deny access to the convert media feature.", "AllowOnTheFlySubtitleExtraction": "Allow subtitle extraction on the fly", "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", + "AllowFfmpegThrottling": "Throttle Transcodes", + "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", "AllowRemoteAccess": "Allow remote connections to this Jellyfin Server.", "AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.", "AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.", diff --git a/src/themes/appletv/theme.css b/src/themes/appletv/theme.css index ccc6eaaab..5ca517bea 100644 --- a/src/themes/appletv/theme.css +++ b/src/themes/appletv/theme.css @@ -112,7 +112,7 @@ html { .inputLabelFocused, .selectLabelFocused, .textareaLabelFocused { - color: green; + color: #00a4dc; } .checkboxOutline { diff --git a/src/themes/dark/theme.css b/src/themes/dark/theme.css index 4d4b0a91b..41af2c32d 100644 --- a/src/themes/dark/theme.css +++ b/src/themes/dark/theme.css @@ -201,6 +201,10 @@ html { } .detailSticky { + background: rgba(32, 32, 32, 0.8); +} + +.noBackdrop .detailSticky { background: #202020; } diff --git a/src/themes/light/theme.css b/src/themes/light/theme.css index cedc8bfb8..d84a1c3b6 100644 --- a/src/themes/light/theme.css +++ b/src/themes/light/theme.css @@ -105,7 +105,7 @@ html { .inputLabelFocused, .selectLabelFocused, .textareaLabelFocused { - color: green; + color: #00a4dc; } .checkboxOutline {