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;
+}