diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9261538ffc..fd4f0eb2d8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -52,6 +52,7 @@ - [MinecraftPlaye](https://github.com/MinecraftPlaye) - [Matthew Jones](https://github.com/matthew-jones-uk) - [taku0](https://github.com/taku0) + - [Viperinius](https://github.com/Viperinius) - [is343](https://github.com/is343) - [Meet Pandya](https://github.com/meet-k-pandya) diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html index c76a1714ae..3f9dfbef1a 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 a1ce0297cf..e65b056037 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1563,6 +1563,25 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return '

' + datetime.getDisplayRunningTime(ticks) + '

'; }; + nowPlayingPositionSlider.getMarkerInfo = function () { + const markers = []; + + const item = currentItem; + + // use markers based on chapters + if (item?.Chapters?.length) { + item.Chapters.forEach(currentChapter => { + markers.push({ + className: 'chapterMarker', + name: currentChapter.Name, + progress: currentChapter.StartPositionTicks / item.RunTimeTicks + }); + }); + } + + return markers; + }; + 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 04a8c94cf3..208e8d9db7 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.markerContainerElement) { + if (!range.triedAddingMarkers) { + addMarkers(range); + } + updateMarkers(range, value); + } }); } @@ -130,6 +137,70 @@ import '../emby-input/emby-input'; }); } + function setMarker(range, valueMarker, marker, valueProgress) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const markerRect = marker.getBoundingClientRect(); + + if (!bubbleTrackRect.width || !markerRect.width) { + // width is not set, most probably because the OSD is currently hidden + return; + } + + let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; + markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); + + marker.style.left = markerPos + 'px'; + + if (valueProgress >= valueMarker) { + marker.classList.remove('unwatched'); + marker.classList.add('watched'); + } else { + marker.classList.add('unwatched'); + marker.classList.remove('watched'); + } + }); + } + + function updateMarkers(range, currentValue) { + if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { + for (let i = 0, length = range.markerElements.length; i < length; i++) { + if (range.markerInfo.length > i) { + setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); + } + } + } + } + + function addMarkers(range) { + range.markerInfo = []; + if (range.getMarkerInfo) { + range.markerInfo = range.getMarkerInfo(); + } + + function getMarkerHtml(markerInfo) { + let markerTypeSpecificClasses = ''; + + if (markerInfo.className === 'chapterMarker') { + markerTypeSpecificClasses = markerInfo.className; + + if (typeof markerInfo.name === 'string' && markerInfo.name.length) { + // limit the class length in case the name contains half a novel + markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; + } + } + + return ``; + } + + range.markerInfo.forEach(info => { + range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); + }); + + range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); + range.triedAddingMarkers = true; + } + EmbySliderPrototype.attachedCallback = function () { if (this.getAttribute('data-embyslider') === 'true') { return; @@ -187,7 +258,9 @@ 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 hasHideBubbleClass = sliderBubble.classList.contains('hide'); + + this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); dom.addEventListener(this, 'input', function () { this.dragging = true; @@ -199,9 +272,9 @@ import '../emby-input/emby-input'; const bubbleValue = mapValueToFraction(this, this.value) * 100; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideBubbleClass = false; } }, { passive: true @@ -215,7 +288,7 @@ import '../emby-input/emby-input'; } sliderBubble.classList.add('hide'); - hasHideClass = true; + hasHideBubbleClass = true; }, { passive: true }); @@ -227,9 +300,9 @@ import '../emby-input/emby-input'; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideBubbleClass = false; } } }, { @@ -239,7 +312,7 @@ 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; + hasHideBubbleClass = true; }, { passive: true }); diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss index f7503d4fd5..eb9ab75c4a 100644 --- a/src/elements/emby-slider/emby-slider.scss +++ b/src/elements/emby-slider/emby-slider.scss @@ -245,3 +245,25 @@ display: block; margin-bottom: 0.25em; } + +.sliderMarkerContainer { + position: absolute; + left: 0; + right: 0; + margin: 0 0.54em; /* half of slider thumb size */ +} + +.sliderMarker { + position: absolute; + width: 2px; + height: 0.5em; + transform: translate3d(0, 25%, 0); +} + +.sliderMarker.unwatched { + background-color: rgba(255, 255, 255, 0.3); +} + +.sliderMarker.watched { + background-color: #00a4dc; +}