cleanup
This commit is contained in:
@@ -140,7 +140,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
// Accept bare array or { options: [...] }
|
// Accept bare array or { options: [...] }
|
||||||
setTypeaheadOptions(Array.isArray(data) ? data : data.options || []);
|
setTypeaheadOptions(Array.isArray(data) ? data : data.options || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Typeahead: error', error);
|
|
||||||
setTypeaheadOptions([]);
|
setTypeaheadOptions([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -208,7 +207,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
const [liveDownloadRate, setLiveDownloadRate] = useState<number | null>(null); // Actual download speed
|
const [liveDownloadRate, setLiveDownloadRate] = useState<number | null>(null); // Actual download speed
|
||||||
const [actualPlayingLevel, setActualPlayingLevel] = useState<number>(-1); // Actual HLS.js level being played
|
const [actualPlayingLevel, setActualPlayingLevel] = useState<number>(-1); // Actual HLS.js level being played
|
||||||
|
|
||||||
// Basic mount diagnostics so we can see logs even if HLS never initializes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {};
|
return () => {};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -310,9 +308,8 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
import('hls.js').then(({ default: Hls }) => {
|
import('hls.js').then(({ default: Hls }) => {
|
||||||
// Double-check audio element still exists after async import
|
// Double-check audio element still exists after async import
|
||||||
if (!audioElement.current) return;
|
if (!audioElement.current) return;
|
||||||
|
|
||||||
if (!Hls.isSupported()) {
|
if (!Hls.isSupported()) {
|
||||||
console.error("HLS not supported");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,12 +417,9 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
let networkRecoveryTimeout: ReturnType<typeof setTimeout> | null = null;
|
let networkRecoveryTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
hls.on(Hls.Events.ERROR, (_event, data) => {
|
hls.on(Hls.Events.ERROR, (_event, data) => {
|
||||||
// Chromium sometimes stutters then fires a fatal error; recover aggressively
|
|
||||||
const details = data?.details || 'unknown';
|
const details = data?.details || 'unknown';
|
||||||
console.warn('[Radio] HLS error', data?.type, details, 'fatal:', data?.fatal);
|
|
||||||
|
|
||||||
if (!data.fatal) {
|
if (!data.fatal) {
|
||||||
// Try to keep playback alive on buffer stalls
|
|
||||||
if (details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
|
if (details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
|
||||||
try { hls.startLoad(); } catch (_) { /* ignore */ }
|
try { hls.startLoad(); } catch (_) { /* ignore */ }
|
||||||
}
|
}
|
||||||
@@ -434,7 +428,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR: {
|
case Hls.ErrorTypes.NETWORK_ERROR: {
|
||||||
console.warn('[Radio] network error, reinitializing stream');
|
|
||||||
hls.destroy();
|
hls.destroy();
|
||||||
hlsInstance.current = null;
|
hlsInstance.current = null;
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -443,11 +436,9 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR: {
|
case Hls.ErrorTypes.MEDIA_ERROR: {
|
||||||
console.warn('[Radio] media error, attempting recovery');
|
|
||||||
try {
|
try {
|
||||||
hls.recoverMediaError();
|
hls.recoverMediaError();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Radio] media recovery failed, restarting', err);
|
|
||||||
hls.destroy();
|
hls.destroy();
|
||||||
hlsInstance.current = null;
|
hlsInstance.current = null;
|
||||||
initializeStream(activeStationRef.current);
|
initializeStream(activeStationRef.current);
|
||||||
@@ -455,7 +446,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
console.error('[Radio] unrecoverable error, restarting');
|
|
||||||
hls.destroy();
|
hls.destroy();
|
||||||
hlsInstance.current = null;
|
hlsInstance.current = null;
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -536,7 +526,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
|
|
||||||
// Handle station changes: reset and start new stream
|
// Handle station changes: reset and start new stream
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.info('[Radio] station effect', activeStation);
|
|
||||||
// Batch all state resets to minimize re-renders
|
// Batch all state resets to minimize re-renders
|
||||||
// Use startTransition to mark this as a non-urgent update
|
// Use startTransition to mark this as a non-urgent update
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
@@ -562,7 +551,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
resetState();
|
resetState();
|
||||||
// Defer stream initialization to next frame to keep UI responsive
|
// Defer stream initialization to next frame to keep UI responsive
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
console.info('[Radio] initializeStream from station effect', activeStation);
|
|
||||||
initializeStream(activeStation);
|
initializeStream(activeStation);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -694,7 +682,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
|
|
||||||
// Check if connection appears dead
|
// Check if connection appears dead
|
||||||
if (ws.readyState !== WebSocket.OPEN || isStale) {
|
if (ws.readyState !== WebSocket.OPEN || isStale) {
|
||||||
console.warn('WebSocket connection appears stale, reconnecting...');
|
|
||||||
// Force close and let onclose handle reconnection
|
// Force close and let onclose handle reconnection
|
||||||
ws.close();
|
ws.close();
|
||||||
}
|
}
|
||||||
@@ -730,7 +717,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
}
|
}
|
||||||
// Ignore any other message types that don't contain track data
|
// Ignore any other message types that don't contain track data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing WebSocket message:', error);
|
// ignore malformed messages
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -753,9 +740,8 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
}, delay);
|
}, delay);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = function (error) {
|
ws.onerror = function () {
|
||||||
console.error('Radio WebSocket error:', error);
|
// Let onclose handle reconnection
|
||||||
// Don't set error state here - let onclose handle reconnection
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wsInstance.current = ws;
|
wsInstance.current = ws;
|
||||||
@@ -876,7 +862,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.error("Skip failed.");
|
toast.error("Skip failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error skipping track:", error);
|
// ignore errors
|
||||||
toast.error("Skip failed.");
|
toast.error("Skip failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -898,7 +884,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.error("Reshuffle failed.");
|
toast.error("Reshuffle failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reshuffling queue:", error);
|
// ignore errors
|
||||||
toast.error("Reshuffle failed.");
|
toast.error("Reshuffle failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -922,7 +908,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.error("Queue shift failed.");
|
toast.error("Queue shift failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error shifting queue:", error);
|
// ignore errors
|
||||||
toast.error("Queue shift failed.");
|
toast.error("Queue shift failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -951,7 +937,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error playing song immediately:", error);
|
// ignore errors
|
||||||
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
toast.update(toastId, { render: "Play Now failed.", type: "error", autoClose: 3000 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -980,7 +966,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error requesting song:", error);
|
// ignore errors
|
||||||
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
toast.update(toastId, { render: "Song request failed.", type: "error", autoClose: 3000 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1004,7 +990,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
toast.error("Remove from queue failed.");
|
toast.error("Remove from queue failed.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing from queue:", error);
|
// ignore errors
|
||||||
toast.error("Remove from queue failed.");
|
toast.error("Remove from queue failed.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1030,7 +1016,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
fetchQueue(); // Refresh to get correct state
|
fetchQueue(); // Refresh to get correct state
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reordering queue:", error);
|
// ignore errors
|
||||||
toast.error("Reorder failed.");
|
toast.error("Reorder failed.");
|
||||||
fetchQueue();
|
fetchQueue();
|
||||||
}
|
}
|
||||||
@@ -1114,7 +1100,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
setNextTrack(null);
|
setNextTrack(null);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching next track:", error);
|
// ignore errors
|
||||||
setNextTrack(null);
|
setNextTrack(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1125,7 +1111,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
const actualRows = rows ?? queueRowsRef.current;
|
const actualRows = rows ?? queueRowsRef.current;
|
||||||
const actualSearch = search ?? queueSearchRef.current;
|
const actualSearch = search ?? queueSearchRef.current;
|
||||||
const start = actualPage * actualRows;
|
const start = actualPage * actualRows;
|
||||||
// console.log("Fetching queue for station (ref):", activeStationRef.current);
|
//
|
||||||
try {
|
try {
|
||||||
const response = await authFetch(`${API_URL}/radio/get_queue`, {
|
const response = await authFetch(`${API_URL}/radio/get_queue`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -1145,19 +1131,19 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
: data.recordsTotal ?? 0
|
: data.recordsTotal ?? 0
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching queue:", error);
|
// ignore errors
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isQueueVisible) {
|
if (isQueueVisible) {
|
||||||
// console.log("Fetching queue for station:", activeStation);
|
//
|
||||||
fetchQueue(queuePage, queueRows, queueSearch);
|
fetchQueue(queuePage, queueRows, queueSearch);
|
||||||
}
|
}
|
||||||
}, [isQueueVisible, queuePage, queueRows, queueSearch, activeStation]);
|
}, [isQueueVisible, queuePage, queueRows, queueSearch, activeStation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log("Active station changed to:", activeStation);
|
//
|
||||||
if (isQueueVisible) {
|
if (isQueueVisible) {
|
||||||
fetchQueue(queuePage, queueRows, queueSearch);
|
fetchQueue(queuePage, queueRows, queueSearch);
|
||||||
}
|
}
|
||||||
@@ -1174,7 +1160,7 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isQueueVisible) {
|
if (isQueueVisible) {
|
||||||
// console.log("Track changed, refreshing queue for station:", activeStation);
|
//
|
||||||
fetchQueue(queuePage, queueRows, queueSearch);
|
fetchQueue(queuePage, queueRows, queueSearch);
|
||||||
}
|
}
|
||||||
}, [currentTrackUuid]);
|
}, [currentTrackUuid]);
|
||||||
@@ -1405,7 +1391,6 @@ export default function Player({ user }: PlayerProps) {
|
|||||||
await audio.play();
|
await audio.play();
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Playback failed, reinitializing stream:', err);
|
|
||||||
// Reinitialize stream on playback failure
|
// Reinitialize stream on playback failure
|
||||||
initializeStream(activeStation);
|
initializeStream(activeStation);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user