Use websocket instead of overriding fetch for monitor play state changes

This commit is contained in:
HighImKevin committed 2025-11-05 18:03:48 -05:00
1 parent d2356b15d3
commit f3957d097c
1 file changed
+64 -48
+64 -48
View File
@@ -23,7 +23,6 @@ In the Custom Tabs plugin, add a new tab with the following HTML content:
const localStorageCache = new window.LocalStorageCache(); const localStorageCache = new window.LocalStorageCache();
// Playback monitoring for watchlist cleanup // Playback monitoring for watchlist cleanup
let originalFetch = window.fetch;
let playbackMonitorInitialized = false; let playbackMonitorInitialized = false;
// Data optimization functions for localStorage storage // Data optimization functions for localStorage storage
@@ -3212,64 +3211,81 @@ In the Custom Tabs plugin, add a new tab with the following HTML content:
return; return;
} }
// Override fetch to intercept playback stopped and watch status update requests // Use WebSocket to listen for UserDataChanged messages instead of intercepting fetch
window.fetch = async function(...args) { // This is more reliable and doesn't interfere with fetch requests
const response = await originalFetch.apply(this, args); function setupWebSocketMonitoring() {
try {
// Make sure socket is open
if (window.ApiClient && typeof window.ApiClient.ensureWebSocket === 'function') {
window.ApiClient.ensureWebSocket();
}
// Check if this is a playback stopped request // Grab the actual socket
if (args[0] && typeof args[0] === 'string' && args[0].includes('/Sessions/Playing/Stopped')) { const socket = (window.ApiClient && (window.ApiClient.webSocket || window.ApiClient._webSocket)) || null;
try {
// Clone the response to avoid consuming it
const clonedResponse = response.clone();
// Get the request body if it exists if (socket) {
if (args[1] && args[1].body) { // Store original handler if it exists
const body = args[1].body; const originalHandler = socket.onmessage;
let payload;
// Handle different body types // Hook into onmessage
if (typeof body === 'string') { socket.onmessage = function(event) {
payload = JSON.parse(body); try {
} else if (body instanceof FormData) { // Parse the WebSocket message data
// FormData - extract JSON if available // The event might have event.Data or event.data depending on implementation
const jsonData = body.get('data'); const messageData = event.Data || event.data;
if (jsonData) { if (!messageData) {
payload = JSON.parse(jsonData); // If no data, just pass through to original handler
if (originalHandler) originalHandler.call(this, event);
return;
} }
} else {
payload = body; const data = typeof messageData === 'string' ? JSON.parse(messageData) : messageData;
// Check if this is a UserDataChanged message
if (data.MessageType === 'UserDataChanged' && data.Data && data.Data.UserDataList && data.Data.UserDataList.length > 0) {
const userData = data.Data.UserDataList[0];
if (userData.ItemId) {
LOG(`Detected UserDataChanged for item: ${userData.ItemId}, Played: ${userData.Played}`);
// Handle the watched status change (works for both played and unplayed)
handleItemWatchedStatusChange(userData.ItemId).catch(err => {
ERR('Error handling UserDataChanged event:', err);
});
}
}
} catch (err) {
// Log parse errors but don't break the original handler
WARN('WebSocket message parse error:', err);
} }
if (payload && payload.ItemId) { // Pass it through so Jellyfin still works normally
await handlePlaybackStopped(payload.ItemId); if (originalHandler) {
originalHandler.call(this, event);
} }
} };
} catch (err) {
ERR('Error processing playback stopped request:', err); playbackMonitorInitialized = true;
LOG('✅ Playback and watch status monitoring initialized via WebSocket');
return true;
} else {
WARN('⚠️ No WebSocket found. Retrying in 1 second...');
return false;
} }
} catch (err) {
ERR('Error setting up WebSocket monitoring:', err);
return false;
} }
}
// Check if this is a watch status update request (PlayedItems) - both POST and DELETE // Try to set up immediately
if (args[0] && typeof args[0] === 'string' && args[0].includes('/PlayedItems/') && args[1] && (args[1].method === 'POST' || args[1].method === 'DELETE')) { if (!setupWebSocketMonitoring()) {
try { // If WebSocket isn't available yet, retry after a short delay
// Extract item ID from URL: /Users/{userId}/PlayedItems/{itemId} // This can happen if the page loads before the WebSocket connection is established
const urlMatch = args[0].match(/\/PlayedItems\/([^/?]+)/); setTimeout(() => {
if (urlMatch && urlMatch[1]) { if (!playbackMonitorInitialized) {
const itemId = urlMatch[1]; setupWebSocketMonitoring();
const method = args[1].method;
LOG(`Detected watch status ${method} request for item:`, itemId);
await handleWatchStatusUpdate(itemId);
}
} catch (err) {
ERR('Error processing watch status update request:', err);
} }
} }, 1000);
}
return response;
};
playbackMonitorInitialized = true;
LOG('Playback and watch status monitoring initialized');
} }
// Shared handler for when items' watched status changes (played or unplayed) // Shared handler for when items' watched status changes (played or unplayed)