mirror of https://github.com/stashapp/stash.git
Add marker videojs plugin (#2447)
This commit is contained in:
parent
df982f7528
commit
510bec655b
|
@ -6,6 +6,7 @@ import "videojs-seek-buttons";
|
||||||
import "videojs-landscape-fullscreen";
|
import "videojs-landscape-fullscreen";
|
||||||
import "./live";
|
import "./live";
|
||||||
import "./PlaylistButtons";
|
import "./PlaylistButtons";
|
||||||
|
import "./markers";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
@ -136,6 +137,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
volumePanel: {
|
volumePanel: {
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
|
chaptersButton: false,
|
||||||
},
|
},
|
||||||
nativeControlsForTouch: false,
|
nativeControlsForTouch: false,
|
||||||
playbackRates: [0.75, 1, 1.5, 2, 3, 4],
|
playbackRates: [0.75, 1, 1.5, 2, 3, 4],
|
||||||
|
@ -160,6 +162,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(player as any).markers();
|
||||||
(player as any).offset();
|
(player as any).offset();
|
||||||
|
|
||||||
player.focus();
|
player.focus();
|
||||||
|
@ -270,6 +273,12 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
// otherwise, the offset will be applied to the next file when
|
// otherwise, the offset will be applied to the next file when
|
||||||
// currentTime is called.
|
// currentTime is called.
|
||||||
(player as any).clearOffsetDuration();
|
(player as any).clearOffsetDuration();
|
||||||
|
|
||||||
|
const tracks = player.remoteTextTracks();
|
||||||
|
if (tracks.length > 0) {
|
||||||
|
player.removeRemoteTextTrack(tracks[0] as any);
|
||||||
|
}
|
||||||
|
|
||||||
player.src(
|
player.src(
|
||||||
scene.sceneStreams.map((stream) => ({
|
scene.sceneStreams.map((stream) => ({
|
||||||
src: stream.url,
|
src: stream.url,
|
||||||
|
@ -277,6 +286,18 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
label: stream.label ?? undefined,
|
label: stream.label ?? undefined,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (scene.paths.chapters_vtt) {
|
||||||
|
player.addRemoteTextTrack(
|
||||||
|
{
|
||||||
|
src: scene.paths.chapters_vtt,
|
||||||
|
kind: "chapters",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
player.currentTime(0);
|
player.currentTime(0);
|
||||||
|
|
||||||
player.loop(
|
player.loop(
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import videojs, { VideoJsPlayer } from "video.js";
|
||||||
|
|
||||||
|
const markers = function (this: VideoJsPlayer) {
|
||||||
|
const player = this;
|
||||||
|
|
||||||
|
function getPosition(marker: VTTCue) {
|
||||||
|
return (marker.startTime / player.duration()) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMarkerToolTip() {
|
||||||
|
const tooltip = videojs.dom.createEl("div") as HTMLElement;
|
||||||
|
tooltip.className = "vjs-marker-tooltip";
|
||||||
|
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMarkerToolTip() {
|
||||||
|
const div = player
|
||||||
|
.el()
|
||||||
|
.querySelector(".vjs-progress-holder .vjs-marker-tooltip");
|
||||||
|
if (div) div.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMarkerDiv(marker: VTTCue) {
|
||||||
|
const markerDiv = videojs.dom.createEl(
|
||||||
|
"div",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
"data-marker-time": marker.startTime,
|
||||||
|
}
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
markerDiv.className = "vjs-marker";
|
||||||
|
markerDiv.style.left = getPosition(marker) + "%";
|
||||||
|
|
||||||
|
// bind click event to seek to marker time
|
||||||
|
markerDiv.addEventListener("click", function () {
|
||||||
|
const time = this.getAttribute("data-marker-time");
|
||||||
|
player.currentTime(Number(time));
|
||||||
|
});
|
||||||
|
|
||||||
|
// show tooltip on hover
|
||||||
|
markerDiv.addEventListener("mouseenter", function () {
|
||||||
|
// create and show tooltip
|
||||||
|
const tooltip = createMarkerToolTip();
|
||||||
|
tooltip.innerText = marker.text;
|
||||||
|
|
||||||
|
const parent = player
|
||||||
|
.el()
|
||||||
|
.querySelector(".vjs-progress-holder .vjs-mouse-display");
|
||||||
|
|
||||||
|
parent?.appendChild(tooltip);
|
||||||
|
|
||||||
|
// hide default tooltip
|
||||||
|
const defaultTooltip = parent?.querySelector(
|
||||||
|
".vjs-time-tooltip"
|
||||||
|
) as HTMLElement;
|
||||||
|
defaultTooltip.style.visibility = "hidden";
|
||||||
|
});
|
||||||
|
|
||||||
|
markerDiv.addEventListener("mouseout", function () {
|
||||||
|
removeMarkerToolTip();
|
||||||
|
|
||||||
|
// show default tooltip
|
||||||
|
const defaultTooltip = player
|
||||||
|
.el()
|
||||||
|
.querySelector(
|
||||||
|
".vjs-progress-holder .vjs-mouse-display .vjs-time-tooltip"
|
||||||
|
) as HTMLElement;
|
||||||
|
if (defaultTooltip) defaultTooltip.style.visibility = "visible";
|
||||||
|
});
|
||||||
|
|
||||||
|
return markerDiv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMarkerDivs() {
|
||||||
|
const divs = player
|
||||||
|
.el()
|
||||||
|
.querySelectorAll(".vjs-progress-holder .vjs-marker");
|
||||||
|
divs.forEach((div) => {
|
||||||
|
div.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on("loadedmetadata", function () {
|
||||||
|
removeMarkerDivs();
|
||||||
|
removeMarkerToolTip();
|
||||||
|
|
||||||
|
const textTracks = player.remoteTextTracks();
|
||||||
|
const seekBar = player.el().querySelector(".vjs-progress-holder");
|
||||||
|
|
||||||
|
if (seekBar && textTracks.length > 0) {
|
||||||
|
const vttTrack = textTracks[0];
|
||||||
|
if (!vttTrack || !vttTrack.cues) return;
|
||||||
|
for (let i = 0; i < vttTrack.cues.length; i++) {
|
||||||
|
const cue = vttTrack.cues[i];
|
||||||
|
const markerDiv = createMarkerDiv(cue as VTTCue);
|
||||||
|
seekBar.appendChild(markerDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the plugin with video.js.
|
||||||
|
videojs.registerPlugin("markers", markers);
|
||||||
|
|
||||||
|
export default markers;
|
|
@ -153,8 +153,9 @@ $sceneTabWidth: 450px;
|
||||||
font-size: 10em;
|
font-size: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jwplayer {
|
.vjs-progress-control .vjs-play-progress .vjs-time-tooltip,
|
||||||
outline: none;
|
.vjs-progress-control:hover .vjs-play-progress .vjs-time-tooltip {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,3 +394,43 @@ $sceneTabWidth: 450px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-marker {
|
||||||
|
background-color: rgba(33, 33, 33, 0.8);
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
-webkit-transition: opacity 0.2s ease;
|
||||||
|
-moz-transition: opacity 0.2s ease;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
width: 6px;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-transform: scale(1.3, 1.3);
|
||||||
|
-moz-transform: scale(1.3, 1.3);
|
||||||
|
-o-transform: scale(1.3, 1.3);
|
||||||
|
-ms-transform: scale(1.3, 1.3);
|
||||||
|
transform: scale(1.3, 1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-marker-tooltip {
|
||||||
|
border-radius: 0.3em;
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 50px;
|
||||||
|
padding: 6px 8px 8px 8px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
right: -80px;
|
||||||
|
top: -5.4em;
|
||||||
|
width: 160px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue