mirror of
https://github.com/stashapp/stash.git
synced 2025-12-08 01:13:09 +01:00
Scene player fixes and improvements (#5340)
* Don't log context canceled error during live transcode * Pause live transcode if still scrubbing * Debounce loading live transcode source to avoid multiple ffmpeg instances * Don't start from start or resume time if seeking before playing * Play video when seeked before playing
This commit is contained in:
parent
3e4515e62a
commit
4697271294
3 changed files with 56 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package ffmpeg
|
package ffmpeg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -230,7 +231,10 @@ func (sm *StreamManager) ServeTranscode(w http.ResponseWriter, r *http.Request,
|
||||||
handler, err := sm.getTranscodeStream(lockCtx, options)
|
handler, err := sm.getTranscodeStream(lockCtx, options)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// don't log context canceled errors
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
logger.Errorf("[transcode] error transcoding video file: %v", err)
|
logger.Errorf("[transcode] error transcoding video file: %v", err)
|
||||||
|
}
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
if _, err := w.Write([]byte(err.Error())); err != nil {
|
if _, err := w.Write([]byte(err.Error())); err != nil {
|
||||||
logger.Warnf("[transcode] error writing response: %v", err)
|
logger.Warnf("[transcode] error writing response: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -451,12 +451,28 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
|
|
||||||
function canplay(this: VideoJsPlayer) {
|
function canplay(this: VideoJsPlayer) {
|
||||||
|
// if we're seeking before starting, don't set the initial timestamp
|
||||||
|
// when starting from the beginning, there is a small delay before the event
|
||||||
|
// is triggered, so we can't just check if the time is 0
|
||||||
|
if (this.currentTime() >= 0.1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (initialTimestamp.current !== -1) {
|
if (initialTimestamp.current !== -1) {
|
||||||
this.currentTime(initialTimestamp.current);
|
this.currentTime(initialTimestamp.current);
|
||||||
initialTimestamp.current = -1;
|
initialTimestamp.current = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeupdate(this: VideoJsPlayer) {
|
||||||
|
// fired when seeking
|
||||||
|
// check if we haven't started playing yet
|
||||||
|
// if so, start playing
|
||||||
|
if (!started.current) {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function playing(this: VideoJsPlayer) {
|
function playing(this: VideoJsPlayer) {
|
||||||
// This still runs even if autoplay failed on Safari,
|
// This still runs even if autoplay failed on Safari,
|
||||||
// only set flag if actually playing
|
// only set flag if actually playing
|
||||||
|
|
@ -477,12 +493,14 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||||
player.on("playing", playing);
|
player.on("playing", playing);
|
||||||
player.on("loadstart", loadstart);
|
player.on("loadstart", loadstart);
|
||||||
player.on("fullscreenchange", fullscreenchange);
|
player.on("fullscreenchange", fullscreenchange);
|
||||||
|
player.on("timeupdate", timeupdate);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
player.off("canplay", canplay);
|
player.off("canplay", canplay);
|
||||||
player.off("playing", playing);
|
player.off("playing", playing);
|
||||||
player.off("loadstart", loadstart);
|
player.off("loadstart", loadstart);
|
||||||
player.off("fullscreenchange", fullscreenchange);
|
player.off("fullscreenchange", fullscreenchange);
|
||||||
|
player.off("timeupdate", timeupdate);
|
||||||
};
|
};
|
||||||
}, [getPlayer]);
|
}, [getPlayer]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { debounce } from "lodash-es";
|
||||||
import videojs, { VideoJsPlayer } from "video.js";
|
import videojs, { VideoJsPlayer } from "video.js";
|
||||||
|
|
||||||
export interface ISource extends videojs.Tech.SourceObject {
|
export interface ISource extends videojs.Tech.SourceObject {
|
||||||
|
|
@ -10,6 +11,9 @@ interface ICue extends TextTrackCue {
|
||||||
_endTime?: number;
|
_endTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delay before loading new source after setting currentTime
|
||||||
|
const loadDelay = 200;
|
||||||
|
|
||||||
function offsetMiddleware(player: VideoJsPlayer) {
|
function offsetMiddleware(player: VideoJsPlayer) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- allow access to private tech methods
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- allow access to private tech methods
|
||||||
let tech: any;
|
let tech: any;
|
||||||
|
|
@ -50,6 +54,34 @@ function offsetMiddleware(player: VideoJsPlayer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadSource = debounce(
|
||||||
|
(seconds: number) => {
|
||||||
|
const srcUrl = new URL(source.src);
|
||||||
|
srcUrl.searchParams.set("start", seconds.toString());
|
||||||
|
source.src = srcUrl.toString();
|
||||||
|
|
||||||
|
const poster = player.poster();
|
||||||
|
const playbackRate = tech.playbackRate();
|
||||||
|
seeking = tech.paused() ? 1 : 2;
|
||||||
|
player.poster("");
|
||||||
|
tech.setSource(source);
|
||||||
|
tech.setPlaybackRate(playbackRate);
|
||||||
|
tech.one("canplay", () => {
|
||||||
|
player.poster(poster);
|
||||||
|
if (seeking === 1 || tech.scrubbing()) {
|
||||||
|
tech.pause();
|
||||||
|
}
|
||||||
|
seeking = 0;
|
||||||
|
});
|
||||||
|
tech.trigger("timeupdate");
|
||||||
|
tech.trigger("pause");
|
||||||
|
tech.trigger("seeking");
|
||||||
|
tech.play();
|
||||||
|
},
|
||||||
|
loadDelay,
|
||||||
|
{ leading: true }
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setTech(newTech: videojs.Tech) {
|
setTech(newTech: videojs.Tech) {
|
||||||
tech = newTech;
|
tech = newTech;
|
||||||
|
|
@ -144,27 +176,7 @@ function offsetMiddleware(player: VideoJsPlayer) {
|
||||||
|
|
||||||
updateOffsetStart(seconds);
|
updateOffsetStart(seconds);
|
||||||
|
|
||||||
const srcUrl = new URL(source.src);
|
loadSource(seconds);
|
||||||
srcUrl.searchParams.set("start", seconds.toString());
|
|
||||||
source.src = srcUrl.toString();
|
|
||||||
|
|
||||||
const poster = player.poster();
|
|
||||||
const playbackRate = tech.playbackRate();
|
|
||||||
seeking = tech.paused() ? 1 : 2;
|
|
||||||
player.poster("");
|
|
||||||
tech.setSource(source);
|
|
||||||
tech.setPlaybackRate(playbackRate);
|
|
||||||
tech.one("canplay", () => {
|
|
||||||
player.poster(poster);
|
|
||||||
if (seeking === 1) {
|
|
||||||
tech.pause();
|
|
||||||
}
|
|
||||||
seeking = 0;
|
|
||||||
});
|
|
||||||
tech.trigger("timeupdate");
|
|
||||||
tech.trigger("pause");
|
|
||||||
tech.trigger("seeking");
|
|
||||||
tech.play();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue