From cc66fb672c199270919cac651027eb1fabebabf5 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 28 Mar 2020 19:42:18 +0300 Subject: [PATCH 01/16] Migrate ScrollManger to ES6 --- package.json | 3 +- src/components/scrollManager.js | 239 +++++++++++++++++--------------- 2 files changed, 126 insertions(+), 116 deletions(-) diff --git a/package.json b/package.json index 81dd250ab5..5672ab6d2e 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "src/components/filedownloader.js", "src/components/filesystem.js", "src/components/input/keyboardnavigation.js", - "src/components/sanatizefilename.js" + "src/components/sanatizefilename.js", + "src/components/scrollManager.js" ], "plugins": ["@babel/plugin-transform-modules-amd"] }] diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 5fc3729bac..96317fa998 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -1,15 +1,23 @@ -define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManager) { - "use strict"; +/* eslint-disable indent */ + +/** + * Module for controlling scroll behavior. + * @module components/scrollManager + */ + +import dom from "dom"; +import browser from "browser"; +import layoutManager from "layoutManager"; /** * Scroll time in ms. */ - var ScrollTime = 270; + const ScrollTime = 270; /** * Epsilon for comparing values. */ - var Epsilon = 1e-6; + const Epsilon = 1e-6; // FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers /** @@ -19,20 +27,20 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage * @return {number} minimum vertical scroll */ function minimumScrollY() { - var topMenu = document.querySelector(".headerTop"); + const topMenu = document.querySelector(".headerTop"); if (topMenu) { return topMenu.clientHeight; } return 0; } - var supportsSmoothScroll = "scrollBehavior" in document.documentElement.style; + const supportsSmoothScroll = "scrollBehavior" in document.documentElement.style; - var supportsScrollToOptions = false; + let supportsScrollToOptions = false; try { - var elem = document.createElement("div"); + const elem = document.createElement("div"); - var opts = Object.defineProperty({}, "behavior", { + const opts = Object.defineProperty({}, "behavior", { // eslint-disable-next-line getter-return get: function () { supportsScrollToOptions = true; @@ -47,9 +55,9 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns value clamped by range [min, max]. * - * @param {number} value clamped value - * @param {number} min begining of range - * @param {number} max ending of range + * @param {number} value - clamped value + * @param {number} min - begining of range + * @param {number} max - ending of range * @return {number} clamped value */ function clamp(value, min, max) { @@ -60,15 +68,15 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage * Returns the required delta to fit range 1 into range 2. * In case of range 1 is bigger than range 2 returns delta to fit most out of range part. * - * @param {number} begin1 begining of range 1 - * @param {number} end1 ending of range 1 - * @param {number} begin2 begining of range 2 - * @param {number} end2 ending of range 2 + * @param {number} begin1 - begining of range 1 + * @param {number} end1 - ending of range 1 + * @param {number} begin2 - begining of range 2 + * @param {number} end2 - ending of range 2 * @return {number} delta: <0 move range1 to the left, >0 - to the right */ function fitRange(begin1, end1, begin2, end2) { - var delta1 = begin1 - begin2; - var delta2 = end2 - end1; + const delta1 = begin1 - begin2; + const delta2 = end2 - end1; if (delta1 < 0 && delta1 < delta2) { return -delta1; } else if (delta2 < 0) { @@ -80,7 +88,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Ease value. * - * @param {number} t value in range [0, 1] + * @param {number} t - value in range [0, 1] * @return {number} eased value in range [0, 1] */ function ease(t) { @@ -100,41 +108,40 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage * * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement */ - function DocumentScroller() { - } - - DocumentScroller.prototype = { + class DocumentScroller { get scrollLeft() { return window.pageXOffset; - }, + } + set scrollLeft(val) { window.scroll(val, window.pageYOffset); - }, + } get scrollTop() { return window.pageYOffset; - }, + } + set scrollTop(val) { window.scroll(window.pageXOffset, val); - }, + } get scrollWidth() { return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); - }, + } get scrollHeight() { return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); - }, + } get clientWidth() { return Math.min(document.documentElement.clientWidth, document.body.clientWidth); - }, + } get clientHeight() { return Math.min(document.documentElement.clientHeight, document.body.clientHeight); - }, + } - getBoundingClientRect: function() { + getBoundingClientRect() { // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport return { left: 0, @@ -142,26 +149,29 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage width: this.clientWidth, height: this.clientHeight }; - }, + } - scrollTo: function() { + scrollTo() { window.scrollTo.apply(window, arguments); } - }; + } - var documentScroller = new DocumentScroller(); + /** + * Default (document) scroller. + */ + const documentScroller = new DocumentScroller(); /** * Returns parent element that can be scrolled. If no such, returns documentElement. * - * @param {HTMLElement} element element for which parent is being searched - * @param {boolean} vertical search for vertical scrollable parent + * @param {HTMLElement} element - element for which parent is being searched + * @param {boolean} vertical - search for vertical scrollable parent */ function getScrollableParent(element, vertical) { if (element) { - var nameScroll = "scrollWidth"; - var nameClient = "clientWidth"; - var nameClass = "scrollX"; + let nameScroll = "scrollWidth"; + let nameClient = "clientWidth"; + let nameClass = "scrollX"; if (vertical) { nameScroll = "scrollHeight"; @@ -169,7 +179,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage nameClass = "scrollY"; } - var parent = element.parentElement; + let parent = element.parentElement; while (parent) { // Skip 'emby-scroller' because it scrolls by itself @@ -187,20 +197,20 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * @typedef {Object} ScrollerData - * @property {number} scrollPos current scroll position - * @property {number} scrollSize scroll size - * @property {number} clientSize client size + * @property {number} scrollPos - current scroll position + * @property {number} scrollSize - scroll size + * @property {number} clientSize - client size */ /** * Returns scroll data for specified orientation. * - * @param {HTMLElement} scroller scroller - * @param {boolean} vertical vertical scroll data + * @param {HTMLElement} scroller - scroller + * @param {boolean} vertical - vertical scroll data * @return {ScrollerData} scroll data */ function getScrollerData(scroller, vertical) { - var data = {}; + let data = {}; if (!vertical) { data.scrollPos = scroller.scrollLeft; @@ -218,14 +228,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns position of child of scroller for specified orientation. * - * @param {HTMLElement} scroller scroller - * @param {HTMLElement} element child of scroller - * @param {boolean} vertical vertical scroll + * @param {HTMLElement} scroller - scroller + * @param {HTMLElement} element - child of scroller + * @param {boolean} vertical - vertical scroll * @return {number} child position */ function getScrollerChildPos(scroller, element, vertical) { - var elementRect = element.getBoundingClientRect(); - var scrollerRect = scroller.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + const scrollerRect = scroller.getBoundingClientRect(); if (!vertical) { return scroller.scrollLeft + elementRect.left - scrollerRect.left; @@ -237,21 +247,21 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns scroll position for element. * - * @param {ScrollerData} scrollerData scroller data - * @param {number} elementPos child element position - * @param {number} elementSize child element size - * @param {boolean} centered scroll to center + * @param {ScrollerData} scrollerData - scroller data + * @param {number} elementPos - child element position + * @param {number} elementSize - child element size + * @param {boolean} centered - scroll to center * @return {number} scroll position */ function calcScroll(scrollerData, elementPos, elementSize, centered) { - var maxScroll = scrollerData.scrollSize - scrollerData.clientSize; + const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; - var scroll; + let scroll; if (centered) { scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; } else { - var delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); + const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); scroll = scrollerData.scrollPos - delta; } @@ -261,14 +271,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Calls scrollTo function in proper way. * - * @param {HTMLElement} scroller scroller - * @param {ScrollToOptions} options scroll options + * @param {HTMLElement} scroller - scroller + * @param {ScrollToOptions} options - scroll options */ function scrollToHelper(scroller, options) { if ("scrollTo" in scroller) { if (!supportsScrollToOptions) { - var scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); - var scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); + const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); + const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); scroller.scrollTo(scrollX, scrollY); } else { scroller.scrollTo(options); @@ -286,14 +296,14 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs built-in scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate - * @param {boolean} smooth smooth scrolling + * @param {HTMLElement} xScroller - horizontal scroller + * @param {number} scrollX - horizontal coordinate + * @param {HTMLElement} yScroller - vertical scroller + * @param {number} scrollY - vertical coordinate + * @param {boolean} smooth - smooth scrolling */ function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - var scrollBehavior = smooth ? "smooth" : "instant"; + const scrollBehavior = smooth ? "smooth" : "instant"; if (xScroller !== yScroller) { scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); @@ -303,7 +313,7 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage } } - var scrollTimer; + let scrollTimer; /** * Resets scroll timer to stop scrolling. @@ -316,29 +326,29 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs animated scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate + * @param {HTMLElement} xScroller - horizontal scroller + * @param {number} scrollX - horizontal coordinate + * @param {HTMLElement} yScroller - vertical scroller + * @param {number} scrollY - vertical coordinate */ function animateScroll(xScroller, scrollX, yScroller, scrollY) { - var ox = xScroller.scrollLeft; - var oy = yScroller.scrollTop; - var dx = scrollX - ox; - var dy = scrollY - oy; + const ox = xScroller.scrollLeft; + const oy = yScroller.scrollTop; + const dx = scrollX - ox; + const dy = scrollY - oy; if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { return; } - var start; + let start; function scrollAnim(currentTimestamp) { start = start || currentTimestamp; - var k = Math.min(1, (currentTimestamp - start) / ScrollTime); + let k = Math.min(1, (currentTimestamp - start) / ScrollTime); if (k === 1) { resetScrollTimer(); @@ -348,8 +358,8 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage k = ease(k); - var x = ox + dx*k; - var y = oy + dy*k; + const x = ox + dx*k; + const y = oy + dy*k; builtinScroll(xScroller, x, yScroller, y, false); @@ -362,11 +372,11 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Performs scroll. * - * @param {HTMLElement} xScroller horizontal scroller - * @param {number} scrollX horizontal coordinate - * @param {HTMLElement} yScroller vertical scroller - * @param {number} scrollY vertical coordinate - * @param {boolean} smooth smooth scrolling + * @param {HTMLElement} xScroller - horizontal scroller + * @param {number} scrollX - horizontal coordinate + * @param {HTMLElement} yScroller - vertical scroller + * @param {number} scrollY - vertical coordinate + * @param {boolean} smooth - smooth scrolling */ function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { @@ -403,26 +413,26 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Returns true if scroll manager is enabled. */ - var isEnabled = function() { + export function isEnabled() { return layoutManager.tv; - }; + } /** * Scrolls the document to a given position. * - * @param {number} scrollX horizontal coordinate - * @param {number} scrollY vertical coordinate - * @param {boolean} [smooth=false] smooth scrolling + * @param {number} scrollX - horizontal coordinate + * @param {number} scrollY - vertical coordinate + * @param {boolean} [smooth=false] - smooth scrolling */ - var scrollTo = function(scrollX, scrollY, smooth) { + export function scrollTo(scrollX, scrollY, smooth) { smooth = !!smooth; // Scroller is document itself by default - var scroller = getScrollableParent(null, false); + const scroller = getScrollableParent(null, false); - var xScrollerData = getScrollerData(scroller, false); - var yScrollerData = getScrollerData(scroller, true); + const xScrollerData = getScrollerData(scroller, false); + const yScrollerData = getScrollerData(scroller, true); scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); @@ -433,39 +443,39 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage /** * Scrolls the document to a given element. * - * @param {HTMLElement} element target element of scroll task - * @param {boolean} [smooth=false] smooth scrolling + * @param {HTMLElement} element - target element of scroll task + * @param {boolean} [smooth=false] - smooth scrolling */ - var scrollToElement = function(element, smooth) { + export function scrollToElement(element, smooth) { smooth = !!smooth; - var scrollCenterX = true; - var scrollCenterY = true; + let scrollCenterX = true; + let scrollCenterY = true; - var offsetParent = element.offsetParent; + const offsetParent = element.offsetParent; // In Firefox offsetParent.offsetParent is BODY - var isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === "fixed"); + const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === "fixed"); // Scroll fixed elements to nearest edge (or do not scroll at all) if (isFixed) { scrollCenterX = scrollCenterY = false; } - var xScroller = getScrollableParent(element, false); - var yScroller = getScrollableParent(element, true); + const xScroller = getScrollableParent(element, false); + const yScroller = getScrollableParent(element, true); - var elementRect = element.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); - var xScrollerData = getScrollerData(xScroller, false); - var yScrollerData = getScrollerData(yScroller, true); + const xScrollerData = getScrollerData(xScroller, false); + const yScrollerData = getScrollerData(yScroller, true); - var xPos = getScrollerChildPos(xScroller, element, false); - var yPos = getScrollerChildPos(yScroller, element, true); + const xPos = getScrollerChildPos(xScroller, element, false); + const yPos = getScrollerChildPos(yScroller, element, true); - var scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); - var scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); + const scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); + let scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); // HACK: Scroll to top for top menu because it is hidden // FIXME: Need a marker to scroll top/bottom @@ -490,9 +500,8 @@ define(["dom", "browser", "layoutManager"], function (dom, browser, layoutManage }, {capture: true}); } - return { + export default { isEnabled: isEnabled, scrollTo: scrollTo, scrollToElement: scrollToElement }; -}); From 460717c7ef8e3c59cc2530842077f45fee81fd0b Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 28 Mar 2020 20:02:22 +0300 Subject: [PATCH 02/16] Migrate AutoFocuser to ES6 --- package.json | 1 + src/components/autoFocuser.js | 45 ++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 5672ab6d2e..3af6fabbb9 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "presets": ["@babel/preset-env"], "overrides": [{ "test": [ + "src/components/autoFocuser.js", "src/components/cardbuilder/cardBuilder.js", "src/components/filedownloader.js", "src/components/filesystem.js", diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index 6d99009e67..93ebb4b3be 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -1,22 +1,29 @@ -define(["focusManager", "layoutManager"], function (focusManager, layoutManager) { - "use strict"; +/* eslint-disable indent */ + +/** + * Module for performing auto-focus. + * @module components/autoFocuser + */ + +import focusManager from "focusManager"; +import layoutManager from "layoutManager"; /** * Previously selected element. */ - var activeElement; + let activeElement; /** - * Returns true if AutoFocuser is enabled. + * Returns _true_ if AutoFocuser is enabled. */ - function isEnabled() { + export function isEnabled() { return layoutManager.tv; } /** * Start AutoFocuser */ - function enable() { + export function enable() { if (!isEnabled()) { return; } @@ -28,24 +35,19 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) console.debug("AutoFocuser enabled"); } - /** - * Create an array from some source. - */ - var arrayFrom = Array.prototype.from || function (src) { - return Array.prototype.slice.call(src); - } - /** * Set focus on a suitable element, taking into account the previously selected. + * @param {HTMLElement} [container] - element to limit scope + * @returns {HTMLElement} focused element */ - function autoFocus(container) { + export function autoFocus(container) { if (!isEnabled()) { - return; + return null; } container = container || document.body; - var candidates = []; + let candidates = []; if (activeElement) { // These elements are recreated @@ -62,10 +64,10 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) candidates.push(activeElement); } - candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnResume"))); - candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnPlay"))); + candidates = candidates.concat(Array.from(container.querySelectorAll(".btnResume"))); + candidates = candidates.concat(Array.from(container.querySelectorAll(".btnPlay"))); - var focusedElement; + let focusedElement; candidates.every(function (element) { if (focusManager.isCurrentlyFocusable(element)) { @@ -79,7 +81,7 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) if (!focusedElement) { // FIXME: Multiple itemsContainers - var itemsContainer = container.querySelector(".itemsContainer"); + const itemsContainer = container.querySelector(".itemsContainer"); if (itemsContainer) { focusedElement = focusManager.autoFocus(itemsContainer); @@ -93,9 +95,8 @@ define(["focusManager", "layoutManager"], function (focusManager, layoutManager) return focusedElement; } - return { + export default { isEnabled: isEnabled, enable: enable, autoFocus: autoFocus }; -}); From 5efc95fd097c7bdcc3139e9fd39bfa305353d0fb Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 29 Mar 2020 00:03:15 +0300 Subject: [PATCH 03/16] Migrate DOM-module to ES6 --- package.json | 1 + src/components/dom.js | 163 +++++++++++++++++++++++++++++++----------- 2 files changed, 121 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 3af6fabbb9..f6f2436b2a 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "test": [ "src/components/autoFocuser.js", "src/components/cardbuilder/cardBuilder.js", + "src/components/dom.js", "src/components/filedownloader.js", "src/components/filesystem.js", "src/components/input/keyboardnavigation.js", diff --git a/src/components/dom.js b/src/components/dom.js index b91e5b1687..fdc5e607eb 100644 --- a/src/components/dom.js +++ b/src/components/dom.js @@ -1,8 +1,18 @@ -define([], function () { - 'use strict'; +/* eslint-disable indent */ - function parentWithAttribute(elem, name, value) { +/** + * Useful DOM utilities. + * @module components/dom + */ + /** + * Returns parent of element with specified attribute value. + * @param {HTMLElement} elem - element whose parent need to find + * @param {string} name - attribute name + * @param {mixed} value - attribute value + * @returns {HTMLElement} Parent with specified attribute value + */ + export function parentWithAttribute(elem, name, value) { while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) { elem = elem.parentNode; @@ -14,8 +24,13 @@ define([], function () { return elem; } - function parentWithTag(elem, tagNames) { - + /** + * Returns parent of element with one of specified tag names. + * @param {HTMLElement} elem - element whose parent need to find + * @param {(string|Array)} tagNames - tag name or array of tag names + * @returns {HTMLElement} Parent with one of specified tag names + */ + export function parentWithTag(elem, tagNames) { // accept both string and array passed in if (!Array.isArray(tagNames)) { tagNames = [tagNames]; @@ -32,9 +47,14 @@ define([], function () { return elem; } + /** + * Returns _true_ if class list contains one of specified names. + * @param {DOMTokenList} classList - class list + * @param {Array} classNames - array of class names + * @returns {boolean} _true_ if class list contains one of specified names + */ function containsAnyClass(classList, classNames) { - - for (var i = 0, length = classNames.length; i < length; i++) { + for (let i = 0, length = classNames.length; i < length; i++) { if (classList.contains(classNames[i])) { return true; } @@ -42,8 +62,13 @@ define([], function () { return false; } - function parentWithClass(elem, classNames) { - + /** + * Returns parent of element with one of specified class names. + * @param {HTMLElement} elem - element whose parent need to find + * @param {(string|Array)} classNames - class name or array of class names + * @returns {HTMLElement} Parent with one of specified class names + */ + export function parentWithClass(elem, classNames) { // accept both string and array passed in if (!Array.isArray(classNames)) { classNames = [classNames]; @@ -60,9 +85,9 @@ define([], function () { return elem; } - var supportsCaptureOption = false; + let supportsCaptureOption = false; try { - var opts = Object.defineProperty({}, 'capture', { + const opts = Object.defineProperty({}, 'capture', { // eslint-disable-next-line getter-return get: function () { supportsCaptureOption = true; @@ -73,29 +98,58 @@ define([], function () { console.debug('error checking capture support'); } - function addEventListenerWithOptions(target, type, handler, options) { - var optionsOrCapture = options || {}; + /** + * Adds event listener to specified target. + * @param {EventTarget} target - event target + * @param {string} type - event type + * @param {function} handler - event handler + * @param {Object} [options] - listener options + */ + export function addEventListener(target, type, handler, options) { + let optionsOrCapture = options || {}; if (!supportsCaptureOption) { optionsOrCapture = optionsOrCapture.capture; } target.addEventListener(type, handler, optionsOrCapture); } - function removeEventListenerWithOptions(target, type, handler, options) { - var optionsOrCapture = options || {}; + /** + * Removes event listener from specified target. + * @param {EventTarget} target - event target + * @param {string} type - event type + * @param {function} handler - event handler + * @param {Object} [options] - listener options + */ + export function removeEventListener(target, type, handler, options) { + let optionsOrCapture = options || {}; if (!supportsCaptureOption) { optionsOrCapture = optionsOrCapture.capture; } target.removeEventListener(type, handler, optionsOrCapture); } - var windowSize; - var windowSizeEventsBound; + /** + * Cached window size. + */ + let windowSize; + + /** + * Flag of event listener bound. + */ + let windowSizeEventsBound; + + /** + * Resets cached window size. + */ function clearWindowSize() { windowSize = null; } - function getWindowSize() { + /** + * Returns window size. + * @returns {Object} Window size + */ + export function getWindowSize() { if (!windowSize) { windowSize = { innerHeight: window.innerHeight, @@ -104,46 +158,60 @@ define([], function () { if (!windowSizeEventsBound) { windowSizeEventsBound = true; - addEventListenerWithOptions(window, "orientationchange", clearWindowSize, { passive: true }); - addEventListenerWithOptions(window, 'resize', clearWindowSize, { passive: true }); + addEventListener(window, "orientationchange", clearWindowSize, { passive: true }); + addEventListener(window, 'resize', clearWindowSize, { passive: true }); } } return windowSize; } - var standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680]; - function getScreenWidth() { - var width = window.innerWidth; - var height = window.innerHeight; + /** + * Standard screen widths. + */ + const standardWidths = [480, 720, 1280, 1440, 1920, 2560, 3840, 5120, 7680]; + + /** + * Returns screen width. + * @returns {number} Screen width + */ + export function getScreenWidth() { + let width = window.innerWidth; + const height = window.innerHeight; if (height > width) { width = height * (16.0 / 9.0); } - var closest = standardWidths.sort(function (a, b) { + const closest = standardWidths.sort(function (a, b) { return Math.abs(width - a) - Math.abs(width - b); })[0]; return closest; } - var _animationEvent; - function whichAnimationEvent() { + /** + * Name of animation end event. + */ + let _animationEvent; + /** + * Returns name of animation end event. + * @returns {string} Name of animation end event + */ + export function whichAnimationEvent() { if (_animationEvent) { return _animationEvent; } - var t; - var el = document.createElement("div"); - var animations = { + const el = document.createElement("div"); + const animations = { "animation": "animationend", "OAnimation": "oAnimationEnd", "MozAnimation": "animationend", "WebkitAnimation": "webkitAnimationEnd" }; - for (t in animations) { + for (let t in animations) { if (el.style[t] !== undefined) { _animationEvent = animations[t]; return animations[t]; @@ -154,26 +222,36 @@ define([], function () { return _animationEvent; } - function whichAnimationCancelEvent() { - + /** + * Returns name of animation cancel event. + * @returns {string} Name of animation cancel event + */ + export function whichAnimationCancelEvent() { return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel'); } - var _transitionEvent; - function whichTransitionEvent() { + /** + * Name of transition end event. + */ + let _transitionEvent; + + /** + * Returns name of transition end event. + * @returns {string} Name of transition end event + */ + export function whichTransitionEvent() { if (_transitionEvent) { return _transitionEvent; } - var t; - var el = document.createElement("div"); - var transitions = { + const el = document.createElement("div"); + const transitions = { "transition": "transitionend", "OTransition": "oTransitionEnd", "MozTransition": "transitionend", "WebkitTransition": "webkitTransitionEnd" }; - for (t in transitions) { + for (let t in transitions) { if (el.style[t] !== undefined) { _transitionEvent = transitions[t]; return transitions[t]; @@ -184,16 +262,15 @@ define([], function () { return _transitionEvent; } - return { + export default { parentWithAttribute: parentWithAttribute, parentWithClass: parentWithClass, parentWithTag: parentWithTag, - addEventListener: addEventListenerWithOptions, - removeEventListener: removeEventListenerWithOptions, + addEventListener: addEventListener, + removeEventListener: removeEventListener, getWindowSize: getWindowSize, getScreenWidth: getScreenWidth, whichTransitionEvent: whichTransitionEvent, whichAnimationEvent: whichAnimationEvent, whichAnimationCancelEvent: whichAnimationCancelEvent }; -}); From eb047175be762e69e0fcefde26199e423a7ed87b Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 29 Mar 2020 00:06:27 +0300 Subject: [PATCH 04/16] Add default export for compatibility --- src/components/cardbuilder/cardBuilder.js | 11 +++++++++++ src/components/input/keyboardnavigation.js | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 9d86bc9d7c..1249f802af 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1853,3 +1853,14 @@ import 'programStyles'; cell.removeAttribute('data-seriestimerid'); } } + +export default { + getCardsHtml: getCardsHtml, + getDefaultBackgroundClass: getDefaultBackgroundClass, + getDefaultText: getDefaultText, + buildCards: buildCards, + onUserDataChanged: onUserDataChanged, + onTimerCreated: onTimerCreated, + onTimerCancelled: onTimerCancelled, + onSeriesTimerCancelled: onSeriesTimerCancelled +}; diff --git a/src/components/input/keyboardnavigation.js b/src/components/input/keyboardnavigation.js index bdcb733179..caddf46797 100644 --- a/src/components/input/keyboardnavigation.js +++ b/src/components/input/keyboardnavigation.js @@ -1,3 +1,8 @@ +/** + * Module for performing keyboard navigation. + * @module components/input/keyboardnavigation + */ + import inputManager from "inputManager"; import layoutManager from "layoutManager"; @@ -55,7 +60,7 @@ if (!hasFieldKey) { /** * Returns key name from event. * - * @param {KeyboardEvent} event keyboard event + * @param {KeyboardEvent} event - keyboard event * @return {string} key name */ export function getKeyName(event) { @@ -65,7 +70,7 @@ export function getKeyName(event) { /** * Returns _true_ if key is used for navigation. * - * @param {string} key name + * @param {string} key - key name * @return {boolean} _true_ if key is used for navigation */ export function isNavigationKey(key) { @@ -155,3 +160,9 @@ function attachGamepadScript(e) { // No need to check for gamepads manually at load time, the eventhandler will be fired for that window.addEventListener("gamepadconnected", attachGamepadScript); + +export default { + enable: enable, + getKeyName: getKeyName, + isNavigationKey: isNavigationKey +}; From 30ecc52ba4af24544f6f6f99b0fe453e1ef91aea Mon Sep 17 00:00:00 2001 From: Nazar78 Date: Sun, 29 Mar 2020 05:40:33 +0800 Subject: [PATCH 05/16] Support H264 Level 52 (Tizen 5.0) - app only --- src/scripts/browserdeviceprofile.js | 38 +++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserdeviceprofile.js index 337463987c..e5e3fd67c0 100644 --- a/src/scripts/browserdeviceprofile.js +++ b/src/scripts/browserdeviceprofile.js @@ -355,6 +355,31 @@ define(['browser'], function (browser) { // Not sure how to test for this var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s; + // Get Tizen version & support + var getTizenVersion = self.tizen && self.tizen.systeminfo ? + parseFloat(tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version')) : null; + + function getTizenSupport(feature, version, current) { + if (getTizenVersion) { + var version = parseFloat(version); + switch(feature) { + case 'supportsDts': + if (getTizenVersion >= version) { + current = false; + } + break; + case 'maxH264Level': + if (getTizenVersion >= version) { + current = 52; + } + break; + default: + break; + } + } + return current; + } + var maxVideoWidth = browser.xboxOne ? (self.screen ? self.screen.width : null) : null; @@ -433,14 +458,8 @@ define(['browser'], function (browser) { var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts; - if (self.tizen && self.tizen.systeminfo) { - var v = tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version'); - - // DTS audio not supported in 2018 models (Tizen 4.0) - if (v && parseFloat(v) >= parseFloat('4.0')) { - supportsDts = false; - } - } + // DTS audio not supported in 2018 models (Tizen 4.0) + supportsDts = getTizenSupport('supportsDts', '4.0', supportsDts); if (supportsDts) { videoAudioCodecs.push('dca'); @@ -766,6 +785,9 @@ define(['browser'], function (browser) { maxH264Level = 51; } + // Support H264 Level 52 (Tizen 5.0) + maxH264Level = getTizenSupport('maxH264Level', '5.0', maxH264Level); + if (browser.tizen || browser.orsay || videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { From 3f11095fec66cf91a7846fef17bfc6b0234b74b0 Mon Sep 17 00:00:00 2001 From: Nazar78 Date: Sun, 29 Mar 2020 05:40:33 +0800 Subject: [PATCH 06/16] Support H264 Level 52 (Tizen 5.0) - app only --- src/scripts/browserdeviceprofile.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserdeviceprofile.js index 337463987c..570c0d2fa1 100644 --- a/src/scripts/browserdeviceprofile.js +++ b/src/scripts/browserdeviceprofile.js @@ -433,13 +433,9 @@ define(['browser'], function (browser) { var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts; - if (self.tizen && self.tizen.systeminfo) { - var v = tizen.systeminfo.getCapability('http://tizen.org/feature/platform.version'); - - // DTS audio not supported in 2018 models (Tizen 4.0) - if (v && parseFloat(v) >= parseFloat('4.0')) { - supportsDts = false; - } + // DTS audio not supported in 2018 models (Tizen 4.0) + if (browser.tizenVersion >= 4) { + supportsDts = false; } if (supportsDts) { @@ -766,6 +762,11 @@ define(['browser'], function (browser) { maxH264Level = 51; } + // Support H264 Level 52 (Tizen 5.0) - app only + if (browser.tizenVersion >= 5 && window.NativeShell) { + maxH264Level = 52; + } + if (browser.tizen || browser.orsay || videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { From 0c0b91b7a5833ff311fa3d8fbddf27e833c40e69 Mon Sep 17 00:00:00 2001 From: Nazar78 Date: Mon, 30 Mar 2020 17:02:16 +0800 Subject: [PATCH 07/16] Support H264 Level 52 (Tizen 5.0) - app only --- src/scripts/browserdeviceprofile.js | 900 ++++++++++++++++++++++++++++ 1 file changed, 900 insertions(+) create mode 100644 src/scripts/browserdeviceprofile.js diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserdeviceprofile.js new file mode 100644 index 0000000000..570c0d2fa1 --- /dev/null +++ b/src/scripts/browserdeviceprofile.js @@ -0,0 +1,900 @@ +define(['browser'], function (browser) { + 'use strict'; + + function canPlayH264(videoTestElement) { + return !!(videoTestElement.canPlayType && videoTestElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')); + } + + function canPlayH265(videoTestElement, options) { + if (browser.tizen || browser.orsay || browser.xboxOne || browser.web0s || options.supportsHevc) { + return true; + } + + var userAgent = navigator.userAgent.toLowerCase(); + if (browser.chromecast) { + var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; + if (isChromecastUltra) { + return true; + } + } + + if (browser.ps4) { + return false; + } + + return !!videoTestElement.canPlayType && + (videoTestElement.canPlayType('video/mp4; codecs="hvc1.1.L120"').replace(/no/, '') || + videoTestElement.canPlayType('video/mp4; codecs="hev1.1.L120"').replace(/no/, '') || + videoTestElement.canPlayType('video/mp4; codecs="hvc1.1.0.L120"').replace(/no/, '') || + videoTestElement.canPlayType('video/mp4; codecs="hev1.1.0.L120"').replace(/no/, '')); + } + + var _supportsTextTracks; + function supportsTextTracks() { + if (browser.tizen || browser.orsay) { + return true; + } + + if (_supportsTextTracks == null) { + _supportsTextTracks = document.createElement('video').textTracks != null; + } + + // For now, until ready + return _supportsTextTracks; + } + + var _canPlayHls; + function canPlayHls() { + if (_canPlayHls == null) { + _canPlayHls = canPlayNativeHls() || canPlayHlsWithMSE(); + } + + return _canPlayHls; + } + + function canPlayNativeHls() { + if (browser.tizen || browser.orsay) { + return true; + } + + var media = document.createElement('video'); + if (media.canPlayType('application/x-mpegURL').replace(/no/, '') || + media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) { + return true; + } + + return false; + } + + function canPlayHlsWithMSE() { + // text tracks don’t work with this in firefox + return window.MediaSource != null; + } + + function supportsAc3(videoTestElement) { + if (browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s) { + return true; + } + + return videoTestElement.canPlayType('audio/mp4; codecs="ac-3"').replace(/no/, ''); + } + + function supportsEac3(videoTestElement) { + if (browser.tizen || browser.orsay || browser.web0s) { + return true; + } + + return videoTestElement.canPlayType('audio/mp4; codecs="ec-3"').replace(/no/, ''); + } + + function supportsAc3InHls(videoTestElement) { + if (browser.tizen || browser.orsay || browser.web0s) { + return true; + } + + if (videoTestElement.canPlayType) { + return videoTestElement.canPlayType('application/x-mpegurl; codecs="avc1.42E01E, ac-3"').replace(/no/, '') || + videoTestElement.canPlayType('application/vnd.apple.mpegURL; codecs="avc1.42E01E, ac-3"').replace(/no/, ''); + } + + return false; + } + + function canPlayAudioFormat(format) { + var typeString; + + if (format === 'flac') { + if (browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp) { + return true; + } + } else if (format === 'wma') { + if (browser.tizen || browser.orsay || browser.edgeUwp) { + return true; + } + } else if (format === 'asf') { + if (browser.tizen || browser.web0s || browser.edgeUwp) { + return true; + } + } else if (format === 'opus') { + if (!browser.web0s) { + typeString = 'audio/ogg; codecs="opus"'; + return !!document.createElement('audio').canPlayType(typeString).replace(/no/, ''); + } + + return false; + } else if (format === 'alac') { + if (browser.iOS || browser.osx) { + return true; + } + } else if (format === 'mp2') { + // For now + return false; + } + + if (format === 'webma') { + typeString = 'audio/webm'; + } else if (format === 'mp2') { + typeString = 'audio/mpeg'; + } else { + typeString = 'audio/' + format; + } + + return !!document.createElement('audio').canPlayType(typeString).replace(/no/, ''); + } + + function testCanPlayMkv(videoTestElement) { + if (browser.tizen || browser.orsay || browser.web0s) { + return true; + } + + if (videoTestElement.canPlayType('video/x-matroska').replace(/no/, '') || + videoTestElement.canPlayType('video/mkv').replace(/no/, '')) { + return true; + } + + // Unfortunately there's no real way to detect mkv support + if (browser.chrome) { + // Not supported on opera tv + if (browser.operaTv) { + return false; + } + + var userAgent = navigator.userAgent.toLowerCase(); + + // Filter out browsers based on chromium that don't support mkv + if (userAgent.indexOf('vivaldi') !== -1 || userAgent.indexOf('opera') !== -1) { + return false; + } + + return true; + } + + if (browser.edgeUwp) { + return true; + } + + return false; + } + + function testCanPlayTs() { + return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + } + + function supportsMpeg2Video() { + return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + } + + function supportsVc1() { + return browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + } + + function getFlvMseDirectPlayProfile() { + var videoAudioCodecs = ['aac']; + if (!browser.edge && !browser.msie) { + videoAudioCodecs.push('mp3'); + } + + return { + Container: 'flv', + Type: 'Video', + VideoCodec: 'h264', + AudioCodec: videoAudioCodecs.join(',') + }; + } + + function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) { + var supported = false; + var profileContainer = container; + var videoCodecs = []; + + switch (container) { + case 'asf': + supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + videoAudioCodecs = []; + break; + case 'avi': + supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + // New Samsung TV don't support XviD/DivX + // Explicitly add supported codecs to make other codecs be transcoded + if (browser.tizenVersion >= 4) { + videoCodecs.push('h264'); + if (canPlayH265(videoTestElement, options)) { + videoCodecs.push('h265'); + videoCodecs.push('hevc'); + } + } + break; + case 'mpg': + case 'mpeg': + supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + break; + case 'flv': + supported = browser.tizen || browser.orsay; + //if (!supported && window.MediaSource != null && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) { + // return getFlvMseDirectPlayProfile(); + //} + break; + case '3gp': + case 'mts': + case 'trp': + case 'vob': + case 'vro': + supported = browser.tizen || browser.orsay; + break; + case 'mov': + supported = browser.tizen || browser.orsay || browser.web0s || browser.chrome || browser.edgeUwp; + videoCodecs.push('h264'); + break; + case 'm2ts': + supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + videoCodecs.push('h264'); + if (supportsVc1()) { + videoCodecs.push('vc1'); + } + if (supportsMpeg2Video()) { + videoCodecs.push('mpeg2video'); + } + break; + case 'wmv': + supported = browser.tizen || browser.orsay || browser.web0s || browser.edgeUwp; + videoAudioCodecs = []; + break; + case 'ts': + supported = testCanPlayTs(); + videoCodecs.push('h264'); + if (canPlayH265(videoTestElement, options)) { + videoCodecs.push('h265'); + videoCodecs.push('hevc'); + } + if (supportsVc1()) { + videoCodecs.push('vc1'); + } + if (supportsMpeg2Video()) { + videoCodecs.push('mpeg2video'); + } + profileContainer = 'ts,mpegts'; + break; + default: + break; + } + + return supported ? { + Container: profileContainer, + Type: 'Video', + VideoCodec: videoCodecs.join(','), + AudioCodec: videoAudioCodecs.join(',') + } : null; + } + + function getMaxBitrate() { + return 120000000; + } + + function getGlobalMaxVideoBitrate() { + var userAgent = navigator.userAgent.toLowerCase(); + if (browser.chromecast) { + var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; + if (isChromecastUltra) { + return null; + } + + // This is a hack to try and detect chromecast on vizio + if (self.screen && self.screen.width >= 3800) { + return null; + } + + return 30000000; + } + + var isTizenFhd = false; + if (browser.tizen) { + try { + var isTizenUhd = webapis.productinfo.isUdPanelSupported(); + isTizenFhd = !isTizenUhd; + console.debug("isTizenFhd = " + isTizenFhd); + } catch (error) { + console.error("isUdPanelSupported() error code = " + error.code); + } + } + + return browser.ps4 ? 8000000 : + (browser.xboxOne ? 12000000 : + (browser.edgeUwp ? null : + (browser.tizen && isTizenFhd ? 20000000 : null))); + } + + return function (options) { + options = options || {}; + + var physicalAudioChannels = options.audioChannels || (browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2); + + var bitrateSetting = getMaxBitrate(); + + var videoTestElement = document.createElement('video'); + + var canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, ''); + var canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, ''); + var webmAudioCodecs = ['vorbis']; + + var canPlayMkv = testCanPlayMkv(videoTestElement); + + var profile = {}; + + profile.MaxStreamingBitrate = bitrateSetting; + profile.MaxStaticBitrate = 100000000; + profile.MusicStreamingTranscodingBitrate = Math.min(bitrateSetting, 192000); + + profile.DirectPlayProfiles = []; + + var videoAudioCodecs = []; + var hlsVideoAudioCodecs = []; + + var supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '') || + videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, ''); + + // Not sure how to test for this + var supportsMp2VideoAudio = browser.edgeUwp || browser.tizen || browser.orsay || browser.web0s; + + var maxVideoWidth = browser.xboxOne ? + (self.screen ? self.screen.width : null) : + null; + + if (options.maxVideoWidth) { + maxVideoWidth = options.maxVideoWidth; + } + + var canPlayAacVideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"').replace(/no/, ''); + + if (canPlayAacVideoAudio && browser.chromecast && physicalAudioChannels <= 2) { + // prioritize this first + videoAudioCodecs.push('aac'); + } + + // Only put mp3 first if mkv support is there + // Otherwise with HLS and mp3 audio we're seeing some browsers + // safari is lying + if (supportsAc3(videoTestElement)) { + + videoAudioCodecs.push('ac3'); + + var eAc3 = supportsEac3(videoTestElement); + if (eAc3) { + videoAudioCodecs.push('eac3'); + } + + // This works in edge desktop, but not mobile + // TODO: Retest this on mobile + if (supportsAc3InHls(videoTestElement)) { + hlsVideoAudioCodecs.push('ac3'); + if (eAc3) { + hlsVideoAudioCodecs.push('eac3'); + } + } + } + + if (canPlayAacVideoAudio && browser.chromecast && videoAudioCodecs.indexOf('aac') === -1) { + // prioritize this first + videoAudioCodecs.push('aac'); + } + + if (supportsMp3VideoAudio) { + videoAudioCodecs.push('mp3'); + + // PS4 fails to load HLS with mp3 audio + if (!browser.ps4) { + // mp3 encoder only supports 2 channels, so only make that preferred if we're only requesting 2 channels + // Also apply it for chromecast because it no longer supports AAC 5.1 + if (physicalAudioChannels <= 2) { + hlsVideoAudioCodecs.push('mp3'); + } + } + } + + if (canPlayAacVideoAudio) { + if (videoAudioCodecs.indexOf('aac') === -1) { + videoAudioCodecs.push('aac'); + } + + hlsVideoAudioCodecs.push('aac'); + } + + if (supportsMp3VideoAudio) { + // PS4 fails to load HLS with mp3 audio + if (!browser.ps4) { + if (hlsVideoAudioCodecs.indexOf('mp3') === -1) { + hlsVideoAudioCodecs.push('mp3'); + } + } + } + + if (supportsMp2VideoAudio) { + videoAudioCodecs.push('mp2'); + } + + var supportsDts = browser.tizen || browser.orsay || browser.web0s || options.supportsDts; + + // DTS audio not supported in 2018 models (Tizen 4.0) + if (browser.tizenVersion >= 4) { + supportsDts = false; + } + + if (supportsDts) { + videoAudioCodecs.push('dca'); + videoAudioCodecs.push('dts'); + } + + if (browser.tizen || browser.orsay || browser.web0s) { + videoAudioCodecs.push('pcm_s16le'); + videoAudioCodecs.push('pcm_s24le'); + } + + if (options.supportsTrueHd) { + videoAudioCodecs.push('truehd'); + } + + if (browser.tizen || browser.orsay) { + videoAudioCodecs.push('aac_latm'); + } + + if (canPlayAudioFormat('opus')) { + videoAudioCodecs.push('opus'); + hlsVideoAudioCodecs.push('opus'); + webmAudioCodecs.push('opus'); + } + + if (canPlayAudioFormat('flac')) { + videoAudioCodecs.push('flac'); + } + + videoAudioCodecs = videoAudioCodecs.filter(function (c) { + return (options.disableVideoAudioCodecs || []).indexOf(c) === -1; + }); + + hlsVideoAudioCodecs = hlsVideoAudioCodecs.filter(function (c) { + return (options.disableHlsVideoAudioCodecs || []).indexOf(c) === -1; + }); + + var mp4VideoCodecs = []; + var hlsVideoCodecs = []; + + if (canPlayH264(videoTestElement)) { + mp4VideoCodecs.push('h264'); + hlsVideoCodecs.push('h264'); + } + + if (canPlayH265(videoTestElement, options)) { + mp4VideoCodecs.push('h265'); + mp4VideoCodecs.push('hevc'); + + if (browser.tizen || browser.web0s) { + hlsVideoCodecs.push('h265'); + hlsVideoCodecs.push('hevc'); + } + } + + if (supportsMpeg2Video()) { + mp4VideoCodecs.push('mpeg2video'); + } + + if (supportsVc1()) { + mp4VideoCodecs.push('vc1'); + } + + if (browser.tizen || browser.orsay) { + mp4VideoCodecs.push('msmpeg4v2'); + } + + if (canPlayVp8) { + mp4VideoCodecs.push('vp8'); + } + + if (canPlayVp9) { + mp4VideoCodecs.push('vp9'); + } + + if (canPlayVp8 || browser.tizen || browser.orsay) { + videoAudioCodecs.push('vorbis'); + } + + if (mp4VideoCodecs.length) { + profile.DirectPlayProfiles.push({ + Container: 'mp4,m4v', + Type: 'Video', + VideoCodec: mp4VideoCodecs.join(','), + AudioCodec: videoAudioCodecs.join(',') + }); + } + + if (canPlayMkv && mp4VideoCodecs.length) { + profile.DirectPlayProfiles.push({ + Container: 'mkv', + Type: 'Video', + VideoCodec: mp4VideoCodecs.join(','), + AudioCodec: videoAudioCodecs.join(',') + }); + } + + // These are formats we can't test for but some devices will support + ['m2ts', 'wmv', 'ts', 'asf', 'avi', 'mpg', 'mpeg', 'flv', '3gp', 'mts', 'trp', 'vob', 'vro', 'mov'].map(function (container) { + return getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options); + }).filter(function (i) { + return i != null; + }).forEach(function (i) { + profile.DirectPlayProfiles.push(i); + }); + + ['opus', 'mp3', 'mp2', 'aac', 'flac', 'alac', 'webma', 'wma', 'wav', 'ogg', 'oga'].filter(canPlayAudioFormat).forEach(function (audioFormat) { + + if (audioFormat === 'mp2') { + profile.DirectPlayProfiles.push({ + Container: 'mp2,mp3', + Type: 'Audio', + AudioCodec: audioFormat + }); + } else if (audioFormat === 'mp3') { + profile.DirectPlayProfiles.push({ + Container: audioFormat, + Type: 'Audio', + AudioCodec: audioFormat + }); + } else { + profile.DirectPlayProfiles.push({ + Container: audioFormat === 'webma' ? 'webma,webm' : audioFormat, + Type: 'Audio' + }); + } + + // aac also appears in the m4a and m4b container + if (audioFormat === 'aac' || audioFormat === 'alac') { + profile.DirectPlayProfiles.push({ + Container: 'm4a,m4b', + AudioCodec: audioFormat, + Type: 'Audio' + }); + } + }); + + if (canPlayVp8) { + profile.DirectPlayProfiles.push({ + Container: 'webm', + Type: 'Video', + AudioCodec: webmAudioCodecs.join(','), + VideoCodec: 'VP8' + }); + } + + if (canPlayVp9) { + profile.DirectPlayProfiles.push({ + Container: 'webm', + Type: 'Video', + AudioCodec: webmAudioCodecs.join(','), + VideoCodec: 'VP9' + }); + } + + profile.TranscodingProfiles = []; + + var hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls() ? true : false; + + if (canPlayHls() && browser.enableHlsAudio !== false) { + profile.TranscodingProfiles.push({ + // hlsjs, edge, and android all seem to require ts container + Container: !canPlayNativeHls() || browser.edge || browser.android ? 'ts' : 'aac', + Type: 'Audio', + AudioCodec: 'aac', + Context: 'Streaming', + Protocol: 'hls', + MaxAudioChannels: physicalAudioChannels.toString(), + MinSegments: browser.iOS || browser.osx ? '2' : '1', + BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames + }); + } + + // For streaming, prioritize opus transcoding after mp3/aac. It is too problematic with random failures + // But for static (offline sync), it will be just fine. + // Prioritize aac higher because the encoder can accept more channels than mp3 + ['aac', 'mp3', 'opus', 'wav'].filter(canPlayAudioFormat).forEach(function (audioFormat) { + profile.TranscodingProfiles.push({ + Container: audioFormat, + Type: 'Audio', + AudioCodec: audioFormat, + Context: 'Streaming', + Protocol: 'http', + MaxAudioChannels: physicalAudioChannels.toString() + }); + }); + + ['opus', 'mp3', 'aac', 'wav'].filter(canPlayAudioFormat).forEach(function (audioFormat) { + profile.TranscodingProfiles.push({ + Container: audioFormat, + Type: 'Audio', + AudioCodec: audioFormat, + Context: 'Static', + Protocol: 'http', + MaxAudioChannels: physicalAudioChannels.toString() + }); + }); + + if (canPlayMkv && !browser.tizen && !browser.orsay && options.enableMkvProgressive !== false) { + profile.TranscodingProfiles.push({ + Container: 'mkv', + Type: 'Video', + AudioCodec: videoAudioCodecs.join(','), + VideoCodec: mp4VideoCodecs.join(','), + Context: 'Streaming', + MaxAudioChannels: physicalAudioChannels.toString(), + CopyTimestamps: true + }); + } + + if (canPlayMkv) { + profile.TranscodingProfiles.push({ + Container: 'mkv', + Type: 'Video', + AudioCodec: videoAudioCodecs.join(','), + VideoCodec: mp4VideoCodecs.join(','), + Context: 'Static', + MaxAudioChannels: physicalAudioChannels.toString(), + CopyTimestamps: true + }); + } + + if (canPlayHls() && hlsVideoAudioCodecs.length && options.enableHls !== false) { + profile.TranscodingProfiles.push({ + Container: 'ts', + Type: 'Video', + AudioCodec: hlsVideoAudioCodecs.join(','), + VideoCodec: hlsVideoCodecs.join(','), + Context: 'Streaming', + Protocol: 'hls', + MaxAudioChannels: physicalAudioChannels.toString(), + MinSegments: browser.iOS || browser.osx ? '2' : '1', + BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames + }); + } + + if (canPlayVp8) { + profile.TranscodingProfiles.push({ + Container: 'webm', + Type: 'Video', + AudioCodec: 'vorbis', + VideoCodec: 'vpx', + Context: 'Streaming', + Protocol: 'http', + // If audio transcoding is needed, limit channels to number of physical audio channels + // Trying to transcode to 5 channels when there are only 2 speakers generally does not sound good + MaxAudioChannels: physicalAudioChannels.toString() + }); + } + + profile.TranscodingProfiles.push({ + Container: 'mp4', + Type: 'Video', + AudioCodec: videoAudioCodecs.join(','), + VideoCodec: 'h264', + Context: 'Static', + Protocol: 'http' + }); + + profile.ContainerProfiles = []; + + profile.CodecProfiles = []; + + var supportsSecondaryAudio = browser.tizen || browser.orsay || videoTestElement.audioTracks; + + var aacCodecProfileConditions = []; + + // Handle he-aac not supported + if (!videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.5"').replace(/no/, '')) { + // TODO: This needs to become part of the stream url in order to prevent stream copy + aacCodecProfileConditions.push({ + Condition: 'NotEquals', + Property: 'AudioProfile', + Value: 'HE-AAC' + }); + } + + if (!supportsSecondaryAudio) { + aacCodecProfileConditions.push({ + Condition: 'Equals', + Property: 'IsSecondaryAudio', + Value: 'false', + IsRequired: false + }); + } + + if (browser.chromecast) { + aacCodecProfileConditions.push({ + Condition: 'LessThanEqual', + Property: 'AudioChannels', + Value: '2', + IsRequired: true + }); + } + + if (aacCodecProfileConditions.length) { + profile.CodecProfiles.push({ + Type: 'VideoAudio', + Codec: 'aac', + Conditions: aacCodecProfileConditions + }); + } + + if (!supportsSecondaryAudio) { + profile.CodecProfiles.push({ + Type: 'VideoAudio', + Conditions: [ + { + Condition: 'Equals', + Property: 'IsSecondaryAudio', + Value: 'false', + IsRequired: false + } + ] + }); + } + + var maxH264Level = 42; + var h264Profiles = 'high|main|baseline|constrained baseline'; + + if (browser.tizen || browser.orsay || browser.web0s || + videoTestElement.canPlayType('video/mp4; codecs="avc1.640833"').replace(/no/, '')) { + maxH264Level = 51; + } + + // Support H264 Level 52 (Tizen 5.0) - app only + if (browser.tizenVersion >= 5 && window.NativeShell) { + maxH264Level = 52; + } + + if (browser.tizen || browser.orsay || + videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { + + // These tests are passing in safari, but playback is failing + if (!browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile) { + h264Profiles += '|high 10'; + } + } + + profile.CodecProfiles.push({ + Type: 'Video', + Codec: 'h264', + Conditions: [ + { + Condition: 'NotEquals', + Property: 'IsAnamorphic', + Value: 'true', + IsRequired: false + }, + { + Condition: 'EqualsAny', + Property: 'VideoProfile', + Value: h264Profiles, + IsRequired: false + }, + { + Condition: 'LessThanEqual', + Property: 'VideoLevel', + Value: maxH264Level.toString(), + IsRequired: false + } + ] + }); + + if (!browser.edgeUwp && !browser.tizen && !browser.orsay && !browser.web0s) { + //profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ + // Condition: 'NotEquals', + // Property: 'IsAVC', + // Value: 'false', + // IsRequired: false + //}); + + //profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ + // Condition: 'NotEquals', + // Property: 'IsInterlaced', + // Value: 'true', + // IsRequired: false + //}); + } + + if (maxVideoWidth) { + profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ + Condition: 'LessThanEqual', + Property: 'Width', + Value: maxVideoWidth.toString(), + IsRequired: false + }); + } + + var globalMaxVideoBitrate = (getGlobalMaxVideoBitrate() || '').toString(); + + var h264MaxVideoBitrate = globalMaxVideoBitrate; + + if (h264MaxVideoBitrate) { + profile.CodecProfiles[profile.CodecProfiles.length - 1].Conditions.push({ + Condition: 'LessThanEqual', + Property: 'VideoBitrate', + Value: h264MaxVideoBitrate, + IsRequired: true + }); + } + + var globalVideoConditions = []; + + if (globalMaxVideoBitrate) { + globalVideoConditions.push({ + Condition: 'LessThanEqual', + Property: 'VideoBitrate', + Value: globalMaxVideoBitrate + }); + } + + if (maxVideoWidth) { + globalVideoConditions.push({ + Condition: 'LessThanEqual', + Property: 'Width', + Value: maxVideoWidth.toString(), + IsRequired: false + }); + } + + if (globalVideoConditions.length) { + profile.CodecProfiles.push({ + Type: 'Video', + Conditions: globalVideoConditions + }); + } + + if (browser.chromecast) { + profile.CodecProfiles.push({ + Type: 'Audio', + Codec: 'flac', + Conditions: [ + { + Condition: 'LessThanEqual', + Property: 'AudioSampleRate', + Value: '96000' + }] + }); + } + + // Subtitle profiles + // External vtt or burn in + profile.SubtitleProfiles = []; + if (supportsTextTracks()) { + profile.SubtitleProfiles.push({ + Format: 'vtt', + Method: 'External' + }); + } + + profile.ResponseProfiles = []; + profile.ResponseProfiles.push({ + Type: 'Video', + Container: 'm4v', + MimeType: 'video/mp4' + }); + + return profile; + }; +}); From a1cc9778725bc8e4eaf4c0408919f39b4640c02b Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 31 Mar 2020 18:59:12 +0300 Subject: [PATCH 08/16] Update documentation --- src/components/autoFocuser.js | 6 +- src/components/dom.js | 52 +++++------ src/components/scrollManager.js | 148 +++++++++++++++++++++----------- 3 files changed, 125 insertions(+), 81 deletions(-) diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index 93ebb4b3be..a469eb8854 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -21,7 +21,7 @@ import layoutManager from "layoutManager"; } /** - * Start AutoFocuser + * Start AutoFocuser. */ export function enable() { if (!isEnabled()) { @@ -37,8 +37,8 @@ import layoutManager from "layoutManager"; /** * Set focus on a suitable element, taking into account the previously selected. - * @param {HTMLElement} [container] - element to limit scope - * @returns {HTMLElement} focused element + * @param {HTMLElement} [container] - Element to limit scope. + * @returns {HTMLElement} Focused element. */ export function autoFocus(container) { if (!isEnabled()) { diff --git a/src/components/dom.js b/src/components/dom.js index fdc5e607eb..3fe4287320 100644 --- a/src/components/dom.js +++ b/src/components/dom.js @@ -7,10 +7,10 @@ /** * Returns parent of element with specified attribute value. - * @param {HTMLElement} elem - element whose parent need to find - * @param {string} name - attribute name - * @param {mixed} value - attribute value - * @returns {HTMLElement} Parent with specified attribute value + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {string} name - Attribute name. + * @param {mixed} value - Attribute value. + * @returns {HTMLElement} Parent with specified attribute value. */ export function parentWithAttribute(elem, name, value) { while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) { @@ -26,9 +26,9 @@ /** * Returns parent of element with one of specified tag names. - * @param {HTMLElement} elem - element whose parent need to find - * @param {(string|Array)} tagNames - tag name or array of tag names - * @returns {HTMLElement} Parent with one of specified tag names + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {(string|Array)} tagNames - Tag name or array of tag names. + * @returns {HTMLElement} Parent with one of specified tag names. */ export function parentWithTag(elem, tagNames) { // accept both string and array passed in @@ -49,9 +49,9 @@ /** * Returns _true_ if class list contains one of specified names. - * @param {DOMTokenList} classList - class list - * @param {Array} classNames - array of class names - * @returns {boolean} _true_ if class list contains one of specified names + * @param {DOMTokenList} classList - Class list. + * @param {Array} classNames - Array of class names. + * @returns {boolean} _true_ if class list contains one of specified names. */ function containsAnyClass(classList, classNames) { for (let i = 0, length = classNames.length; i < length; i++) { @@ -64,9 +64,9 @@ /** * Returns parent of element with one of specified class names. - * @param {HTMLElement} elem - element whose parent need to find - * @param {(string|Array)} classNames - class name or array of class names - * @returns {HTMLElement} Parent with one of specified class names + * @param {HTMLElement} elem - Element whose parent need to find. + * @param {(string|Array)} classNames - Class name or array of class names. + * @returns {HTMLElement} Parent with one of specified class names. */ export function parentWithClass(elem, classNames) { // accept both string and array passed in @@ -100,10 +100,10 @@ /** * Adds event listener to specified target. - * @param {EventTarget} target - event target - * @param {string} type - event type - * @param {function} handler - event handler - * @param {Object} [options] - listener options + * @param {EventTarget} target - Event target. + * @param {string} type - Event type. + * @param {function} handler - Event handler. + * @param {Object} [options] - Listener options. */ export function addEventListener(target, type, handler, options) { let optionsOrCapture = options || {}; @@ -115,10 +115,10 @@ /** * Removes event listener from specified target. - * @param {EventTarget} target - event target - * @param {string} type - event type - * @param {function} handler - event handler - * @param {Object} [options] - listener options + * @param {EventTarget} target - Event target. + * @param {string} type - Event type. + * @param {function} handler - Event handler. + * @param {Object} [options] - Listener options. */ export function removeEventListener(target, type, handler, options) { let optionsOrCapture = options || {}; @@ -147,7 +147,7 @@ /** * Returns window size. - * @returns {Object} Window size + * @returns {Object} Window size. */ export function getWindowSize() { if (!windowSize) { @@ -173,7 +173,7 @@ /** * Returns screen width. - * @returns {number} Screen width + * @returns {number} Screen width. */ export function getScreenWidth() { let width = window.innerWidth; @@ -197,7 +197,7 @@ /** * Returns name of animation end event. - * @returns {string} Name of animation end event + * @returns {string} Name of animation end event. */ export function whichAnimationEvent() { if (_animationEvent) { @@ -224,7 +224,7 @@ /** * Returns name of animation cancel event. - * @returns {string} Name of animation cancel event + * @returns {string} Name of animation cancel event. */ export function whichAnimationCancelEvent() { return whichAnimationEvent().replace('animationend', 'animationcancel').replace('AnimationEnd', 'AnimationCancel'); @@ -237,7 +237,7 @@ /** * Returns name of transition end event. - * @returns {string} Name of transition end event + * @returns {string} Name of transition end event. */ export function whichTransitionEvent() { if (_transitionEvent) { diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 96317fa998..037ca5b059 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -24,7 +24,7 @@ import layoutManager from "layoutManager"; * Returns minimum vertical scroll. * Scroll less than that value will be zeroed. * - * @return {number} minimum vertical scroll + * @return {number} Minimum vertical scroll. */ function minimumScrollY() { const topMenu = document.querySelector(".headerTop"); @@ -55,10 +55,10 @@ import layoutManager from "layoutManager"; /** * Returns value clamped by range [min, max]. * - * @param {number} value - clamped value - * @param {number} min - begining of range - * @param {number} max - ending of range - * @return {number} clamped value + * @param {number} value - Clamped value. + * @param {number} min - Begining of range. + * @param {number} max - Ending of range. + * @return {number} Clamped value. */ function clamp(value, min, max) { return value <= min ? min : value >= max ? max : value; @@ -68,11 +68,11 @@ import layoutManager from "layoutManager"; * Returns the required delta to fit range 1 into range 2. * In case of range 1 is bigger than range 2 returns delta to fit most out of range part. * - * @param {number} begin1 - begining of range 1 - * @param {number} end1 - ending of range 1 - * @param {number} begin2 - begining of range 2 - * @param {number} end2 - ending of range 2 - * @return {number} delta: <0 move range1 to the left, >0 - to the right + * @param {number} begin1 - Begining of range 1. + * @param {number} end1 - Ending of range 1. + * @param {number} begin2 - Begining of range 2. + * @param {number} end2 - Ending of range 2. + * @return {number} Delta: <0 move range1 to the left, >0 - to the right. */ function fitRange(begin1, end1, begin2, end2) { const delta1 = begin1 - begin2; @@ -88,13 +88,21 @@ import layoutManager from "layoutManager"; /** * Ease value. * - * @param {number} t - value in range [0, 1] - * @return {number} eased value in range [0, 1] + * @param {number} t - Value in range [0, 1]. + * @return {number} Eased value in range [0, 1]. */ function ease(t) { return t*(2 - t); // easeOutQuad === ease-out } + /** + * @typedef {Object} Rect + * @property {number} left - X coordinate of top-left corner. + * @property {number} top - Y coordinate of top-left corner. + * @property {number} width - Width. + * @property {number} height - Height. + */ + /** * Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * @@ -109,6 +117,10 @@ import layoutManager from "layoutManager"; * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement */ class DocumentScroller { + /** + * Horizontal scroll position. + * @type {number} + */ get scrollLeft() { return window.pageXOffset; } @@ -117,6 +129,10 @@ import layoutManager from "layoutManager"; window.scroll(val, window.pageYOffset); } + /** + * Vertical scroll position. + * @type {number} + */ get scrollTop() { return window.pageYOffset; } @@ -125,22 +141,42 @@ import layoutManager from "layoutManager"; window.scroll(window.pageXOffset, val); } + /** + * Horizontal scroll size (scroll width). + * @type {number} + */ get scrollWidth() { return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); } + /** + * Vertical scroll size (scroll height). + * @type {number} + */ get scrollHeight() { return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); } + /** + * Horizontal client size (client width). + * @type {number} + */ get clientWidth() { return Math.min(document.documentElement.clientWidth, document.body.clientWidth); } + /** + * Vertical client size (client height). + * @type {number} + */ get clientHeight() { return Math.min(document.documentElement.clientHeight, document.body.clientHeight); } + /** + * Returns bounding client rect. + * @return {Rect} Bounding client rect. + */ getBoundingClientRect() { // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport return { @@ -151,6 +187,10 @@ import layoutManager from "layoutManager"; }; } + /** + * Scrolls window. + * @param {...mixed} args See window.scrollTo. + */ scrollTo() { window.scrollTo.apply(window, arguments); } @@ -162,10 +202,11 @@ import layoutManager from "layoutManager"; const documentScroller = new DocumentScroller(); /** - * Returns parent element that can be scrolled. If no such, returns documentElement. + * Returns parent element that can be scrolled. If no such, returns document scroller. * - * @param {HTMLElement} element - element for which parent is being searched - * @param {boolean} vertical - search for vertical scrollable parent + * @param {HTMLElement} element - Element for which parent is being searched. + * @param {boolean} vertical - Search for vertical scrollable parent. + * @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller. */ function getScrollableParent(element, vertical) { if (element) { @@ -197,17 +238,17 @@ import layoutManager from "layoutManager"; /** * @typedef {Object} ScrollerData - * @property {number} scrollPos - current scroll position - * @property {number} scrollSize - scroll size - * @property {number} clientSize - client size + * @property {number} scrollPos - Current scroll position. + * @property {number} scrollSize - Scroll size. + * @property {number} clientSize - Client size. */ /** - * Returns scroll data for specified orientation. + * Returns scroller data for specified orientation. * - * @param {HTMLElement} scroller - scroller - * @param {boolean} vertical - vertical scroll data - * @return {ScrollerData} scroll data + * @param {HTMLElement} scroller - Scroller. + * @param {boolean} vertical - Vertical scroller data. + * @return {ScrollerData} Scroller data. */ function getScrollerData(scroller, vertical) { let data = {}; @@ -228,10 +269,10 @@ import layoutManager from "layoutManager"; /** * Returns position of child of scroller for specified orientation. * - * @param {HTMLElement} scroller - scroller - * @param {HTMLElement} element - child of scroller - * @param {boolean} vertical - vertical scroll - * @return {number} child position + * @param {HTMLElement} scroller - Scroller. + * @param {HTMLElement} element - Child of scroller. + * @param {boolean} vertical - Vertical scroll. + * @return {number} Child position. */ function getScrollerChildPos(scroller, element, vertical) { const elementRect = element.getBoundingClientRect(); @@ -247,11 +288,11 @@ import layoutManager from "layoutManager"; /** * Returns scroll position for element. * - * @param {ScrollerData} scrollerData - scroller data - * @param {number} elementPos - child element position - * @param {number} elementSize - child element size - * @param {boolean} centered - scroll to center - * @return {number} scroll position + * @param {ScrollerData} scrollerData - Scroller data. + * @param {number} elementPos - Child element position. + * @param {number} elementSize - Child element size. + * @param {boolean} centered - Scroll to center. + * @return {number} Scroll position. */ function calcScroll(scrollerData, elementPos, elementSize, centered) { const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; @@ -271,8 +312,8 @@ import layoutManager from "layoutManager"; /** * Calls scrollTo function in proper way. * - * @param {HTMLElement} scroller - scroller - * @param {ScrollToOptions} options - scroll options + * @param {HTMLElement} scroller - Scroller. + * @param {ScrollToOptions} options - Scroll options. */ function scrollToHelper(scroller, options) { if ("scrollTo" in scroller) { @@ -296,11 +337,11 @@ import layoutManager from "layoutManager"; /** * Performs built-in scroll. * - * @param {HTMLElement} xScroller - horizontal scroller - * @param {number} scrollX - horizontal coordinate - * @param {HTMLElement} yScroller - vertical scroller - * @param {number} scrollY - vertical coordinate - * @param {boolean} smooth - smooth scrolling + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} smooth - Smooth scrolling. */ function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { const scrollBehavior = smooth ? "smooth" : "instant"; @@ -313,6 +354,9 @@ import layoutManager from "layoutManager"; } } + /** + * Requested frame for animated scroll. + */ let scrollTimer; /** @@ -326,10 +370,10 @@ import layoutManager from "layoutManager"; /** * Performs animated scroll. * - * @param {HTMLElement} xScroller - horizontal scroller - * @param {number} scrollX - horizontal coordinate - * @param {HTMLElement} yScroller - vertical scroller - * @param {number} scrollY - vertical coordinate + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. */ function animateScroll(xScroller, scrollX, yScroller, scrollY) { @@ -372,11 +416,11 @@ import layoutManager from "layoutManager"; /** * Performs scroll. * - * @param {HTMLElement} xScroller - horizontal scroller - * @param {number} scrollX - horizontal coordinate - * @param {HTMLElement} yScroller - vertical scroller - * @param {number} scrollY - vertical coordinate - * @param {boolean} smooth - smooth scrolling + * @param {HTMLElement} xScroller - Horizontal scroller. + * @param {number} scrollX - Horizontal coordinate. + * @param {HTMLElement} yScroller - Vertical scroller. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} smooth - Smooth scrolling. */ function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { @@ -420,9 +464,9 @@ import layoutManager from "layoutManager"; /** * Scrolls the document to a given position. * - * @param {number} scrollX - horizontal coordinate - * @param {number} scrollY - vertical coordinate - * @param {boolean} [smooth=false] - smooth scrolling + * @param {number} scrollX - Horizontal coordinate. + * @param {number} scrollY - Vertical coordinate. + * @param {boolean} [smooth=false] - Smooth scrolling. */ export function scrollTo(scrollX, scrollY, smooth) { @@ -443,8 +487,8 @@ import layoutManager from "layoutManager"; /** * Scrolls the document to a given element. * - * @param {HTMLElement} element - target element of scroll task - * @param {boolean} [smooth=false] - smooth scrolling + * @param {HTMLElement} element - Target element of scroll task. + * @param {boolean} [smooth=false] - Smooth scrolling. */ export function scrollToElement(element, smooth) { From 8789b1b69b2b5298fe90aaee51ce7ec1f151622c Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 31 Mar 2020 19:07:03 +0300 Subject: [PATCH 09/16] Update documentation --- src/components/input/keyboardnavigation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/input/keyboardnavigation.js b/src/components/input/keyboardnavigation.js index caddf46797..d356854a3e 100644 --- a/src/components/input/keyboardnavigation.js +++ b/src/components/input/keyboardnavigation.js @@ -60,8 +60,8 @@ if (!hasFieldKey) { /** * Returns key name from event. * - * @param {KeyboardEvent} event - keyboard event - * @return {string} key name + * @param {KeyboardEvent} event - Keyboard event. + * @return {string} Key name. */ export function getKeyName(event) { return KeyNames[event.keyCode] || event.key; @@ -70,8 +70,8 @@ export function getKeyName(event) { /** * Returns _true_ if key is used for navigation. * - * @param {string} key - key name - * @return {boolean} _true_ if key is used for navigation + * @param {string} key - Key name. + * @return {boolean} _true_ if key is used for navigation. */ export function isNavigationKey(key) { return NavigationKeys.indexOf(key) != -1; From 3d7e7105e177b033f9d150ba8aa9028538af5636 Mon Sep 17 00:00:00 2001 From: Medzhnun Date: Wed, 1 Apr 2020 12:00:43 +0000 Subject: [PATCH 10/16] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/bg/ --- src/strings/bg-bg.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/strings/bg-bg.json b/src/strings/bg-bg.json index bf264bbcfb..4a9c3146d5 100644 --- a/src/strings/bg-bg.json +++ b/src/strings/bg-bg.json @@ -10,7 +10,7 @@ "All": "Всички", "AllLibraries": "Всички библиотеки", "Art": "Картина", - "Artists": "Изпълнители", + "Artists": "Артисти", "AttributeNew": "Нови", "Audio": "Звук", "Auto": "Автоматично", @@ -839,5 +839,7 @@ "LabelKodiMetadataSaveImagePathsHelp": "Препоръчително е ако имате изображения, пътят към които не е съобразен с изискванията на Коди.", "LabelKodiMetadataSaveImagePaths": "Записване на пътеките към изображенията в nfo файловете", "LabelChannels": "Канали:", - "DropShadow": "Сянка" + "DropShadow": "Сянка", + "Raised": "Повишено", + "OptionResElement": "рес. елемент" } From e69bb1534b7e2231fa910222a894cd2b7d19f17f Mon Sep 17 00:00:00 2001 From: KGT1 Date: Tue, 31 Mar 2020 23:33:47 +0000 Subject: [PATCH 11/16] Translated using Weblate (German) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/de/ --- src/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/de.json b/src/strings/de.json index 97cb1f959e..3dab52c547 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -1401,7 +1401,7 @@ "TitleSupport": "Hilfe", "Whitelist": "Erlaubt", "AuthProviderHelp": "Auswählen eines Authentifizierungsanbieter, der zur Authentifizierung des Passworts dieses Benutzes verwendet werden soll.", - "Features": "Features", + "Features": "Funktionen", "HeaderFavoriteBooks": "Lieblingsbücher", "HeaderFavoriteMovies": "Lieblingsfilme", "HeaderFavoriteShows": "Lieblingsserien", From bb834a79e7be143235e1b1ed02c5616e323046c0 Mon Sep 17 00:00:00 2001 From: amirmasoud Date: Wed, 1 Apr 2020 12:24:21 +0000 Subject: [PATCH 12/16] Translated using Weblate (Persian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fa/ --- src/strings/fa.json | 93 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/strings/fa.json b/src/strings/fa.json index d9afcd66cc..6dcc8cbb66 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -224,5 +224,96 @@ "AddedOnValue": "{0} افزوده شد", "AddToPlaylist": "افزودن به لیست پخش", "AddToPlayQueue": "افزودن به صف پخش", - "AddToCollection": "افزودن به مجموعه" + "AddToCollection": "افزودن به مجموعه", + "ExitFullscreen": "خروج از تمام صفحه", + "EveryNDays": "هر {0} روز", + "ErrorMessageStartHourGreaterThanEnd": "زمان پایان باید پس از زمان شروع باشد.", + "Episodes": "قسمت‌ها", + "EndsAtValue": "تمام شده در {0}", + "Ended": "تمام شده", + "EnableThemeVideos": "تم فیلم‌ها", + "EnableThemeSongs": "آهنگ‌های تم", + "EnableStreamLooping": "چرخش خودکار پخش‌های زنده", + "EnablePhotos": "نمایش عکس‌ها", + "EnableNextVideoInfoOverlay": "نمایش اطلاعات ودیوی بعدی حین پخش ویدیو", + "EnableHardwareEncoding": "فعال سازی رمزگذاری سخت افزاری", + "EnableExternalVideoPlayersHelp": "یک منوی پخش کننده ویدیوی خارجی، زمانی که شروع به پخش ویدیو می‌شود نمایش داده خواهد شد.", + "EnableExternalVideoPlayers": "پخش کننده ویدیوی خارجی", + "EnableDisplayMirroring": "نمایش حالت آینه", + "EnableCinemaMode": "حالت سینما", + "EnableBackdrops": "پشت‌زمینه‌ها", + "EditSubtitles": "ویرایش زیرنویس‌ها", + "EditMetadata": "ویرایش ابرداده", + "EditImages": "ویرایش عکس‌ها", + "Edit": "ویرایش", + "DropShadow": "سایه پشت زمینه", + "DrmChannelsNotImported": "کانال‌ها با DRM وارد نخواند شد.", + "DownloadsValue": "{0} بارگیری‌ها", + "Download": "بارگیری", + "Down": "پایین", + "DoNotRecord": "ضبط نکن", + "DisplayModeHelp": "نوع صفحه نمایشی که Jellyfin را اجرا می‌کنید را انتخاب کنید‌‌.", + "DisplayMissingEpisodesWithinSeasons": "قسمت‌های ناموجود در فصل‌ها را نمایش بده", + "DisplayInMyMedia": "نمایش در صفحه‌ی خانه", + "Display": "نمایش", + "Dislike": "دوست نداشتن", + "Disconnect": "قطع اتصال", + "Disc": "دیسک", + "Directors": "کارگردانان", + "Director": "کارگردان", + "DirectStreaming": "پخش مستقیم", + "DirectStreamHelp2": "پخش مستقیم فایل از قدرت پردازش بسیار کمی بدون از دست دادن کیفیت ویدیو استفاده می‌کند.", + "DirectPlaying": "پخش مستقیم", + "DetectingDevices": "در حال تشخیص دستگاه‌ها", + "Descending": "پایین رونده", + "Depressed": "پژمرده", + "DeleteUserConfirmation": "آیا اطمینان دارید که می‌خواهید این کاربر را حذف کنید؟", + "DeleteUser": "حذف کاربر", + "DeleteImageConfirmation": "آیا اطمینان دارید که می‌خواهید این تصویر را حذف کنید؟", + "DeleteImage": "حذف تصویر", + "DeleteDeviceConfirmation": "آیا از حذف این دستگاه اطمینان دارید؟ هنگامی که یک کاربر دوباره با آن دستگاه وارد شود، دوباره نمایش داده می‌شود.", + "Delete": "حذف", + "DefaultMetadataLangaugeDescription": "این موارد پیشفرض‌های شماست و می‌توانید برای هر کتابخانه آن را شخصی سازی کنید.", + "DefaultErrorMessage": "خطایی در پردازش درخواست رخ داد. لطفا اندکی بعد دوباره تلاش کنید.", + "Default": "پیشفرض", + "DeathDateValue": "تلف شد: {0}", + "DatePlayed": "تاریخ پخش شده", + "DateAdded": "تاریخ اضافه شده", + "CriticRating": "امتیاز منتقدان", + "CopyStreamURLError": "در کپی کردن آدرس خطایی رخ داد.", + "CopyStreamURLSuccess": "آدرس با موفقیت کپی شد.", + "CopyStreamURL": "کپی آدرس پخش", + "Continuing": "ادامه", + "ContinueWatching": "ادامه تماشا", + "Connect": "اتصال", + "ConfirmEndPlayerSession": "آیا می‌خواهید Jellyfin را روی {0} خاموش کنید؟", + "ConfirmDeletion": "تایید حذف", + "ConfirmDeleteImage": "حذف تصویر؟", + "Composer": "آهنگساز", + "CommunityRating": "امتیاز عمومی", + "ColorTransfer": "انتقال رنگ", + "ColorSpace": "فضای رنگی", + "ColorPrimaries": "مقدمات رنگی", + "ClientSettings": "تنظیمات مشتری", + "ChannelNumber": "شماره کانال", + "ChannelNameOnly": "تنها کانال {0}", + "Categories": "دسته‌بندی‌ها", + "CancelSeries": "لغو سریال‌ها", + "CancelRecording": "لغو ضبط", + "ButtonWebsite": "وبسایت", + "ButtonViewWebsite": "بازدید وبسایت", + "ButtonUp": "بالا", + "ButtonUninstall": "حذف نصب", + "ButtonTrailer": "تریلر", + "ButtonSubtitles": "زیرنویس‌ها", + "ButtonSubmit": "تایید", + "ButtonSplit": "جدا کردن", + "ButtonStop": "توقف", + "ButtonStart": "شروع", + "ButtonSignIn": "ورود", + "ButtonShutdown": "خاموش", + "ButtonSelectDirectory": "انتخاب مسیر", + "ButtonEditOtherUserPreferences": "نمایه، تصویر و ترجیحات شخصی این کاربر را ویرایش کنید.", + "BrowsePluginCatalogMessage": "برای مرور کردن افزونه‌های موجود، به فروشگاه افزونه‌های ما سر بزنید.", + "AuthProviderHelp": "ارائه دهنده تأیید اعتبار را انتخاب کنید تا برای تأیید اعتبار گذرواژه این کاربر استفاده شود." } From da45e28b721f36df28122a47ab03f5969e373243 Mon Sep 17 00:00:00 2001 From: Louis Hermier Date: Wed, 1 Apr 2020 17:09:15 +0000 Subject: [PATCH 13/16] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/fr/ --- src/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strings/fr.json b/src/strings/fr.json index ced0674031..53aac4f66b 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1446,7 +1446,7 @@ "LabelAudioChannels": "Canaux audio :", "HeaderFavoriteBooks": "Livres préférés", "FetchingData": "Récuperer des données suplémentaires", - "CopyStreamURLSuccess": "URL copiée avec succès", + "CopyStreamURLSuccess": "URL copiée avec succès.", "CopyStreamURL": "Copier l'URL du flux", "LabelBaseUrlHelp": "Vous pouvez ajouter un sous-répertoire personalisé ici pour accéder au serveur depuis une URL plus exclusive.", "HeaderFavoritePeople": "Personnes préférées", From f3ce3c6166efdbe836503d387ac2c5f7331f8347 Mon Sep 17 00:00:00 2001 From: MG Date: Wed, 1 Apr 2020 15:50:50 +0000 Subject: [PATCH 14/16] Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/it/ --- src/strings/it.json | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/strings/it.json b/src/strings/it.json index cf9e06910e..69680a46c4 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -264,7 +264,7 @@ "HeaderAddUser": "Aggiungi utente", "HeaderAdditionalParts": "Parti addizionali", "HeaderAdmin": "Admin", - "HeaderAlbumArtists": "Artisti dell' Album", + "HeaderAlbumArtists": "Artisti degli Album", "HeaderAlbums": "Album", "HeaderAlert": "Avviso", "HeaderAllowMediaDeletionFrom": "Abilita Eliminazione Media Da", @@ -572,7 +572,7 @@ "LabelEvent": "Evento:", "LabelEveryXMinutes": "Tutti:", "LabelExtractChaptersDuringLibraryScan": "Estrarre immagini capitolo durante la scansione della libreria", - "LabelExtractChaptersDuringLibraryScanHelp": "Genera le immagini del capitolo quando i video vengono importati durante la scansione della libreria. Altrimenti verranno estratti durante l'operazione pianificata di estrazione delle immagini capitolo, permettendo la scansione della libreria più velocemente.", + "LabelExtractChaptersDuringLibraryScanHelp": "Genera le immagini capitolo quando i video vengono importati durante la scansione della libreria. Alternativamente, verranno estratti durante l'operazione pianificata di estrazione delle immagini capitolo, permettendo la scansione della libreria più velocemente.", "LabelFailed": "Fallito", "LabelFileOrUrl": "File o URL:", "LabelFinish": "Finito", @@ -947,8 +947,8 @@ "OptionCustomUsers": "Personalizza", "OptionDaily": "Giornaliero", "OptionDateAdded": "Aggiunto il", - "OptionDateAddedFileTime": "Utilizzare file di data di creazione", - "OptionDateAddedImportTime": "Utilizza la data scansionato in biblioteca", + "OptionDateAddedFileTime": "Utilizzare la data di creazione del file", + "OptionDateAddedImportTime": "Utilizza la data di scansione nella libreria", "OptionDatePlayed": "Visto il", "OptionDescending": "Decrescente", "OptionDisableUser": "Disabilita questo utente", @@ -1076,7 +1076,7 @@ "Programs": "Programmi", "Quality": "Qualità", "QueueAllFromHere": "In coda tutto da qui in poi", - "Raised": "Sospeso", + "Raised": "Rilievo", "Rate": "Vota", "RecentlyWatched": "Visti di recente", "RecommendationBecauseYouLike": "Perché ti piace {0}", @@ -1168,7 +1168,7 @@ "SystemDlnaProfilesHelp": "I profili di sistema sono in sola lettura. Le modifiche ad un profilo di sistema verranno salvate in un nuovo profilo personalizzato.", "TabAccess": "Accesso", "TabAdvanced": "Avanzato", - "TabAlbumArtists": "Artisti degli album", + "TabAlbumArtists": "Artisti degli Album", "TabAlbums": "Album", "TabArtists": "Artisti", "TabCatalog": "Catalogo", @@ -1312,7 +1312,7 @@ "HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteSongs": "Brani Preferiti", "HeaderFavoriteVideos": "Video Preferiti", - "HeaderFetcherSettings": "Impostazioni Fetcher", + "HeaderFetcherSettings": "Impostazioni del Fetcher", "HeaderImageOptions": "Opzioni Immagine", "HeaderRestartingServer": "Riavvio Server", "Home": "Home", @@ -1467,5 +1467,8 @@ "LabelCorruptedFrames": "Frame corrotti:", "AskAdminToCreateLibrary": "Chiedi ad un amministratore di creare una libreria.", "AllowFfmpegThrottlingHelp": "Quando una transcodifica o un remux sono abbastanza avanti rispetto alla corrente posizione di riproduzione, pausa il processo così da consumare meno risorse. Questo è utile quando si guarda un video senza avanzare spesso durante la riproduzione. Disattiva questa opzione se stai avendo problemi di riproduzione.", - "AllowFfmpegThrottling": "Acceleratore Transcodifica" + "AllowFfmpegThrottling": "Acceleratore Transcodifica", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferisci le informazioni incorporate nell'episodio rispetto ai nomi dei file", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Questo utilizza le informazioni dell'episodio provenienti dai metadata incorporati, se disponibili.", + "ClientSettings": "Impostazioni del client" } From 555e57105c8df319ae78d670bc953268a021cca1 Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Wed, 1 Apr 2020 18:34:31 +0000 Subject: [PATCH 15/16] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index f1bffad58c..761e773d71 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1467,5 +1467,8 @@ "PlaybackErrorNoCompatibleStream": "Houve um erro na criação de perfil do cliente e o servidor não está enviando um formato de mídia compatível.", "EnableFastImageFadeInHelp": "Habilitar animações rápidas de aparecimento para imagens carregadas", "LabelDroppedFrames": "Quadros caídos:", - "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução." + "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução.", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações do episodio incorporados ao invés dos nomes dos arquivos", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso utiliza as informações do episodio provida pelos metadados incorporados caso estejam disponíveis.", + "ClientSettings": "Configurações do cliente" } From b3e1809abef71834229c378a494ee79310445c60 Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Thu, 2 Apr 2020 03:57:13 +0000 Subject: [PATCH 16/16] Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Web Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-web/pt_BR/ --- src/strings/pt-br.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index 761e773d71..45681762ca 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -1468,7 +1468,7 @@ "EnableFastImageFadeInHelp": "Habilitar animações rápidas de aparecimento para imagens carregadas", "LabelDroppedFrames": "Quadros caídos:", "AllowFfmpegThrottlingHelp": "Quando uma transcodificação ou remux estiver suficientemente avançada da posição atual de reprodução, pause o processo para que consuma menos recursos. Isso é mais proveitoso para quando não há avanço ou retrocesso do vídeo com frequência. Desative se tiver problemas de reprodução.", - "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir informações do episodio incorporados ao invés dos nomes dos arquivos", - "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso utiliza as informações do episodio provida pelos metadados incorporados caso estejam disponíveis.", + "PreferEmbeddedEpisodeInfosOverFileNames": "Preferir as informações incorporadas nos arquivos dos episódios ao invés dos nomes", + "PreferEmbeddedEpisodeInfosOverFileNamesHelp": "Isso utiliza as informações dos episódios incorporadas nos metadados dos arquivos se estiverem disponíveis.", "ClientSettings": "Configurações do cliente" }