|
import { get, writable } from 'svelte/store'; |
|
|
|
export enum LCMLiveStatus { |
|
CONNECTED = 'connected', |
|
DISCONNECTED = 'disconnected', |
|
CONNECTING = 'connecting', |
|
WAIT = 'wait', |
|
SEND_FRAME = 'send_frame', |
|
TIMEOUT = 'timeout', |
|
ERROR = 'error' |
|
} |
|
|
|
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED; |
|
|
|
export const lcmLiveStatus = writable<LCMLiveStatus>(initStatus); |
|
export const streamId = writable<string | null>(null); |
|
|
|
|
|
let websocket: WebSocket | null = null; |
|
|
|
let intentionalClosure = false; |
|
|
|
|
|
if (typeof window !== 'undefined') { |
|
window.addEventListener('beforeunload', () => { |
|
|
|
intentionalClosure = true; |
|
|
|
if (websocket && websocket.readyState === WebSocket.OPEN) { |
|
websocket.close(1000, 'Page unload'); |
|
} |
|
}); |
|
} |
|
export const lcmLiveActions = { |
|
async start(getSreamdata: () => any[]) { |
|
return new Promise((resolve, reject) => { |
|
try { |
|
|
|
lcmLiveStatus.set(LCMLiveStatus.CONNECTING); |
|
|
|
const userId = crypto.randomUUID(); |
|
const websocketURL = `${ |
|
window.location.protocol === 'https:' ? 'wss' : 'ws' |
|
}:${window.location.host}/api/ws/${userId}`; |
|
|
|
|
|
if (websocket && websocket.readyState !== WebSocket.CLOSED) { |
|
websocket.close(); |
|
} |
|
|
|
websocket = new WebSocket(websocketURL); |
|
|
|
|
|
const connectionTimeout = setTimeout(() => { |
|
if (websocket && websocket.readyState !== WebSocket.OPEN) { |
|
console.error('WebSocket connection timeout'); |
|
lcmLiveStatus.set(LCMLiveStatus.ERROR); |
|
streamId.set(null); |
|
reject(new Error('Connection timeout. Please try again.')); |
|
websocket.close(); |
|
} |
|
}, 10000); |
|
|
|
websocket.onopen = () => { |
|
clearTimeout(connectionTimeout); |
|
console.log('Connected to websocket'); |
|
}; |
|
|
|
websocket.onclose = (event) => { |
|
clearTimeout(connectionTimeout); |
|
console.log(`Disconnected from websocket: ${event.code} ${event.reason}`); |
|
|
|
|
|
if (get(lcmLiveStatus) !== LCMLiveStatus.ERROR) { |
|
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED); |
|
} |
|
|
|
|
|
if (event.code === 1006 && streamId.get() === null) { |
|
reject(new Error('Cannot connect to server. Please try again later.')); |
|
} |
|
}; |
|
|
|
websocket.onerror = (err) => { |
|
clearTimeout(connectionTimeout); |
|
console.error('WebSocket error:', err); |
|
lcmLiveStatus.set(LCMLiveStatus.ERROR); |
|
streamId.set(null); |
|
reject(new Error('Connection error. Please try again.')); |
|
}; |
|
|
|
websocket.onmessage = (event) => { |
|
try { |
|
const data = JSON.parse(event.data); |
|
switch (data.status) { |
|
case 'connected': |
|
lcmLiveStatus.set(LCMLiveStatus.CONNECTED); |
|
streamId.set(userId); |
|
resolve({ status: 'connected', userId }); |
|
break; |
|
case 'send_frame': |
|
lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME); |
|
try { |
|
const streamData = getSreamdata(); |
|
|
|
this.send({ status: 'next_frame' }); |
|
for (const d of streamData) { |
|
this.send(d); |
|
} |
|
} catch (error) { |
|
console.error('Error sending frame data:', error); |
|
} |
|
break; |
|
case 'wait': |
|
lcmLiveStatus.set(LCMLiveStatus.WAIT); |
|
break; |
|
case 'timeout': |
|
console.log('Session timeout'); |
|
lcmLiveStatus.set(LCMLiveStatus.TIMEOUT); |
|
streamId.set(null); |
|
reject(new Error('Session timeout. Please restart.')); |
|
break; |
|
case 'error': |
|
console.error('Server error:', data.message); |
|
lcmLiveStatus.set(LCMLiveStatus.ERROR); |
|
streamId.set(null); |
|
reject(new Error(data.message || 'Server error occurred')); |
|
break; |
|
default: |
|
console.log('Unknown message status:', data.status); |
|
} |
|
} catch (error) { |
|
console.error('Error handling websocket message:', error); |
|
} |
|
}; |
|
} catch (err) { |
|
console.error('Error initializing websocket:', err); |
|
lcmLiveStatus.set(LCMLiveStatus.ERROR); |
|
streamId.set(null); |
|
reject(err); |
|
} |
|
}); |
|
}, |
|
send(data: Blob | { [key: string]: any }) { |
|
try { |
|
if (websocket && websocket.readyState === WebSocket.OPEN) { |
|
if (data instanceof Blob) { |
|
websocket.send(data); |
|
} else { |
|
websocket.send(JSON.stringify(data)); |
|
} |
|
} else { |
|
const readyStateText = websocket |
|
? ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][websocket.readyState] |
|
: 'null'; |
|
console.warn(`WebSocket not ready for sending: ${readyStateText}`); |
|
|
|
|
|
if (!websocket || websocket.readyState === WebSocket.CLOSED) { |
|
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED); |
|
streamId.set(null); |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Error sending data through WebSocket:', error); |
|
|
|
this.stop(); |
|
} |
|
}, |
|
|
|
async reconnect(getSreamdata: () => any[]) { |
|
try { |
|
await this.stop(); |
|
|
|
await new Promise((resolve) => setTimeout(resolve, 500)); |
|
return await this.start(getSreamdata); |
|
} catch (error) { |
|
console.error('Reconnection failed:', error); |
|
throw error; |
|
} |
|
}, |
|
|
|
async stop() { |
|
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED); |
|
try { |
|
if (websocket) { |
|
|
|
if (websocket.readyState !== WebSocket.CLOSED) { |
|
|
|
websocket.onclose = () => { |
|
console.log('WebSocket closed cleanly during stop()'); |
|
}; |
|
|
|
|
|
websocket.onerror = () => {}; |
|
|
|
websocket.close(1000, 'Client initiated disconnect'); |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Error during WebSocket closure:', error); |
|
} finally { |
|
|
|
websocket = null; |
|
streamId.set(null); |
|
} |
|
} |
|
}; |
|
|