diff --git a/src/components/apphost.js b/src/components/apphost.js
index df2f7c2d2c..1afe9f543f 100644
--- a/src/components/apphost.js
+++ b/src/components/apphost.js
@@ -33,7 +33,6 @@ function getDeviceProfile(item, options = {}) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else {
const builderOpts = getBaseProfileOptions(item);
- builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats');
profile = profileBuilder(builderOpts);
}
diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js
index 782e3d38e1..44a9b607d6 100644
--- a/src/components/playbackSettings/playbackSettings.js
+++ b/src/components/playbackSettings/playbackSettings.js
@@ -145,6 +145,8 @@ import 'emby-checkbox';
showHideQualityFields(context, user, apiClient);
+ context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels();
+
apiClient.getCultures().then(allCultures => {
populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures);
@@ -187,6 +189,7 @@ import 'emby-checkbox';
}
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
+ context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
@@ -222,10 +225,11 @@ import 'emby-checkbox';
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio');
+ userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value);
user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value;
user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked;
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
-
+ userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
diff --git a/src/components/playbackSettings/playbackSettings.template.html b/src/components/playbackSettings/playbackSettings.template.html
index d10b069bb2..ae6429ed95 100644
--- a/src/components/playbackSettings/playbackSettings.template.html
+++ b/src/components/playbackSettings/playbackSettings.template.html
@@ -4,6 +4,16 @@
${HeaderAudioSettings}
+
+
+
+
@@ -49,6 +59,14 @@
${TabAdvanced}
+
+
+
${PreferFmp4HlsContainerHelp}
+
+
+
+
+
+
+
+
diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js
index 6a54e8105d..48b31e4dc1 100644
--- a/src/controllers/dashboard/encodingsettings.js
+++ b/src/controllers/dashboard/encodingsettings.js
@@ -13,6 +13,7 @@ import libraryMenu from 'libraryMenu';
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
+ page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
$('#selectThreadCount', page).val(config.EncodingThreadCount);
$('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost);
@@ -98,6 +99,7 @@ import libraryMenu from 'libraryMenu';
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
+ config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
updateEncoder(form);
}, function () {
diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js
index 3ce60cfe7a..5b3ee64ac4 100644
--- a/src/scripts/browserDeviceProfile.js
+++ b/src/scripts/browserDeviceProfile.js
@@ -1,4 +1,4 @@
-define(['browser'], function (browser) {
+define(['browser', 'userSettings', 'appSettings'], function (browser, userSettings, appSettings) {
'use strict';
browser = browser.default || browser;
@@ -7,7 +7,7 @@ define(['browser'], function (browser) {
return !!(videoTestElement.canPlayType && videoTestElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''));
}
- function canPlayH265(videoTestElement, options) {
+ function canPlayHevc(videoTestElement, options) {
if (browser.tizen || browser.xboxOne || browser.web0s || options.supportsHevc) {
return true;
}
@@ -16,6 +16,7 @@ define(['browser'], function (browser) {
return false;
}
+ // hevc main level 4.0
return !!videoTestElement.canPlayType &&
(videoTestElement.canPlayType('video/mp4; codecs="hvc1.1.L120"').replace(/no/, '') ||
videoTestElement.canPlayType('video/mp4; codecs="hev1.1.L120"').replace(/no/, '') ||
@@ -207,8 +208,7 @@ define(['browser'], function (browser) {
// Explicitly add supported codecs to make other codecs be transcoded
if (browser.tizenVersion >= 4) {
videoCodecs.push('h264');
- if (canPlayH265(videoTestElement, options)) {
- videoCodecs.push('h265');
+ if (canPlayHevc(videoTestElement, options)) {
videoCodecs.push('hevc');
}
}
@@ -248,8 +248,8 @@ define(['browser'], function (browser) {
case 'ts':
supported = testCanPlayTs();
videoCodecs.push('h264');
- if (canPlayH265(videoTestElement, options)) {
- videoCodecs.push('h265');
+ // safari doesn't support hevc in TS-HLS
+ if ((browser.tizen || browser.web0s) && canPlayHevc(videoTestElement, options)) {
videoCodecs.push('hevc');
}
if (supportsVc1(videoTestElement)) {
@@ -297,7 +297,9 @@ define(['browser'], function (browser) {
return function (options) {
options = options || {};
- const physicalAudioChannels = options.audioChannels || (browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2);
+ const isSurroundSoundSupportedBrowser = browser.safari || browser.chrome || browser.edgeChromium || browser.firefox;
+ const allowedAudioChannels = parseInt(userSettings.allowedAudioChannels() || '-1');
+ const physicalAudioChannels = (allowedAudioChannels > 0 ? allowedAudioChannels : null) || options.audioChannels || (isSurroundSoundSupportedBrowser || browser.tv || browser.ps4 || browser.xboxOne ? 6 : 2);
const bitrateSetting = getMaxBitrate();
@@ -313,12 +315,13 @@ define(['browser'], function (browser) {
profile.MaxStreamingBitrate = bitrateSetting;
profile.MaxStaticBitrate = 100000000;
- profile.MusicStreamingTranscodingBitrate = Math.min(bitrateSetting, 192000);
+ profile.MusicStreamingTranscodingBitrate = Math.min(bitrateSetting, 384000);
profile.DirectPlayProfiles = [];
let videoAudioCodecs = [];
- let hlsVideoAudioCodecs = [];
+ let hlsInTsVideoAudioCodecs = [];
+ let hlsInFmp4VideoAudioCodecs = [];
const supportsMp3VideoAudio = videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"').replace(/no/, '')
|| videoTestElement.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"').replace(/no/, '')
@@ -353,16 +356,19 @@ define(['browser'], function (browser) {
// Transcoding codec is the first in hlsVideoAudioCodecs
// Put ac3/eac3 first only when the audio channels > 2 and need transcoding
if (canPlayAc3VideoAudioInHls && physicalAudioChannels > 2) {
- hlsVideoAudioCodecs.push('ac3');
+ hlsInTsVideoAudioCodecs.push('ac3');
+ hlsInFmp4VideoAudioCodecs.push('ac3');
if (canPlayEac3VideoAudio) {
- hlsVideoAudioCodecs.push('eac3');
+ hlsInTsVideoAudioCodecs.push('eac3');
+ hlsInFmp4VideoAudioCodecs.push('eac3');
}
}
}
if (canPlayAacVideoAudio) {
videoAudioCodecs.push('aac');
- hlsVideoAudioCodecs.push('aac');
+ hlsInTsVideoAudioCodecs.push('aac');
+ hlsInFmp4VideoAudioCodecs.push('aac');
}
if (supportsMp3VideoAudio) {
@@ -370,16 +376,31 @@ define(['browser'], function (browser) {
// PS4 fails to load HLS with mp3 audio
if (!browser.ps4) {
- hlsVideoAudioCodecs.push('mp3');
+ hlsInTsVideoAudioCodecs.push('mp3');
}
+
+ hlsInFmp4VideoAudioCodecs.push('mp3');
}
// For ac3/eac3 directstream
if (canPlayAc3VideoAudio) {
- if (canPlayAc3VideoAudioInHls && hlsVideoAudioCodecs.indexOf('ac3') === -1) {
- hlsVideoAudioCodecs.push('ac3');
- if (canPlayEac3VideoAudio && hlsVideoAudioCodecs.indexOf('eac3') === -1) {
- hlsVideoAudioCodecs.push('eac3');
+ if (canPlayAc3VideoAudioInHls) {
+ if (hlsInTsVideoAudioCodecs.indexOf('ac3') === -1) {
+ hlsInTsVideoAudioCodecs.push('ac3');
+ }
+
+ if (hlsInFmp4VideoAudioCodecs.indexOf('ac3') === -1) {
+ hlsInFmp4VideoAudioCodecs.push('ac3');
+ }
+
+ if (canPlayEac3VideoAudio) {
+ if (hlsInTsVideoAudioCodecs.indexOf('eac3') === -1) {
+ hlsInTsVideoAudioCodecs.push('eac3');
+ }
+
+ if (hlsInFmp4VideoAudioCodecs.indexOf('eac3') === -1) {
+ hlsInFmp4VideoAudioCodecs.push('eac3');
+ }
}
}
}
@@ -415,38 +436,54 @@ define(['browser'], function (browser) {
if (canPlayAudioFormat('opus')) {
videoAudioCodecs.push('opus');
- hlsVideoAudioCodecs.push('opus');
+ hlsInTsVideoAudioCodecs.push('opus');
webmAudioCodecs.push('opus');
}
if (canPlayAudioFormat('flac')) {
videoAudioCodecs.push('flac');
+ hlsInFmp4VideoAudioCodecs.push('flac');
+ }
+
+ if (canPlayAudioFormat('alac')) {
+ videoAudioCodecs.push('alac');
+ hlsInFmp4VideoAudioCodecs.push('alac');
}
videoAudioCodecs = videoAudioCodecs.filter(function (c) {
return (options.disableVideoAudioCodecs || []).indexOf(c) === -1;
});
- hlsVideoAudioCodecs = hlsVideoAudioCodecs.filter(function (c) {
+ hlsInTsVideoAudioCodecs = hlsInTsVideoAudioCodecs.filter(function (c) {
+ return (options.disableHlsVideoAudioCodecs || []).indexOf(c) === -1;
+ });
+
+ hlsInFmp4VideoAudioCodecs = hlsInFmp4VideoAudioCodecs.filter(function (c) {
return (options.disableHlsVideoAudioCodecs || []).indexOf(c) === -1;
});
const mp4VideoCodecs = [];
const webmVideoCodecs = [];
- const hlsVideoCodecs = [];
+ const hlsInTsVideoCodecs = [];
+ const hlsInFmp4VideoCodecs = [];
+
+ if (browser.safari || browser.tizen || browser.web0s && canPlayHevc(videoTestElement, options)) {
+ hlsInFmp4VideoCodecs.push('hevc');
+ }
if (canPlayH264(videoTestElement)) {
mp4VideoCodecs.push('h264');
- hlsVideoCodecs.push('h264');
+ hlsInTsVideoCodecs.push('h264');
+
+ if (browser.safari || browser.tizen || browser.web0s) {
+ hlsInFmp4VideoCodecs.push('h264');
+ }
}
- if (canPlayH265(videoTestElement, options)) {
- mp4VideoCodecs.push('h265');
- mp4VideoCodecs.push('hevc');
-
- if (browser.tizen || browser.web0s) {
- hlsVideoCodecs.push('h265');
- hlsVideoCodecs.push('hevc');
+ if (canPlayHevc(videoTestElement, options)) {
+ // safari is lying on HDR and 60fps videos, use fMP4 instead
+ if (!browser.safari) {
+ mp4VideoCodecs.push('hevc');
}
}
@@ -606,18 +643,34 @@ define(['browser'], function (browser) {
});
}
- 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 (canPlayHls() && options.enableHls !== false) {
+ if (hlsInFmp4VideoCodecs.length && hlsInFmp4VideoAudioCodecs.length && userSettings.preferFmp4HlsContainer() && (browser.safari || browser.tizen || browser.web0s)) {
+ profile.TranscodingProfiles.push({
+ Container: 'mp4',
+ Type: 'Video',
+ AudioCodec: hlsInFmp4VideoAudioCodecs.join(','),
+ VideoCodec: hlsInFmp4VideoCodecs.join(','),
+ Context: 'Streaming',
+ Protocol: 'hls',
+ MaxAudioChannels: physicalAudioChannels.toString(),
+ MinSegments: browser.iOS || browser.osx ? '2' : '1',
+ BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames
+ });
+ }
+
+ if (hlsInTsVideoCodecs.length && hlsInTsVideoAudioCodecs.length) {
+ profile.TranscodingProfiles.push({
+ Container: 'ts',
+ Type: 'Video',
+ AudioCodec: hlsInTsVideoAudioCodecs.join(','),
+ VideoCodec: hlsInTsVideoCodecs.join(','),
+ Context: 'Streaming',
+ Protocol: 'hls',
+ MaxAudioChannels: physicalAudioChannels.toString(),
+ MinSegments: browser.iOS || browser.osx ? '2' : '1',
+ BreakOnNonKeyFrames: hlsBreakOnNonKeyFrames
+ });
+ }
}
if (canPlayVp8) {
@@ -713,6 +766,36 @@ define(['browser'], function (browser) {
}
}
+ let maxHevcLevel = 120;
+ let hevcProfiles = 'main';
+
+ // hevc main level 4.1
+ if (videoTestElement.canPlayType('video/mp4; codecs="hvc1.1.4.L123"').replace(/no/, '') ||
+ videoTestElement.canPlayType('video/mp4; codecs="hev1.1.4.L123"').replace(/no/, '')) {
+ maxHevcLevel = 123;
+ }
+
+ // hevc main10 level 4.1
+ if (videoTestElement.canPlayType('video/mp4; codecs="hvc1.2.4.L123"').replace(/no/, '') ||
+ videoTestElement.canPlayType('video/mp4; codecs="hev1.2.4.L123"').replace(/no/, '')) {
+ maxHevcLevel = 123;
+ hevcProfiles = 'main|main 10';
+ }
+
+ // hevc main10 level 5.1
+ if (videoTestElement.canPlayType('video/mp4; codecs="hvc1.2.4.L153"').replace(/no/, '') ||
+ videoTestElement.canPlayType('video/mp4; codecs="hev1.2.4.L153"').replace(/no/, '')) {
+ maxHevcLevel = 153;
+ hevcProfiles = 'main|main 10';
+ }
+
+ // hevc main10 level 6.1
+ if (videoTestElement.canPlayType('video/mp4; codecs="hvc1.2.4.L183"').replace(/no/, '') ||
+ videoTestElement.canPlayType('video/mp4; codecs="hvc1.2.4.L183"').replace(/no/, '')) {
+ maxHevcLevel = 183;
+ hevcProfiles = 'main|main 10';
+ }
+
const h264CodecProfileConditions = [
{
Condition: 'NotEquals',
@@ -734,6 +817,27 @@ define(['browser'], function (browser) {
}
];
+ const hevcCodecProfileConditions = [
+ {
+ Condition: 'NotEquals',
+ Property: 'IsAnamorphic',
+ Value: 'true',
+ IsRequired: false
+ },
+ {
+ Condition: 'EqualsAny',
+ Property: 'VideoProfile',
+ Value: hevcProfiles,
+ IsRequired: false
+ },
+ {
+ Condition: 'LessThanEqual',
+ Property: 'VideoLevel',
+ Value: maxHevcLevel.toString(),
+ IsRequired: false
+ }
+ ];
+
if (!browser.edgeUwp && !browser.tizen && !browser.web0s) {
h264CodecProfileConditions.push({
Condition: 'NotEquals',
@@ -741,6 +845,13 @@ define(['browser'], function (browser) {
Value: 'true',
IsRequired: false
});
+
+ hevcCodecProfileConditions.push({
+ Condition: 'NotEquals',
+ Property: 'IsInterlaced',
+ Value: 'true',
+ IsRequired: false
+ });
}
if (maxVideoWidth) {
@@ -750,12 +861,21 @@ define(['browser'], function (browser) {
Value: maxVideoWidth.toString(),
IsRequired: false
});
+
+ hevcCodecProfileConditions.push({
+ Condition: 'LessThanEqual',
+ Property: 'Width',
+ Value: maxVideoWidth.toString(),
+ IsRequired: false
+ });
}
const globalMaxVideoBitrate = (getGlobalMaxVideoBitrate() || '').toString();
const h264MaxVideoBitrate = globalMaxVideoBitrate;
+ const hevcMaxVideoBitrate = globalMaxVideoBitrate;
+
if (h264MaxVideoBitrate) {
h264CodecProfileConditions.push({
Condition: 'LessThanEqual',
@@ -765,6 +885,15 @@ define(['browser'], function (browser) {
});
}
+ if (hevcMaxVideoBitrate) {
+ hevcCodecProfileConditions.push({
+ Condition: 'LessThanEqual',
+ Property: 'VideoBitrate',
+ Value: hevcMaxVideoBitrate,
+ IsRequired: true
+ });
+ }
+
// On iOS 12.x, for TS container max h264 level is 4.2
if (browser.iOS && browser.iOSVersion < 13) {
const codecProfile = {
@@ -792,6 +921,12 @@ define(['browser'], function (browser) {
Conditions: h264CodecProfileConditions
});
+ profile.CodecProfiles.push({
+ Type: 'Video',
+ Codec: 'hevc',
+ Conditions: hevcCodecProfileConditions
+ });
+
const globalVideoConditions = [];
if (globalMaxVideoBitrate) {
@@ -827,7 +962,7 @@ define(['browser'], function (browser) {
Method: 'External'
});
}
- if (options.enableSsaRender) {
+ if (options.enableSsaRender !== false && (!options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats')) {
profile.SubtitleProfiles.push({
Format: 'ass',
Method: 'External'
diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js
index 263d74ed37..4a27d7ad9a 100644
--- a/src/scripts/settings/userSettings.js
+++ b/src/scripts/settings/userSettings.js
@@ -114,6 +114,33 @@ export class UserSettings {
});
}
+ /**
+ * Get or set 'Allowed Audio Channels'.
+ * @param {string|undefined} val - 'Allowed Audio Channels'.
+ * @return {string} 'Allowed Audio Channels'.
+ */
+ allowedAudioChannels(val) {
+ if (val !== undefined) {
+ return this.set('allowedAudioChannels', val, false);
+ }
+
+ return this.get('allowedAudioChannels', false) || '-1';
+ }
+
+ /**
+ * Get or set 'Perfer fMP4-HLS Container' state.
+ * @param {boolean|undefined} val - Flag to enable 'Perfer fMP4-HLS Container' or undefined.
+ * @return {boolean} 'Prefer fMP4-HLS Container' state.
+ */
+ preferFmp4HlsContainer(val) {
+ if (val !== undefined) {
+ return this.set('preferFmp4HlsContainer', val.toString(), false);
+ }
+
+ val = this.get('preferFmp4HlsContainer', false);
+ return val === 'true';
+ }
+
/**
* Get or set 'Cinema Mode' state.
* @param {boolean|undefined} val - Flag to enable 'Cinema Mode' or undefined.
@@ -457,6 +484,8 @@ export const importFrom = currentSettings.importFrom.bind(currentSettings);
export const set = currentSettings.set.bind(currentSettings);
export const get = currentSettings.get.bind(currentSettings);
export const serverConfig = currentSettings.serverConfig.bind(currentSettings);
+export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(currentSettings);
+export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
diff --git a/src/strings/en-us.json b/src/strings/en-us.json
index c2ad064922..23cead75bd 100644
--- a/src/strings/en-us.json
+++ b/src/strings/en-us.json
@@ -1424,5 +1424,12 @@
"SubtitleVerticalPositionHelp": "Line number where text appears. Positive numbers indicate top down. Negative numbers indicate bottom up.",
"Preview": "Preview",
"LabelMaxMuxingQueueSize": "Max muxing queue size:",
- "LabelMaxMuxingQueueSizeHelp": "Maximum number of packets that can be buffered while waiting for all streams to initialize. Try to increase it if you still encounter \"Too many packets buffered for output stream\" error in ffmpeg logs. The recommended value is 2048."
+ "LabelMaxMuxingQueueSizeHelp": "Maximum number of packets that can be buffered while waiting for all streams to initialize. Try to increase it if you still encounter \"Too many packets buffered for output stream\" error in ffmpeg logs. The recommended value is 2048.",
+ "PreferFmp4HlsContainer": "Prefer fMP4-HLS Media Container",
+ "PreferFmp4HlsContainerHelp": "Prefer to use fMP4 as the default container for HLS, making it possible to direct streaming HEVC content on supported devices.",
+ "AllowHevcEncoding": "Allow encoding in HEVC format",
+ "LabelAllowedAudioChannels": "Maximum Allowed Audio Channels",
+ "LabelSelectAudioChannels": "Channels",
+ "LabelSelectMono": "Mono",
+ "LabelSelectStereo": "Stereo"
}
diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json
index d6603c6eb9..af8d036da2 100644
--- a/src/strings/zh-cn.json
+++ b/src/strings/zh-cn.json
@@ -1425,5 +1425,12 @@
"OptionAllowContentDownload": "允许媒体下载",
"HeaderDeleteDevices": "删除所有设备",
"DeleteDevicesConfirmation": "您确定要删除所有设备吗?所有其他会话将被注销。用户下次登录时,设备会重新出现。",
- "DeleteAll": "删除全部"
+ "DeleteAll": "删除全部",
+ "PreferFmp4HlsContainer": "优先使用 fMP4-HLS 媒体容器",
+ "PreferFmp4HlsContainerHelp": "优先使用 fMP4 作为 HLS 播放的默认容器,从而可以在支持的设备上直接串流 HEVC 格式的内容。",
+ "AllowHevcEncoding": "允许以 HEVC 格式编码",
+ "LabelAllowedAudioChannels": "允许的最大声道数量",
+ "LabelSelectAudioChannels": "声道",
+ "LabelSelectMono": "单声道",
+ "LabelSelectStereo": "立体声"
}