Skip to content

Commit b7ff2f7

Browse files
committed
Fix WebSocket connection issues and bump version to 3.4.1
Fixes #37 - Fixed Socket.IO server configuration with proper timeouts and CORS - Fixed client transport order (polling first, then upgrade to WebSocket) - Added explicit Socket.IO path configuration - Enhanced error logging and debugging information - Added ping/pong connection testing functionality - Improved UI status messages and error handling - Added automated WebSocket test script (scripts/test_websocket.py) - Updated version to 3.4.1 across all files - Updated CHANGELOG.md with v3.4.1 release notes Changes: - Server: ping_timeout=60s, ping_interval=25s, enhanced logging - Client: transport order ['polling', 'websocket'], timeout=20s - All WebSocket tests passing (connection, ping/pong, TTS streaming) - Docker image tested and verified
1 parent 1030232 commit b7ff2f7

File tree

10 files changed

+325
-26
lines changed

10 files changed

+325
-26
lines changed

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.4.1] - 2025-11-08
9+
10+
### Fixed
11+
- **WebSocket connection issues** ([#37](https://github.com/dbccccccc/ttsfm/issues/37))
12+
- Fixed Socket.IO server configuration with proper timeouts and CORS settings
13+
- Fixed client transport order (polling first, then upgrade to WebSocket)
14+
- Added explicit Socket.IO path configuration (`/socket.io/`)
15+
- Enhanced connection timeout and upgrade settings
16+
- Improved error logging and debugging information
17+
- **WebSocket client improvements**:
18+
- Added ping/pong connection testing functionality
19+
- Better status messages and error handling in UI
20+
- Enhanced console logging for troubleshooting
21+
- Automatic connection testing on successful connect
22+
23+
### Added
24+
- **WebSocket test script** (`scripts/test_websocket.py`)
25+
- Automated WebSocket connection testing
26+
- Ping/pong latency testing
27+
- TTS generation verification
28+
- Comprehensive test output with status indicators
29+
- **WebSocket troubleshooting documentation** (`docs/websocket-troubleshooting.md`)
30+
- Common issues and solutions
31+
- Configuration reference
32+
- Debugging steps
33+
- Browser compatibility notes
34+
35+
### Changed
36+
- Socket.IO server configuration now includes:
37+
- `ping_timeout: 60` seconds
38+
- `ping_interval: 25` seconds
39+
- `logger` and `engineio_logger` enabled in debug mode
40+
- `cors_credentials: True` for proper CORS handling
41+
- Socket.IO client configuration now includes:
42+
- Transport order: `['polling', 'websocket']` for better reliability
43+
- `upgrade: true` and `rememberUpgrade: true` for transport optimization
44+
- `timeout: 20000` ms connection timeout
45+
- Explicit path configuration
46+
47+
### Technical
48+
- WebSocket connections now successfully establish in both local and Docker environments
49+
- Transport upgrade from polling to WebSocket working correctly
50+
- All WebSocket tests passing (connection, ping/pong, TTS streaming)
51+
- Docker image tested and verified (594MB full variant)
52+
853
## [3.4.0] - 2025-10-28
954

1055
### Added

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ ttsfm = "ttsfm.cli:main"
8686
version_scheme = "no-guess-dev"
8787
local_scheme = "no-local-version"
8888

89-
fallback_version = "3.4.0"
89+
fallback_version = "3.4.1"
9090
[tool.setuptools]
9191
packages = ["ttsfm"]
9292

scripts/test_websocket.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python
2+
"""
3+
Test WebSocket connection to TTSFM server.
4+
5+
This script tests the WebSocket functionality by connecting to the server
6+
and performing a simple TTS generation request.
7+
"""
8+
9+
import time
10+
import socketio
11+
12+
# Create a Socket.IO client
13+
sio = socketio.Client(logger=True, engineio_logger=True)
14+
15+
# Track connection state
16+
connected = False
17+
stream_complete = False
18+
chunks_received = 0
19+
20+
21+
@sio.on('connect')
22+
def on_connect():
23+
"""Handle connection event."""
24+
global connected
25+
connected = True
26+
print('\n✅ Connected to WebSocket server!')
27+
print(f'Session ID: {sio.sid}')
28+
29+
30+
@sio.on('connected')
31+
def on_session_ready(data):
32+
"""Handle session ready event."""
33+
print(f'\n✅ Session established: {data}')
34+
35+
36+
@sio.on('disconnect')
37+
def on_disconnect():
38+
"""Handle disconnection event."""
39+
global connected
40+
connected = False
41+
print('\n❌ Disconnected from WebSocket server')
42+
43+
44+
@sio.on('connect_error')
45+
def on_connect_error(data):
46+
"""Handle connection error."""
47+
print(f'\n❌ Connection error: {data}')
48+
49+
50+
@sio.on('pong')
51+
def on_pong(data):
52+
"""Handle pong response."""
53+
print(f'\n✅ Pong received: {data}')
54+
55+
56+
@sio.on('stream_started')
57+
def on_stream_started(data):
58+
"""Handle stream started event."""
59+
print(f'\n✅ Stream started: {data}')
60+
61+
62+
@sio.on('stream_progress')
63+
def on_stream_progress(data):
64+
"""Handle stream progress event."""
65+
progress = data.get('progress', 0)
66+
status = data.get('status', 'unknown')
67+
print(f'📊 Progress: {progress}% - Status: {status}')
68+
69+
70+
@sio.on('audio_chunk')
71+
def on_audio_chunk(data):
72+
"""Handle audio chunk event."""
73+
global chunks_received
74+
chunks_received += 1
75+
chunk_index = data.get('chunk_index', 0)
76+
total_chunks = data.get('total_chunks', 0)
77+
print(f'🎵 Received audio chunk {chunk_index + 1}/{total_chunks}')
78+
79+
80+
@sio.on('stream_complete')
81+
def on_stream_complete(data):
82+
"""Handle stream complete event."""
83+
global stream_complete
84+
stream_complete = True
85+
print(f'\n✅ Stream complete: {data}')
86+
print(f'Total chunks received: {chunks_received}')
87+
88+
89+
@sio.on('stream_error')
90+
def on_stream_error(data):
91+
"""Handle stream error event."""
92+
print(f'\n❌ Stream error: {data}')
93+
94+
95+
def test_connection(url='http://localhost:8000'):
96+
"""Test WebSocket connection."""
97+
print(f'🔌 Connecting to {url}...')
98+
99+
try:
100+
# Connect to the server
101+
sio.connect(url, transports=['polling', 'websocket'])
102+
103+
# Wait for connection
104+
timeout = 10
105+
start_time = time.time()
106+
while not connected and (time.time() - start_time) < timeout:
107+
time.sleep(0.1)
108+
109+
if not connected:
110+
print('❌ Failed to connect within timeout')
111+
return False
112+
113+
# Test ping/pong
114+
print('\n📡 Testing ping/pong...')
115+
sio.emit('ping', {'timestamp': time.time()})
116+
time.sleep(1)
117+
118+
# Test TTS generation
119+
print('\n🎤 Testing TTS generation...')
120+
request_data = {
121+
'request_id': f'test_{int(time.time())}',
122+
'text': 'Hello, this is a WebSocket test!',
123+
'voice': 'alloy',
124+
'format': 'mp3',
125+
'chunk_size': 512
126+
}
127+
128+
sio.emit('generate_stream', request_data)
129+
130+
# Wait for stream to complete
131+
timeout = 30
132+
start_time = time.time()
133+
while not stream_complete and (time.time() - start_time) < timeout:
134+
time.sleep(0.1)
135+
136+
if stream_complete:
137+
print('\n✅ WebSocket test completed successfully!')
138+
return True
139+
else:
140+
print('\n⚠️ Stream did not complete within timeout')
141+
return False
142+
143+
except Exception as e:
144+
print(f'\n❌ Error during test: {e}')
145+
import traceback
146+
traceback.print_exc()
147+
return False
148+
149+
finally:
150+
# Disconnect
151+
if connected:
152+
print('\n🔌 Disconnecting...')
153+
sio.disconnect()
154+
time.sleep(1)
155+
156+
157+
if __name__ == '__main__':
158+
import sys
159+
160+
# Get URL from command line or use default
161+
url = sys.argv[1] if len(sys.argv) > 1 else 'http://localhost:8000'
162+
163+
print('=' * 60)
164+
print('TTSFM WebSocket Connection Test')
165+
print('=' * 60)
166+
167+
success = test_connection(url)
168+
169+
print('\n' + '=' * 60)
170+
if success:
171+
print('✅ All tests passed!')
172+
sys.exit(0)
173+
else:
174+
print('❌ Some tests failed')
175+
sys.exit(1)
176+

ttsfm-web/app.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,17 @@ def _default_host() -> str:
110110
# Initialize SocketIO with proper async mode
111111
# Using eventlet for production, threading for development
112112
async_mode = "eventlet" if not DEBUG else "threading"
113-
socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
113+
socketio = SocketIO(
114+
app,
115+
cors_allowed_origins="*",
116+
async_mode=async_mode,
117+
logger=DEBUG, # Enable logging in debug mode for troubleshooting
118+
engineio_logger=DEBUG,
119+
ping_timeout=60, # Time to wait for pong response
120+
ping_interval=25, # Interval between ping messages
121+
cors_credentials=True, # Allow credentials in CORS requests
122+
manage_session=False # Don't interfere with Flask sessions
123+
)
114124

115125
# Initialize i18n support
116126
init_i18n(app)
@@ -499,7 +509,7 @@ def get_status():
499509
{
500510
"status": "online",
501511
"tts_service": "openai.fm (free)",
502-
"package_version": "3.4.0",
512+
"package_version": "3.4.1",
503513
"timestamp": datetime.now().isoformat(),
504514
}
505515
)
@@ -526,7 +536,7 @@ def health_check():
526536
return jsonify(
527537
{
528538
"status": "healthy",
529-
"package_version": "3.4.0",
539+
"package_version": "3.4.1",
530540
"image_variant": caps.get_capabilities()["image_variant"],
531541
"ffmpeg_available": caps.ffmpeg_available,
532542
"timestamp": datetime.now().isoformat(),

ttsfm-web/static/js/websocket-tts.js

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,23 @@ class WebSocketTTSClient {
3333
this.log('Already connected');
3434
return;
3535
}
36-
36+
3737
this.log('Connecting to WebSocket server...');
38-
39-
// Initialize Socket.IO connection
38+
39+
// Initialize Socket.IO connection with explicit configuration
4040
this.socket = io(this.socketUrl, {
41-
transports: ['websocket', 'polling'],
41+
path: '/socket.io/', // Explicit Socket.IO path
42+
transports: ['polling', 'websocket'], // Try polling first, then upgrade to websocket
4243
reconnection: true,
4344
reconnectionAttempts: this.maxReconnectAttempts,
44-
reconnectionDelay: this.reconnectDelay
45+
reconnectionDelay: this.reconnectDelay,
46+
upgrade: true, // Allow transport upgrades
47+
rememberUpgrade: true, // Remember successful upgrades
48+
timeout: 20000, // Connection timeout (20 seconds)
49+
autoConnect: true, // Automatically connect on creation
50+
forceNew: false // Reuse existing connection if available
4551
});
46-
52+
4753
// Set up event handlers
4854
this.setupEventHandlers();
4955
}
@@ -52,24 +58,38 @@ class WebSocketTTSClient {
5258
// Connection events
5359
this.socket.on('connect', () => {
5460
this.log('Connected to WebSocket server');
61+
this.log('Socket ID:', this.socket.id);
62+
this.log('Transport:', this.socket.io.engine.transport.name);
5563
this.reconnectAttempts = 0;
5664
this.onConnect();
5765
});
58-
66+
5967
this.socket.on('disconnect', (reason) => {
6068
this.log('Disconnected from WebSocket server:', reason);
6169
this.onDisconnect(reason);
6270
});
63-
71+
6472
this.socket.on('connect_error', (error) => {
6573
this.log('Connection error:', error);
74+
this.log('Error message:', error.message);
75+
this.log('Error type:', error.type);
6676
this.reconnectAttempts++;
6777
this.onError({
6878
type: 'connection_error',
69-
message: error.message,
79+
message: error.message || 'Failed to connect to WebSocket server',
7080
attempts: this.reconnectAttempts
7181
});
7282
});
83+
84+
// Log transport upgrade
85+
this.socket.io.engine.on('upgrade', (transport) => {
86+
this.log('Transport upgraded to:', transport.name);
87+
});
88+
89+
// Log any errors from the engine
90+
this.socket.io.engine.on('error', (error) => {
91+
this.log('Engine error:', error);
92+
});
7393

7494
// TTS streaming events
7595
this.socket.on('connected', (data) => {
@@ -103,6 +123,25 @@ class WebSocketTTSClient {
103123
this.socket.on('stream_cancelled', (data) => {
104124
this.handleStreamCancelled(data);
105125
});
126+
127+
// Ping/pong for connection testing
128+
this.socket.on('pong', (data) => {
129+
this.log('Pong received:', data);
130+
});
131+
}
132+
133+
/**
134+
* Test the connection with a ping
135+
*/
136+
testConnection() {
137+
if (!this.socket || !this.socket.connected) {
138+
this.log('Cannot test connection - not connected');
139+
return false;
140+
}
141+
142+
this.socket.emit('ping', { timestamp: Date.now() });
143+
this.log('Ping sent');
144+
return true;
106145
}
107146

108147
/**

ttsfm-web/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<a class="navbar-brand" href="{{ url_for('index') }}">
8989
<i class="fas fa-microphone-alt me-2"></i>
9090
<span class="fw-bold">TTSFM</span>
91-
<span class="badge bg-primary ms-2 small">v3.4.0</span>
91+
<span class="badge bg-primary ms-2 small">v3.4.1</span>
9292
</a>
9393

9494
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">

ttsfm-web/templates/docs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ <h4>Key Features</h4>
119119

120120
<div class="alert alert-success">
121121
<i class="fas fa-star me-2"></i>
122-
<strong>Version 3.4.0:</strong> Now with image variant detection, improved error handling, and comprehensive format support!
122+
<strong>Version 3.4.1:</strong> WebSocket connection fixes, improved Socket.IO configuration, and enhanced error handling!
123123
</div>
124124
</section>
125125

0 commit comments

Comments
 (0)