1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
This commit is contained in:
TheMelmacian 2025-03-30 11:01:13 -04:00 committed by GitHub
commit 6aa843af18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 96 additions and 8 deletions

View file

@ -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 {

View file

@ -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>

View file

@ -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');

View file

@ -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());

View file

@ -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"
}