This commit is contained in:
AlexandreRouma
2025-11-04 16:07:18 -05:00
parent 140bc3c3f5
commit f494612908
11 changed files with 411 additions and 445 deletions

View File

@@ -1,49 +1,122 @@
package main
// Packages
import "github.com/gorilla/websocket"
import "errors"
import "log"
import "net/http"
import "sync"
import "time"
import "github.com/gorilla/websocket"
// Display instance
type Display struct {
// WebSocket used to communicate with the display
sock *websocket.Conn
sockSendMtx sync.Mutex
// User currently connected to the display
user *User
// One-time-pass currently shown on the display
otpMtx sync.Mutex
otp string
// Channel to pass the answer from the display coroutine to the user couroutine
answerCh chan string
}
// Helper function to flush channels
func chFlush(ch *chan string) {
for {
empty := false
select {
// If data is available, read it and try again
case <-*ch:
continue
// If no data is available, stop reading
default:
empty = true
}
if empty { break }
}
}
// Helper function to read from a channel with a timeout
func chReadTimeout(ch *chan string, timeoutMS int) (string, error) {
select {
// If data is available, return it with no error
case data := <-*ch:
return data, nil
// If no data has been received and the timeout is reached, return an error
case <-time.After(timeoutMS * time.Millisecond):
return "", errors.New("timeout")
}
}
// List of all connected displays
var displaysLck sync.Mutex
var displays map[string]*Display
var displays = map[string]*Display{}
// Get the display back to its idle state
func (this *Display) reset() {
// Acquire the sending mutex
this.sockSendMtx.Lock()
// Send a reset command
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
mtype: "reset",
}))
// Release the sending mutex
this.sockSendMtx.Unlock()
}
// Switch the display to streaming mode
func (this *Display) stream() {
// Acquire the sending mutex
this.sockSendMtx.Lock()
// Send a show-pin command
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
mtype: "stream",
}))
// Release the sending mutex
this.sockSendMtx.Unlock()
}
// Send a WebRTC offer to the display and get an answer
func (this *Display) webRTCOffer(offer string, timeoutMS int) string {
// TODO
return ""
func (this *Display) webRTCOffer(offer string, timeoutMS int) (string, error) {
// Flush the answer channel
chFlush(&this.answerCh)
// Acquire the sending mutex
this.sockSendMtx.Lock()
// Send the offer
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
mtype: "webrtc-offer",
arguments: map[string]interface{}{
"offer": offer,
},
}))
// Release the sending mutex
this.sockSendMtx.Unlock()
// TODO: Close the connection if the display failed to respond?
// Receive the answer
return chReadTimeout(&this.answerCh, CONF_TIMEOUT_MS)
}
// Send an ICE candiate to the display
func (this *Display) iceCandidate(candidate string) {
// Acquire the sending mutex
this.sockSendMtx.Lock()
// Send the candidate
sendMessage(this.sock, Message{
mtype: "ice-candidate",
@@ -51,9 +124,95 @@ func (this *Display) iceCandidate(candidate string) {
"candidate": candidate,
},
})
// Release the sending mutex
this.sockSendMtx.Unlock()
}
// Connection handler for displays
func displayHandler(sock *websocket.Conn, dispID string) {
func displayHandler(sock *websocket.Conn, dispID string, otp string) {
// Create the display object
disp := Display{ sock: sock, otp: otp }
// Acquire the sending mutex
disp.sockSendMtx.Lock()
// Acquire the display list
displaysLck.Lock()
// Check that a display with that ID doesn't already exist
if displays[dispID] != nil {
// Release the display list
displaysLck.Unlock()
// Send back an error
sendErrorMessage(sock, http.StatusConflict)
// Release the sending mutex
disp.sockSendMtx.Unlock()
}
// Add the display to the list
displays[dispID] = &disp
// Release the display list
displaysLck.Unlock()
// Send back the config for the display to use
sendMessage(sock, Message{
mtype: "config",
arguments: map[string]interface{}{
"timeout": CONF_TIMEOUT_MS,
"iceServers": CONF_ICE_SERVERS,
},
})
// Release the sending mutex
disp.sockSendMtx.Unlock()
// Log the new display
log.Println("New display: ID='" + dispID + "', OTP='" + otp + "'")
// Message loop
for {
// Receive a message
msg, err := recvMessage(sock, 0)
// Give up on the connection if there was an error
if (err != nil) { break }
// Handle the message depending on its type
switch msg.mtype {
case "otp":
// Check that the message contains an OTP
otp, valid := msg.arguments["otp"].(string)
if (!valid) { break }
// Acquire the display's OTP
disp.otpMtx.Lock()
// Update the OTP
disp.otp = otp
// Release the display's OTP
disp.otpMtx.Unlock()
case "answer":
// Check that the message contains an answer
answer, valid := msg.arguments["answer"].(string)
if (!valid) { break }
// Send the answer through the display's answer channel
disp.answerCh <- answer
case "ice-candidate":
// TODO
default:
// Give up
break
}
}
// TODO: Gracefull disconnect the connected user if there is one
}

View File

@@ -14,9 +14,9 @@ func main() {
http.Handle("/", static)
// Create a handler for the signaling backend
// http.HandleFunc("/sig", wsHandler)
http.HandleFunc("/sig", wsHandler)
// Run the server
err := http.ListenAndServe(":3000", nil)
if( err != nil) { log.Fatal(err) }
if (err != nil) { log.Fatal(err) }
}

View File

@@ -1,48 +1,110 @@
package main
import "encoding/json"
import "errors"
import "time"
import "github.com/gorilla/websocket"
//import "encoding/json"
// Backend message object
type Message struct {
// Type of message
mtype string
mtype string;
// Arguments of the message
arguments map[string]interface{}
arguments map[string]interface{};
}
// Get the display back to its idle state
func encodeMessage(msg Message) []byte {
// TODO
return nil
// Create the message map and set the message type
msgJson := map[string]interface{}{};
msgJson["type"] = msg.mtype;
// Add all arguments
for k, v := range msg.arguments {
// Skip the type key
if (k == "type") { continue };
// Add the key/value to the argument map
msgJson[k] = v;
}
// Serialize the message
data, _ := json.Marshal(msgJson);
// Return the data
return data;
}
// Get the display back to its idle state
func decodeMessage(data []byte) Message {
// TODO
return Message{}
func decodeMessage(data []byte) (Message, error) {
// Attempt to parse the message
var msgJson map[string]interface{};
err := json.Unmarshal(data, &msgJson);
if (err != nil) { return Message{}, err; }
// If no message type is given, return an error
if msgJson["type"] == nil { return Message{}, errors.New("Invalid message"); }
// If the message type is not a string, return an error
mtype, valid := msgJson["type"].(string);
if !valid { return Message{}, errors.New("Invalid message"); }
// Create the message object
msg := Message{ mtype: mtype, arguments: map[string]interface{}{} };
// Extract the arguments
for k, v := range msgJson {
// Skip the type key
if (k == "type") { continue; }
// Add the key/value to the argument map
msg.arguments[k] = v;
}
// Return the decoded message with no error
return msg, nil;
}
// Encode a message and send it over a WebSocket
func sendMessage(sock *websocket.Conn, msg Message) {
// Encode and send the message
sock.WriteMessage(websocket.TextMessage, encodeMessage(msg))
sock.WriteMessage(websocket.TextMessage, encodeMessage(msg));
}
// Receive a message from a WebSocket and decode it
func recvMessage(sock *websocket.Conn, timeoutMS int) Message {
// TODO
return Message{}
func recvMessage(sock *websocket.Conn, timeoutMS int) (Message, error) {
for {
// Configure the timeout
if timeoutMS > 0 {
// Milliseconds given
sock.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(timeoutMS)))
} else {
// No timeout given
sock.SetReadDeadline(time.Time{});
}
// Receive a WebSocket message
mt, msgData, err := sock.ReadMessage();
// If there was an error, give up and return it
if (err != nil) { return Message{}, err; }
// If the message is not a text message, continue waiting
if (mt != websocket.TextMessage) { continue; }
// Return the decoded message
return decodeMessage(msgData);
}
}
// Encode an error message and send it over a WebSocket
func sendErrorMessage(sock *websocket.Conn, err string) {
func sendErrorMessage(sock *websocket.Conn, code int) {
// Send the error message
sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
mtype: "error",
arguments: map[string]interface{}{
"error": err,
"code": code,
},
}))
}));
}

137
user.go
View File

@@ -1,29 +1,27 @@
package main
// Packages
//import "log"
import "github.com/gorilla/websocket"
//import "encoding/json"
import "log"
import "sync"
import "net/http"
import "github.com/gorilla/websocket"
// General client instance
type User struct {
// WebSocket used to communicate with the user
sock *websocket.Conn
sock *websocket.Conn;
// Display mutex
displayMtx sync.Mutex
displayMtx sync.Mutex;
// Display that the user is connecting to
display *Display
display *Display;
}
// TODO: Check type
// Connection handler for users
func userHandler(sock *websocket.Conn) {
// Initialize the user instance
user := User{ sock: sock, display: nil }
user := User{ sock: sock, display: nil };
// Send back the config for the user to use
sendMessage(sock, Message{
@@ -32,110 +30,116 @@ func userHandler(sock *websocket.Conn) {
"timeout": CONF_TIMEOUT_MS,
"iceServers": CONF_ICE_SERVERS,
},
})
});
// Message loop
for {
// Receive a message
msg := recvMessage(sock, 0)
msg, err := recvMessage(sock, 0);
// TODO: exit on error
// Give up on the connection if there was an error
if (err != nil) { break; }
// Handle the message depending on its type
switch msg.mtype {
case "connect":
// Check that a display ID was provided
if msg.arguments["dispID"] == nil {
sendErrorMessage(sock, "Missing display ID")
continue;
}
dispID, valid := msg.arguments["dispID"].(string)
if (!valid) { break; }
// Check that an OTP was provided
if msg.arguments["otp"] == nil {
sendErrorMessage(sock, "Missing OTP")
continue;
}
otp, valid := msg.arguments["otp"].(string)
if (!valid) { break; }
// Acquire the display ID list
displaysLck.Lock()
displaysLck.Lock();
// Check that the display ID exists
dispID := msg.arguments["dispID"].(string)
if displays[dispID] == nil {
if (displays[dispID] == nil) {
// Release the display list
displaysLck.Unlock()
displaysLck.Unlock();
// Send back an error
sendErrorMessage(sock, "Unknown display")
sendErrorMessage(sock, http.StatusNotFound);
continue;
}
// Acquire the displays OTP
displays[dispID].otpMtx.Lock();
// Check the OTP
otp := msg.arguments["otp"].(string)
if otp == "" || otp != displays[dispID].otp {
if (otp == "" || otp != displays[dispID].otp) {
// Release the display's OTP
displays[dispID].otpMtx.Unlock();
// Release the display list
displaysLck.Unlock()
displaysLck.Unlock();
// Send back an error
sendErrorMessage(sock, "Invalid OTP")
sendErrorMessage(sock, http.StatusUnauthorized);
continue;
}
// TODO: Check types
// Release the display's OTP
displays[dispID].otpMtx.Unlock();
// Acquire the user's display pointer
user.displayMtx.Lock()
user.displayMtx.Lock();
// Register the user and display to each other
user.display = displays[dispID]
user.display.user = &user
user.display = displays[dispID];
user.display.user = &user;
// Put the display into streaming mode
user.display.stream()
user.display.stream();
// TODO: Check for error
// Release the user's display pointer
user.displayMtx.Lock()
user.displayMtx.Lock();
// Release the display list
displaysLck.Unlock()
displaysLck.Unlock();
// Log the connection
log.Println("User successfully connected to display: ID='", dispID, "'");
// Notify the user of the successful connection
sendMessage(sock, Message{
mtype: "success",
})
});
case "webrtc-offer":
// Check that the message contains an offer
if msg.arguments["offer"] == nil {
// Send back an error
sendErrorMessage(sock, "No offer given")
continue;
}
// TODO: Check type
offer, valid := msg.arguments["offer"].(string)
if (!valid) { break; }
// Acquire the user's display pointer
user.displayMtx.Lock()
user.displayMtx.Lock();
// Check that the user is connected to a display
if user.display == nil {
if (user.display == nil) {
// Release the user's display pointer
user.displayMtx.Unlock()
user.displayMtx.Unlock();
// Send back an error
sendErrorMessage(sock, "Not connected")
sendErrorMessage(sock, http.StatusForbidden);
continue;
}
// Send the offer to the display and get the response
answer := user.display.webRTCOffer(msg.arguments["offer"].(string), CONF_TIMEOUT_MS)
answer, err := user.display.webRTCOffer(offer, CONF_TIMEOUT_MS);
if (err != nil) {
// Release the user's display pointer
user.displayMtx.Unlock();
// TODO: Check for error
// Send back an error
sendErrorMessage(sock, http.StatusBadGateway);
continue;
}
// Release the user's display pointer
user.displayMtx.Unlock()
user.displayMtx.Unlock();
// Send back the response
sendMessage(sock, Message{
@@ -143,42 +147,39 @@ func userHandler(sock *websocket.Conn) {
arguments: map[string]interface{}{
"answer": answer,
},
})
});
case "ice-candidate":
// Check that the message contains an ice candidate
if msg.arguments["candidate"] == nil {
// Send back an error
sendErrorMessage(sock, "No offer given")
continue;
}
// TODO: Check type
candidate, valid := msg.arguments["candidate"].(string)
if (!valid) { break; }
// Acquire the user's display pointer
user.displayMtx.Lock()
user.displayMtx.Lock();
// Check that the user is connected to a display
if user.display == nil {
if (user.display == nil) {
// Release the user's display pointer
user.displayMtx.Unlock()
user.displayMtx.Unlock();
// Send back an error
sendErrorMessage(sock, "Not connected")
sendErrorMessage(sock, http.StatusForbidden);
continue;
}
// Send the ice candidtate to the display
user.display.iceCandidate(msg.arguments["candidate"].(string))
user.display.iceCandidate(candidate);
// TODO: Check error
// Release the user's display pointer
user.displayMtx.Unlock()
user.displayMtx.Unlock();
default:
// Send back an error
sendErrorMessage(sock, "Invalid message type")
// Give up
break;
}
}
// If the user was connected to a display, disconnect it
// TODO: Gracefull disconnect the connected display if there is one
}

View File

@@ -21,7 +21,10 @@ func wsHandler(respWriter http.ResponseWriter, req *http.Request) {
defer sock.Close()
// Receive the init message
msg := recvMessage(sock, 5000)
msg, err := recvMessage(sock, 5000)
// If there was an error or timeout, give up on the connection
if err != nil { return }
// If it's not an init message, give up
if msg.mtype != "init" { return }
@@ -34,9 +37,14 @@ func wsHandler(respWriter http.ResponseWriter, req *http.Request) {
case "display":
// Check that the display has provided its ID
if msg.arguments["dispID"] == nil { return }
dispID, valid := msg.arguments["dispID"].(string)
if !valid { return }
// Check that the display has provided its OTP
otp, valid := msg.arguments["otp"].(string)
if !valid { return }
// Handle as a display
displayHandler(sock, msg.arguments["dispID"].(string))
displayHandler(sock, dispID, otp)
}
}

View File

@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<!-- Title of the software -->
<title>Quick Screen Share - Screen</title>
<title>WisCast - Display</title>
<!-- Load the stylesheet from the main style file -->
<link rel="stylesheet" href="/css/style.css">
@@ -15,11 +15,8 @@
<div id="idleScreen">
<span id="dispIDLabel">Display ID:</span><br>
<span id="dispID"></span>
</div>
<div id="pinScreen" hidden>
<span id="pinLabel">PIN:</span><br>
<span id="pin"></span>
<span id="otpLabel">OTP:</span><br>
<span id="otp"></span>
</div>
<!-- Video widget -->
@@ -28,9 +25,9 @@
<!-- Footer to show credit to the software and developers -->
<footer id="credits">
Powered by <a href="https://x.com/ryzerth" target="_blank">Quick Screen Share</a>. Made with ❤️ by <a href="https://x.com/ryzerth" target="_blank">Ryzerth</a>
Powered by <a href="https://wiscast.org/" target="_blank">WisCast</a>. Made with ❤️ by <a href="https://x.com/ryzerth" target="_blank">Ryzerth</a>
</footer>
<!-- Main screen code -->
<script src="/scripts/screen.js"></script>
<script src="/scripts/display.js"></script>
</html>

View File

@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<!-- Title of the software -->
<title>Quick Screen Share</title>
<title>WisCast - Connect</title>
<!-- Load the stylesheet from the main style file -->
<link rel="stylesheet" href="/css/style.css">
@@ -30,9 +30,9 @@
<!-- Footer to show credit to the software and developers -->
<footer>
Powered by <a href="https://x.com/ryzerth" target="_blank">Quick Screen Share</a>. Made with ❤️ by <a href="https://x.com/ryzerth" target="_blank">Ryzerth</a>
Powered by <a href="https://wiscast.org/" target="_blank">WisCast</a>. Made with ❤️ by <a href="https://x.com/ryzerth" target="_blank">Ryzerth</a>
</footer>
<!-- Main client code -->
<script src="/scripts/client.js"></script>
<script src="/scripts/user.js"></script>
</html>

View File

@@ -1,185 +0,0 @@
// Streaming objects
let sock = null;
let stream = null;
let conn = null;
// GUI Objects
let connForm = document.querySelector('#connForm');
let dispNameTb = document.querySelector('#dispName');
let connBtn = document.querySelector('#connect');
let pinValForm = document.querySelector('#pinValForm');
let dispPINTb = document.querySelector('#dispPIN');
let validateBtn = document.querySelector('#validate');
let streamForm = document.querySelector('#streamForm');
let locPlayback = document.querySelector('#localPlayback');
// Make sure the connect button cannot be pressed if there is no display name
function onDispNameChange() {
connBtn.disabled = !(dispNameTb.value.length > 0);
}
function onPINChange() {
validateBtn.disabled = !(dispPINTb.value.length === 6);
}
// Handle enter key pressed
dispNameTb.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !connBtn.disabled) {
connBtn.click();
}
})
dispPINTb.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !validateBtn.disabled) {
validateBtn.click();
}
})
// WebSocket message handler
async function onMessage(data) {
// Parse the message
console.log(data)
const msg = JSON.parse(data);
// Process depending on the type
switch (msg.type) {
case 'disp-ok':
// Switch to the PIN screen
connForm.hidden = true;
pinValForm.hidden = false;
dispPINTb.focus();
dispPINTb.select();
break;
case 'disp-error':
// Show the error
// TODO
console.log('ERROR: ' + msg.error)
break;
case 'auth-ok':
// Show the status update
validateBtn.textContent = 'Starting the Stream...';
await startStream();
break;
case 'answer':
console.log('Got answer')
// Pass on the offer to WebRTC
await conn.setRemoteDescription(new RTCSessionDescription(msg.answer));
break;
case 'ice-candidate':
console.log('Got ice candidate')
// Add the ice candidate to the WebRTC connection
await conn.addIceCandidate(msg.candidate);
break;
case 'disconnect':
// Reload the page
location.reload();
break;
default:
console.dir(msg)
break;
}
}
async function startStream() {
// Get the stream for the screen
stream = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: 'always' } });
// Create the connection
conn = new RTCPeerConnection({'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]});
// Handle ice candidates
conn.addEventListener('icecandidate', async (event) => {
// If there is a new candidate, send it to the peer through websockets
if (event.candidate) {
await sock.send(JSON.stringify({
type: 'ice-candidate',
candidate: event.candidate
}))
}
});
// Handle connection and disconnection of peer
conn.addEventListener('connectionstatechange', (event) => {
switch (conn.connectionState) {
case 'connected':
// Switch to stream screen
pinValForm.hidden = true;
streamForm.hidden = false;
locPlayback.srcObject = stream;
console.log("Connected!")
break;
case 'disconnected':
console.log("Disconnected.")
// Reload the page to ensure the state is complete reset
location.reload();
break;
default:
break;
}
});
// Start streaming the screen
stream.getTracks().forEach(track => {
conn.addTrack(track, stream);
});
// If the stream ends, reload the page
stream.getVideoTracks()[0].addEventListener('ended', (event) => {
location.reload();
})
// Create and send an offer
const offer = await conn.createOffer();
await conn.setLocalDescription(offer);
await sock.send(JSON.stringify({
type: 'offer',
offer: offer
}))
}
async function connect() {
// Disable the connect button and show status
dispNameTb.disabled = true;
connBtn.disabled = true;
connBtn.textContent = 'Connecting...';
// Send a connect command to the server
await sock.send(JSON.stringify({
type: 'connect',
dispID: dispNameTb.value
}));
}
async function validatePIN() {
// Disable the validate button and show status
dispPINTb.disabled = true;
validateBtn.disabled = true;
validateBtn.textContent = 'Checking the PIN...';
// Send the validate pin command to the server
await sock.send(JSON.stringify({
type: 'validate-pin',
pin: dispPINTb.value
}));
}
async function disconnect() {
// Just reload the page
location.reload();
}
// Connect to the server using WebSockets
console.log('Connecting to websocket...')
sock = new WebSocket(`ws://${location.host}/sig`);
sock.addEventListener('open', async (event) => {
console.log('Connected to websocket')
});
sock.addEventListener('message', (event) => { onMessage(event.data); })

39
www/scripts/display.js Normal file
View File

@@ -0,0 +1,39 @@
// Streaming objects
let sock = null;
let conn = null;
// GUI Objects
let idleScreen = document.querySelector('#idleScreen');
let dispIDSpan = document.querySelector('#dispID');
let pinScreen = document.querySelector('#pinScreen');
let pinSpan = document.querySelector('#pin');
let playback = document.querySelector('#playback');
let credits = document.querySelector('#credits');
// Connect to the server using WebSockets
console.log('Connecting to websocket...')
sock = new WebSocket(`ws://${location.host}/sig`);
sock.addEventListener('open', async (event) => {
console.log('Connected to websocket')
// // DEBUGGING ONLY
// await sock.send(JSON.stringify({
// type: 'init',
// pin: dispPINTb.value
// }))
await sock.send(JSON.stringify({
type: 'init',
clientType: 'display',
dispID: 'TEST',
otp: '123456'
}));
});
sock.addEventListener('message', (event) => {
console.log(event.data)
});
sock.addEventListener('close', (event) => {
console.log('Disconnected from websocket')
});

View File

@@ -1,155 +0,0 @@
// Get or generate a display ID
let params = new URLSearchParams(document.location.search);
let dispID = params.get("dispID");
if (dispID === null) {
// Generate a random name (TODO)
dispID = self.crypto.randomUUID().substring(0, 6);
}
// App objects
let sock = null;
let conn = null;
// GUI Objects
let idleScreen = document.querySelector('#idleScreen');
let dispIDSpan = document.querySelector('#dispID');
let pinScreen = document.querySelector('#pinScreen');
let pinSpan = document.querySelector('#pin');
let playback = document.querySelector('#playback');
let credits = document.querySelector('#credits');
// Show the display ID
dispIDSpan.textContent = dispID;
async function reset() {
// Completely reset the state
playback.hidden = true;
pinScreen.hidden = true;
idleScreen.hidden = false;
credits.hidden = false;
playback.srcObject = null;
conn = null;
// Initialize WebRTC
await initRTC();
}
// WebSocket message handler
async function onMessage(data) {
// Parse the message
console.log(data)
const msg = JSON.parse(data);
// Process depending on the type
switch (msg.type) {
case 'conn-req':
// Show the pin
pinSpan.textContent = msg.pin;
// Switch to pin mode
idleScreen.hidden = true;
pinScreen.hidden = false;
break;
case 'offer':
console.log('Got offer')
// Pass on the offer to WebRTC
await conn.setRemoteDescription(new RTCSessionDescription(msg.offer));
// Create an answer
answer = await conn.createAnswer();
await conn.setLocalDescription(answer);
// Encode and send the answer
await sock.send(JSON.stringify({
type: 'answer',
answer: answer
}))
break;
case 'ice-candidate':
console.log('Got ice candidate')
// Add the ice candidate to the WebRTC connection
await conn.addIceCandidate(msg.candidate);
break;
case 'auth-ok':
break;
case 'disconnect':
// Reset the display
reset();
break;
default:
break;
}
}
async function initRTC() {
// Create the WebRTC connection
conn = new RTCPeerConnection({'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]});
// Handle new ice candidates
conn.addEventListener('icecandidate', async (event) => {
// If there is a new candidate, send it to the peer through websockets
if (event.candidate) {
await sock.send(JSON.stringify({
type: 'ice-candidate',
candidate: event.candidate
}))
}
});
// Handle connection and disconnection of peer
conn.addEventListener('connectionstatechange', (event) => {
switch (conn.connectionState) {
case 'connected':
// Switch to playback mode
pinScreen.hidden = true;
credits.hidden = true;
playback.hidden = false;
break;
case 'disconnected':
// Reset the display
reset();
break;
default:
break;
}
});
// Send remote stream to the playback widget
conn.addEventListener('track', (event) => {
const [remoteStream] = event.streams;
playback.srcObject = remoteStream;
});
}
// Main function for the app
async function main() {
// Connect to the server using WebSockets
console.log('Connecting to websocket...')
sock = new WebSocket(`ws://${location.host}/sig`);
// Add handlers for the socket
sock.addEventListener('message', (event) => { onMessage(event.data); })
sock.addEventListener('open', async (event) => {
console.log('Connected to websocket')
// Tell the server that this is a screen
await sock.send(JSON.stringify({
type: 'screen',
name: dispID
}))
// Initialize WebRTC
await initRTC();
});
}
// Run the main function
main();

40
www/scripts/user.js Normal file
View File

@@ -0,0 +1,40 @@
// Streaming objects
let sock = null;
let stream = null;
let conn = null;
// GUI Objects
let connForm = document.querySelector('#connForm');
let dispNameTb = document.querySelector('#dispName');
let connBtn = document.querySelector('#connect');
let pinValForm = document.querySelector('#pinValForm');
let dispPINTb = document.querySelector('#dispPIN');
let validateBtn = document.querySelector('#validate');
let streamForm = document.querySelector('#streamForm');
let locPlayback = document.querySelector('#localPlayback');
// Connect to the server using WebSockets
console.log('Connecting to websocket...')
sock = new WebSocket(`ws://${location.host}/sig`);
sock.addEventListener('open', async (event) => {
console.log('Connected to websocket')
// // DEBUGGING ONLY
// await sock.send(JSON.stringify({
// type: 'init',
// pin: dispPINTb.value
// }))
await sock.send(JSON.stringify({
type: 'init',
clientType: 'user'
}));
});
sock.addEventListener('message', (event) => {
console.log(event.data)
});
sock.addEventListener('close', (event) => {
console.log('Disconnected from websocket')
});