Created apache-sse-config.conf with required Apache settings to support long-running SSE connections. Apache's mod_proxy_fcgi has a default timeout of 30-60 seconds which kills SSE connections prematurely. The configuration sets ProxyTimeout to 21600 seconds (6 hours) to match HAProxy's timeout and allow long streaming sessions. Added note to .htaccess explaining this requirement, as ProxyTimeout cannot be set in .htaccess and must be configured in the virtual host. To fix 504 Gateway Timeout errors: 1. Add ProxyTimeout directive to Apache virtual host config 2. Reload Apache 3. Test SSE connection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Multi-User Transcription Server (PHP)
A simple PHP server that allows multiple Local Transcription clients to merge their captions into a single stream. Perfect for multiple streamers playing together who want synchronized captions.
Features
- ✅ Room-based isolation (multiple groups can use the same server)
- ✅ Passphrase authentication per room
- ✅ Real-time streaming via Server-Sent Events (SSE)
- ✅ Different colors for each user
- ✅ Auto-fade transcriptions
- ✅ Works on standard PHP hosting (no special requirements)
- ✅ File-based storage (no database needed)
- ✅ Automatic cleanup of old rooms
Requirements
- PHP 7.4 or higher
- Web server (Apache/Nginx)
- Writable data directory
Installation
1. Upload Files
Upload these files to your web server:
your-domain.com/
└── transcription/
├── server.php
├── display.php
├── config.php
├── .htaccess
└── data/ (will be created automatically)
2. Set Permissions
Make sure the PHP process can write to the directory:
chmod 755 server.php display.php config.php
chmod 755 .
The data/ directory will be created automatically with proper permissions.
3. Test Installation
Visit: https://your-domain.com/transcription/server.php
You should see:
{
"service": "Local Transcription Multi-User Server",
"version": "1.0.0",
...
}
Usage
For Streamers (Desktop App)
- Open the Local Transcription app
- Go to Settings
- Enable "Server Sync"
- Enter:
- Server URL:
https://your-domain.com/transcription/server.php - Room Name: Choose a unique name (e.g., "gaming-session-123")
- Passphrase: A shared secret for your group (e.g., "mysecretpass")
- Server URL:
- Start transcription
For OBS (Browser Source)
-
Add a "Browser" source in OBS
-
Set URL to:
https://your-domain.com/transcription/display.php?room=ROOM&passphrase=PASS&fade=10×tamps=trueReplace:
ROOM= Your room namePASS= Your passphrasefade=10= Seconds before text fades (0 = never)timestamps=true= Show timestamps (false to hide)
-
Set width/height as desired (e.g., 1920x300)
-
Check "Shutdown source when not visible" (optional)
API Endpoints
Send Transcription
POST /server.php?action=send
Content-Type: application/json
{
"room": "my-room",
"passphrase": "my-secret",
"user_name": "Alice",
"text": "Hello everyone!",
"timestamp": "12:34:56"
}
Stream Transcriptions (SSE)
GET /server.php?action=stream&room=my-room&passphrase=my-secret
Returns Server-Sent Events stream with new transcriptions.
List Recent Transcriptions
GET /server.php?action=list&room=my-room&passphrase=my-secret
Returns JSON array of recent transcriptions.
Configuration
Edit config.php to customize:
// Session lifetime (seconds)
define('SESSION_LIFETIME', 3600);
// Max transcriptions stored per room
define('MAX_TRANSCRIPTIONS_PER_ROOM', 100);
// Storage directory
define('STORAGE_DIR', __DIR__ . '/data');
// Enable CORS
define('ENABLE_CORS', true);
// Cleanup threshold (seconds)
define('CLEANUP_THRESHOLD', 7200);
Security
Passphrases
- Each room is protected by a passphrase
- Passphrases are hashed using PHP's
password_hash() - The first person to create a room sets its passphrase
- All subsequent users must use the same passphrase
Best Practices
- Use strong passphrases (e.g.,
MyStream2024!SecurePass) - Don't share passphrases publicly
- Use unique room names (e.g., include date/time)
- Enable HTTPS on your server
- Regularly update PHP
Data Storage
- Room data is stored in
data/room_HASH.json - Files are automatically cleaned up after 2 hours of inactivity
- No personally identifiable information is logged
Troubleshooting
"Invalid passphrase" error
- Make sure all clients use the exact same passphrase
- Passphrases are case-sensitive
- First user to join creates the room and sets the passphrase
Transcriptions not appearing
- Check browser console for errors
- Verify Server-Sent Events (SSE) is supported
- Check that the room name and passphrase match
"Permission denied" on data directory
chmod 755 /path/to/transcription
# Data directory will be created automatically
Server disconnects frequently
- Increase PHP's
max_execution_timefor SSE:set_time_limit(0); - Check server timeout settings (Apache/Nginx)
Advanced Usage
Multiple Rooms on Same Server
Each room is completely isolated. Example:
- Room "podcast-team-1" with passphrase "secret1"
- Room "gaming-squad-2" with passphrase "secret2"
They don't interfere with each other.
Customizing Display
Add URL parameters to display.php:
?fade=20- Fade after 20 seconds?fade=0- Never fade?timestamps=false- Hide timestamps?font=Arial- Change font (future feature)
Using with Shared Hosting
This works on most shared hosting providers:
- No database required
- No special PHP extensions needed
- Uses standard PHP file operations
- Compatible with Apache .htaccess
Upgrading to Redis/MySQL
For high-traffic scenarios, replace file storage in server.php:
// Instead of file_put_contents()
// Use Redis:
$redis->set("room:$room", json_encode($roomData));
// Or MySQL:
$pdo->prepare("INSERT INTO rooms ...")->execute(...);
Performance
- Tested: 10 concurrent clients per room
- Latency: < 2 seconds
- Storage: ~1KB per transcription
- Bandwidth: Minimal (text-only)
Limitations
- File-based storage (not suitable for very high traffic)
- Server-Sent Events may not work with some proxies
- Rooms expire after 2 hours of inactivity
- No user management or admin panel (by design)
License
Part of the Local Transcription project. Generated with Claude Code.
Support
For issues or questions:
- Check this README
- Review server logs
- Test with browser's Network tab
- Create an issue on GitHub