mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #2621 from iwalton3/auto-set-tracks
Add subtitle/audio auto-set feature.
This commit is contained in:
commit
ee20a51c48
5 changed files with 142 additions and 6 deletions
|
@ -2107,7 +2107,7 @@ class PlaybackManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playInternal(item, playOptions, onPlaybackStartedFn) {
|
function playInternal(item, playOptions, onPlaybackStartedFn, prevSource) {
|
||||||
if (item.IsPlaceHolder) {
|
if (item.IsPlaceHolder) {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
showPlaybackInfoErrorMessage(self, 'PlaybackErrorPlaceHolder');
|
showPlaybackInfoErrorMessage(self, 'PlaybackErrorPlaceHolder');
|
||||||
|
@ -2132,7 +2132,7 @@ class PlaybackManager {
|
||||||
const mediaType = item.MediaType;
|
const mediaType = item.MediaType;
|
||||||
|
|
||||||
const onBitrateDetectionFailure = function () {
|
const onBitrateDetectionFailure = function () {
|
||||||
return playAfterBitrateDetect(getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn);
|
return playAfterBitrateDetect(getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn, prevSource);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isServerItem(item) || itemHelper.isLocalItem(item)) {
|
if (!isServerItem(item) || itemHelper.isLocalItem(item)) {
|
||||||
|
@ -2145,7 +2145,7 @@ class PlaybackManager {
|
||||||
return apiClient.detectBitrate().then(function (bitrate) {
|
return apiClient.detectBitrate().then(function (bitrate) {
|
||||||
appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate);
|
appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate);
|
||||||
|
|
||||||
return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn);
|
return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn, prevSource);
|
||||||
}, onBitrateDetectionFailure);
|
}, onBitrateDetectionFailure);
|
||||||
} else {
|
} else {
|
||||||
onBitrateDetectionFailure();
|
onBitrateDetectionFailure();
|
||||||
|
@ -2226,7 +2226,104 @@ class PlaybackManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn) {
|
function rankStreamType(prevIndex, prevSource, mediaSource, streamType) {
|
||||||
|
if (prevIndex == -1) {
|
||||||
|
console.debug(`AutoSet ${streamType} - No Stream Set`);
|
||||||
|
if (streamType == 'Subtitle')
|
||||||
|
mediaSource.DefaultSubtitleStreamIndex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prevSource.MediaStreams || !mediaSource.MediaStreams) {
|
||||||
|
console.debug(`AutoSet ${streamType} - No MediaStreams`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bestStreamIndex = null;
|
||||||
|
let bestStreamScore = 0;
|
||||||
|
const prevStream = prevSource.MediaStreams[prevIndex];
|
||||||
|
|
||||||
|
if (!prevStream) {
|
||||||
|
console.debug(`AutoSet ${streamType} - No prevStream`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(`AutoSet ${streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`);
|
||||||
|
|
||||||
|
let prevRelIndex = 0;
|
||||||
|
for (const stream of prevSource.MediaStreams) {
|
||||||
|
if (stream.Type != streamType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (stream.Index == prevIndex)
|
||||||
|
break;
|
||||||
|
|
||||||
|
prevRelIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newRelIndex = 0;
|
||||||
|
for (const stream of mediaSource.MediaStreams) {
|
||||||
|
if (stream.Type != streamType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
if (prevStream.Codec == stream.Codec)
|
||||||
|
score += 1;
|
||||||
|
if (prevRelIndex == newRelIndex)
|
||||||
|
score += 1;
|
||||||
|
if (prevStream.Title && prevStream.Title == stream.Title)
|
||||||
|
score += 2;
|
||||||
|
if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language)
|
||||||
|
score += 2;
|
||||||
|
|
||||||
|
console.debug(`AutoSet ${streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`);
|
||||||
|
if (score > bestStreamScore && score >= 3) {
|
||||||
|
bestStreamScore = score;
|
||||||
|
bestStreamIndex = stream.Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
newRelIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestStreamIndex != null) {
|
||||||
|
console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`);
|
||||||
|
if (streamType == 'Subtitle')
|
||||||
|
mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex;
|
||||||
|
if (streamType == 'Audio')
|
||||||
|
mediaSource.DefaultAudioStreamIndex = bestStreamIndex;
|
||||||
|
} else {
|
||||||
|
console.debug(`AutoSet ${streamType} - Threshold not met. Using default.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoSetNextTracks(prevSource, mediaSource) {
|
||||||
|
try {
|
||||||
|
if (!prevSource) return;
|
||||||
|
|
||||||
|
if (!mediaSource) {
|
||||||
|
console.warn('AutoSet - No mediaSource');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof prevSource.DefaultAudioStreamIndex != 'number'
|
||||||
|
|| typeof prevSource.DefaultSubtitleStreamIndex != 'number')
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (typeof mediaSource.DefaultAudioStreamIndex != 'number'
|
||||||
|
|| typeof mediaSource.DefaultSubtitleStreamIndex != 'number') {
|
||||||
|
console.warn('AutoSet - No stream indexes (but prevSource has them)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio');
|
||||||
|
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`AutoSet - Caught unexpected error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn, prevSource) {
|
||||||
const startPosition = playOptions.startPositionTicks;
|
const startPosition = playOptions.startPositionTicks;
|
||||||
|
|
||||||
const player = getPlayer(item, playOptions);
|
const player = getPlayer(item, playOptions);
|
||||||
|
@ -2285,6 +2382,9 @@ class PlaybackManager {
|
||||||
playOptions.items = null;
|
playOptions.items = null;
|
||||||
|
|
||||||
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
|
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
|
||||||
|
if (userSettings.enableSetUsingLastTracks())
|
||||||
|
autoSetNextTracks(prevSource, mediaSource);
|
||||||
|
|
||||||
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
|
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
|
||||||
|
|
||||||
streamInfo.fullscreen = playOptions.fullscreen;
|
streamInfo.fullscreen = playOptions.fullscreen;
|
||||||
|
@ -2643,6 +2743,16 @@ class PlaybackManager {
|
||||||
return self.previousTrack(player);
|
return self.previousTrack(player);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getPreviousSource(player) {
|
||||||
|
const prevSource = self.currentMediaSource(player);
|
||||||
|
const prevPlayerData = getPlayerData(player);
|
||||||
|
return {
|
||||||
|
...prevSource,
|
||||||
|
DefaultAudioStreamIndex: prevPlayerData.audioStreamIndex,
|
||||||
|
DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
self.nextTrack = function (player) {
|
self.nextTrack = function (player) {
|
||||||
player = player || self._currentPlayer;
|
player = player || self._currentPlayer;
|
||||||
if (player && !enableLocalPlaylistManagement(player)) {
|
if (player && !enableLocalPlaylistManagement(player)) {
|
||||||
|
@ -2658,7 +2768,7 @@ class PlaybackManager {
|
||||||
|
|
||||||
playInternal(newItemInfo.item, newItemPlayOptions, function () {
|
playInternal(newItemInfo.item, newItemPlayOptions, function () {
|
||||||
setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index);
|
setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index);
|
||||||
});
|
}, getPreviousSource(player));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2679,7 +2789,7 @@ class PlaybackManager {
|
||||||
|
|
||||||
playInternal(newItem, newItemPlayOptions, function () {
|
playInternal(newItem, newItemPlayOptions, function () {
|
||||||
setPlaylistState(newItem.PlaylistItemId, newIndex);
|
setPlaylistState(newItem.PlaylistItemId, newIndex);
|
||||||
});
|
}, getPreviousSource(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -195,6 +195,7 @@ import template from './playbackSettings.template.html';
|
||||||
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
||||||
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
||||||
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
||||||
|
context.querySelector('.chkSetUsingLastTracks').checked = userSettings.enableSetUsingLastTracks();
|
||||||
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
|
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
|
||||||
|
|
||||||
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
|
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
|
||||||
|
@ -236,6 +237,7 @@ import template from './playbackSettings.template.html';
|
||||||
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
||||||
|
|
||||||
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
||||||
|
userSettingsInstance.enableSetUsingLastTracks(context.querySelector('.chkSetUsingLastTracks').checked);
|
||||||
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
|
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
|
||||||
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
|
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
|
||||||
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
|
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
|
||||||
|
|
|
@ -82,6 +82,14 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" is="emby-checkbox" class="chkSetUsingLastTracks" />
|
||||||
|
<span>${SetUsingLastTracks}</span>
|
||||||
|
</label>
|
||||||
|
<div class="fieldDescription checkboxFieldDescription">${SetUsingLastTracksHelp}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay hide">
|
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay hide">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" is="emby-checkbox" class="chkEnableNextVideoOverlay" />
|
<input type="checkbox" is="emby-checkbox" class="chkEnableNextVideoOverlay" />
|
||||||
|
|
|
@ -169,6 +169,19 @@ export class UserSettings {
|
||||||
return val !== 'false';
|
return val !== 'false';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set 'SetUsingLastTracks' state.
|
||||||
|
* @param {boolean|undefined} val - Flag to enable 'SetUsingLastTracks' or undefined.
|
||||||
|
* @return {boolean} 'SetUsingLastTracks' state.
|
||||||
|
*/
|
||||||
|
enableSetUsingLastTracks(val) {
|
||||||
|
if (val !== undefined) {
|
||||||
|
return this.set('enableSetUsingLastTracks', val.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.get('enableSetUsingLastTracks', false) !== 'false';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set 'Theme Songs' state.
|
* Get or set 'Theme Songs' state.
|
||||||
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
|
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
|
||||||
|
@ -528,6 +541,7 @@ export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(cu
|
||||||
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
|
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
|
||||||
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
|
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
|
||||||
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
|
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
|
||||||
|
export const enableSetUsingLastTracks = currentSettings.enableSetUsingLastTracks.bind(currentSettings);
|
||||||
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
|
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
|
||||||
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
|
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
|
||||||
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
|
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
|
||||||
|
|
|
@ -1362,6 +1362,8 @@
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"SettingsSaved": "Settings saved.",
|
"SettingsSaved": "Settings saved.",
|
||||||
"SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.",
|
"SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.",
|
||||||
|
"SetUsingLastTracks": "Set Subtitle/Audio Tracks with Previous Item",
|
||||||
|
"SetUsingLastTracksHelp": "Attempts to set the Subtitle/Audio track to the closest match to the last video.",
|
||||||
"Share": "Share",
|
"Share": "Share",
|
||||||
"ShowAdvancedSettings": "Show advanced settings",
|
"ShowAdvancedSettings": "Show advanced settings",
|
||||||
"ShowIndicatorsFor": "Show indicators for:",
|
"ShowIndicatorsFor": "Show indicators for:",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue