mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-27 00:44:41 +03:00
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* skip vod clips that start after their audio ends When a camera's audio dropped out, a recording segment could end its audio early. If a playback range started past that point in the segment, nginx-vod saw no audio track in the first clip, failed the whole range with a track count mismatch, and left the player spinning for up to an hour of footage. Recording now stores each segment's audio length alongside its keyframe index, from the same ffprobe pass, and plan_clip skips a clip whose window starts at or after its audio ends when the range has other clips. A range made of only that clip still plays video-only, and legacy rows without the new column are unchanged. * keep audio skip consistent with playback Clips were skipped even when the range would be served video-only, and seeking into a skipped span landed on later footage. The skip now only applies when every row in the range carries audio, and seeks inside a zero-duration span land on the next clip.
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
import { Recording } from "@/types/record";
|
|
|
|
/** the HLS endpoint returns the vod segments with the first
|
|
* segment of the hour trimmed, meaning it will start at
|
|
* the beginning of the hour, cutting off any difference
|
|
* that the segment has.
|
|
*/
|
|
export function calculateInpointOffset(
|
|
timeRangeStart: number | undefined,
|
|
firstRecordingSegment: Recording | undefined,
|
|
): number {
|
|
if (!timeRangeStart || !firstRecordingSegment) {
|
|
return 0;
|
|
}
|
|
|
|
// if the first recording segment does not cross over
|
|
// the beginning of the time range then there is no offset
|
|
if (
|
|
firstRecordingSegment.start_time < timeRangeStart &&
|
|
firstRecordingSegment.end_time > timeRangeStart
|
|
) {
|
|
return timeRangeStart - firstRecordingSegment.start_time;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Calculates the video player time (in seconds) for a given timestamp
|
|
* by iterating through recording segments and summing their durations.
|
|
* This accounts for the fact that the video is a concatenation of segments,
|
|
* not a single continuous stream.
|
|
*
|
|
* @param timestamp - The target timestamp to seek to
|
|
* @param recordings - Array of recording segments
|
|
* @param inpointOffset - HLS inpoint offset to subtract from the result
|
|
* @returns The calculated seek position in seconds, or undefined if timestamp is out of range
|
|
*/
|
|
export function calculateSeekPosition(
|
|
timestamp: number,
|
|
recordings: Recording[],
|
|
inpointOffset: number = 0,
|
|
): number | undefined {
|
|
if (!recordings || recordings.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
// Check if timestamp is within the recordings range
|
|
if (
|
|
timestamp < recordings[0].start_time ||
|
|
timestamp > recordings[recordings.length - 1].end_time
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
let seekSeconds = 0;
|
|
|
|
(recordings || []).every((segment) => {
|
|
// if the next segment is past the desired time, stop calculating
|
|
if (segment.start_time > timestamp) {
|
|
return false;
|
|
}
|
|
|
|
// skipped clips have no playlist media, so a seek inside one lands on
|
|
// the next clip that plays
|
|
if (segment.duration === 0) {
|
|
return true;
|
|
}
|
|
|
|
// playlist duration exceeds wall length when the clip carries
|
|
// keyframe back-snap lead-in
|
|
const wallLength = segment.end_time - segment.start_time;
|
|
const leadIn = Math.max(0, segment.duration - wallLength);
|
|
|
|
if (segment.end_time < timestamp) {
|
|
seekSeconds += segment.duration;
|
|
return true;
|
|
}
|
|
|
|
// We're in this segment - calculate position within it
|
|
seekSeconds += leadIn + (timestamp - segment.start_time);
|
|
return true;
|
|
});
|
|
|
|
// Adjust for HLS inpoint offset
|
|
seekSeconds -= inpointOffset;
|
|
|
|
return seekSeconds >= 0 ? seekSeconds : undefined;
|
|
}
|
|
|
|
/**
|
|
* Attempts to play the video, and if it fails due to a NotAllowedError (often caused by browser autoplay restrictions),
|
|
* it temporarily mutes the video and tries to play again.
|
|
* @param video - The HTMLVideoElement to play
|
|
*/
|
|
export function playWithTemporaryMuteFallback(video: HTMLVideoElement) {
|
|
return video.play().catch((error: { name?: string }) => {
|
|
if (error.name === "NotAllowedError" && !video.muted) {
|
|
video.muted = true;
|
|
|
|
return video.play().catch(() => undefined);
|
|
}
|
|
|
|
throw error;
|
|
});
|
|
}
|