diff --git a/src/lib/components/MudConnection.svelte b/src/lib/components/MudConnection.svelte
index 8c3eca7..2a785fd 100644
--- a/src/lib/components/MudConnection.svelte
+++ b/src/lib/components/MudConnection.svelte
@@ -1,21 +1,16 @@
{#if $connectionStatus[profileId]}
@@ -607,4 +427,4 @@
white-space: nowrap;
border-width: 0;
}
-
+
\ No newline at end of file
diff --git a/src/lib/components/MudMdi.svelte b/src/lib/components/MudMdi.svelte
index 0468544..a305469 100644
--- a/src/lib/components/MudMdi.svelte
+++ b/src/lib/components/MudMdi.svelte
@@ -381,6 +381,10 @@
{
+ // Just log the event, actual playback is handled by ClientMediaPackage
+ console.log(`MudMdi received playSound event for ${tab.id}:`, event.detail);
+ }}
/>
({});
+// Simple store for active connections
+export const connections = writable>({});
/**
* ConnectionManager - Singleton service to manage MUD connections
- * This ensures connections stay alive even when components are unmounted during tab switches
+ * Simplified to just maintain a central registry of active connections
*/
export class ConnectionManager {
private static instance: ConnectionManager;
@@ -29,63 +28,79 @@ export class ConnectionManager {
}
/**
- * Get an existing connection without creating a new one
+ * Get all connections
*/
- public getExistingConnection(profileId: string): MudConnection | null {
- const connections = get(activeConnections);
- return connections[profileId] || null;
+ public getConnections(): Record {
+ return get(connections);
}
-
+
/**
- * Create a new connection or return an existing one
+ * Get a connection by profile ID
*/
- public getConnection(profileId: string, options: {
- host: string;
- port: number;
+ public getConnection(profileId: string): MudConnection | null {
+ const allConnections = get(connections);
+ return allConnections[profileId] || null;
+ }
+
+ /**
+ * Create or get a connection
+ */
+ public createConnection(options: {
+ profileId: string;
+ host: string;
+ port: number;
useSSL?: boolean;
- gmcpHandler?: GmcpHandler;
}): MudConnection {
- // Get current connections
- const connections = get(activeConnections);
+ const { profileId, host, port, useSSL } = options;
// Check if a connection already exists for this profile
- if (connections[profileId]) {
- console.log(`Returning existing connection for profile ${profileId}`);
- return connections[profileId];
+ const existingConnection = this.getConnection(profileId);
+ if (existingConnection) {
+ console.log(`Connection already exists for profile ${profileId}`);
+ return existingConnection;
}
- // Create a new connection
+ // Create a new connection with the profile ID as the connection ID
console.log(`Creating new connection for profile ${profileId}`);
const connection = new MudConnection({
- host: options.host,
- port: options.port,
- useSSL: options.useSSL,
- gmcpHandler: options.gmcpHandler
+ id: profileId,
+ host,
+ port,
+ useSSL
});
- // Set up event handlers
- this.setupConnectionEvents(connection, profileId);
+ // Set up event listeners
+ this.setupConnectionEvents(connection);
- // Store the connection
- activeConnections.update(connections => ({
- ...connections,
+ // Store in the connections registry
+ connections.update(conns => ({
+ ...conns,
[profileId]: connection
}));
- // Return the connection
+ // Update connection status
+ connectionStatus.update(statuses => ({
+ ...statuses,
+ [profileId]: 'disconnected'
+ }));
+
return connection;
}
/**
* Connect to a MUD server
+ * Returns the connection object
*/
public connect(profileId: string, options: {
host: string;
port: number;
useSSL?: boolean;
- gmcpHandler?: GmcpHandler;
- }): void {
- const connection = this.getConnection(profileId, options);
+ }): MudConnection {
+ // Get or create the connection
+ const connection = this.createConnection({
+ profileId,
+ ...options
+ });
// Update connection status
connectionStatus.update(statuses => ({
@@ -95,16 +110,18 @@ export class ConnectionManager {
// Connect
connection.connect();
+
+ return connection;
}
/**
* Disconnect from a MUD server
*/
public disconnect(profileId: string): void {
- const connections = get(activeConnections);
+ const connection = this.getConnection(profileId);
- if (connections[profileId]) {
- connections[profileId].disconnect();
+ if (connection) {
+ connection.disconnect();
// Update connection status
connectionStatus.update(statuses => ({
@@ -118,28 +135,30 @@ export class ConnectionManager {
* Send text to a MUD server
*/
public send(profileId: string, text: string): void {
- const connections = get(activeConnections);
+ const connection = this.getConnection(profileId);
- if (connections[profileId]) {
- connections[profileId].send(text);
+ if (connection) {
+ connection.send(text);
}
}
/**
- * Close a connection and remove it
+ * Removes a connection from the registry
*/
- public closeConnection(profileId: string): void {
- const connections = get(activeConnections);
+ public removeConnection(profileId: string): void {
+ const connection = this.getConnection(profileId);
- if (connections[profileId]) {
- // Disconnect first
- connections[profileId].disconnect();
+ if (connection) {
+ // Disconnect first if needed
+ if (connection.isConnected()) {
+ connection.disconnect();
+ }
- // Then remove from store
- activeConnections.update(connections => {
- const newConnections = { ...connections };
- delete newConnections[profileId];
- return newConnections;
+ // Remove from store
+ connections.update(conns => {
+ const newConns = { ...conns };
+ delete newConns[profileId];
+ return newConns;
});
// Update connection status
@@ -154,10 +173,12 @@ export class ConnectionManager {
/**
* Set up event handlers for a connection
*/
- private setupConnectionEvents(connection: MudConnection, profileId: string): void {
+ private setupConnectionEvents(connection: MudConnection): void {
+ const profileId = connection.id;
+
// Handle connection established
connection.on('connected', () => {
- console.log(`ConnectionManager: Connection established for profile ${profileId}`);
+ console.log(`Connection established for profile ${profileId}`);
// Update connection status
connectionStatus.update(statuses => ({
@@ -168,7 +189,7 @@ export class ConnectionManager {
// Handle connection closed
connection.on('disconnected', () => {
- console.log(`ConnectionManager: Connection closed for profile ${profileId}`);
+ console.log(`Connection closed for profile ${profileId}`);
// Update connection status
connectionStatus.update(statuses => ({
@@ -179,7 +200,7 @@ export class ConnectionManager {
// Handle connection error
connection.on('error', (error) => {
- console.error(`ConnectionManager: Connection error for profile ${profileId}:`, error);
+ console.error(`Connection error for profile ${profileId}:`, error);
// Update connection status
connectionStatus.update(statuses => ({
diff --git a/src/lib/connection/MudConnection.ts b/src/lib/connection/MudConnection.ts
index 177d31b..7d9ec5c 100644
--- a/src/lib/connection/MudConnection.ts
+++ b/src/lib/connection/MudConnection.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from '$lib/utils/EventEmitter';
-import type { GmcpHandler } from '$lib/gmcp/GmcpHandler';
+import { GmcpHandler } from '$lib/gmcp/GmcpHandler';
// IAC codes for telnet negotiation
enum TelnetCommand {
@@ -16,38 +16,77 @@ enum TelnetCommand {
interface MudConnectionOptions {
host: string;
port: number;
- gmcpHandler?: GmcpHandler;
useSSL?: boolean;
+ id: string;
}
+/**
+ * MudConnection - Handles a single connection to a MUD server
+ * Each instance has its own GMCP handler and maintains its own state
+ */
export class MudConnection extends EventEmitter {
private host: string;
private port: number;
private useSSL: boolean;
private webSocket: WebSocket | null = null;
- private gmcpHandler: GmcpHandler | null = null;
+ private gmcpHandler: GmcpHandler;
private buffer: number[] = [];
private connected: boolean = false;
private negotiationBuffer: number[] = [];
private isInIAC: boolean = false;
private inSubnegotiation: boolean = false;
- private simulationMode: boolean = false; // Disable simulation mode to use real connections
+ public readonly id: string;
constructor(options: MudConnectionOptions) {
super();
this.host = options.host;
this.port = options.port;
this.useSSL = options.useSSL || false;
- this.gmcpHandler = options.gmcpHandler || null;
+ this.id = options.id;
+
+ // Create GMCP handler
+ this.gmcpHandler = new GmcpHandler();
+
+ // Set up GMCP event forwarding
+ this.setupGmcpEvents();
+
+ console.log(`MudConnection created for ${this.host}:${this.port} with ID ${this.id}`);
+ }
+
+ /**
+ * Set up event forwarding from GMCP handler
+ */
+ private setupGmcpEvents(): void {
+ // Forward all GMCP events to listeners of this connection
+ this.gmcpHandler.on('gmcp', (module, data) => {
+ this.emit('gmcp', module, data);
+ });
+
+ // Forward specific module events (like gmcp:Core.Ping)
+ this.gmcpHandler.on('*', (eventName, ...args) => {
+ if (eventName.startsWith('gmcp:')) {
+ this.emit(eventName, ...args);
+ }
+ });
+
+ // Handle GMCP events that need special processing
+ this.gmcpHandler.on('playSound', (url, volume, loop) => {
+ console.log(`MudConnection forwarding playSound event: ${url}`);
+ this.emit('playSound', { url, volume, loop });
+ });
+
+ // Listen for sendGmcp events from the GMCP handler
+ this.gmcpHandler.on('sendGmcp', (module, data) => {
+ this.sendGmcp(module, data);
+ });
}
/**
* Connect to the MUD server
*/
public connect(): void {
- // For development/testing purposes, we'll use a simulated connection
- if (this.simulationMode) {
- this.connectSimulated();
+ if (this.connected) {
+ console.log(`Already connected to ${this.host}:${this.port}`);
return;
}
@@ -63,24 +102,24 @@ export class MudConnection extends EventEmitter {
wsUrl = `${wsProtocol}://${window.location.host}/mud-ws?host=${encodeURIComponent(this.host)}&port=${this.port}&useSSL=${this.useSSL}`;
}
- console.log('Connecting to WebSocket server:', wsUrl);
+ console.log(`Connecting to WebSocket server: ${wsUrl}`);
this.webSocket = new WebSocket(wsUrl);
-
this.webSocket.binaryType = 'arraybuffer';
this.webSocket.onopen = () => {
this.connected = true;
+ console.log(`Connected to ${this.host}:${this.port}`);
this.emit('connected');
// Send GMCP negotiation upon connection
- if (this.gmcpHandler) {
- this.sendIAC(TelnetCommand.WILL, TelnetCommand.GMCP);
- }
+ console.log('Sending GMCP negotiation');
+ this.sendIAC(TelnetCommand.WILL, TelnetCommand.GMCP);
};
this.webSocket.onclose = () => {
this.connected = false;
+ console.log(`Disconnected from ${this.host}:${this.port}`);
this.emit('disconnected');
};
@@ -109,35 +148,6 @@ export class MudConnection extends EventEmitter {
};
}
- /**
- * Connect to a simulated MUD server for testing
- */
- private connectSimulated(): void {
- console.log('Using simulated MUD connection');
-
- // Simulate connection delay
- setTimeout(() => {
- this.connected = true;
- this.emit('connected');
-
- // Send welcome message
- const welcomeMessage = `
-============================================================
-| Welcome to ${this.host} |
-============================================================
-
-This is a simulated connection for testing purposes.
-Commands you type will be echoed back to the terminal.
-
-Type 'help' for a list of available commands.
-Type 'look' to see the current room.
-`;
- setTimeout(() => {
- this.emit('received', welcomeMessage);
- }, 500);
- }, 1000);
- }
-
/**
* Send text to the MUD server
*/
@@ -146,12 +156,6 @@ Type 'look' to see the current room.
throw new Error('Not connected to MUD server');
}
- // In simulation mode, generate appropriate responses
- if (this.simulationMode) {
- this.handleSimulatedCommand(text);
- return;
- }
-
if (!this.webSocket) {
throw new Error('WebSocket not initialized');
}
@@ -175,76 +179,10 @@ Type 'look' to see the current room.
}
}
- /**
- * Handle commands in simulation mode
- */
- private handleSimulatedCommand(command: string): void {
- // Emit that we sent the command
- this.emit('sent', command);
-
- // Process command with a small delay to simulate network latency
- setTimeout(() => {
- const cmd = command.trim().toLowerCase();
-
- if (cmd === 'help') {
- this.emit('received', `
-Available commands:
-- help: Show this help message
-- look: Look at the current room
-- say : Say something
-- who: Show who's online
-- inventory: Show your inventory
-- quit: Disconnect
-`);
- } else if (cmd === 'look') {
- this.emit('received', `
-The Testing Room
-================
-You are in a simple testing room. The walls are white and featureless,
-with a single door to the north. A sign on the wall reads "Simulation Mode".
-
-Obvious exits: north
-
-You see a test object here.
-`);
- } else if (cmd.startsWith('say ')) {
- const message = command.substring(4);
- this.emit('received', `You say, "${message}"\n`);
- } else if (cmd === 'who') {
- this.emit('received', `
-Players online:
-- TestPlayer1 (Idle)
-- TestPlayer2 (AFK)
-- You
-
-Total: 3 players
-`);
- } else if (cmd === 'inventory' || cmd === 'i') {
- this.emit('received', `
-You are carrying:
-- a test item
-- a notebook
-- some coins (15)
-`);
- } else if (cmd === 'quit' || cmd === 'exit') {
- this.emit('received', 'Goodbye! Thanks for testing.\n');
- setTimeout(() => this.disconnect(), 1000);
- } else {
- this.emit('received', `Unknown command: ${command}\nType 'help' for a list of commands.\n`);
- }
- }, 200);
- }
-
/**
* Disconnect from the MUD server
*/
public disconnect(): void {
- if (this.simulationMode) {
- this.connected = false;
- this.emit('disconnected');
- return;
- }
-
if (this.webSocket) {
this.webSocket.close();
this.webSocket = null;
@@ -255,8 +193,6 @@ You are carrying:
* Handle incoming data from the MUD server
*/
private handleIncomingData(data: Uint8Array): void {
- console.log(`Received ${data.length} bytes from server`);
-
// Quickly check if we need to handle telnet negotiation
let containsIAC = false;
for (let i = 0; i < data.length; i++) {
@@ -266,6 +202,12 @@ You are carrying:
}
}
+ // Debug: Log raw data for debugging if it contains IAC
+ if (containsIAC) {
+ const hexData = Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ');
+ console.log(`Raw data with IAC: ${hexData}`);
+ }
+
// Fast path if no IAC codes
if (!containsIAC && !this.isInIAC) {
const text = new TextDecoder().decode(data);
@@ -273,12 +215,6 @@ You are carrying:
return;
}
- // Debug: Log the raw bytes if IAC is present
- if (containsIAC) {
- const hexData = Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ');
- console.log(`Data with IAC: ${hexData}`);
- }
-
// Process each byte in the incoming data
for (let i = 0; i < data.length; i++) {
const byte = data[i];
@@ -294,7 +230,7 @@ You are carrying:
this.negotiationBuffer.length > 0 &&
this.negotiationBuffer[this.negotiationBuffer.length - 2] === TelnetCommand.IAC) {
- console.log('FOUND IAC SE SEQUENCE - End of subnegotiation');
+ console.log('End of subnegotiation found');
// Process the complete subnegotiation
this.handleCompleteSubnegotiation();
@@ -309,10 +245,8 @@ You are carrying:
if (byte === TelnetCommand.SB) {
// Start of subnegotiation
this.inSubnegotiation = true;
- console.log('Started subnegotiation - waiting for option code');
} else if (byte === TelnetCommand.WILL || byte === TelnetCommand.DO) {
// Need one more byte for option
- console.log(`Telnet WILL/DO command, waiting for option`);
} else {
// Simple 3-byte command
this.processSimpleTelnetCommand();
@@ -329,7 +263,7 @@ You are carrying:
// Start of telnet command
this.isInIAC = true;
this.negotiationBuffer = [byte];
- console.log('Found IAC - start of telnet command');
+ console.log('IAC command detected');
} else {
// Normal data byte, add to buffer
this.buffer.push(byte);
@@ -353,19 +287,15 @@ You are carrying:
try {
const [iac, command, option] = this.negotiationBuffer;
- console.log(`Processing telnet command: IAC(${iac}) ${command} ${option}`);
-
// Handle specific commands
if ((command === TelnetCommand.WILL || command === TelnetCommand.DO) && option === TelnetCommand.GMCP) {
- console.log('Server indicates WILL/DO GMCP, responding with DO GMCP');
+ console.log('Server supports GMCP, responding with DO GMCP');
// Server wants to use GMCP, we'll respond with IAC DO GMCP
this.sendIAC(TelnetCommand.DO, TelnetCommand.GMCP);
- // And request Core.Hello
- if (this.gmcpHandler) {
- console.log('Requesting GMCP capabilities');
- this.gmcpHandler.requestCapabilities();
- }
+ // Request GMCP capabilities
+ console.log('Requesting GMCP capabilities');
+ this.gmcpHandler.requestCapabilities();
}
} catch (error) {
console.error('Error processing telnet command:', error);
@@ -377,39 +307,31 @@ You are carrying:
*/
private handleCompleteSubnegotiation(): void {
try {
- console.log(`Handling subnegotiation, buffer length: ${this.negotiationBuffer.length}`);
+ // Debug buffer contents
+ const bufferHex = this.negotiationBuffer.map(b => b.toString(16).padStart(2, '0')).join(' ');
+ console.log(`Processing subnegotiation, buffer: ${bufferHex}`);
// Check if this is a GMCP subnegotiation
// IAC SB GMCP ... IAC SE
// Indexes: 0 1 2 ... -2 -1
if (this.negotiationBuffer.length >= 5 && this.negotiationBuffer[2] === TelnetCommand.GMCP) {
- console.log('Processing complete GMCP subnegotiation');
+ console.log('Processing GMCP subnegotiation');
try {
// Extract the GMCP data (skip IAC SB GMCP, and the final IAC SE)
const gmcpData = this.negotiationBuffer.slice(3, -2);
const gmcpText = new TextDecoder().decode(new Uint8Array(gmcpData));
- console.log('RECEIVED GMCP DATA:', gmcpText);
+ console.log(`GMCP message: ${gmcpText}`);
- // Process the GMCP message
- if (this.gmcpHandler) {
- setTimeout(() => {
- try {
- console.log('Passing GMCP to handler:', gmcpText);
- this.gmcpHandler?.handleGmcpMessage(gmcpText);
- } catch (error) {
- console.error('Error in GMCP handler:', error);
- }
- }, 0);
- } else {
- console.warn('No GMCP handler available for message:', gmcpText);
- }
+ // Process the GMCP message immediately
+ console.log('Passing GMCP to handler:', gmcpText);
+ this.gmcpHandler.handleGmcpMessage(gmcpText);
} catch (error) {
- console.error('Error processing GMCP data:', error, 'Buffer:', this.negotiationBuffer);
+ console.error('Error processing GMCP data:', error);
}
} else {
- console.log(`Non-GMCP subnegotiation complete, option: ${this.negotiationBuffer[2]}`);
+ console.log(`Non-GMCP subnegotiation received: ${this.negotiationBuffer[2]}`);
}
} catch (error) {
console.error('Error handling subnegotiation:', error);
@@ -432,19 +354,12 @@ You are carrying:
* Send a GMCP message
*/
public sendGmcp(module: string, data: any): void {
- if (!this.connected) {
- return;
- }
-
- if (this.simulationMode) {
- console.log(`GMCP send (simulated): ${module}`, data);
- return;
- }
-
- if (!this.webSocket) {
+ if (!this.connected || !this.webSocket) {
+ console.log('Cannot send GMCP - not connected');
return;
}
+ console.log(`Sending GMCP: ${module}`, data);
const gmcpString = `${module} ${JSON.stringify(data)}`;
const gmcpData = new TextEncoder().encode(gmcpString);
@@ -460,4 +375,18 @@ You are carrying:
this.webSocket.send(telnetSequence);
}
+
+ /**
+ * Get the GMCP handler associated with this connection
+ */
+ public getGmcpHandler(): GmcpHandler {
+ return this.gmcpHandler;
+ }
+
+ /**
+ * Check if the connection is active
+ */
+ public isConnected(): boolean {
+ return this.connected;
+ }
}
\ No newline at end of file
diff --git a/src/lib/gmcp/GmcpHandler.ts b/src/lib/gmcp/GmcpHandler.ts
index a2a987d..1e04079 100644
--- a/src/lib/gmcp/GmcpHandler.ts
+++ b/src/lib/gmcp/GmcpHandler.ts
@@ -92,10 +92,20 @@ export class GmcpHandler extends EventEmitter {
// Find the appropriate package handler
let handled = false;
+ console.log(`Looking for handler for GMCP module: ${module}`);
+ console.log(`Available package handlers: ${Array.from(this.packageHandlers.keys()).join(', ')}`);
+
for (const [packagePrefix, handler] of this.packageHandlers.entries()) {
+ console.log(`Checking if ${module} starts with ${packagePrefix}`);
if (module.startsWith(packagePrefix)) {
- handler.handleMessage(module, data);
- handled = true;
+ console.log(`Found handler for ${module}: ${packagePrefix}`);
+ try {
+ handler.handleMessage(module, data);
+ console.log(`Successfully processed ${module} with handler ${packagePrefix}`);
+ handled = true;
+ } catch (error) {
+ console.error(`Error in handler ${packagePrefix} for module ${module}:`, error);
+ }
break;
}
}
diff --git a/src/lib/gmcp/packages/ClientMediaPackage.ts b/src/lib/gmcp/packages/ClientMediaPackage.ts
index 94f6238..5a8f0da 100644
--- a/src/lib/gmcp/packages/ClientMediaPackage.ts
+++ b/src/lib/gmcp/packages/ClientMediaPackage.ts
@@ -29,6 +29,23 @@ export class ClientMediaPackage implements GmcpPackageHandler {
// Subscribe to global volume changes to update active sounds
this.setupVolumeSubscription();
+
+ // Log that we're ready to receive GMCP messages
+ console.log('ClientMediaPackage ready to handle Client.Media.* GMCP messages');
+
+ // Double check event listeners
+ if (this.emitter) {
+ this.emitter.on('test', () => {
+ console.log('Test event received by ClientMediaPackage');
+ });
+
+ // Test emit an event to confirm EventEmitter works properly
+ setTimeout(() => {
+ if (this.emitter) {
+ this.emitter.emit('test');
+ }
+ }, 500);
+ }
}
/**
@@ -73,10 +90,16 @@ export class ClientMediaPackage implements GmcpPackageHandler {
handleMessage(module: string, data: any): void {
try {
+ console.log(`ClientMediaPackage handling message: ${module}`, data);
+
if (module === 'Client.Media.Play') {
+ console.log('Processing Client.Media.Play message');
this.handlePlay(data);
} else if (module === 'Client.Media.Stop') {
+ console.log('Processing Client.Media.Stop message');
this.handleStop(data);
+ } else {
+ console.log(`Unhandled Client.Media message type: ${module}`);
}
} catch (error) {
console.error('Error handling GMCP Media message:', error);
@@ -169,17 +192,45 @@ export class ClientMediaPackage implements GmcpPackageHandler {
this.tagToIdsMap.get(tag)?.add(soundId);
}
- // Play the sound
- sound.play();
+ try {
+ // Play the sound directly
+ console.log(`Directly playing sound ${soundId} from ${fullUrl} with volume ${soundVolume}`);
+
+ // Register event handlers for debugging
+ sound.once('load', () => {
+ console.log(`Sound ${soundId} loaded successfully from ${fullUrl}`);
+ });
+
+ sound.once('play', () => {
+ console.log(`Sound ${soundId} started playing`);
+ });
+
+ sound.once('end', () => {
+ console.log(`Sound ${soundId} finished playing`);
+ this.cleanupSound(soundId);
+ });
+
+ sound.on('loaderror', (id, error) => {
+ console.error(`Error loading sound ${soundId} from ${fullUrl}:`, error);
+ });
+
+ sound.on('playerror', (id, error) => {
+ console.error(`Error playing sound ${soundId} from ${fullUrl}:`, error);
+ });
+
+ // Play the sound
+ const soundId2 = sound.play();
+ console.log(`Sound started with Howler id: ${soundId2}`);
+ } catch (error) {
+ console.error(`Error playing sound ${soundId}:`, error);
+ }
- // Set up cleanup when sound ends
- sound.once('end', () => {
- console.log(`Sound ${soundId} finished playing`);
- this.cleanupSound(soundId);
- });
-
- // Emit an event for other components
- this.emitter?.emit('playSound', fullUrl, soundVolume, !!data.loop);
+ // Emit an event for informational purposes only
+ // We don't need other components to play the sound
+ if (this.emitter) {
+ console.log(`Emitting notification of sound playback: ${fullUrl}`);
+ this.emitter.emit('playSound', fullUrl, soundVolume, !!data.loop);
+ }
} catch (error) {
console.error('Error in handlePlay:', error);
}