mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge 19af2bf834
into 7d84185d0e
This commit is contained in:
commit
6aa843af18
5 changed files with 96 additions and 8 deletions
|
@ -88,6 +88,11 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
/* to prevent truncation of the displayed text when the scrollbar is visible */
|
||||
.actionSheetMenuItem {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.actionSheetScroller-tv {
|
||||
|
|
|
@ -70,6 +70,10 @@
|
|||
<span class="xlargePaperIconButton material-icons favorite" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button is="paper-icon-button-light" type="button" class="btnChapters hide autoSize paper-icon-button-light" title="${Chapters}">
|
||||
<span class="xlargePaperIconButton material-icons format_list_numbered" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button is="paper-icon-button-light" class="btnSubtitles hide autoSize" title="${Subtitles}">
|
||||
<span class="xlargePaperIconButton material-icons closed_caption" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
|
|
@ -135,6 +135,25 @@ export default function (view) {
|
|||
btnUserRating.setItem(null);
|
||||
}
|
||||
|
||||
if (currentItem.Chapters) {
|
||||
const chapters = currentItem.Chapters;
|
||||
// make sure all displayed chapter numbers and timestamps have the same length for a cleaner look
|
||||
const chapterNumberPadLength = `${chapters.length}`.length;
|
||||
const maxChapterStartTime = Math.max(...chapters.map(chpt => chpt.StartPositionTicks));
|
||||
const durationStringMaxLength = datetime.getDisplayRunningTime(maxChapterStartTime, true).length;
|
||||
|
||||
chapterSelectionOptions = chapters.map((chapter, index) => {
|
||||
const chapterNumber = `${index + 1}`.padStart(chapterNumberPadLength, '0');
|
||||
const chapterName = chapter.Name || `${globalize.translate('Chapter')} ${chapterNumber}`;
|
||||
|
||||
return {
|
||||
name: `${chapterNumber}: ${chapterName}`,
|
||||
asideText: `[${datetime.getDisplayRunningTime(chapter.StartPositionTicks, true).padStart(durationStringMaxLength, '0')}]`,
|
||||
id: chapter.StartPositionTicks
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Update trickplay data
|
||||
trickplayResolution = null;
|
||||
|
||||
|
@ -194,6 +213,7 @@ export default function (view) {
|
|||
nowPlayingPositionSlider.disabled = true;
|
||||
btnFastForward.disabled = true;
|
||||
btnRewind.disabled = true;
|
||||
view.querySelector('.btnChapters').classList.add('hide');
|
||||
view.querySelector('.btnSubtitles').classList.add('hide');
|
||||
view.querySelector('.btnAudio').classList.add('hide');
|
||||
view.querySelector('.osdTitle').innerHTML = '';
|
||||
|
@ -208,6 +228,12 @@ export default function (view) {
|
|||
btnFastForward.disabled = false;
|
||||
btnRewind.disabled = false;
|
||||
|
||||
if (currentItem.Chapters?.length) {
|
||||
view.querySelector('.btnChapters').classList.remove('hide');
|
||||
} else {
|
||||
view.querySelector('.btnChapters').classList.add('hide');
|
||||
}
|
||||
|
||||
if (playbackManager.subtitleTracks(player).length) {
|
||||
view.querySelector('.btnSubtitles').classList.remove('hide');
|
||||
toggleSubtitleSync();
|
||||
|
@ -974,6 +1000,8 @@ export default function (view) {
|
|||
stats: true,
|
||||
suboffset: showSubOffset,
|
||||
onOption: onSettingsOption
|
||||
}).catch(() => {
|
||||
// prevent 'ActionSheet closed without resolving' error
|
||||
}).finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
@ -1047,6 +1075,8 @@ export default function (view) {
|
|||
if (index !== currentIndex) {
|
||||
playbackManager.setAudioStreamIndex(index, player);
|
||||
}
|
||||
}).catch(() => {
|
||||
// prevent 'ActionSheet closed without resolving' error
|
||||
}).finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
@ -1094,10 +1124,11 @@ export default function (view) {
|
|||
playbackManager.setSecondarySubtitleStreamIndex(index, player);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
}).catch(() => {
|
||||
// prevent 'ActionSheet closed without resolving' error
|
||||
}).finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
||||
setTimeout(resetIdle, 0);
|
||||
}
|
||||
|
@ -1174,6 +1205,45 @@ export default function (view) {
|
|||
}
|
||||
|
||||
toggleSubtitleSync();
|
||||
}).catch(() => {
|
||||
// prevent 'ActionSheet closed without resolving' error
|
||||
}).finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
||||
setTimeout(resetIdle, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function showChapterSelection() {
|
||||
// At the moment Jellyfin doesn't support most of MKV's chapter features (hidden- and standard-flags, multiple Editions, linked chapters, sub-chapters, multi language support, ...).
|
||||
// For MKV files that use one or more of these features it's not guaranteed that chapters are correctly ordered or displayed.
|
||||
// If support of one of these features is added in the future, it's maybe necessary to adjust the chapter handling.
|
||||
|
||||
const player = currentPlayer;
|
||||
const currentTicks = playbackManager.getCurrentTicks(player);
|
||||
|
||||
const menuItems = chapterSelectionOptions.map((chapter, index) => {
|
||||
return {
|
||||
...chapter,
|
||||
selected: currentTicks >= chapter.id // the id is equal to StartPositionTicks
|
||||
&& (chapterSelectionOptions[index + 1] == null
|
||||
|| currentTicks < chapterSelectionOptions[index + 1].id)
|
||||
};
|
||||
});
|
||||
|
||||
const positionTo = this;
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
title: globalize.translate('Chapters'),
|
||||
items: menuItems,
|
||||
positionTo: positionTo,
|
||||
scrollY: true
|
||||
}).then(
|
||||
chapterStartPositionTicks => playbackManager.seek(chapterStartPositionTicks, player)
|
||||
).catch(() => {
|
||||
// prevent 'ActionSheet closed without resolving' error
|
||||
}).finally(() => {
|
||||
resetIdle();
|
||||
});
|
||||
|
@ -1641,6 +1711,7 @@ export default function (view) {
|
|||
let programEndDateMs = 0;
|
||||
let playbackStartTimeTicks = 0;
|
||||
let subtitleSyncOverlay;
|
||||
let chapterSelectionOptions = [];
|
||||
let trickplayResolution = null;
|
||||
const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider');
|
||||
const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer');
|
||||
|
@ -1960,6 +2031,7 @@ export default function (view) {
|
|||
});
|
||||
view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection);
|
||||
view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection);
|
||||
view.querySelector('.btnChapters').addEventListener('click', showChapterSelection);
|
||||
|
||||
// HACK: Remove `emby-button` from the rating button to make it look like the other buttons
|
||||
view.querySelector('.btnUserRating').classList.remove('emby-button');
|
||||
|
|
|
@ -71,7 +71,12 @@ export function getDisplayDuration(ticks) {
|
|||
return result.join(' ');
|
||||
}
|
||||
|
||||
export function getDisplayRunningTime(ticks) {
|
||||
/**
|
||||
* Return a string in h:mm:ss format for the running time.
|
||||
* @param {number} ticks - Running ime in ticks.
|
||||
* @param {boolean} showZeroValues - If true, hours and minutes are always visible in resulting string, even if 0
|
||||
*/
|
||||
export function getDisplayRunningTime(ticks, showZeroValues = false) {
|
||||
const ticksPerHour = 36000000000;
|
||||
const ticksPerMinute = 600000000;
|
||||
const ticksPerSecond = 10000000;
|
||||
|
@ -81,7 +86,7 @@ export function getDisplayRunningTime(ticks) {
|
|||
let hours = ticks / ticksPerHour;
|
||||
hours = Math.floor(hours);
|
||||
|
||||
if (hours) {
|
||||
if (hours || showZeroValues) {
|
||||
parts.push(hours.toLocaleString(globalize.getCurrentDateTimeLocale()));
|
||||
}
|
||||
|
||||
|
@ -92,7 +97,7 @@ export function getDisplayRunningTime(ticks) {
|
|||
|
||||
ticks -= (minutes * ticksPerMinute);
|
||||
|
||||
if (minutes < 10 && hours) {
|
||||
if (minutes < 10 && (hours || showZeroValues)) {
|
||||
minutes = (0).toLocaleString(globalize.getCurrentDateTimeLocale()) + minutes.toLocaleString(globalize.getCurrentDateTimeLocale());
|
||||
} else {
|
||||
minutes = minutes.toLocaleString(globalize.getCurrentDateTimeLocale());
|
||||
|
|
|
@ -1819,5 +1819,7 @@
|
|||
"ExtractTrickplayImagesHelp": "Trickplay images are similar to chapter images, except they span the entire length of the content and are used to show a preview when scrubbing through videos.",
|
||||
"LabelExtractTrickplayDuringLibraryScan": "Extract trickplay images during the library scan",
|
||||
"LabelExtractTrickplayDuringLibraryScanHelp": "Generate trickplay images when videos are imported during the library scan. Otherwise, they will be extracted during the trickplay images scheduled task. If generation is set to non-blocking this will not affect the time a library scan takes to complete.",
|
||||
"LogLoadFailure": "Failed to load the log file. It may still be actively written to."
|
||||
"LogLoadFailure": "Failed to load the log file. It may still be actively written to.",
|
||||
"Chapter": "Chapter",
|
||||
"Chapters": "Chapters"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue