mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
trakt scrobble optimization
This commit is contained in:
parent
64981dd110
commit
08f356cfa4
6 changed files with 599 additions and 49 deletions
|
|
@ -108,6 +108,10 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
// Track recently removed items to prevent immediate re-addition
|
||||
const recentlyRemovedRef = useRef<Set<string>>(new Set());
|
||||
const REMOVAL_IGNORE_DURATION = 10000; // 10 seconds
|
||||
|
||||
// Track last Trakt sync to prevent excessive API calls
|
||||
const lastTraktSyncRef = useRef<number>(0);
|
||||
const TRAKT_SYNC_COOLDOWN = 5 * 60 * 1000; // 5 minutes between Trakt syncs
|
||||
|
||||
// Cache for metadata to avoid redundant API calls
|
||||
const metadataCache = useRef<Record<string, { metadata: any; basicContent: StreamingContent | null; timestamp: number }>>({});
|
||||
|
|
@ -368,6 +372,15 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
const traktService = TraktService.getInstance();
|
||||
const isAuthed = await traktService.isAuthenticated();
|
||||
if (!isAuthed) return;
|
||||
|
||||
// Check Trakt sync cooldown to prevent excessive API calls
|
||||
const now = Date.now();
|
||||
if (now - lastTraktSyncRef.current < TRAKT_SYNC_COOLDOWN) {
|
||||
logger.log(`[TraktSync] Skipping Trakt sync - cooldown active (${Math.round((TRAKT_SYNC_COOLDOWN - (now - lastTraktSyncRef.current)) / 1000)}s remaining)`);
|
||||
return;
|
||||
}
|
||||
|
||||
lastTraktSyncRef.current = now;
|
||||
const historyItems = await traktService.getWatchedEpisodesHistory(1, 200);
|
||||
const latestWatchedByShow: Record<string, { season: number; episode: number; watchedAt: number }> = {};
|
||||
for (const item of historyItems) {
|
||||
|
|
@ -384,18 +397,21 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
}
|
||||
}
|
||||
|
||||
const perShowPromises = Object.entries(latestWatchedByShow).map(async ([showId, info]) => {
|
||||
// Collect all valid Trakt items first, then merge as a batch
|
||||
const traktBatch: ContinueWatchingItem[] = [];
|
||||
|
||||
for (const [showId, info] of Object.entries(latestWatchedByShow)) {
|
||||
try {
|
||||
// Check if this show was recently removed by the user
|
||||
const showKey = `series:${showId}`;
|
||||
if (recentlyRemovedRef.current.has(showKey)) {
|
||||
logger.log(`🚫 [TraktSync] Skipping recently removed show: ${showKey}`);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const nextEpisode = info.episode + 1;
|
||||
const cachedData = await getCachedMetadata('series', showId);
|
||||
if (!cachedData?.basicContent) return;
|
||||
if (!cachedData?.basicContent) continue;
|
||||
const { metadata, basicContent } = cachedData;
|
||||
let nextEpisodeVideo = null;
|
||||
if (metadata?.videos && Array.isArray(metadata.videos)) {
|
||||
|
|
@ -405,18 +421,16 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
}
|
||||
if (nextEpisodeVideo && isEpisodeReleased(nextEpisodeVideo)) {
|
||||
logger.log(`➕ [TraktSync] Adding next episode for ${showId}: S${info.season}E${nextEpisode}`);
|
||||
await mergeBatchIntoState([
|
||||
{
|
||||
...basicContent,
|
||||
id: showId,
|
||||
type: 'series',
|
||||
progress: 0,
|
||||
lastUpdated: info.watchedAt,
|
||||
season: info.season,
|
||||
episode: nextEpisode,
|
||||
episodeTitle: `Episode ${nextEpisode}`,
|
||||
} as ContinueWatchingItem,
|
||||
]);
|
||||
traktBatch.push({
|
||||
...basicContent,
|
||||
id: showId,
|
||||
type: 'series',
|
||||
progress: 0,
|
||||
lastUpdated: info.watchedAt,
|
||||
season: info.season,
|
||||
episode: nextEpisode,
|
||||
episodeTitle: `Episode ${nextEpisode}`,
|
||||
} as ContinueWatchingItem);
|
||||
}
|
||||
|
||||
// Persist "watched" progress for the episode that Trakt reported (only if not recently removed)
|
||||
|
|
@ -445,8 +459,12 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
} catch (err) {
|
||||
// Continue with other shows even if one fails
|
||||
}
|
||||
});
|
||||
await Promise.allSettled(perShowPromises);
|
||||
}
|
||||
|
||||
// Merge all Trakt items as a single batch to ensure proper sorting
|
||||
if (traktBatch.length > 0) {
|
||||
await mergeBatchIntoState(traktBatch);
|
||||
}
|
||||
} catch (err) {
|
||||
// Continue even if Trakt history merge fails
|
||||
}
|
||||
|
|
@ -475,7 +493,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
appState.current.match(/inactive|background/) &&
|
||||
nextAppState === 'active'
|
||||
) {
|
||||
// App has come to the foreground - trigger a background refresh
|
||||
// App has come to the foreground - force Trakt sync by resetting cooldown
|
||||
lastTraktSyncRef.current = 0; // Reset cooldown to allow immediate Trakt sync
|
||||
loadContinueWatching(true);
|
||||
}
|
||||
appState.current = nextAppState;
|
||||
|
|
@ -493,9 +512,10 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
clearTimeout(refreshTimerRef.current);
|
||||
}
|
||||
refreshTimerRef.current = setTimeout(() => {
|
||||
// Trigger a background refresh
|
||||
// Only trigger background refresh for local progress updates, not Trakt sync
|
||||
// This prevents the feedback loop where Trakt sync triggers more progress updates
|
||||
loadContinueWatching(true);
|
||||
}, 800); // Shorter debounce for snappier UI without battery impact
|
||||
}, 2000); // Increased debounce to reduce frequency
|
||||
};
|
||||
|
||||
// Try to set up a custom event listener or use a timer as fallback
|
||||
|
|
@ -543,7 +563,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
// Expose the refresh function via the ref
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
refresh: async () => {
|
||||
// Allow manual refresh to show loading indicator
|
||||
// Manual refresh bypasses Trakt cooldown to get fresh data
|
||||
lastTraktSyncRef.current = 0; // Reset cooldown for manual refresh
|
||||
await loadContinueWatching(false);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1187,6 +1187,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
if (isMounted.current) {
|
||||
setSeekTime(null);
|
||||
isSeeking.current = false;
|
||||
|
||||
// IMMEDIATE SYNC: Update Trakt progress immediately after seeking
|
||||
if (duration > 0 && data?.currentTime !== undefined) {
|
||||
traktAutosync.handleProgressUpdate(data.currentTime, duration, true); // force=true for immediate sync
|
||||
}
|
||||
|
||||
// Resume playback on iOS if we paused for seeking
|
||||
if (Platform.OS === 'ios') {
|
||||
const shouldResume = wasPlayingBeforeDragRef.current || iosWasPausedDuringSeekRef.current === false || isDragging;
|
||||
|
|
|
|||
|
|
@ -866,6 +866,9 @@ const KSPlayerCore: React.FC = () => {
|
|||
if (DEBUG_MODE) {
|
||||
logger.log(`[VideoPlayer] KSPlayer seek completed to ${timeInSeconds.toFixed(2)}s`);
|
||||
}
|
||||
|
||||
// IMMEDIATE SYNC: Update Trakt progress immediately after seeking
|
||||
traktAutosync.handleProgressUpdate(timeInSeconds, duration, true); // force=true for immediate sync
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
const hasStartedWatching = useRef(false);
|
||||
const hasStopped = useRef(false); // New: Track if we've already stopped for this session
|
||||
const isSessionComplete = useRef(false); // New: Track if session is completely finished (scrobbled)
|
||||
const isUnmounted = useRef(false); // New: Track if component has unmounted
|
||||
const lastSyncTime = useRef(0);
|
||||
const lastSyncProgress = useRef(0);
|
||||
const sessionKey = useRef<string | null>(null);
|
||||
|
|
@ -43,21 +44,23 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
|
||||
// Generate a unique session key for this content instance
|
||||
useEffect(() => {
|
||||
const contentKey = options.type === 'movie'
|
||||
const contentKey = options.type === 'movie'
|
||||
? `movie:${options.imdbId}`
|
||||
: `episode:${options.imdbId}:${options.season}:${options.episode}`;
|
||||
: `episode:${options.showImdbId || options.imdbId}:${options.season}:${options.episode}`;
|
||||
sessionKey.current = `${contentKey}:${Date.now()}`;
|
||||
|
||||
// Reset all session state for new content
|
||||
hasStartedWatching.current = false;
|
||||
hasStopped.current = false;
|
||||
isSessionComplete.current = false;
|
||||
isUnmounted.current = false; // Reset unmount flag for new mount
|
||||
lastStopCall.current = 0;
|
||||
|
||||
logger.log(`[TraktAutosync] Session started for: ${sessionKey.current}`);
|
||||
|
||||
return () => {
|
||||
unmountCount.current++;
|
||||
isUnmounted.current = true; // Mark as unmounted to prevent post-unmount operations
|
||||
logger.log(`[TraktAutosync] Component unmount #${unmountCount.current} for: ${sessionKey.current}`);
|
||||
};
|
||||
}, [options.imdbId, options.season, options.episode, options.type]);
|
||||
|
|
@ -104,8 +107,10 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
|
||||
// Start watching (scrobble start)
|
||||
const handlePlaybackStart = useCallback(async (currentTime: number, duration: number) => {
|
||||
if (isUnmounted.current) return; // Prevent execution after component unmount
|
||||
|
||||
logger.log(`[TraktAutosync] handlePlaybackStart called: time=${currentTime}, duration=${duration}, authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}, alreadyStarted=${hasStartedWatching.current}, alreadyStopped=${hasStopped.current}, sessionComplete=${isSessionComplete.current}, session=${sessionKey.current}`);
|
||||
|
||||
|
||||
if (!isAuthenticated || !autosyncSettings.enabled) {
|
||||
logger.log(`[TraktAutosync] Skipping handlePlaybackStart: authenticated=${isAuthenticated}, enabled=${autosyncSettings.enabled}`);
|
||||
return;
|
||||
|
|
@ -156,6 +161,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
duration: number,
|
||||
force: boolean = false
|
||||
) => {
|
||||
if (isUnmounted.current) return; // Prevent execution after component unmount
|
||||
|
||||
if (!isAuthenticated || !autosyncSettings.enabled || duration <= 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -231,6 +238,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
|
||||
// Handle playback end/pause
|
||||
const handlePlaybackEnd = useCallback(async (currentTime: number, duration: number, reason: 'ended' | 'unmount' | 'user_close' = 'ended') => {
|
||||
if (isUnmounted.current) return; // Prevent execution after component unmount
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Removed excessive logging for handlePlaybackEnd calls
|
||||
|
|
@ -339,12 +348,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
return;
|
||||
}
|
||||
|
||||
// For natural end events, ensure we cross Trakt's 80% scrobble threshold reliably.
|
||||
// If close to the end, boost to 95% to avoid rounding issues.
|
||||
if (reason === 'ended' && progressPercent < 95) {
|
||||
logger.log(`[TraktAutosync] Natural end detected at ${progressPercent.toFixed(1)}%, boosting to 95% for scrobble`);
|
||||
progressPercent = 95;
|
||||
}
|
||||
// Note: No longer boosting progress since Trakt API handles 80% threshold correctly
|
||||
|
||||
// Mark stop attempt and update timestamp
|
||||
lastStopCall.current = now;
|
||||
|
|
@ -368,8 +372,8 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
currentTime
|
||||
);
|
||||
|
||||
// Mark session as complete if high progress (scrobbled)
|
||||
if (progressPercent >= 80) {
|
||||
// Mark session as complete if >= user completion threshold
|
||||
if (progressPercent >= autosyncSettings.completionThreshold) {
|
||||
isSessionComplete.current = true;
|
||||
logger.log(`[TraktAutosync] Session marked as complete (scrobbled) at ${progressPercent.toFixed(1)}%`);
|
||||
|
||||
|
|
@ -420,6 +424,7 @@ export function useTraktAutosync(options: TraktAutosyncOptions) {
|
|||
hasStartedWatching.current = false;
|
||||
hasStopped.current = false;
|
||||
isSessionComplete.current = false;
|
||||
isUnmounted.current = false;
|
||||
lastSyncTime.current = 0;
|
||||
lastSyncProgress.current = 0;
|
||||
unmountCount.current = 0;
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ export class TraktService {
|
|||
|
||||
// Rate limiting - Optimized for real-time scrobbling
|
||||
private lastApiCall: number = 0;
|
||||
private readonly MIN_API_INTERVAL = 1000; // Reduced from 3000ms to 1000ms for real-time updates
|
||||
private readonly MIN_API_INTERVAL = 500; // Reduced to 500ms for faster updates
|
||||
private requestQueue: Array<() => Promise<any>> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
|
||||
|
|
@ -1740,8 +1740,8 @@ export class TraktService {
|
|||
const watchingKey = this.getWatchingKey(contentData);
|
||||
const lastSync = this.lastSyncTimes.get(watchingKey) || 0;
|
||||
|
||||
// IMMEDIATE SYNC: Remove debouncing for instant sync, only prevent truly rapid calls (< 300ms)
|
||||
if (!force && (now - lastSync) < 300) {
|
||||
// IMMEDIATE SYNC: Remove debouncing for instant sync, only prevent truly rapid calls (< 100ms)
|
||||
if (!force && (now - lastSync) < 100) {
|
||||
return true; // Skip this sync, but return success
|
||||
}
|
||||
|
||||
|
|
@ -1791,13 +1791,12 @@ export class TraktService {
|
|||
// Record this stop attempt
|
||||
this.lastStopCalls.set(watchingKey, now);
|
||||
|
||||
// Respect higher user threshold by pausing below effective threshold
|
||||
const effectiveThreshold = Math.max(80, this.completionThreshold);
|
||||
// Use pause if below user threshold, stop only when ready to scrobble
|
||||
const useStop = progress >= this.completionThreshold;
|
||||
const result = await this.queueRequest(async () => {
|
||||
if (progress < effectiveThreshold) {
|
||||
return await this.pauseWatching(contentData, progress);
|
||||
}
|
||||
return await this.stopWatching(contentData, progress);
|
||||
return useStop
|
||||
? await this.stopWatching(contentData, progress)
|
||||
: await this.pauseWatching(contentData, progress);
|
||||
});
|
||||
|
||||
if (result) {
|
||||
|
|
@ -1810,7 +1809,8 @@ export class TraktService {
|
|||
logger.log(`[TraktService] Marked as scrobbled to prevent restarts: ${watchingKey}`);
|
||||
}
|
||||
|
||||
const action = progress >= effectiveThreshold ? 'scrobbled' : 'paused';
|
||||
// Action reflects actual endpoint used based on user threshold
|
||||
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
|
||||
logger.log(`[TraktService] Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
|
||||
|
||||
return true;
|
||||
|
|
@ -1889,11 +1889,11 @@ export class TraktService {
|
|||
|
||||
this.lastStopCalls.set(watchingKey, Date.now());
|
||||
|
||||
// BYPASS QUEUE: Respect higher user threshold by pausing below effective threshold
|
||||
const effectiveThreshold = Math.max(80, this.completionThreshold);
|
||||
const result = progress < effectiveThreshold
|
||||
? await this.pauseWatching(contentData, progress)
|
||||
: await this.stopWatching(contentData, progress);
|
||||
// BYPASS QUEUE: Use pause if below user threshold, stop only when ready to scrobble
|
||||
const useStop = progress >= this.completionThreshold;
|
||||
const result = useStop
|
||||
? await this.stopWatching(contentData, progress)
|
||||
: await this.pauseWatching(contentData, progress);
|
||||
|
||||
if (result) {
|
||||
this.currentlyWatching.delete(watchingKey);
|
||||
|
|
@ -1904,7 +1904,8 @@ export class TraktService {
|
|||
this.scrobbledTimestamps.set(watchingKey, Date.now());
|
||||
}
|
||||
|
||||
const action = progress >= effectiveThreshold ? 'scrobbled' : 'paused';
|
||||
// Action reflects actual endpoint used based on user threshold
|
||||
const action = progress >= this.completionThreshold ? 'scrobbled' : 'paused';
|
||||
logger.log(`[TraktService] IMMEDIATE: Stopped watching ${contentData.type}: ${contentData.title} (${progress.toFixed(1)}% - ${action})`);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
514
trakt/docs.md
Normal file
514
trakt/docs.md
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
Scrobble / Start / Start watching in a media center POSThttps://api.trakt.tv/scrobble/startRequestStart watching a movie by sending a standard movie object.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
},
|
||||
"progress": 1.25
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 0,
|
||||
"action": "start",
|
||||
"progress": 1.25,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestStart watching an episode by sending a standard episode object.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"episode": {
|
||||
"ids": {
|
||||
"trakt": 16
|
||||
}
|
||||
},
|
||||
"progress": 10
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 0,
|
||||
"action": "start",
|
||||
"progress": 10,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"title": "Pilot",
|
||||
"ids": {
|
||||
"trakt": 16,
|
||||
"tvdb": 349232,
|
||||
"imdb": "tt0959621",
|
||||
"tmdb": 62085
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"slug": "breaking-bad",
|
||||
"tvdb": 81189,
|
||||
"imdb": "tt0903747",
|
||||
"tmdb": 1396
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestStart watching an episode if you don't have episode ids, but have show info. Send show and episode objects.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"tvdb": 81189
|
||||
}
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1
|
||||
},
|
||||
"progress": 10
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 0,
|
||||
"action": "start",
|
||||
"progress": 10,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"title": "Pilot",
|
||||
"ids": {
|
||||
"trakt": 16,
|
||||
"tvdb": 349232,
|
||||
"imdb": "tt0959621",
|
||||
"tmdb": 62085
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"slug": "breaking-bad",
|
||||
"tvdb": 81189,
|
||||
"imdb": "tt0903747",
|
||||
"tmdb": 1396
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestStart watching an episode using absolute numbering (useful for Anime and Donghua). Send show and episode objects.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"show": {
|
||||
"title": "One Piece",
|
||||
"year": 1999,
|
||||
"ids": {
|
||||
"trakt": 37696
|
||||
}
|
||||
},
|
||||
"episode": {
|
||||
"number_abs": 164
|
||||
},
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"progress": 10
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 0,
|
||||
"action": "start",
|
||||
"progress": 10,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 9,
|
||||
"number": 21,
|
||||
"title": "Light the Fire of Shandia! Wiper the Warrior",
|
||||
"ids": {
|
||||
"trakt": 856373,
|
||||
"tvdb": 362082,
|
||||
"imdb": null,
|
||||
"tmdb": null
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "One Piece",
|
||||
"year": 1999,
|
||||
"ids": {
|
||||
"trakt": 37696,
|
||||
"slug": "one-piece",
|
||||
"tvdb": 81797,
|
||||
"imdb": "tt0388629",
|
||||
"tmdb": 37854
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Scrobble / Pause / Pause watching in a media center POSThttps://api.trakt.tv/scrobble/pauseRequest
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
},
|
||||
"progress": 75
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "pause",
|
||||
"progress": 75,
|
||||
"sharing": {
|
||||
"twitter": false,
|
||||
"mastodon": false,
|
||||
"tumblr": false
|
||||
},
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BODY
|
||||
{
|
||||
"id": 3373536622,
|
||||
"action": "scrobble",
|
||||
"progress": 99.9,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestScrobble an episode by sending a standard episode object.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"episode": {
|
||||
"ids": {
|
||||
"trakt": 16
|
||||
}
|
||||
},
|
||||
"progress": 85
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 3373536623,
|
||||
"action": "scrobble",
|
||||
"progress": 85,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"title": "Pilot",
|
||||
"ids": {
|
||||
"trakt": 16,
|
||||
"tvdb": 349232,
|
||||
"imdb": "tt0959621",
|
||||
"tmdb": 62085
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"slug": "breaking-bad",
|
||||
"tvdb": 81189,
|
||||
"imdb": "tt0903747",
|
||||
"tmdb": 1396
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestScrobble an episode if you don't have episode ids, but have show info. Send show and episode objects.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"tvdb": 81189
|
||||
}
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1
|
||||
},
|
||||
"progress": 85
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 3373536623,
|
||||
"action": "scrobble",
|
||||
"progress": 85,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"title": "Pilot",
|
||||
"ids": {
|
||||
"trakt": 16,
|
||||
"tvdb": 349232,
|
||||
"imdb": "tt0959621",
|
||||
"tmdb": 62085
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "Breaking Bad",
|
||||
"year": 2008,
|
||||
"ids": {
|
||||
"trakt": 1,
|
||||
"slug": "breaking-bad",
|
||||
"tvdb": 81189,
|
||||
"imdb": "tt0903747",
|
||||
"tmdb": 1396
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestScrobble an episode using absolute numbering (useful for Anime and Donghua). Send show and episode objects.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"show": {
|
||||
"title": "One Piece",
|
||||
"year": 1999,
|
||||
"ids": {
|
||||
"trakt": 37696
|
||||
}
|
||||
},
|
||||
"episode": {
|
||||
"number_abs": 164
|
||||
},
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"progress": 90
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 3373536624,
|
||||
"action": "scrobble",
|
||||
"progress": 90,
|
||||
"sharing": {
|
||||
"twitter": true,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"episode": {
|
||||
"season": 9,
|
||||
"number": 21,
|
||||
"title": "Light the Fire of Shandia! Wiper the Warrior",
|
||||
"ids": {
|
||||
"trakt": 856373,
|
||||
"tvdb": 362082,
|
||||
"imdb": null,
|
||||
"tmdb": null
|
||||
}
|
||||
},
|
||||
"show": {
|
||||
"title": "One Piece",
|
||||
"year": 1999,
|
||||
"ids": {
|
||||
"trakt": 37696,
|
||||
"slug": "one-piece",
|
||||
"tvdb": 81797,
|
||||
"imdb": "tt0388629",
|
||||
"tmdb": 37854
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestIf the progress is < 80%, the video will be treated a a pause and the playback position will be saved.
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
Authorization:Bearer [access_token]
|
||||
trakt-api-version:2
|
||||
trakt-api-key:[client_id]
|
||||
BODY
|
||||
{
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
},
|
||||
"progress": 75
|
||||
}
|
||||
Response
|
||||
201
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "pause",
|
||||
"progress": 75,
|
||||
"sharing": {
|
||||
"twitter": false,
|
||||
"mastodon": true,
|
||||
"tumblr": false
|
||||
},
|
||||
"movie": {
|
||||
"title": "Guardians of the Galaxy",
|
||||
"year": 2014,
|
||||
"ids": {
|
||||
"trakt": 28,
|
||||
"slug": "guardians-of-the-galaxy-2014",
|
||||
"imdb": "tt2015381",
|
||||
"tmdb": 118340
|
||||
}
|
||||
}
|
||||
}
|
||||
ResponseThe same item was recently scrobbled.
|
||||
409
|
||||
HEADERS
|
||||
Content-Type:application/json
|
||||
BODY
|
||||
{
|
||||
"watched_at": "2014-10-15T22:21:29.000Z",
|
||||
"expires_at": "2014-10-15T23:21:29.000Z"
|
||||
}
|
||||
Loading…
Reference in a new issue