The watch progress system provides hooks for tracking video playback, managing episode watch states, and syncing progress across devices.
useWatchProgress
Retrieves the current watch progress for a specific media item.
Usage
import { useWatchProgress } from '@/hooks/useWatchProgress';
function ResumeWatchingCard({ movieId }) {
const { progress } = useWatchProgress(movieId, 'movie');
if (!progress || progress.percent === 0) return null;
return (
<div>
<h3>Resume Watching</h3>
<ProgressBar value={progress.percent} />
<p>{progress.percent}% complete</p>
</div>
);
}
Parameters
TMDB ID of the media item
Return Values
Watch progress information, or null if no progress existsShow WatchProgressData properties
Progress percentage (0-100)
Playback position in seconds (currently always 0)
Total duration in seconds (currently always 0)
Episode context for TV shows (season/episode numbers)
useContinueWatching
Retrieves all in-progress items for a “Continue Watching” section.
Usage
import { useContinueWatching } from '@/hooks/useWatchProgress';
function ContinueWatchingSection() {
const { items } = useContinueWatching();
if (items.length === 0) return null;
return (
<section>
<h2>Continue Watching</h2>
<div className="grid">
{items.map((item) => (
<MediaCard
key={item.id}
id={item.id}
type={item.type}
progress={item.percent}
/>
))}
</div>
</section>
);
}
Return Values
Array of items with progress between 0% and 100% (exclusive)
Same as items (legacy alias)
useEpisodeWatched
Comprehensive hook for managing TV show episode watch states with automatic progress synchronization.
Usage
import { useEpisodeWatched } from '@/hooks/useWatchProgress';
function EpisodeBrowser({ tvId, seasons, showMeta }) {
const totalEpisodes = seasons.reduce((acc, s) => acc + s.episode_count, 0);
const episodeTracker = useEpisodeWatched(tvId, totalEpisodes, {
title: showMeta.name,
image: showMeta.poster_path,
release_date: showMeta.first_air_date,
overview: showMeta.overview,
rating: showMeta.vote_average,
status: showMeta.status,
});
return (
<div>
<p>Watched: {episodeTracker.watchedCount} / {totalEpisodes}</p>
{seasons.map((season) => (
<SeasonCard
key={season.season_number}
season={season}
tracker={episodeTracker}
/>
))}
</div>
);
}
Parameters
totalEpisodes
number
default:"undefined"
Total episode count across all seasons (for progress calculation)
TV show metadata for progress status updates
Show status (e.g., “Ended”, “Returning Series”) - used to determine if “finished” status is appropriate
Return Values
isEpisodeWatched
(season: number, episode: number) => boolean
Function to check if a specific episode is marked as watched
toggleEpisodeWatched
(season: number, episode: number) => void
Toggle watch status for a single episode. Automatically syncs progress percentage and status.
markSeasonWatched
(season: number, episodes: number[]) => void
Mark all episodes in a season as watched. Accepts array of episode numbers.
unmarkSeasonWatched
(season: number, episodes: number[]) => void
Unmark all episodes in a season. Accepts array of episode numbers.
isSeasonFullyWatched
(season: number, totalEpisodesCount: number) => boolean
Check if all episodes in a season are marked as watched
getSeasonWatchedCount
(season: number, totalEpisodesCount: number) => number
Get the count of watched episodes in a specific season
markShowCompleted
(totalEpisodesOverride: number) => void
Mark the entire show as completed (sets progress to 100% and status to “finished”)
Total number of episodes marked as watched across all seasons
Real-World Example
import { useEpisodeWatched } from '@/hooks/useWatchProgress';
function SeasonCard({ season, tracker, tvId }) {
const { season_number, episode_count } = season;
const seenAll = tracker.isSeasonFullyWatched(season_number, episode_count);
const watchedCount = tracker.getSeasonWatchedCount(season_number, episode_count);
const episodeNumbers = Array.from({ length: episode_count }, (_, i) => i + 1);
const handleSeasonToggle = () => {
if (seenAll) {
tracker.unmarkSeasonWatched(season_number, episodeNumbers);
} else {
tracker.markSeasonWatched(season_number, episodeNumbers);
}
};
return (
<div>
<div>
<h3>Season {season_number}</h3>
<span>{watchedCount}/{episode_count} watched</span>
<button onClick={handleSeasonToggle}>
{seenAll ? 'Unmark Season' : 'Mark Season Watched'}
</button>
</div>
{episodeNumbers.map(ep => (
<EpisodeRow
key={ep}
episode={ep}
isWatched={tracker.isEpisodeWatched(season_number, ep)}
onToggle={() => tracker.toggleEpisodeWatched(season_number, ep)}
/>
))}
</div>
);
}
useEpisodeProgress
Get watch progress for a specific episode.
Usage
import { useEpisodeProgress } from '@/hooks/useWatchProgress';
function EpisodeCard({ tvId, season, episode }) {
const progress = useEpisodeProgress(tvId, season, episode);
return (
<div>
<h4>S{season}E{episode}</h4>
{progress === 100 && <CheckIcon />}
</div>
);
}
Parameters
Return Value
Returns 100 if the episode is watched, 0 otherwise.
usePlayerProgressListener
Listens for player events via postMessage and automatically persists progress.
Usage
import { usePlayerProgressListener } from '@/hooks/useWatchProgress';
function App() {
usePlayerProgressListener();
return <Router />;
}
Implementation Notes
- Listens for
PLAYER_EVENT messages from embedded video player
- Automatically saves progress every 2% change
- Auto-marks episodes as watched at 95% completion
- Syncs with Convex for authenticated users, localStorage for guests
- Debounces updates to avoid excessive API calls
window.postMessage(JSON.stringify({
type: 'PLAYER_EVENT',
data: {
event: 'timeupdate', // or 'play', 'pause', 'ended', 'seeked'
currentTime: 120,
duration: 3600,
progress: 33.3,
id: '12345',
mediaType: 'tv',
season: 1,
episode: 5,
},
}), '*');
buildPlayerUrl
Utility function to construct embedded player URLs with progress restoration.
Usage
import { buildPlayerUrl } from '@/hooks/useWatchProgress';
function WatchButton({ movie, progress }) {
const playerUrl = buildPlayerUrl({
type: 'movie',
tmdbId: movie.id,
savedProgress: progress?.percent,
});
return <a href={playerUrl}>Watch Now</a>;
}
Parameters
Season number (for TV shows)
Episode number (for TV shows)
Saved progress percentage. Only applied if > 10%
Return Value
Returns a complete player URL with query parameters for autoplay, next episode navigation, and progress restoration.
Source
Location: ~/workspace/source/src/hooks/useWatchProgress.ts
Progress tracking integrates with both Convex backend and local storage via Zustand.