diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css
index 67afef2376..0bf65d83a7 100644
--- a/src/assets/css/librarybrowser.css
+++ b/src/assets/css/librarybrowser.css
@@ -115,7 +115,7 @@
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: inline-flex;
- margin: 0.3em 0 0 0.5em;
+ margin: 0 0 0 0.5em;
height: 1.7em;
-webkit-box-align: center;
-webkit-align-items: center;
@@ -128,6 +128,10 @@
margin-top: 0;
}
+.layout-mobile .pageTitleWithDefaultLogo {
+ background-image: url(../img/icon-transparent.png);
+}
+
.headerLeft,
.skinHeader {
display: -webkit-box;
@@ -242,7 +246,6 @@
}
@media all and (min-width: 40em) {
- .dashboardDocument .adminDrawerLogo,
.dashboardDocument .mainDrawerButton {
display: none !important;
}
@@ -268,12 +271,6 @@
}
}
-@media all and (max-width: 60em) {
- .libraryDocument .mainDrawerButton {
- display: none;
- }
-}
-
@media all and (max-width: 84em) {
.withSectionTabs .headerTop {
padding-bottom: 0.55em;
diff --git a/src/components/castSenderApi.js b/src/components/castSenderApi.js
new file mode 100644
index 0000000000..a1e7bd8755
--- /dev/null
+++ b/src/components/castSenderApi.js
@@ -0,0 +1,34 @@
+define([], function() {
+ 'use strict';
+
+ if (window.appMode === "cordova" || window.appMode === "android") {
+ return {
+ load: function () {
+ window.chrome = window.chrome || {};
+ return Promise.resolve();
+ }
+ };
+ } else {
+ var ccLoaded = false;
+ return {
+ load: function () {
+ if (ccLoaded) {
+ return Promise.resolve();
+ }
+
+ return new Promise(function (resolve, reject) {
+ var fileref = document.createElement("script");
+ fileref.setAttribute("type", "text/javascript");
+
+ fileref.onload = function () {
+ ccLoaded = true;
+ resolve();
+ };
+
+ fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js");
+ document.querySelector("head").appendChild(fileref);
+ });
+ }
+ };
+ }
+});
diff --git a/src/components/confirm/confirm.js b/src/components/confirm/confirm.js
index f104350c87..fa9a156679 100644
--- a/src/components/confirm/confirm.js
+++ b/src/components/confirm/confirm.js
@@ -1,40 +1,65 @@
-define(['dialog', 'globalize'], function (dialog, globalize) {
+define(["browser", "dialog", "globalize"], function(browser, dialog, globalize) {
'use strict';
- return function (text, title) {
+ function replaceAll(str, find, replace) {
+ return str.split(find).join(replace);
+ }
- var options;
- if (typeof text === 'string') {
- options = {
- title: title,
- text: text
- };
- } else {
- options = text;
- }
-
- var items = [];
-
- items.push({
- name: options.cancelText || globalize.translate('ButtonCancel'),
- id: 'cancel',
- type: 'cancel'
- });
-
- items.push({
- name: options.confirmText || globalize.translate('ButtonOk'),
- id: 'ok',
- type: options.primary === 'delete' ? 'delete' : 'submit'
- });
-
- options.buttons = items;
-
- return dialog(options).then(function (result) {
- if (result === 'ok') {
- return Promise.resolve();
+ if (browser.tv && window.confirm) {
+ // Use the native confirm dialog
+ return function (options) {
+ if (typeof options === 'string') {
+ options = {
+ title: '',
+ text: options
+ };
}
- return Promise.reject();
- });
- };
+ var text = replaceAll(options.text || '', '
', '\n');
+ var result = confirm(text);
+
+ if (result) {
+ return Promise.resolve();
+ } else {
+ return Promise.reject();
+ }
+ };
+ } else {
+ // Use our own dialog
+ return function (text, title) {
+ var options;
+ if (typeof text === 'string') {
+ options = {
+ title: title,
+ text: text
+ };
+ } else {
+ options = text;
+ }
+
+ var items = [];
+
+ items.push({
+ name: options.cancelText || globalize.translate('ButtonCancel'),
+ id: 'cancel',
+ type: 'cancel'
+ });
+
+ items.push({
+ name: options.confirmText || globalize.translate('ButtonOk'),
+ id: 'ok',
+ type: options.primary === 'delete' ? 'delete' : 'submit'
+ });
+
+ options.buttons = items;
+
+ return dialog(options).then(function (result) {
+ if (result === 'ok') {
+ return Promise.resolve();
+ }
+
+ return Promise.reject();
+ });
+ };
+ }
});
diff --git a/src/components/confirm/nativeconfirm.js b/src/components/confirm/nativeconfirm.js
deleted file mode 100644
index 7d72bc5eaf..0000000000
--- a/src/components/confirm/nativeconfirm.js
+++ /dev/null
@@ -1,27 +0,0 @@
-define([], function () {
- 'use strict';
-
- function replaceAll(str, find, replace) {
-
- return str.split(find).join(replace);
- }
-
- return function (options) {
-
- if (typeof options === 'string') {
- options = {
- title: '',
- text: options
- };
- }
-
- var text = replaceAll(options.text || '', '
', '\n');
- var result = confirm(text);
-
- if (result) {
- return Promise.resolve();
- } else {
- return Promise.reject();
- }
- };
-});
diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js
index d8ddc13c0a..e3410776a8 100644
--- a/src/components/dialogHelper/dialogHelper.js
+++ b/src/components/dialogHelper/dialogHelper.js
@@ -169,6 +169,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}, {
passive: true
});
+
+ dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
+ if (e.target === dlg.dialogContainer) {
+ // Close the application dialog menu
+ close(dlg);
+ // Prevent the default browser context menu from appearing
+ e.preventDefault();
+ }
+ });
}
function isHistoryEnabled(dlg) {
diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js
index 3a3bddd989..8de1ffc190 100644
--- a/src/components/playback/playbackmanager.js
+++ b/src/components/playback/playbackmanager.js
@@ -3162,7 +3162,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
// User clicked stop or content ended
var state = self.getPlayerState(player);
- var streamInfo = getPlayerData(player).streamInfo;
+ var data = getPlayerData(player);
+ var streamInfo = data.streamInfo;
var nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null;
@@ -3210,6 +3211,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
showPlaybackInfoErrorMessage(self, displayErrorCode, nextItem);
} else if (nextItem) {
self.nextTrack();
+ } else {
+ // Nothing more to play - clear data
+ data.streamInfo = null;
}
}
diff --git a/src/components/prompt/nativeprompt.js b/src/components/prompt/nativeprompt.js
deleted file mode 100644
index ba7f1a9a49..0000000000
--- a/src/components/prompt/nativeprompt.js
+++ /dev/null
@@ -1,28 +0,0 @@
-define([], function () {
- 'use strict';
-
- function replaceAll(str, find, replace) {
-
- return str.split(find).join(replace);
- }
-
- return function (options) {
-
- if (typeof options === 'string') {
- options = {
- label: '',
- text: options
- };
- }
-
- var label = replaceAll(options.label || '', '
', '\n');
-
- var result = prompt(label, options.text || '');
-
- if (result) {
- return Promise.resolve(result);
- } else {
- return Promise.reject(result);
- }
- };
-});
diff --git a/src/components/prompt/prompt.js b/src/components/prompt/prompt.js
index 8f4f839eaa..41d40c4a48 100644
--- a/src/components/prompt/prompt.js
+++ b/src/components/prompt/prompt.js
@@ -1,6 +1,10 @@
-define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function (dialogHelper, layoutManager, scrollHelper, globalize, dom, require) {
+define(["browser", 'dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle'], function(browser, dialogHelper, layoutManager, scrollHelper, globalize, dom, require) {
'use strict';
+ function replaceAll(str, find, replace) {
+ return str.split(find).join(replace);
+ }
+
function setInputProperties(dlg, options) {
var txtInput = dlg.querySelector('#txtInput');
@@ -13,7 +17,6 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
}
function showDialog(options, template) {
-
var dialogOptions = {
removeOnClose: true,
scrollY: false
@@ -71,34 +74,49 @@ define(['dialogHelper', 'layoutManager', 'scrollHelper', 'globalize', 'dom', 're
dlg.style.minWidth = (Math.min(400, dom.getWindowSize().innerWidth - 50)) + 'px';
return dialogHelper.open(dlg).then(function () {
-
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
- var value = submitValue;
-
- if (value) {
- return value;
+ if (submitValue) {
+ return submitValue;
} else {
return Promise.reject();
}
});
}
- return function (options) {
+ if ((browser.tv || browser.xboxOne) && window.confirm) {
+ return function (options) {
+ if (typeof options === 'string') {
+ options = {
+ label: '',
+ text: options
+ };
+ }
- return new Promise(function (resolve, reject) {
- require(['text!./prompt.template.html'], function (template) {
+ var label = replaceAll(options.label || '', '
', '\n');
+ var result = prompt(label, options.text || '');
- if (typeof options === 'string') {
- options = {
- title: '',
- text: options
- };
- }
- showDialog(options, template).then(resolve, reject);
+ if (result) {
+ return Promise.resolve(result);
+ } else {
+ return Promise.reject(result);
+ }
+ };
+ } else {
+ return function (options) {
+ return new Promise(function (resolve, reject) {
+ require(['text!./prompt.template.html'], function (template) {
+ if (typeof options === 'string') {
+ options = {
+ title: '',
+ text: options
+ };
+ }
+ showDialog(options, template).then(resolve, reject);
+ });
});
- });
- };
+ };
+ }
});
diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js
index a1265e7cc9..cf11e5736d 100644
--- a/src/components/tvproviders/schedulesdirect.js
+++ b/src/components/tvproviders/schedulesdirect.js
@@ -264,17 +264,13 @@ define(["jQuery", "loading", "emby-checkbox", "listViewStyle", "emby-input", "em
self.init = function () {
options = options || {};
- if (options.showCancelButton) {
- page.querySelector(".btnCancel").classList.remove("hide");
- } else {
- page.querySelector(".btnCancel").classList.add("hide");
- }
+ // Only hide the buttons if explicitly set to false; default to showing if undefined or null
+ // FIXME: rename this option to clarify logic
+ var hideCancelButton = options.showCancelButton === false;
+ page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton);
- if (options.showSubmitButton) {
- page.querySelector(".btnSubmitListings").classList.remove("hide");
- } else {
- page.querySelector(".btnSubmitListings").classList.add("hide");
- }
+ var hideSubmitButton = options.showSubmitButton === false;
+ page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
$(".formLogin", page).on("submit", function () {
submitLoginForm();
diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js
index a86a1e1099..7e7d381f09 100644
--- a/src/components/tvproviders/xmltv.js
+++ b/src/components/tvproviders/xmltv.js
@@ -163,17 +163,13 @@ define(["jQuery", "loading", "emby-checkbox", "emby-input", "listViewStyle", "pa
self.init = function () {
options = options || {};
- if (false !== options.showCancelButton) {
- page.querySelector(".btnCancel").classList.remove("hide");
- } else {
- page.querySelector(".btnCancel").classList.add("hide");
- }
+ // Only hide the buttons if explicitly set to false; default to showing if undefined or null
+ // FIXME: rename this option to clarify logic
+ var hideCancelButton = options.showCancelButton === false;
+ page.querySelector(".btnCancel").classList.toggle("hide", hideCancelButton);
- if (false !== options.showSubmitButton) {
- page.querySelector(".btnSubmitListings").classList.remove("hide");
- } else {
- page.querySelector(".btnSubmitListings").classList.add("hide");
- }
+ var hideSubmitButton = options.showSubmitButton === false;
+ page.querySelector(".btnSubmitListings").classList.toggle("hide", hideSubmitButton);
$("form", page).on("submit", function () {
submitListingsForm();
diff --git a/src/controllers/playback/videoosd.js b/src/controllers/playback/videoosd.js
index 24b6548544..c61fd14a7a 100644
--- a/src/controllers/playback/videoosd.js
+++ b/src/controllers/playback/videoosd.js
@@ -671,7 +671,8 @@ define(["playbackManager", "dom", "inputManager", "datetime", "itemHelper", "med
}
function onTimeUpdate(e) {
- if (isEnabled) {
+ // Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint
+ if (isEnabled && currentItem) {
var now = new Date().getTime();
if (!(now - lastUpdateTime < 700)) {
diff --git a/src/scripts/browser.js b/src/scripts/browser.js
index 791ac7411b..f9e1942320 100644
--- a/src/scripts/browser.js
+++ b/src/scripts/browser.js
@@ -271,6 +271,9 @@ define([], function () {
if (!browser.tizen) {
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
+ } else {
+ var v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/);
+ browser.tizenVersion = parseInt(v[1]);
}
if (browser.edgeUwp) {
diff --git a/src/scripts/browserdeviceprofile.js b/src/scripts/browserdeviceprofile.js
index b45bdc59bf..337463987c 100644
--- a/src/scripts/browserdeviceprofile.js
+++ b/src/scripts/browserdeviceprofile.js
@@ -214,6 +214,15 @@ define(['browser'], function (browser) {
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':
diff --git a/src/scripts/librarymenu.js b/src/scripts/librarymenu.js
index 001c75787d..5e6562ee42 100644
--- a/src/scripts/librarymenu.js
+++ b/src/scripts/librarymenu.js
@@ -73,7 +73,7 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
}
if (user && user.localUser) {
- if (headerHomeButton) {
+ if (headerHomeButton && !layoutManager.mobile) {
headerHomeButton.classList.remove("hide");
}
@@ -788,7 +788,7 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
var headerCastButton;
var headerSearchButton;
var headerAudioPlayerButton;
- var enableLibraryNavDrawer = !layoutManager.tv;
+ var enableLibraryNavDrawer = layoutManager.desktop;
var skinHeader = document.querySelector(".skinHeader");
var requiresUserRefresh = true;
var lastOpenTime = new Date().getTime();
@@ -863,6 +863,7 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
pageClassOn("pageshow", "page", function (e) {
var page = this;
var isDashboardPage = page.classList.contains("type-interior");
+ var isHomePage = page.classList.contains("homePage");
var isLibraryPage = !isDashboardPage && page.classList.contains("libraryPage");
var apiClient = getCurrentApiClient();
@@ -874,7 +875,7 @@ define(["dom", "layoutManager", "inputManager", "connectionManager", "events", "
refreshDashboardInfoInDrawer(apiClient);
} else {
if (mainDrawerButton) {
- if (enableLibraryNavDrawer) {
+ if (enableLibraryNavDrawer || isHomePage) {
mainDrawerButton.classList.remove("hide");
} else {
mainDrawerButton.classList.add("hide");
diff --git a/src/scripts/site.js b/src/scripts/site.js
index 0ab8e9a1f0..6739e3cd59 100644
--- a/src/scripts/site.js
+++ b/src/scripts/site.js
@@ -355,39 +355,6 @@ var AppInfo = {};
return headroom;
}
- function getCastSenderApiLoader() {
- var ccLoaded = false;
- return {
- load: function () {
- if (ccLoaded) {
- return Promise.resolve();
- }
-
- return new Promise(function (resolve, reject) {
- var fileref = document.createElement("script");
- fileref.setAttribute("type", "text/javascript");
-
- fileref.onload = function () {
- ccLoaded = true;
- resolve();
- };
-
- fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js");
- document.querySelector("head").appendChild(fileref);
- });
- }
- };
- }
-
- function getDummyCastSenderApiLoader() {
- return {
- load: function () {
- window.chrome = window.chrome || {};
- return Promise.resolve();
- }
- };
- }
-
function createSharedAppFooter(appFooter) {
return new appFooter({});
}
@@ -439,28 +406,16 @@ var AppInfo = {};
defineResizeObserver();
define("dialog", [componentsPath + "/dialog/dialog"], returnFirstDependency);
- if (preferNativeAlerts && window.confirm) {
- define("confirm", [componentsPath + "/confirm/nativeconfirm"], returnFirstDependency);
- } else {
- define("confirm", [componentsPath + "/confirm/confirm"], returnFirstDependency);
- }
+ define("confirm", [componentsPath + "/confirm/confirm"], returnFirstDependency);
- if ((preferNativeAlerts || browser.xboxOne) && window.confirm) {
- define("prompt", [componentsPath + "/prompt/nativeprompt"], returnFirstDependency);
- } else {
- define("prompt", [componentsPath + "/prompt/prompt"], returnFirstDependency);
- }
+ define("prompt", [componentsPath + "/prompt/prompt"], returnFirstDependency);
define("loading", [componentsPath + "/loading/loading"], returnFirstDependency);
define("multi-download", [componentsPath + "/multidownload"], returnFirstDependency);
define("fileDownloader", [componentsPath + "/filedownloader"], returnFirstDependency);
define("localassetmanager", [bowerPath + "/apiclient/localassetmanager"], returnFirstDependency);
- if ("cordova" === self.appMode || "android" === self.appMode) {
- define("castSenderApiLoader", [], getDummyCastSenderApiLoader);
- } else {
- define("castSenderApiLoader", [], getCastSenderApiLoader);
- }
+ define("castSenderApiLoader", [componentsPath + "castSenderApi"], returnFirstDependency);
define("transfermanager", [bowerPath + "/apiclient/sync/transfermanager"], returnFirstDependency);
define("filerepository", [bowerPath + "/apiclient/sync/filerepository"], returnFirstDependency);
diff --git a/src/strings/fa.json b/src/strings/fa.json
index 7b7393c6b9..d4b60fe7c6 100644
--- a/src/strings/fa.json
+++ b/src/strings/fa.json
@@ -139,5 +139,8 @@
"AllLanguages": "تمام زبان ها",
"AllLibraries": "تمام کتابخانه ها",
"AllowHWTranscodingHelp": "اگر فعال شود, اجازه میدهید تبدیل ( کم و زیاد کردن کیفیت ) درلحظه و توسط کارت دریافت سیگنال صورت گیرد. این کمک میکند به اینکه سرور جلیفین کمتر عمل تبدیل را انجام دهد.",
- "AllowOnTheFlySubtitleExtraction": "اجازه میدهد در لحظه زیرنویس بازشود"
+ "AllowOnTheFlySubtitleExtraction": "اجازه میدهد در لحظه زیرنویس بازشود",
+ "Add": "افزودن",
+ "Actor": "بازیگر",
+ "AccessRestrictedTryAgainLater": "دسترسی در حال حاضر محدود شده است. لطفا دوباره تلاش کنید."
}
diff --git a/src/strings/hu.json b/src/strings/hu.json
index a6c2a14da2..7ade41afff 100644
--- a/src/strings/hu.json
+++ b/src/strings/hu.json
@@ -1213,7 +1213,7 @@
"Screenshots": "Képernyőképek",
"SearchForCollectionInternetMetadata": "Keresés az interneten artwork és metaadat után",
"Series": "Sorozatok",
- "SeriesCancelled": "A sorozat törölt.",
+ "SeriesCancelled": "Sorozat törölve.",
"SeriesRecordingScheduled": "A sorozatfelvétel ütemezett.",
"SeriesSettings": "Sorozat beállítások",
"ServerRestartNeededAfterPluginInstall": "A bővítmény telepítése után újra kell indítani a Jellyfin Szerver-t.",
diff --git a/src/strings/pt.json b/src/strings/pt.json
index da6d26d947..ecd6ccb079 100644
--- a/src/strings/pt.json
+++ b/src/strings/pt.json
@@ -1213,5 +1213,10 @@
"CopyStreamURLError": "Ocorreu um erro ao copiar o URL.",
"ButtonSplit": "Dividir",
"AskAdminToCreateLibrary": "Peça a um administrador para criar a biblioteca.",
- "AllowFfmpegThrottling": "Transcodificação com falhas"
+ "AllowFfmpegThrottling": "Transcodificação com falhas",
+ "DashboardOperatingSystem": "Sistema Operativo",
+ "LabelUserLoginAttemptsBeforeLockout": "Número de tentativas de login falhadas antes do bloqueio do utilizador:",
+ "LabelTrackNumber": "Número da faixa:",
+ "LabelSportsCategories": "Categorias de Desportos:",
+ "Yesterday": "Ontem"
}
diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json
index 547f55dc3a..b0c9a59694 100644
--- a/src/strings/zh-cn.json
+++ b/src/strings/zh-cn.json
@@ -537,8 +537,8 @@
"LabelDisplayName": "显示名称:",
"LabelDisplayOrder": "显示顺序:",
"LabelDisplaySpecialsWithinSeasons": "显示季中所播出的特集",
- "LabelDownMixAudioScale": "缩混音频增强:",
- "LabelDownMixAudioScaleHelp": "缩混音频增强。值为A将保留原来的音量。",
+ "LabelDownMixAudioScale": "降混音频增强:",
+ "LabelDownMixAudioScaleHelp": "降混音时增强音频。值为 1 时将保留原始音量。",
"LabelDownloadLanguages": "下载语言:",
"LabelDropImageHere": "拖拽或点击选择图像于此处。",
"LabelDroppedFrames": "丢弃的帧:",