mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-20 22:32:50 +00:00
Receivers: Module refractoring for portability
This commit is contained in:
parent
37514dc814
commit
1622a9b752
10 changed files with 54 additions and 61 deletions
|
@ -43,16 +43,14 @@ export function setUiUpdateCallbacks(callbacks: any) {
|
||||||
|
|
||||||
export class ConnectionMonitor {
|
export class ConnectionMonitor {
|
||||||
private static logger: Logger;
|
private static logger: Logger;
|
||||||
private static initialized = false;
|
|
||||||
private static connectionPingTimeout = 2500;
|
private static connectionPingTimeout = 2500;
|
||||||
private static heartbeatRetries = new Map();
|
private static heartbeatRetries = new Map<string, number>();
|
||||||
private static backendConnections = new Map();
|
private static backendConnections = new Map<string, any>(); // is `ListenerService`, but cant import backend module in frontend
|
||||||
private static uiConnectUpdateTimeout = 100;
|
private static uiConnectUpdateTimeout = 100;
|
||||||
private static uiDisconnectUpdateTimeout = 2000; // Senders may reconnect, but generally need more time
|
private static uiDisconnectUpdateTimeout = 2000; // Senders may reconnect, but generally need more time
|
||||||
private static uiUpdateMap = new Map();
|
private static uiUpdateMap = new Map<string, any>(); // { event: string, uiUpdateCallback: () => void }
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!ConnectionMonitor.initialized) {
|
|
||||||
ConnectionMonitor.logger = new Logger('ConnectionMonitor', LoggerType.BACKEND);
|
ConnectionMonitor.logger = new Logger('ConnectionMonitor', LoggerType.BACKEND);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
@ -78,17 +76,14 @@ export class ConnectionMonitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, ConnectionMonitor.connectionPingTimeout);
|
}, ConnectionMonitor.connectionPingTimeout);
|
||||||
|
|
||||||
ConnectionMonitor.initialized = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static onPingPong(value: any, isWebsockets: boolean) {
|
public static onPingPong(sessionId: string, isWebsockets: boolean) {
|
||||||
ConnectionMonitor.logger.debug(`Received response from ${value.sessionId}`);
|
ConnectionMonitor.logger.debug(`Received response from ${sessionId}`);
|
||||||
|
|
||||||
// Websocket clients currently don't support ping-pong commands
|
// Websocket clients currently don't support ping-pong commands
|
||||||
if (!isWebsockets) {
|
if (!isWebsockets) {
|
||||||
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
|
ConnectionMonitor.heartbeatRetries.set(sessionId, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,17 +91,13 @@ export class ConnectionMonitor {
|
||||||
ConnectionMonitor.logger.info(`Device connected: ${JSON.stringify(value)}`);
|
ConnectionMonitor.logger.info(`Device connected: ${JSON.stringify(value)}`);
|
||||||
const idMapping = isWebsockets ? value.sessionId : value.data.address;
|
const idMapping = isWebsockets ? value.sessionId : value.data.address;
|
||||||
|
|
||||||
if (!ConnectionMonitor.uiUpdateMap.has(idMapping)) {
|
|
||||||
ConnectionMonitor.uiUpdateMap.set(idMapping, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isWebsockets) {
|
if (!isWebsockets) {
|
||||||
ConnectionMonitor.backendConnections.set(value.sessionId, listener);
|
ConnectionMonitor.backendConnections.set(value.sessionId, listener);
|
||||||
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
|
ConnectionMonitor.heartbeatRetries.set(value.sessionId, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Occasionally senders seem to instantaneously disconnect and reconnect, so suppress those ui updates
|
// Occasionally senders seem to instantaneously disconnect and reconnect, so suppress those ui updates
|
||||||
const senderUpdateQueue = ConnectionMonitor.uiUpdateMap.get(idMapping);
|
const senderUpdateQueue = ConnectionMonitor.uiUpdateMap.get(idMapping) ?? [];
|
||||||
senderUpdateQueue.push({ event: 'connect', uiUpdateCallback: uiUpdateCallback });
|
senderUpdateQueue.push({ event: 'connect', uiUpdateCallback: uiUpdateCallback });
|
||||||
ConnectionMonitor.uiUpdateMap.set(idMapping, senderUpdateQueue);
|
ConnectionMonitor.uiUpdateMap.set(idMapping, senderUpdateQueue);
|
||||||
|
|
||||||
|
@ -115,7 +106,7 @@ export class ConnectionMonitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static onDisconnect(listener: any, value: any, isWebsockets: boolean, uiUpdateCallback: any) {
|
public static onDisconnect(value: any, isWebsockets: boolean, uiUpdateCallback: any) {
|
||||||
ConnectionMonitor.logger.info(`Device disconnected: ${JSON.stringify(value)}`);
|
ConnectionMonitor.logger.info(`Device disconnected: ${JSON.stringify(value)}`);
|
||||||
|
|
||||||
if (!isWebsockets) {
|
if (!isWebsockets) {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import mdns from 'modules/@futo/mdns-js';
|
||||||
import { Logger, LoggerType } from 'common/Logger';
|
import { Logger, LoggerType } from 'common/Logger';
|
||||||
import { getAppName, getAppVersion, getComputerName } from 'src/Main';
|
import { getAppName, getAppVersion, getComputerName } from 'src/Main';
|
||||||
import { PROTOCOL_VERSION } from 'common/Packets';
|
import { PROTOCOL_VERSION } from 'common/Packets';
|
||||||
|
import { TcpListenerService } from './TcpListenerService';
|
||||||
|
import { WebSocketListenerService } from './WebSocketListenerService';
|
||||||
const logger = new Logger('DiscoveryService', LoggerType.BACKEND);
|
const logger = new Logger('DiscoveryService', LoggerType.BACKEND);
|
||||||
|
|
||||||
export class DiscoveryService {
|
export class DiscoveryService {
|
||||||
|
@ -19,13 +21,15 @@ export class DiscoveryService {
|
||||||
// Note that txt field must be populated, otherwise certain mdns stacks have undefined behavior/issues
|
// Note that txt field must be populated, otherwise certain mdns stacks have undefined behavior/issues
|
||||||
// when connecting to the receiver. Also mdns-js internally gets a lot of parsing errors when txt records
|
// when connecting to the receiver. Also mdns-js internally gets a lot of parsing errors when txt records
|
||||||
// are not defined.
|
// are not defined.
|
||||||
this.serviceTcp = mdns.createAdvertisement(mdns.tcp('_fcast'), 46899, { name: name, txt: {
|
this.serviceTcp = mdns.createAdvertisement(mdns.tcp('_fcast'), TcpListenerService.PORT,
|
||||||
|
{ name: name, txt: {
|
||||||
version: PROTOCOL_VERSION,
|
version: PROTOCOL_VERSION,
|
||||||
appName: getAppName(),
|
appName: getAppName(),
|
||||||
appVersion: getAppVersion(),
|
appVersion: getAppVersion(),
|
||||||
} });
|
} });
|
||||||
this.serviceTcp.start();
|
this.serviceTcp.start();
|
||||||
this.serviceWs = mdns.createAdvertisement(mdns.tcp('_fcast-ws'), 46898, { name: name, txt: {
|
this.serviceWs = mdns.createAdvertisement(mdns.tcp('_fcast-ws'), WebSocketListenerService.PORT,
|
||||||
|
{ name: name, txt: {
|
||||||
version: PROTOCOL_VERSION,
|
version: PROTOCOL_VERSION,
|
||||||
appName: getAppName(),
|
appName: getAppName(),
|
||||||
appVersion: getAppVersion(),
|
appVersion: getAppVersion(),
|
||||||
|
|
|
@ -244,8 +244,8 @@ export class FCastSession {
|
||||||
this.emitter.on("setvolume", (body: SetVolumeMessage) => { emitter.emit("setvolume", body) });
|
this.emitter.on("setvolume", (body: SetVolumeMessage) => { emitter.emit("setvolume", body) });
|
||||||
this.emitter.on("setspeed", (body: SetSpeedMessage) => { emitter.emit("setspeed", body) });
|
this.emitter.on("setspeed", (body: SetSpeedMessage) => { emitter.emit("setspeed", body) });
|
||||||
this.emitter.on("version", (body: VersionMessage) => { emitter.emit("version", body) });
|
this.emitter.on("version", (body: VersionMessage) => { emitter.emit("version", body) });
|
||||||
this.emitter.on("ping", () => { emitter.emit("ping", { sessionId: this.sessionId }) });
|
this.emitter.on("ping", () => { emitter.emit("ping", this.sessionId) });
|
||||||
this.emitter.on("pong", () => { emitter.emit("pong", { sessionId: this.sessionId }) });
|
this.emitter.on("pong", () => { emitter.emit("pong", this.sessionId) });
|
||||||
this.emitter.on("initial", (body: InitialSenderMessage) => { emitter.emit("initial", body) });
|
this.emitter.on("initial", (body: InitialSenderMessage) => { emitter.emit("initial", body) });
|
||||||
this.emitter.on("setplaylistitem", (body: SetPlaylistItemMessage) => { emitter.emit("setplaylistitem", body) });
|
this.emitter.on("setplaylistitem", (body: SetPlaylistItemMessage) => { emitter.emit("setplaylistitem", body) });
|
||||||
this.emitter.on("subscribeevent", (body: SubscribeEventMessage) => { emitter.emit("subscribeevent", { sessionId: this.sessionId, body: body }) });
|
this.emitter.on("subscribeevent", (body: SubscribeEventMessage) => { emitter.emit("subscribeevent", { sessionId: this.sessionId, body: body }) });
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { errorHandler } from 'src/Main';
|
||||||
const logger = new Logger('ListenerService', LoggerType.BACKEND);
|
const logger = new Logger('ListenerService', LoggerType.BACKEND);
|
||||||
|
|
||||||
export abstract class ListenerService {
|
export abstract class ListenerService {
|
||||||
public readonly PORT: number;
|
|
||||||
public emitter: EventEmitter = new EventEmitter();
|
public emitter: EventEmitter = new EventEmitter();
|
||||||
protected sessionMap: Map<string, FCastSession> = new Map();
|
protected sessionMap: Map<string, FCastSession> = new Map();
|
||||||
private eventSubscribers: Map<string, EventSubscribeObject[]> = new Map();
|
private eventSubscribers: Map<string, EventSubscribeObject[]> = new Map();
|
||||||
|
|
||||||
public abstract start(): void;
|
public abstract start(): void;
|
||||||
public abstract stop(): void;
|
public abstract stop(): void;
|
||||||
|
public abstract disconnect(sessionId: string): void;
|
||||||
|
|
||||||
public send(opcode: number, message = null, sessionId = null) {
|
public send(opcode: number, message = null, sessionId = null) {
|
||||||
// logger.info(`Sending message ${JSON.stringify(message)}`);
|
// logger.info(`Sending message ${JSON.stringify(message)}`);
|
||||||
|
|
|
@ -24,7 +24,7 @@ class CacheObject {
|
||||||
export class MediaCache {
|
export class MediaCache {
|
||||||
private static instance: MediaCache = null;
|
private static instance: MediaCache = null;
|
||||||
private cache: Map<number, CacheObject>;
|
private cache: Map<number, CacheObject>;
|
||||||
private cacheUrlMap: Map<string,number>;
|
private cacheUrlMap: Map<string, number>;
|
||||||
private playlist: PlaylistContent;
|
private playlist: PlaylistContent;
|
||||||
private playlistIndex: number;
|
private playlistIndex: number;
|
||||||
private quota: number;
|
private quota: number;
|
||||||
|
@ -72,9 +72,7 @@ export class MediaCache {
|
||||||
|
|
||||||
MediaCache.instance = null;
|
MediaCache.instance = null;
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
this.cache = null;
|
|
||||||
this.cacheUrlMap.clear();
|
this.cacheUrlMap.clear();
|
||||||
this.cacheUrlMap = null;
|
|
||||||
this.playlist = null;
|
this.playlist = null;
|
||||||
this.quota = 0;
|
this.quota = 0;
|
||||||
this.cacheSize = 0;
|
this.cacheSize = 0;
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const supportedImageTypes = [
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/svg+xml',
|
'image/svg+xml',
|
||||||
'image/vnd.microsoft.icon',
|
'image/vnd.microsoft.icon',
|
||||||
'image/webp'
|
'image/webp',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const supportedImageExtensions = [
|
export const supportedImageExtensions = [
|
||||||
|
@ -57,7 +57,7 @@ export const supportedImageExtensions = [
|
||||||
'.jpeg', '.jpg', '.jpe', '.jif', '.jfif', '.jfi',
|
'.jpeg', '.jpg', '.jpe', '.jif', '.jfif', '.jfi',
|
||||||
'.png',
|
'.png',
|
||||||
'.svg',
|
'.svg',
|
||||||
'.webp'
|
'.webp',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const supportedPlayerTypes = streamingMediaTypes.concat(
|
export const supportedPlayerTypes = streamingMediaTypes.concat(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Logger, LoggerType } from 'common/Logger';
|
||||||
const logger = new Logger('TcpListenerService', LoggerType.BACKEND);
|
const logger = new Logger('TcpListenerService', LoggerType.BACKEND);
|
||||||
|
|
||||||
export class TcpListenerService extends ListenerService {
|
export class TcpListenerService extends ListenerService {
|
||||||
public readonly PORT = 46899;
|
public static readonly PORT = 46899;
|
||||||
private server: net.Server;
|
private server: net.Server;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -15,7 +15,7 @@ export class TcpListenerService extends ListenerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.server = net.createServer()
|
this.server = net.createServer()
|
||||||
.listen(this.PORT)
|
.listen(TcpListenerService.PORT)
|
||||||
.on("connection", this.handleConnection.bind(this))
|
.on("connection", this.handleConnection.bind(this))
|
||||||
.on("error", this.handleServerError.bind(this));
|
.on("error", this.handleServerError.bind(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { WebSocket, WebSocketServer } from 'modules/ws';
|
||||||
const logger = new Logger('WebSocketListenerService', LoggerType.BACKEND);
|
const logger = new Logger('WebSocketListenerService', LoggerType.BACKEND);
|
||||||
|
|
||||||
export class WebSocketListenerService extends ListenerService {
|
export class WebSocketListenerService extends ListenerService {
|
||||||
public readonly PORT = 46898;
|
public static readonly PORT = 46898;
|
||||||
private server: WebSocketServer;
|
private server: WebSocketServer;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -14,7 +14,7 @@ export class WebSocketListenerService extends ListenerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.server = new WebSocketServer({ port: this.PORT })
|
this.server = new WebSocketServer({ port: WebSocketListenerService.PORT })
|
||||||
.on("connection", this.handleConnection.bind(this))
|
.on("connection", this.handleConnection.bind(this))
|
||||||
.on("error", this.handleServerError.bind(this));
|
.on("error", this.handleServerError.bind(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,16 +249,16 @@ export class Main {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
l.emitter.on('disconnect', (message) => {
|
l.emitter.on('disconnect', (message) => {
|
||||||
ConnectionMonitor.onDisconnect(l, message, l instanceof WebSocketListenerService, () => {
|
ConnectionMonitor.onDisconnect(message, l instanceof WebSocketListenerService, () => {
|
||||||
Main.mainWindow?.webContents?.send('disconnect', message);
|
Main.mainWindow?.webContents?.send('disconnect', message);
|
||||||
Main.playerWindow?.webContents?.send('disconnect', message);
|
Main.playerWindow?.webContents?.send('disconnect', message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
l.emitter.on('ping', (message) => {
|
l.emitter.on('ping', (sessionId: string) => {
|
||||||
ConnectionMonitor.onPingPong(message, l instanceof WebSocketListenerService);
|
ConnectionMonitor.onPingPong(sessionId, l instanceof WebSocketListenerService);
|
||||||
});
|
});
|
||||||
l.emitter.on('pong', (message) => {
|
l.emitter.on('pong', (sessionId: string) => {
|
||||||
ConnectionMonitor.onPingPong(message, l instanceof WebSocketListenerService);
|
ConnectionMonitor.onPingPong(sessionId, l instanceof WebSocketListenerService);
|
||||||
});
|
});
|
||||||
l.emitter.on('initial', (message) => {
|
l.emitter.on('initial', (message) => {
|
||||||
logger.info(`Received 'Initial' message from sender: ${message}`);
|
logger.info(`Received 'Initial' message from sender: ${message}`);
|
||||||
|
|
|
@ -241,15 +241,15 @@ export class Main {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
l.emitter.on('disconnect', (message) => {
|
l.emitter.on('disconnect', (message) => {
|
||||||
ConnectionMonitor.onDisconnect(l, message, l instanceof WebSocketListenerService, () => {
|
ConnectionMonitor.onDisconnect(message, l instanceof WebSocketListenerService, () => {
|
||||||
Main.emitter.emit('disconnect', message);
|
Main.emitter.emit('disconnect', message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
l.emitter.on('ping', (message) => {
|
l.emitter.on('ping', (sessionId: string) => {
|
||||||
ConnectionMonitor.onPingPong(message, l instanceof WebSocketListenerService);
|
ConnectionMonitor.onPingPong(sessionId, l instanceof WebSocketListenerService);
|
||||||
});
|
});
|
||||||
l.emitter.on('pong', (message) => {
|
l.emitter.on('pong', (sessionId: string) => {
|
||||||
ConnectionMonitor.onPingPong(message, l instanceof WebSocketListenerService);
|
ConnectionMonitor.onPingPong(sessionId, l instanceof WebSocketListenerService);
|
||||||
});
|
});
|
||||||
l.emitter.on('initial', (message) => {
|
l.emitter.on('initial', (message) => {
|
||||||
logger.info(`Received 'Initial' message from sender: ${message}`);
|
logger.info(`Received 'Initial' message from sender: ${message}`);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue