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:
WithoutPants 2024-10-07 09:00:49 +11:00 committed by GitHub
parent 3e4515e62a
commit 4697271294
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 22 deletions

View File

@ -1,6 +1,7 @@
package ffmpeg
import (
"context"
"errors"
"io"
"net/http"
@ -230,7 +231,10 @@ func (sm *StreamManager) ServeTranscode(w http.ResponseWriter, r *http.Request,
handler, err := sm.getTranscodeStream(lockCtx, options)
if err != nil {
logger.Errorf("[transcode] error transcoding video file: %v", err)
// don't log context canceled errors
if !errors.Is(err, context.Canceled) {
logger.Errorf("[transcode] error transcoding video file: %v", err)
}
w.WriteHeader(http.StatusBadRequest)
if _, err := w.Write([]byte(err.Error())); err != nil {
logger.Warnf("[transcode] error writing response: %v", err)

View File

@ -451,12 +451,28 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
if (!player) return;
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) {
this.currentTime(initialTimestamp.current);
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) {
// This still runs even if autoplay failed on Safari,
// only set flag if actually playing
@ -477,12 +493,14 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
player.on("playing", playing);
player.on("loadstart", loadstart);
player.on("fullscreenchange", fullscreenchange);
player.on("timeupdate", timeupdate);
return () => {
player.off("canplay", canplay);
player.off("playing", playing);
player.off("loadstart", loadstart);
player.off("fullscreenchange", fullscreenchange);
player.off("timeupdate", timeupdate);
};
}, [getPlayer]);

View File

@ -1,3 +1,4 @@
import { debounce } from "lodash-es";
import videojs, { VideoJsPlayer } from "video.js";
export interface ISource extends videojs.Tech.SourceObject {
@ -10,6 +11,9 @@ interface ICue extends TextTrackCue {
_endTime?: number;
}
// delay before loading new source after setting currentTime
const loadDelay = 200;
function offsetMiddleware(player: VideoJsPlayer) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- allow access to private tech methods
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 {
setTech(newTech: videojs.Tech) {
tech = newTech;
@ -144,27 +176,7 @@ function offsetMiddleware(player: VideoJsPlayer) {
updateOffsetStart(seconds);
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.pause();
}
seeking = 0;
});
tech.trigger("timeupdate");
tech.trigger("pause");
tech.trigger("seeking");
tech.play();
loadSource(seconds);
return 0;
},