From cb1964492e54b45303c0330b87dfd1c830c1fdf7 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 10 Nov 2025 14:55:13 -0700 Subject: [PATCH] adjust confidence based on adjustments to prior skip --- .../player/internals/Backend/SkipTracker.tsx | 142 ++++++++++++++---- 1 file changed, 116 insertions(+), 26 deletions(-) diff --git a/src/components/player/internals/Backend/SkipTracker.tsx b/src/components/player/internals/Backend/SkipTracker.tsx index f7f2c3a8..7026d9d6 100644 --- a/src/components/player/internals/Backend/SkipTracker.tsx +++ b/src/components/player/internals/Backend/SkipTracker.tsx @@ -1,45 +1,95 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useSkipTracking } from "@/components/player/hooks/useSkipTracking"; import { usePlayerStore } from "@/stores/player/store"; +// Import SkipEvent type +type SkipEvent = NonNullable["latestSkip"]>; + /** * Component that tracks and reports completed skip sessions to analytics backend. * Sessions are detected when users accumulate 30+ seconds of forward movement * within a 5-second window and end after 8 seconds of no activity. * Ignores skips that start after 20% of video duration (unlikely to be intro skipping). */ +interface PendingSkip { + skip: SkipEvent; + originalConfidence: number; + startTime: number; + endTime: number; + hasBackwardMovement: boolean; + timer: ReturnType; +} + export function SkipTracker() { const { latestSkip } = useSkipTracking(30); const lastLoggedSkipRef = useRef(0); + const [pendingSkips, setPendingSkips] = useState([]); + const lastPlayerTimeRef = useRef(0); // Player metadata for context const meta = usePlayerStore((s) => s.meta); + const progress = usePlayerStore((s) => s.progress); const turnstileToken = ""; - const sendSkipAnalytics = useCallback(async () => { - if (!latestSkip) return; + const sendSkipAnalytics = useCallback( + async (skip: SkipEvent, adjustedConfidence: number) => { + try { + await fetch("https://skips.pstream.mov/send", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + start_time: skip.startTime, + end_time: skip.endTime, + skip_duration: skip.skipDuration, + content_id: meta?.tmdbId, + content_type: meta?.type, + season_id: meta?.season?.tmdbId, + episode_id: meta?.episode?.tmdbId, + confidence: adjustedConfidence, + turnstile_token: turnstileToken ?? "", + }), + }); + } catch (error) { + console.error("Failed to send skip analytics:", error); + } + }, + [meta, turnstileToken], + ); - try { - await fetch("https://skips.pstream.mov/send", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - start_time: latestSkip.startTime, - end_time: latestSkip.endTime, - skip_duration: latestSkip.skipDuration, - content_id: meta?.tmdbId, - content_type: meta?.type, - season_id: meta?.season?.tmdbId, - episode_id: meta?.episode?.tmdbId, - confidence: latestSkip.confidence, - turnstile_token: turnstileToken ?? "", - }), - }); - } catch (error) { - console.error("Failed to send skip analytics:", error); - } - }, [latestSkip, meta]); + const createPendingSkip = useCallback( + (skip: SkipEvent) => { + const timer = setTimeout(() => { + // Timer expired, send analytics with final confidence + setPendingSkips((prev) => { + const pendingSkip = prev.find( + (p) => p.skip.timestamp === skip.timestamp, + ); + if (!pendingSkip) return prev; + + const adjustedConfidence = pendingSkip.hasBackwardMovement + ? Math.max(0.1, pendingSkip.originalConfidence * 0.5) // Reduce confidence by half if adjusted + : pendingSkip.originalConfidence; + + // Send analytics + sendSkipAnalytics(pendingSkip.skip, adjustedConfidence); + + // Remove from pending + return prev.filter((p) => p.skip.timestamp !== skip.timestamp); + }); + }, 10000); // 10 second delay + + return { + skip, + originalConfidence: skip.confidence, + startTime: progress.time, + endTime: skip.endTime, + hasBackwardMovement: false, + timer, + }; + }, + [progress.time, sendSkipAnalytics], + ); useEffect(() => { if (!latestSkip || !meta) return; @@ -51,11 +101,51 @@ export function SkipTracker() { // eslint-disable-next-line no-console console.log(`Skip session completed: ${latestSkip.skipDuration}s total`); - // Send analytics data to backend - sendSkipAnalytics(); + // Create pending skip with 10-second delay + const pendingSkip = createPendingSkip(latestSkip); + setPendingSkips((prev) => [...prev, pendingSkip]); lastLoggedSkipRef.current = latestSkip.timestamp; - }, [latestSkip, meta, sendSkipAnalytics]); + }, [latestSkip, meta, createPendingSkip]); + + // Monitor for backward movements during pending skip periods + useEffect(() => { + const currentTime = progress.time; + + // Check for backward movement + if ( + lastPlayerTimeRef.current > 0 && + currentTime < lastPlayerTimeRef.current + ) { + // Backward movement detected, mark relevant pending skips as adjusted + setPendingSkips((prev) => + prev.map((pending) => { + // Check if we're within the monitoring period (between start and end time of skip) + const isWithinSkipRange = + currentTime >= pending.startTime && currentTime <= pending.endTime; + if (isWithinSkipRange && !pending.hasBackwardMovement) { + // eslint-disable-next-line no-console + console.log( + `Backward adjustment detected for skip, reducing confidence`, + ); + return { ...pending, hasBackwardMovement: true }; + } + return pending; + }), + ); + } + + lastPlayerTimeRef.current = currentTime; + }, [progress.time]); + + // Cleanup timers on unmount + useEffect(() => { + return () => { + pendingSkips.forEach((pending) => { + clearTimeout(pending.timer); + }); + }; + }, [pendingSkips]); return null; }