From fcf9fbb56eef10afc212cdc1bf67a371b7072a71 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 10 Nov 2025 14:52:31 -0700 Subject: [PATCH] add skip confidence --- .../player/atoms/SkipIntroButton.tsx | 54 ++++++++----------- .../player/hooks/useSkipTracking.ts | 49 +++++++++++++++++ .../player/internals/Backend/SkipTracker.tsx | 1 + 3 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/components/player/atoms/SkipIntroButton.tsx b/src/components/player/atoms/SkipIntroButton.tsx index b743622a..9af7f43a 100644 --- a/src/components/player/atoms/SkipIntroButton.tsx +++ b/src/components/player/atoms/SkipIntroButton.tsx @@ -2,8 +2,8 @@ import classNames from "classnames"; import { useCallback } from "react"; import { Icon, Icons } from "@/components/Icon"; +import { useSkipTracking } from "@/components/player/hooks/useSkipTracking"; import { Transition } from "@/components/utils/Transition"; -import { useAuthStore } from "@/stores/auth"; import { usePlayerStore } from "@/stores/player/store"; function shouldShowSkipButton( @@ -49,7 +49,7 @@ export function SkipIntroButton(props: { const status = usePlayerStore((s) => s.status); const display = usePlayerStore((s) => s.display); const meta = usePlayerStore((s) => s.meta); - const account = useAuthStore((s) => s.account); + const { addSkipEvent } = useSkipTracking(30); const showingState = shouldShowSkipButton(time, props.skipTime); const animation = showingState === "hover" ? "slide-up" : "fade"; let bottom = "bottom-[calc(6rem+env(safe-area-inset-bottom))]"; @@ -59,32 +59,6 @@ export function SkipIntroButton(props: { : "bottom-[calc(3rem+env(safe-area-inset-bottom))]"; } - const sendSkipAnalytics = useCallback( - async (startTime: number, endTime: number, skipDuration: number) => { - try { - await fetch("https://skips.pstream.mov/send", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - start_time: startTime, - end_time: endTime, - skip_duration: skipDuration, - content_id: meta?.tmdbId, - content_type: meta?.type, - season_id: meta?.season?.tmdbId, - episode_id: meta?.episode?.tmdbId, - user_id: account?.userId, - session_id: `session_${Date.now()}`, - turnstile_token: "", - }), - }); - } catch (error) { - console.error("Failed to send skip analytics:", error); - } - }, - [meta, account], - ); - const handleSkip = useCallback(() => { if (typeof props.skipTime === "number" && display) { const startTime = time; @@ -93,12 +67,30 @@ export function SkipIntroButton(props: { display.setTime(props.skipTime); - // Send analytics for intro skip button usage + // Add manual skip event with high confidence (user explicitly clicked skip intro) + addSkipEvent({ + startTime, + endTime, + skipDuration, + confidence: 0.95, // High confidence for explicit user action + meta: meta + ? { + title: + meta.type === "show" && meta.episode + ? `${meta.title} - S${meta.season?.number || 0}E${meta.episode.number || 0}` + : meta.title, + type: meta.type === "movie" ? "Movie" : "TV Show", + tmdbId: meta.tmdbId, + seasonNumber: meta.season?.number, + episodeNumber: meta.episode?.number, + } + : undefined, + }); + // eslint-disable-next-line no-console console.log(`Skip intro button used: ${skipDuration}s total`); - sendSkipAnalytics(startTime, endTime, skipDuration); } - }, [props.skipTime, display, time, sendSkipAnalytics]); + }, [props.skipTime, display, time, addSkipEvent, meta]); if (!props.inControl) return null; let show = false; diff --git a/src/components/player/hooks/useSkipTracking.ts b/src/components/player/hooks/useSkipTracking.ts index c22253a0..9594b5f4 100644 --- a/src/components/player/hooks/useSkipTracking.ts +++ b/src/components/player/hooks/useSkipTracking.ts @@ -7,6 +7,7 @@ interface SkipEvent { endTime: number; skipDuration: number; timestamp: number; + confidence: number; // 0.0-1.0 confidence score meta?: { title: string; type: string; @@ -23,6 +24,31 @@ interface SkipTrackingResult { latestSkip: SkipEvent | null; /** Clear the skip history */ clearHistory: () => void; + /** Add a manual skip event (e.g., from skip intro button) */ + addSkipEvent: (event: Omit) => void; +} + +/** + * Calculate confidence score for automatic skip detection + * Based on skip duration and timing within the video + */ +function calculateSkipConfidence( + skipDuration: number, + startTime: number, + duration: number, +): number { + // Duration confidence: longer skips are more confident + // 30s = 0.5, 60s = 0.75, 90s+ = 0.85 + const durationConfidence = Math.min(0.85, 0.5 + (skipDuration - 30) * 0.01); + + // Timing confidence: earlier skips are more confident + // Start time as percentage of total duration + const startPercentage = startTime / duration; + // Higher confidence for earlier starts (0% = 1.0, 20% = 0.8) + const timingConfidence = Math.max(0.7, 1.0 - startPercentage * 0.75); + + // Combine factors (weighted average) + return durationConfidence * 0.6 + timingConfidence * 0.4; } /** @@ -59,6 +85,23 @@ export function useSkipTracking( sessionTotalRef.current = 0; }, []); + const addSkipEvent = useCallback( + (event: Omit) => { + const skipEvent: SkipEvent = { + ...event, + timestamp: Date.now(), + }; + + setSkipHistory((prev) => { + const newHistory = [...prev, skipEvent]; + return newHistory.length > maxHistory + ? newHistory.slice(newHistory.length - maxHistory) + : newHistory; + }); + }, + [maxHistory], + ); + const detectSkip = useCallback(() => { const now = Date.now(); const currentTime = progress.time; @@ -123,6 +166,11 @@ export function useSkipTracking( endTime: currentTime, skipDuration: sessionTotalRef.current, timestamp: now, + confidence: calculateSkipConfidence( + sessionTotalRef.current, + skipSessionStartRef.current, + duration, + ), meta: meta ? { title: @@ -170,5 +218,6 @@ export function useSkipTracking( latestSkip: skipHistory.length > 0 ? skipHistory[skipHistory.length - 1] : null, clearHistory, + addSkipEvent, }; } diff --git a/src/components/player/internals/Backend/SkipTracker.tsx b/src/components/player/internals/Backend/SkipTracker.tsx index f1a75325..f7f2c3a8 100644 --- a/src/components/player/internals/Backend/SkipTracker.tsx +++ b/src/components/player/internals/Backend/SkipTracker.tsx @@ -32,6 +32,7 @@ export function SkipTracker() { content_type: meta?.type, season_id: meta?.season?.tmdbId, episode_id: meta?.episode?.tmdbId, + confidence: latestSkip.confidence, turnstile_token: turnstileToken ?? "", }), });