This commit is contained in:
Pas 2025-05-17 19:09:27 -06:00
parent e37eeae3fe
commit 0cacd84da7
7 changed files with 3714 additions and 1344 deletions

View file

@ -8,7 +8,7 @@ export async function sendPlayerStatus({
roomCode,
isHost,
content,
player
player,
}: {
userId: string;
roomCode: string;
@ -102,7 +102,6 @@ export async function getRoomStatuses(roomCode: string) {
*/
export function ModifiedWebhookReporter() {
// Example replacing the Discord webhook code
/*
useEffect(() => {
// Skip if watch party is not enabled or no status
@ -193,4 +192,4 @@ export function ModifiedWebhookReporter() {
isHost,
]);
*/
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,26 +12,28 @@ Send a player status update.
```json
{
"userId": "user123", // Required: User identifier
"roomCode": "room456", // Required: Room code
"isHost": true, // Optional: Whether the user is the host
"content": { // Optional: Content information
"userId": "user123", // Required: User identifier
"roomCode": "room456", // Required: Room code
"isHost": true, // Optional: Whether the user is the host
"content": {
// Optional: Content information
"title": "Movie Title",
"type": "Movie", // "Movie", "TV Show", etc.
"tmdbId": 12345, // Optional: TMDB ID for the content
"seasonNumber": 1, // Optional: Season number (for TV shows)
"episodeNumber": 3 // Optional: Episode number (for TV shows)
"type": "Movie", // "Movie", "TV Show", etc.
"tmdbId": 12345, // Optional: TMDB ID for the content
"seasonNumber": 1, // Optional: Season number (for TV shows)
"episodeNumber": 3 // Optional: Episode number (for TV shows)
},
"player": { // Optional: Player state
"player": {
// Optional: Player state
"isPlaying": true,
"isPaused": false,
"isLoading": false,
"hasPlayedOnce": true,
"time": 120.5, // Current playback position in seconds
"duration": 3600, // Total content duration in seconds
"volume": 0.8, // Volume level (0-1)
"playbackRate": 1, // Playback speed
"buffered": 180 // Buffered seconds
"time": 120.5, // Current playback position in seconds
"duration": 3600, // Total content duration in seconds
"volume": 0.8, // Volume level (0-1)
"playbackRate": 1, // Playback speed
"buffered": 180 // Buffered seconds
}
}
```
@ -41,7 +43,7 @@ Send a player status update.
```json
{
"success": true,
"timestamp": 1625097600000 // The timestamp assigned to this status update
"timestamp": 1625097600000 // The timestamp assigned to this status update
}
```
@ -50,6 +52,7 @@ Send a player status update.
Get status updates for a specific user in a specific room.
**Query Parameters:**
- `userId`: User identifier
- `roomCode`: Room code
@ -94,6 +97,7 @@ Get status updates for a specific user in a specific room.
Get status updates for all users in a specific room.
**Query Parameters:**
- `roomCode`: Room code
**Response:**
@ -120,7 +124,7 @@ Get status updates for all users in a specific room.
"isLoading": false,
"hasPlayedOnce": true,
"time": 120.5,
"duration": 3600,
"duration": 3600,
"volume": 0.8,
"playbackRate": 1,
"buffered": 180
@ -140,4 +144,4 @@ Get status updates for all users in a specific room.
- Status data is automatically cleaned up if it's older than 1 minute
- The system keeps a maximum of 5 status updates per user per room
- Timestamps are in milliseconds since epoch (Unix timestamp)
- Timestamps are in milliseconds since epoch (Unix timestamp)

View file

@ -1,56 +1,56 @@
import { defineEventHandler, getQuery, createError } from 'h3';
import { playerStatusStore, CLEANUP_INTERVAL } from '~/utils/playerStatus';
export default defineEventHandler((event) => {
export default defineEventHandler(event => {
const query = getQuery(event);
const userId = query.userId as string;
const roomCode = query.roomCode as string;
// If roomCode is provided but no userId, return all statuses for that room
if (roomCode && !userId) {
const cutoffTime = Date.now() - CLEANUP_INTERVAL;
const roomStatuses: Record<string, any[]> = {};
for (const [key, statuses] of playerStatusStore.entries()) {
if (key.includes(`:${roomCode}`)) {
const userId = key.split(':')[0];
const recentStatuses = statuses.filter(status => status.timestamp >= cutoffTime);
if (recentStatuses.length > 0) {
roomStatuses[userId] = recentStatuses;
}
}
}
return {
roomCode,
users: roomStatuses
users: roomStatuses,
};
}
// If both userId and roomCode are provided, return status for that user in that room
if (userId && roomCode) {
const key = `${userId}:${roomCode}`;
const statuses = playerStatusStore.get(key) || [];
// Remove statuses older than 15 minutes
const cutoffTime = Date.now() - CLEANUP_INTERVAL;
const recentStatuses = statuses.filter(status => status.timestamp >= cutoffTime);
if (recentStatuses.length !== statuses.length) {
playerStatusStore.set(key, recentStatuses);
}
return {
userId,
roomCode,
statuses: recentStatuses
statuses: recentStatuses,
};
}
// If neither is provided, return error
throw createError({
statusCode: 400,
statusMessage: 'Missing required query parameters: roomCode and/or userId'
statusMessage: 'Missing required query parameters: roomCode and/or userId',
});
});
});

View file

@ -1,16 +1,16 @@
import { defineEventHandler, readBody, createError } from 'h3';
import { playerStatusStore, PlayerStatus } from '~/utils/playerStatus';
export default defineEventHandler(async (event) => {
export default defineEventHandler(async event => {
const body = await readBody(event);
if (!body || !body.userId || !body.roomCode) {
throw createError({
statusCode: 400,
statusMessage: 'Missing required fields: userId, roomCode'
statusMessage: 'Missing required fields: userId, roomCode',
});
}
const status: PlayerStatus = {
userId: body.userId,
roomCode: body.roomCode,
@ -22,7 +22,7 @@ export default defineEventHandler(async (event) => {
seasonId: body.content?.seasonId,
episodeId: body.content?.episodeId,
seasonNumber: body.content?.seasonNumber,
episodeNumber: body.content?.episodeNumber
episodeNumber: body.content?.episodeNumber,
},
player: {
isPlaying: body.player?.isPlaying || false,
@ -35,19 +35,19 @@ export default defineEventHandler(async (event) => {
playbackRate: body.player?.playbackRate || 1,
buffered: body.player?.buffered || 0,
},
timestamp: Date.now()
timestamp: Date.now(),
};
const key = `${status.userId}:${status.roomCode}`;
const existingStatuses = playerStatusStore.get(key) || [];
// Add new status and keep only the last 5 statuses
existingStatuses.push(status);
if (existingStatuses.length > 5) {
existingStatuses.shift();
}
playerStatusStore.set(key, existingStatuses);
return { success: true, timestamp: status.timestamp };
});
});

View file

@ -15,8 +15,14 @@ const progressMetaSchema = z.object({
const progressItemSchema = z.object({
meta: progressMetaSchema,
tmdbId: z.string().transform(val => val || randomUUID()),
duration: z.number().min(0).transform(n => Math.round(n)),
watched: z.number().min(0).transform(n => Math.round(n)),
duration: z
.number()
.min(0)
.transform(n => Math.round(n)),
watched: z
.number()
.min(0)
.transform(n => Math.round(n)),
seasonId: z.string().optional(),
episodeId: z.string().optional(),
seasonNumber: z.number().optional(),

View file

@ -36,10 +36,10 @@ export const CLEANUP_INTERVAL = 1 * 60 * 1000;
// Clean up old status entries
function cleanupOldStatuses() {
const cutoffTime = Date.now() - CLEANUP_INTERVAL;
for (const [key, statuses] of playerStatusStore.entries()) {
const filteredStatuses = statuses.filter(status => status.timestamp >= cutoffTime);
if (filteredStatuses.length === 0) {
playerStatusStore.delete(key);
} else {
@ -49,4 +49,4 @@ function cleanupOldStatuses() {
}
// Schedule cleanup every 1 minute
setInterval(cleanupOldStatuses, 1 * 60 * 1000);
setInterval(cleanupOldStatuses, 1 * 60 * 1000);