Skip to content

Instantly share code, notes, and snippets.

@mmitou
Created May 23, 2021 09:20
Show Gist options
  • Save mmitou/c8afd108ba195b8b3acceb7ea4301b94 to your computer and use it in GitHub Desktop.
Save mmitou/c8afd108ba195b8b3acceb7ea4301b94 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>signaling</title>
</head>
<body>
<div class="localVideo">
<video width="320" height="240" style="border: 1px solid black;" autoplay></video>
<button >open camera</button>
</div>
<div class="remoteVideo">
<video width="320" height="240" style="border: 1px solid black;" autoplay></video>
</div>
<div class="websocketControl">
<button class="websocketControl_openCloseButton">open</button>
</div>
<div class="input">
<input type="text">
<button class="input_sendButton">send</button>
</div>
<div class="output">
</div>
<script src="main.js"></script>
</body>
</html>
package main
import (
"context"
"fmt"
"time"
"github.com/gorilla/websocket"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
)
var upgrader = websocket.Upgrader{}
type wsClient struct {
id string
conn *websocket.Conn
done <-chan struct{}
}
type websocketMessage struct {
MessageType int
Payload []byte
}
type Message struct {
id string
websocketMessage
err error
}
func (c wsClient) close() {
c.conn.Close()
}
func (c wsClient) runReciever(srvrCh chan<- Message) {
go func() {
for {
select {
case <-c.done:
return
default:
mt, p, err := c.conn.ReadMessage()
if err != nil {
srvrCh <- Message{id: c.id, err: err}
} else {
srvrCh <- Message{c.id, websocketMessage{MessageType: mt, Payload: p}, nil}
}
}
}
}()
}
func (c wsClient) runSender(srvrCh chan<- Message) chan<- websocketMessage {
ch := make(chan websocketMessage, 10)
go func() {
for {
select {
case <-c.done:
return
case msg := <-ch:
err := c.conn.WriteMessage(msg.MessageType, msg.Payload)
if err != nil {
srvrCh <- Message{id: c.id, err: err}
}
}
}
}()
return ch
}
type connection struct {
wsClient wsClient
sndrCh chan<- websocketMessage
done chan<- struct{}
}
func (c wsClient) connect(msgCh chan<- Message) connection {
done := make(chan struct{})
c.done = done
c.runReciever(msgCh)
sndrCh := c.runSender(msgCh)
return connection{wsClient: c, sndrCh: sndrCh, done: done}
}
func (c connection) close() {
close(c.done)
c.wsClient.close()
}
func runEchoServer(ctx context.Context) chan<- wsClient {
registrar := make(chan wsClient)
go func() {
clients := make(map[string]connection)
msgCh := make(chan Message)
defer func() {
for _, c := range clients {
c.close()
}
close(msgCh)
}()
for {
select {
case <-ctx.Done():
return
case c := <-registrar:
clients[c.id] = c.connect(msgCh)
case msg := <-msgCh:
if msg.err != nil {
log.Error().Err(msg.err).Msg("")
} else {
for _, connection := range clients {
connection.sndrCh <- msg.websocketMessage
}
}
}
}
}()
return registrar
}
func hub(registrar chan<- wsClient) func(c echo.Context) error {
i := 0
return func(c echo.Context) error {
i++
w := c.Response()
r := c.Request()
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return err
}
registrar <- wsClient{id: fmt.Sprintf("hello%d", i), conn: conn}
return nil
}
}
func main() {
zerolog.TimeFieldFormat = time.RFC3339Nano
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
zerolog.SetGlobalLevel(zerolog.DebugLevel)
e := echo.New()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
registrar := runEchoServer(ctx)
e.HTTPErrorHandler = func(err error, c echo.Context) {
log.Debug().Err(err).Msg(fmt.Sprintf("%+v", err))
e.DefaultHTTPErrorHandler(err, c)
}
e.GET("/ws", hub(registrar))
e.Static("/", "./web")
e.Logger.Fatal(e.Start(":8080"))
}
"use strict";
let ws = null;
const print = ((output) => {
return (msg) => {
const d = document.createElement("div");
d.textContent = msg;
output.appendChild(d);
output.scroll(0, output.scrollHeight);
};
})(document.querySelector("div.output"));
const initWebsocket = (websocket) => {
websocket.addEventListener("open", () => print("open"));
websocket.addEventListener("close", () => {
print("close");
ws = null;
});
websocket.addEventListener("message", (event) => {
print("response: " + event.data);
});
websocket.addEventListener("error", (event) => {
console.log("error:", event.data);
});
return websocket;
};
document
.querySelector(".websocketControl_openCloseButton")
.addEventListener("click", (event) => {
console.log("openClose button clicked", ws == null);
if (ws == null) {
// open websocket
ws = new WebSocket("ws://localhost:8080/ws");
initWebsocket(ws);
// change button text for close
event.target.innerText = "close";
} else {
ws.close();
// change button text for open
event.target.innerText = "open";
}
});
document
.querySelector(".input_sendButton")
.addEventListener("click", (event) => {
if (ws == null) {
print("ws is null");
return;
}
const input = document.querySelector("input");
print("send: " + input.value);
ws.send(input.value);
return;
});
// video control
const showVideoButton = document.querySelector(".localVideo > button");
showVideoButton.addEventListener("click", async (event) => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: true,
});
const video = document.querySelector(".localVideo > video");
// const videoTracks = stream.getVideoTracks();
// window.stream = stream;
video.srcObject = stream;
event.target.disabled = true;
} catch (err) {
console.log(err);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment