This commit is contained in:
Ryzerth
2025-11-04 19:18:53 -05:00
parent f494612908
commit 6605a2d933
4 changed files with 115 additions and 43 deletions

View File

@@ -11,18 +11,21 @@ import "github.com/gorilla/websocket"
// Display instance // Display instance
type Display struct { type Display struct {
// WebSocket used to communicate with the display // WebSocket used to communicate with the display
sock *websocket.Conn sock *websocket.Conn;
sockSendMtx sync.Mutex sockSendMtx sync.Mutex;
// User mutex
userMtx sync.Mutex;
// User currently connected to the display // User currently connected to the display
user *User user *User;
// One-time-pass currently shown on the display // One-time-pass currently shown on the display
otpMtx sync.Mutex otpMtx sync.Mutex;
otp string otp string;
// Channel to pass the answer from the display coroutine to the user couroutine // Channel to pass the answer from the display coroutine to the user couroutine
answerCh chan string answerCh chan string;
} }
// Helper function to flush channels // Helper function to flush channels
@@ -50,7 +53,7 @@ func chReadTimeout(ch *chan string, timeoutMS int) (string, error) {
return data, nil return data, nil
// If no data has been received and the timeout is reached, return an error // If no data has been received and the timeout is reached, return an error
case <-time.After(timeoutMS * time.Millisecond): case <-time.After(time.Millisecond * time.Duration(timeoutMS)):
return "", errors.New("timeout") return "", errors.New("timeout")
} }
} }
@@ -206,7 +209,28 @@ func displayHandler(sock *websocket.Conn, dispID string, otp string) {
disp.answerCh <- answer disp.answerCh <- answer
case "ice-candidate": case "ice-candidate":
// TODO // Check that the message contains an ice candidate
candidate, valid := msg.arguments["candidate"].(string)
if (!valid) { break; }
// Acquire the user's display pointer
disp.userMtx.Lock();
// Check that a user is connected to a display
if (disp.user == nil) {
// Release the user's display pointer
disp.userMtx.Unlock();
// Send back an error
sendErrorMessage(sock, http.StatusForbidden);
continue;
}
// Send the ice candidtate to the display
disp.user.iceCandidate(candidate);
// Release the user's display pointer
disp.userMtx.Unlock();
default: default:
// Give up // Give up

20
user.go
View File

@@ -10,6 +10,7 @@ import "github.com/gorilla/websocket"
type User struct { type User struct {
// WebSocket used to communicate with the user // WebSocket used to communicate with the user
sock *websocket.Conn; sock *websocket.Conn;
sockSendMtx sync.Mutex;
// Display mutex // Display mutex
displayMtx sync.Mutex; displayMtx sync.Mutex;
@@ -18,6 +19,23 @@ type User struct {
display *Display; display *Display;
} }
// Send an ICE candiate to the user
func (this *User) iceCandidate(candidate string) {
// Acquire the sending mutex
this.sockSendMtx.Lock()
// Send the candidate
sendMessage(this.sock, Message{
mtype: "ice-candidate",
arguments: map[string]interface{}{
"candidate": candidate,
},
})
// Release the sending mutex
this.sockSendMtx.Unlock()
}
// Connection handler for users // Connection handler for users
func userHandler(sock *websocket.Conn) { func userHandler(sock *websocket.Conn) {
// Initialize the user instance // Initialize the user instance
@@ -170,8 +188,6 @@ func userHandler(sock *websocket.Conn) {
// Send the ice candidtate to the display // Send the ice candidtate to the display
user.display.iceCandidate(candidate); user.display.iceCandidate(candidate);
// TODO: Check error
// Release the user's display pointer // Release the user's display pointer
user.displayMtx.Unlock(); user.displayMtx.Unlock();

View File

@@ -15,16 +15,16 @@
<!-- Connection form --> <!-- Connection form -->
<div id="connForm"> <div id="connForm">
<p class="TV">📺</p><br> <p class="TV">📺</p><br>
<input type="text" id="dispName" placeholder="Display ID" required autocomplete="off" oninput="onDispNameChange()"><br><br> <input type="text" id="dispName" placeholder="Display ID" required autocomplete="off"><br><br>
<input type="text" id="otp" placeholder="OTP" required autocomplete="off" oninput="onOTPChange()"><br><br> <input type="text" id="otp" placeholder="OTP" required autocomplete="off"><br><br>
<button id="connect" onclick="connect()" disabled autocomplete="off">Connect</button> <button id="connect" disabled autocomplete="off">Connect</button>
</div> </div>
<!-- Streaming form --> <!-- Streaming form -->
<div id="streamForm" hidden> <div id="streamForm" hidden>
<h1>You are live!</h1> <h1>You are live!</h1>
<video id="localPlayback" autoplay playsinline></video><br><br> <video id="localPlayback" autoplay playsinline></video><br><br>
<button id="disconnect" onclick="disconnect()">Disconnect</button> <button id="disconnect">Disconnect</button>
</div> </div>
</body> </body>

View File

@@ -4,37 +4,69 @@ let stream = null;
let conn = null; let conn = null;
// GUI Objects // GUI Objects
let connForm = document.querySelector('#connForm'); let connForm = document.getElementById('connForm');
let dispNameTb = document.querySelector('#dispName'); let dispNameTb = document.getElementById('dispName');
let connBtn = document.querySelector('#connect'); let connBtn = document.getElementById('connect');
let pinValForm = document.querySelector('#pinValForm'); let pinValForm = document.getElementById('pinValForm');
let dispPINTb = document.querySelector('#dispPIN'); let dispPINTb = document.getElementById('dispPIN');
let validateBtn = document.querySelector('#validate'); let validateBtn = document.getElementById('validate');
let streamForm = document.querySelector('#streamForm'); let streamForm = document.getElementById('streamForm');
let locPlayback = document.querySelector('#localPlayback'); let locPlayback = document.getElementById('localPlayback');
// Connect to the server using WebSockets // User API class
console.log('Connecting to websocket...') class WisCastUserAPI {
sock = new WebSocket(`ws://${location.host}/sig`); // Socket to the API endpoint
sock.addEventListener('open', async (event) => { #sock;
console.log('Connected to websocket')
// // DEBUGGING ONLY // Endpoint URL
// await sock.send(JSON.stringify({ #endpoint;
// type: 'init',
// pin: dispPINTb.value
// }))
await sock.send(JSON.stringify({ constructor(endpoint) {
type: 'init', // Save the endpoint
clientType: 'user' this.endpoint = endpoint;
})); }
});
sock.addEventListener('message', (event) => { // Connect to the API
console.log(event.data) async connect() {
}); // Connect to the WebSocket endpoint
console.log('Connecting to the API...')
this.#sock = new WebSocket(endpoint);
sock.addEventListener('close', (event) => { // Handle connection
console.log('Disconnected from websocket') sock.addEventListener('open', this.#connectHandler);
});
// Handle messages
sock.addEventListener('message', this.#messageHandler);
// Handle disconnection
sock.addEventListener('close', this.#disconnectHandler);
}
// Connect to a display using its ID and OTP
async connectDisplay(dispID, OTP) {
}
#connectHandler(event) {
console.log('Connected!')
}
#messageHandler(event) {
console.log(event.data)
}
#disconnectHandler(event) {
console.log('Disconnected :/')
}
}
async function main() {
// Create the API connection
const api = new WisCastUserAPI(`ws://${location.host}/sig`);
// Connect to the server
await api.connect();
}
// Run the main function
main();