diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html index c76a1714ae..278b1d4d4a 100644 --- a/src/controllers/playback/video/index.html +++ b/src/controllers/playback/video/index.html @@ -21,6 +21,7 @@
+
diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index d5da82b740..baaec52ff2 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1574,6 +1574,28 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return '

' + datetime.getDisplayRunningTime(ticks) + '

'; }; + nowPlayingPositionSlider.getChapterFractions = function () { + showOsd(); + + const item = currentItem; + + if (item && item.Chapters && item.Chapters.length) { + const chapterFractions = []; + const runtimeDuration = item.RunTimeTicks; + + for (let i = 0, length = item.Chapters.length; i < length; i++) { + const currentChapter = item.Chapters[i]; + + const fraction = currentChapter.StartPositionTicks / runtimeDuration; + chapterFractions.push(fraction); + } + + return chapterFractions; + } + + return []; + }; + view.querySelector('.btnPreviousTrack').addEventListener('click', function () { playbackManager.previousTrack(currentPlayer); }); diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 0bb20270e6..04aa7c4778 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -102,6 +102,13 @@ import '../emby-input/emby-input'; fraction *= 100; backgroundLower.style.width = fraction + '%'; } + + if (range.chapterMarkContainerElement) { + if (!range.triedChapterMarksAdd) { + addChapterMarks(range); + } + updateChapterMarks(range, value); + } }); } @@ -130,6 +137,57 @@ import '../emby-input/emby-input'; }); } + function setChapterMark(range, valueChapterMark, chapterMark, valueProgress) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const chapterMarkRect = chapterMark.getBoundingClientRect(); + + if (!bubbleTrackRect.width || !chapterMarkRect.width) { + // width is not set, most probably because the OSD is currently hidden + return; + } + + let chapterMarkPos = (bubbleTrackRect.width * valueChapterMark / 100) - chapterMarkRect.width / 2; + chapterMarkPos = Math.min(Math.max(chapterMarkPos, - chapterMarkRect.width / 2), bubbleTrackRect.width - chapterMarkRect.width / 2); + + chapterMark.style.left = chapterMarkPos + 'px'; + + if (valueProgress >= valueChapterMark) { + chapterMark.classList.remove('unwatched'); + chapterMark.classList.add('watched'); + } else { + chapterMark.classList.add('unwatched'); + chapterMark.classList.remove('watched'); + } + }); + } + + function updateChapterMarks(range, currentValue) { + if (range.chapterFractions && range.chapterFractions.length && range.chapterMarkElements && range.chapterMarkElements.length) { + for (let i = 0, length = range.chapterMarkElements.length; i < length; i++) { + if (range.chapterFractions.length > i) { + setChapterMark(range, mapFractionToValue(range, range.chapterFractions[i]), range.chapterMarkElements[i], currentValue); + } + } + } + } + + function addChapterMarks(range) { + range.chapterFractions = []; + if (range.getChapterFractions) { + range.chapterFractions = range.getChapterFractions(); + } + + const htmlToInsert = ''; + + range.chapterFractions.forEach(() => { + range.chapterMarkContainerElement.insertAdjacentHTML('beforeend', htmlToInsert); + }); + + range.chapterMarkElements = range.chapterMarkContainerElement.querySelectorAll('.sliderChapterMark'); + range.triedChapterMarksAdd = true; + } + EmbySliderPrototype.attachedCallback = function () { if (this.getAttribute('data-embyslider') === 'true') { return; @@ -187,7 +245,13 @@ import '../emby-input/emby-input'; this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); const sliderBubble = containerElement.querySelector('.sliderBubble'); - let hasHideClass = sliderBubble.classList.contains('hide'); + let hasHideClassBubble = sliderBubble.classList.contains('hide'); + + this.chapterMarkContainerElement = containerElement.querySelector('.sliderChapterMarkContainer'); + let hasHideClassChapterMarkContainer = false; + if (this.chapterMarkContainerElement) { + hasHideClassChapterMarkContainer = this.chapterMarkContainerElement.classList.contains('hide'); + } dom.addEventListener(this, 'input', function () { this.dragging = true; @@ -199,9 +263,14 @@ import '../emby-input/emby-input'; const bubbleValue = mapValueToFraction(this, this.value) * 100; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideClassBubble) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideClassBubble = false; + } + + if (hasHideClassChapterMarkContainer) { + this.chapterMarkContainerElement.classList.remove('hide'); + hasHideClassChapterMarkContainer = false; } }, { passive: true @@ -215,7 +284,10 @@ import '../emby-input/emby-input'; } sliderBubble.classList.add('hide'); - hasHideClass = true; + hasHideClassBubble = true; + + this.chapterMarkContainerElement.classList.add('hide'); + hasHideClassChapterMarkContainer = true; }, { passive: true }); @@ -227,9 +299,14 @@ import '../emby-input/emby-input'; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideClassBubble) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideClassBubble = false; + } + + if (hasHideClassChapterMarkContainer) { + this.chapterMarkContainerElement.classList.remove('hide'); + hasHideClassChapterMarkContainer = false; } } }, { @@ -239,7 +316,10 @@ import '../emby-input/emby-input'; /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { sliderBubble.classList.add('hide'); - hasHideClass = true; + hasHideClassBubble = true; + + this.chapterMarkContainerElement.classList.add('hide'); + hasHideClassChapterMarkContainer = true; }, { passive: true }); diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss index f7503d4fd5..cd07533efd 100644 --- a/src/elements/emby-slider/emby-slider.scss +++ b/src/elements/emby-slider/emby-slider.scss @@ -245,3 +245,23 @@ display: block; margin-bottom: 0.25em; } + +.sliderChapterMarkContainer { + position: absolute; + left: 0; + right: 0; + margin: 0 0.54em; /* half of slider thumb size */ +} + +.sliderChapterMark { + position: absolute; + transform: translate3d(0, -100%, 0) rotate(90deg); +} + +.sliderChapterMark.unwatched { + color: rgba(255, 255, 255, 0.3); +} + +.sliderChapterMark.watched { + color: #00a4dc; +}