mirror of
https://github.com/AlexandreRouma/wiscast.git
synced 2026-04-20 08:22:42 +00:00
progress
This commit is contained in:
171
display.go
171
display.go
@@ -1,49 +1,122 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// Packages
|
// Packages
|
||||||
import "github.com/gorilla/websocket"
|
import "errors"
|
||||||
|
import "log"
|
||||||
|
import "net/http"
|
||||||
import "sync"
|
import "sync"
|
||||||
|
import "time"
|
||||||
|
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
|
||||||
|
|
||||||
// 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
|
||||||
otp string
|
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
|
// List of all connected displays
|
||||||
var displaysLck sync.Mutex
|
var displaysLck sync.Mutex
|
||||||
var displays map[string]*Display
|
var displays = map[string]*Display{}
|
||||||
|
|
||||||
// Get the display back to its idle state
|
// Get the display back to its idle state
|
||||||
func (this *Display) reset() {
|
func (this *Display) reset() {
|
||||||
|
// Acquire the sending mutex
|
||||||
|
this.sockSendMtx.Lock()
|
||||||
|
|
||||||
// Send a reset command
|
// Send a reset command
|
||||||
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
||||||
mtype: "reset",
|
mtype: "reset",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Release the sending mutex
|
||||||
|
this.sockSendMtx.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch the display to streaming mode
|
// Switch the display to streaming mode
|
||||||
func (this *Display) stream() {
|
func (this *Display) stream() {
|
||||||
|
// Acquire the sending mutex
|
||||||
|
this.sockSendMtx.Lock()
|
||||||
|
|
||||||
// Send a show-pin command
|
// Send a show-pin command
|
||||||
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
this.sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
||||||
mtype: "stream",
|
mtype: "stream",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Release the sending mutex
|
||||||
|
this.sockSendMtx.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a WebRTC offer to the display and get an answer
|
// Send a WebRTC offer to the display and get an answer
|
||||||
func (this *Display) webRTCOffer(offer string, timeoutMS int) string {
|
func (this *Display) webRTCOffer(offer string, timeoutMS int) (string, error) {
|
||||||
// TODO
|
// Flush the answer channel
|
||||||
return ""
|
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
|
// Send an ICE candiate to the display
|
||||||
func (this *Display) iceCandidate(candidate string) {
|
func (this *Display) iceCandidate(candidate string) {
|
||||||
|
// Acquire the sending mutex
|
||||||
|
this.sockSendMtx.Lock()
|
||||||
|
|
||||||
// Send the candidate
|
// Send the candidate
|
||||||
sendMessage(this.sock, Message{
|
sendMessage(this.sock, Message{
|
||||||
mtype: "ice-candidate",
|
mtype: "ice-candidate",
|
||||||
@@ -51,9 +124,95 @@ func (this *Display) iceCandidate(candidate string) {
|
|||||||
"candidate": candidate,
|
"candidate": candidate,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Release the sending mutex
|
||||||
|
this.sockSendMtx.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection handler for displays
|
// 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
|
||||||
}
|
}
|
||||||
4
main.go
4
main.go
@@ -14,9 +14,9 @@ func main() {
|
|||||||
http.Handle("/", static)
|
http.Handle("/", static)
|
||||||
|
|
||||||
// Create a handler for the signaling backend
|
// Create a handler for the signaling backend
|
||||||
// http.HandleFunc("/sig", wsHandler)
|
http.HandleFunc("/sig", wsHandler)
|
||||||
|
|
||||||
// Run the server
|
// Run the server
|
||||||
err := http.ListenAndServe(":3000", nil)
|
err := http.ListenAndServe(":3000", nil)
|
||||||
if( err != nil) { log.Fatal(err) }
|
if (err != nil) { log.Fatal(err) }
|
||||||
}
|
}
|
||||||
92
message.go
92
message.go
@@ -1,48 +1,110 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
import "errors"
|
||||||
|
import "time"
|
||||||
import "github.com/gorilla/websocket"
|
import "github.com/gorilla/websocket"
|
||||||
//import "encoding/json"
|
|
||||||
|
|
||||||
// Backend message object
|
// Backend message object
|
||||||
type Message struct {
|
type Message struct {
|
||||||
// Type of message
|
// Type of message
|
||||||
mtype string
|
mtype string;
|
||||||
|
|
||||||
// Arguments of the message
|
// Arguments of the message
|
||||||
arguments map[string]interface{}
|
arguments map[string]interface{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the display back to its idle state
|
// Get the display back to its idle state
|
||||||
func encodeMessage(msg Message) []byte {
|
func encodeMessage(msg Message) []byte {
|
||||||
// TODO
|
// Create the message map and set the message type
|
||||||
return nil
|
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
|
// Get the display back to its idle state
|
||||||
func decodeMessage(data []byte) Message {
|
func decodeMessage(data []byte) (Message, error) {
|
||||||
// TODO
|
// Attempt to parse the message
|
||||||
return 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
|
// Encode a message and send it over a WebSocket
|
||||||
func sendMessage(sock *websocket.Conn, msg Message) {
|
func sendMessage(sock *websocket.Conn, msg Message) {
|
||||||
// Encode and send the 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
|
// Receive a message from a WebSocket and decode it
|
||||||
func recvMessage(sock *websocket.Conn, timeoutMS int) Message {
|
func recvMessage(sock *websocket.Conn, timeoutMS int) (Message, error) {
|
||||||
// TODO
|
for {
|
||||||
return Message{}
|
// 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
|
// 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
|
// Send the error message
|
||||||
sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
sock.WriteMessage(websocket.TextMessage, encodeMessage(Message{
|
||||||
mtype: "error",
|
mtype: "error",
|
||||||
arguments: map[string]interface{}{
|
arguments: map[string]interface{}{
|
||||||
"error": err,
|
"code": code,
|
||||||
},
|
},
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
137
user.go
137
user.go
@@ -1,29 +1,27 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// Packages
|
// Packages
|
||||||
//import "log"
|
import "log"
|
||||||
import "github.com/gorilla/websocket"
|
|
||||||
//import "encoding/json"
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
import "net/http"
|
||||||
|
import "github.com/gorilla/websocket"
|
||||||
|
|
||||||
// General client instance
|
// General client instance
|
||||||
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;
|
||||||
|
|
||||||
// Display mutex
|
// Display mutex
|
||||||
displayMtx sync.Mutex
|
displayMtx sync.Mutex;
|
||||||
|
|
||||||
// Display that the user is connecting to
|
// Display that the user is connecting to
|
||||||
display *Display
|
display *Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check type
|
|
||||||
|
|
||||||
// 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
|
||||||
user := User{ sock: sock, display: nil }
|
user := User{ sock: sock, display: nil };
|
||||||
|
|
||||||
// Send back the config for the user to use
|
// Send back the config for the user to use
|
||||||
sendMessage(sock, Message{
|
sendMessage(sock, Message{
|
||||||
@@ -32,110 +30,116 @@ func userHandler(sock *websocket.Conn) {
|
|||||||
"timeout": CONF_TIMEOUT_MS,
|
"timeout": CONF_TIMEOUT_MS,
|
||||||
"iceServers": CONF_ICE_SERVERS,
|
"iceServers": CONF_ICE_SERVERS,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// Message loop
|
// Message loop
|
||||||
for {
|
for {
|
||||||
// Receive a message
|
// 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
|
// Handle the message depending on its type
|
||||||
switch msg.mtype {
|
switch msg.mtype {
|
||||||
case "connect":
|
case "connect":
|
||||||
// Check that a display ID was provided
|
// Check that a display ID was provided
|
||||||
if msg.arguments["dispID"] == nil {
|
dispID, valid := msg.arguments["dispID"].(string)
|
||||||
sendErrorMessage(sock, "Missing display ID")
|
if (!valid) { break; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that an OTP was provided
|
// Check that an OTP was provided
|
||||||
if msg.arguments["otp"] == nil {
|
otp, valid := msg.arguments["otp"].(string)
|
||||||
sendErrorMessage(sock, "Missing OTP")
|
if (!valid) { break; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire the display ID list
|
// Acquire the display ID list
|
||||||
displaysLck.Lock()
|
displaysLck.Lock();
|
||||||
|
|
||||||
// Check that the display ID exists
|
// Check that the display ID exists
|
||||||
dispID := msg.arguments["dispID"].(string)
|
if (displays[dispID] == nil) {
|
||||||
if displays[dispID] == nil {
|
|
||||||
// Release the display list
|
// Release the display list
|
||||||
displaysLck.Unlock()
|
displaysLck.Unlock();
|
||||||
|
|
||||||
// Send back an error
|
// Send back an error
|
||||||
sendErrorMessage(sock, "Unknown display")
|
sendErrorMessage(sock, http.StatusNotFound);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Acquire the displays OTP
|
||||||
|
displays[dispID].otpMtx.Lock();
|
||||||
|
|
||||||
// Check the OTP
|
// 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
|
// Release the display list
|
||||||
displaysLck.Unlock()
|
displaysLck.Unlock();
|
||||||
|
|
||||||
// Send back an error
|
// Send back an error
|
||||||
sendErrorMessage(sock, "Invalid OTP")
|
sendErrorMessage(sock, http.StatusUnauthorized);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check types
|
// Release the display's OTP
|
||||||
|
displays[dispID].otpMtx.Unlock();
|
||||||
|
|
||||||
// Acquire the user's display pointer
|
// Acquire the user's display pointer
|
||||||
user.displayMtx.Lock()
|
user.displayMtx.Lock();
|
||||||
|
|
||||||
// Register the user and display to each other
|
// Register the user and display to each other
|
||||||
user.display = displays[dispID]
|
user.display = displays[dispID];
|
||||||
user.display.user = &user
|
user.display.user = &user;
|
||||||
|
|
||||||
// Put the display into streaming mode
|
// Put the display into streaming mode
|
||||||
user.display.stream()
|
user.display.stream();
|
||||||
|
|
||||||
// TODO: Check for error
|
// TODO: Check for error
|
||||||
|
|
||||||
// Release the user's display pointer
|
// Release the user's display pointer
|
||||||
user.displayMtx.Lock()
|
user.displayMtx.Lock();
|
||||||
|
|
||||||
// Release the display list
|
// 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
|
// Notify the user of the successful connection
|
||||||
sendMessage(sock, Message{
|
sendMessage(sock, Message{
|
||||||
mtype: "success",
|
mtype: "success",
|
||||||
})
|
});
|
||||||
|
|
||||||
case "webrtc-offer":
|
case "webrtc-offer":
|
||||||
// Check that the message contains an offer
|
// Check that the message contains an offer
|
||||||
if msg.arguments["offer"] == nil {
|
offer, valid := msg.arguments["offer"].(string)
|
||||||
// Send back an error
|
if (!valid) { break; }
|
||||||
sendErrorMessage(sock, "No offer given")
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check type
|
|
||||||
|
|
||||||
// Acquire the user's display pointer
|
// Acquire the user's display pointer
|
||||||
user.displayMtx.Lock()
|
user.displayMtx.Lock();
|
||||||
|
|
||||||
// Check that the user is connected to a display
|
// Check that the user is connected to a display
|
||||||
if user.display == nil {
|
if (user.display == nil) {
|
||||||
// Release the user's display pointer
|
// Release the user's display pointer
|
||||||
user.displayMtx.Unlock()
|
user.displayMtx.Unlock();
|
||||||
|
|
||||||
// Send back an error
|
// Send back an error
|
||||||
sendErrorMessage(sock, "Not connected")
|
sendErrorMessage(sock, http.StatusForbidden);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the offer to the display and get the response
|
// 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
|
// Release the user's display pointer
|
||||||
user.displayMtx.Unlock()
|
user.displayMtx.Unlock();
|
||||||
|
|
||||||
// Send back the response
|
// Send back the response
|
||||||
sendMessage(sock, Message{
|
sendMessage(sock, Message{
|
||||||
@@ -143,42 +147,39 @@ func userHandler(sock *websocket.Conn) {
|
|||||||
arguments: map[string]interface{}{
|
arguments: map[string]interface{}{
|
||||||
"answer": answer,
|
"answer": answer,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
case "ice-candidate":
|
case "ice-candidate":
|
||||||
// Check that the message contains an ice candidate
|
// Check that the message contains an ice candidate
|
||||||
if msg.arguments["candidate"] == nil {
|
candidate, valid := msg.arguments["candidate"].(string)
|
||||||
// Send back an error
|
if (!valid) { break; }
|
||||||
sendErrorMessage(sock, "No offer given")
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check type
|
|
||||||
|
|
||||||
// Acquire the user's display pointer
|
// Acquire the user's display pointer
|
||||||
user.displayMtx.Lock()
|
user.displayMtx.Lock();
|
||||||
|
|
||||||
// Check that the user is connected to a display
|
// Check that the user is connected to a display
|
||||||
if user.display == nil {
|
if (user.display == nil) {
|
||||||
// Release the user's display pointer
|
// Release the user's display pointer
|
||||||
user.displayMtx.Unlock()
|
user.displayMtx.Unlock();
|
||||||
|
|
||||||
// Send back an error
|
// Send back an error
|
||||||
sendErrorMessage(sock, "Not connected")
|
sendErrorMessage(sock, http.StatusForbidden);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the ice candidtate to the display
|
// 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
|
// Release the user's display pointer
|
||||||
user.displayMtx.Unlock()
|
user.displayMtx.Unlock();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Send back an error
|
// Give up
|
||||||
sendErrorMessage(sock, "Invalid message type")
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user was connected to a display, disconnect it
|
// TODO: Gracefull disconnect the connected display if there is one
|
||||||
}
|
}
|
||||||
14
wshandler.go
14
wshandler.go
@@ -21,7 +21,10 @@ func wsHandler(respWriter http.ResponseWriter, req *http.Request) {
|
|||||||
defer sock.Close()
|
defer sock.Close()
|
||||||
|
|
||||||
// Receive the init message
|
// 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 it's not an init message, give up
|
||||||
if msg.mtype != "init" { return }
|
if msg.mtype != "init" { return }
|
||||||
@@ -34,9 +37,14 @@ func wsHandler(respWriter http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
case "display":
|
case "display":
|
||||||
// Check that the display has provided its ID
|
// 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
|
// Handle as a display
|
||||||
displayHandler(sock, msg.arguments["dispID"].(string))
|
displayHandler(sock, dispID, otp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
<!-- Title of the software -->
|
<!-- Title of the software -->
|
||||||
<title>Quick Screen Share - Screen</title>
|
<title>WisCast - Display</title>
|
||||||
|
|
||||||
<!-- Load the stylesheet from the main style file -->
|
<!-- Load the stylesheet from the main style file -->
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
@@ -15,11 +15,8 @@
|
|||||||
<div id="idleScreen">
|
<div id="idleScreen">
|
||||||
<span id="dispIDLabel">Display ID:</span><br>
|
<span id="dispIDLabel">Display ID:</span><br>
|
||||||
<span id="dispID"></span>
|
<span id="dispID"></span>
|
||||||
</div>
|
<span id="otpLabel">OTP:</span><br>
|
||||||
|
<span id="otp"></span>
|
||||||
<div id="pinScreen" hidden>
|
|
||||||
<span id="pinLabel">PIN:</span><br>
|
|
||||||
<span id="pin"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Video widget -->
|
<!-- Video widget -->
|
||||||
@@ -28,9 +25,9 @@
|
|||||||
|
|
||||||
<!-- Footer to show credit to the software and developers -->
|
<!-- Footer to show credit to the software and developers -->
|
||||||
<footer id="credits">
|
<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>
|
</footer>
|
||||||
|
|
||||||
<!-- Main screen code -->
|
<!-- Main screen code -->
|
||||||
<script src="/scripts/screen.js"></script>
|
<script src="/scripts/display.js"></script>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
<!-- Title of the software -->
|
<!-- Title of the software -->
|
||||||
<title>Quick Screen Share</title>
|
<title>WisCast - Connect</title>
|
||||||
|
|
||||||
<!-- Load the stylesheet from the main style file -->
|
<!-- Load the stylesheet from the main style file -->
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
@@ -30,9 +30,9 @@
|
|||||||
|
|
||||||
<!-- Footer to show credit to the software and developers -->
|
<!-- Footer to show credit to the software and developers -->
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
|
|
||||||
<!-- Main client code -->
|
<!-- Main client code -->
|
||||||
<script src="/scripts/client.js"></script>
|
<script src="/scripts/user.js"></script>
|
||||||
</html>
|
</html>
|
||||||
@@ -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
39
www/scripts/display.js
Normal 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')
|
||||||
|
});
|
||||||
@@ -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
40
www/scripts/user.js
Normal 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')
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user