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; }); }