mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-11 17:55:33 +00:00
add skip confidence
This commit is contained in:
parent
aeb131b26d
commit
fcf9fbb56e
3 changed files with 73 additions and 31 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<SkipEvent, "timestamp">) => 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<SkipEvent, "timestamp">) => {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ?? "",
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue