mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-26 22:31:54 +03:00
* Initial copy timestamp url implementation
* revise url format
* Implement share timestamp dialog
* Use translations
* Add comments
* Add validations to shared link
* Switch to searchEffect implementation
* Add missing accessibility related dialog description
* Change URL format to unix timestamps
* Remove unnecessary useEffect
* Remove duplicated dialog title
* Fixes/improvements based off PR review comments
* Add missing cancel button & separators to dialog
* Make share description clearer
* Bugfix: guard against showing toasts twice
Because this effect ends up running multiple times
* Clamp future timestamps to now
* Revert "Bugfix: guard against showing toasts twice"
This reverts commit 99fa5e1dee.
* Use normal separator
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Fixes based off PR review comments
* Bugfix: Share dialog was not receiving the player timestamp after removing key that triggered remounts
* Defer `setRecording` and return true from hook for cleanup
* Remove timeout defer hack in favor of refactored hook
* Attempt to replay video muted on NotAllowedError
* Use separate persistent mute and temporary forced mute states
* Align cancel button with other dialogs
* Prevent wrapping on dialog title
* Remove extra "back" button on mobile drawer
* Fix back navigation when coming from direct shared timestamp links
* Use new timeformat hook
* Simplify dialog radio buttons
* Apply suggestions from code review
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
98 lines
2.9 KiB
TypeScript
98 lines
2.9 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;
|
|
}
|
|
|
|
if (segment.end_time < timestamp) {
|
|
// Add the full duration of this segment
|
|
seekSeconds += segment.end_time - segment.start_time;
|
|
return true;
|
|
}
|
|
|
|
// We're in this segment - calculate position within it
|
|
seekSeconds +=
|
|
segment.end_time - segment.start_time - (segment.end_time - timestamp);
|
|
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;
|
|
});
|
|
}
|