diff --git a/Dockerfile b/Dockerfile index 0546d7ac..fb7508dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get install libvpx-dev -y RUN go get github.com/pions/webrtc #RUN go get github.com/gordonklaus/portaudio RUN go get github.com/gorilla/mux +RUN go get github.com/gorilla/websocket RUN go install github.com/giongto35/cloud-game EXPOSE 8000 diff --git a/games/Super Mario Bros 3.nes b/games/Super Mario Bros 3.nes new file mode 100644 index 00000000..d0f6a272 Binary files /dev/null and b/games/Super Mario Bros 3.nes differ diff --git a/index.html b/index_http.html similarity index 69% rename from index.html rename to index_http.html index 21e4429d..ad7c5adb 100644 --- a/index.html +++ b/index_http.html @@ -7,7 +7,27 @@ textarea { } + + + +

+

+ +

Instruction

Use Up, Down, Left, Right to Move
Z to jump (A)
@@ -15,20 +35,30 @@ textarea { C is start button
V is select button
- Fullscreen media for better gaming experience
-
+ +
- -
+

Log:

+

 
 
- Refresh to retry + 🎮Refresh to retry when checking is too long
diff --git a/index_ws.html b/index_ws.html new file mode 100644 index 00000000..7774dc16 --- /dev/null +++ b/index_ws.html @@ -0,0 +1,283 @@ + + + + + + + +

+ +

+ +

Instruction

+
+ Use Up, Down, Left, Right to Move
+ Z to jump (A)
+ X to sprint (B)
+ C is start button
+ V is select button
+ + +

+ +

Log:

+

+
+
+ 🎮Refresh to retry when checking is too long +
+ + diff --git a/main.go b/main.go index 80116e84..6f8b27bc 100644 --- a/main.go +++ b/main.go @@ -1,26 +1,36 @@ package main import ( + pionRTC "github.com/pions/webrtc" + "os" + "fmt" "image" "io/ioutil" "log" "net/http" - // "time" - "github.com/giongto35/cloud-game/ui" "github.com/giongto35/cloud-game/util" "github.com/giongto35/cloud-game/webrtc" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + + "encoding/json" ) // var webRTC *webrtc.WebRTC var width = 256 var height = 240 -var gameName = "Contra.nes" +var index string = "./index_http.html" -// var FPS = 60 +var upgrader = websocket.Upgrader{} + +type WSPacket struct { + ID string `json:"id"` + Data string `json:"data"` +} func init() { } @@ -30,23 +40,127 @@ func startGame(path string, imageChannel chan *image.RGBA, inputChannel chan int } func main() { + fmt.Printf("Usage: %s [ws]\n", os.Args[0]) fmt.Println("http://localhost:8000") + if len(os.Args) > 1 { + log.Println("Using websocket") + index = "./index_ws.html" - router := mux.NewRouter() - router.HandleFunc("/", getWeb).Methods("GET") - router.HandleFunc("/session", postSession).Methods("POST") - - http.ListenAndServe(":8000", router) + // ignore origin + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + http.HandleFunc("/", getWeb) + http.HandleFunc("/ws", ws) + http.ListenAndServe(":8000", nil) + } else { + log.Println("Using http") + router := mux.NewRouter() + router.HandleFunc("/", getWeb).Methods("GET") + router.HandleFunc("/session", postSession).Methods("POST") + http.ListenAndServe(":8000", router) + } } func getWeb(w http.ResponseWriter, r *http.Request) { - bs, err := ioutil.ReadFile("./index.html") + bs, err := ioutil.ReadFile(index) if err != nil { log.Fatal(err) } w.Write(bs) } +func ws(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + + log.Println("New Connection") + webRTC := webrtc.NewWebRTC() + + // streaming game + + // start new games and webrtc stuff? + isDone := false + + var gameName string + + for !isDone { + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("[!] read:", err) + break + } + + req := WSPacket{} + err = json.Unmarshal(message, &req) + if err != nil { + log.Println("[!] json unmarshal:", err) + break + } + // log.Println(req) + + // connectivity + res := WSPacket{} + switch req.ID { + case "ping": + gameName = req.Data + log.Println("Ping from server with game:", gameName) + res.ID = "pong" + + case "sdp": + log.Println("Received user SDP") + localSession, err := webRTC.StartClient(req.Data, width, height) + if err != nil { + log.Fatalln(err) + } + + res.ID = "sdp" + res.Data = localSession + + case "candidate": + hi := pionRTC.ICECandidateInit{} + err = json.Unmarshal([]byte(req.Data), &hi) + if err != nil { + log.Println("[!] Cannot parse candidate: ", err) + } else { + // webRTC.AddCandidate(hi) + } + res.ID = "candidate" + + case "start": + log.Println("Starting game") + imageChannel := make(chan *image.RGBA, 100) + go screenshotLoop(imageChannel, webRTC) + go startGame("games/" + gameName, imageChannel, webRTC.InputChannel, webRTC) + res.ID = "start" + isDone = true + } + + stRes, err := json.Marshal(res) + if err != nil { + log.Println("json marshal:", err) + } + + err = c.WriteMessage(mt, []byte(stRes)) + if err != nil { + log.Println("write:", err) + break + } + + } +} + + + + +type SessionPacket struct { + Game string `json:"game"` + SDP string `json:"sdp"` +} + + func postSession(w http.ResponseWriter, r *http.Request) { bs, err := ioutil.ReadAll(r.Body) if err != nil { @@ -54,16 +168,24 @@ func postSession(w http.ResponseWriter, r *http.Request) { } r.Body.Close() + var postPacket SessionPacket + err = json.Unmarshal(bs, &postPacket) + if err != nil { + log.Fatalln(err) + } + + log.Println("Got session with game request:", postPacket.Game) + webRTC := webrtc.NewWebRTC() - localSession, err := webRTC.StartClient(string(bs), width, height) + localSession, err := webRTC.StartClient(postPacket.SDP, width, height) if err != nil { log.Fatalln(err) } imageChannel := make(chan *image.RGBA, 100) go screenshotLoop(imageChannel, webRTC) - go startGame("games/"+gameName, imageChannel, webRTC.InputChannel, webRTC) + go startGame("games/" + postPacket.Game, imageChannel, webRTC.InputChannel, webRTC) w.Write([]byte(localSession)) } @@ -81,5 +203,7 @@ func screenshotLoop(imageChannel chan *image.RGBA, webRTC *webrtc.WebRTC) { yuv := util.RgbaToYuv(image) webRTC.ImageChannel <- yuv } + // time.Sleep(10 * time.Millisecond) + // time.Sleep(time.Duration(1000 / FPS) * time.Millisecond) } } diff --git a/ui/director.go b/ui/director.go index d44a1293..e55af00a 100644 --- a/ui/director.go +++ b/ui/director.go @@ -1,4 +1,3 @@ -// credit to https://github.com/fogleman/nes package ui import ( @@ -22,11 +21,11 @@ type Director struct { timestamp float64 imageChannel chan *image.RGBA inputChannel chan int - webRTC *webrtc.WebRTC + webRTC *webrtc.WebRTC } func NewDirector(imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) *Director { - // func NewDirector(audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director { +// func NewDirector(audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director { director := Director{} // director.audio = audio director.imageChannel = imageChannel diff --git a/ui/gameview.go b/ui/gameview.go index 5f9796b6..6d636a61 100644 --- a/ui/gameview.go +++ b/ui/gameview.go @@ -24,6 +24,7 @@ type GameView struct { imageChannel chan *image.RGBA inputChannel chan int + } func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View { @@ -85,7 +86,11 @@ func (view *GameView) Update(t, dt float64) { view.updateControllers() //fmt.Println(console.Buffer()) console.StepSeconds(dt) + + // fps to set frame view.imageChannel <- console.Buffer() + + if view.record { view.frames = append(view.frames, copyImage(console.Buffer())) } diff --git a/ui/util.go b/ui/util.go index 22a78186..8398cdff 100644 --- a/ui/util.go +++ b/ui/util.go @@ -1,4 +1,3 @@ -// credit to https://github.com/fogleman/nes package ui import ( diff --git a/webrtc/webrtc.go b/webrtc/webrtc.go index abba2498..eab138d5 100644 --- a/webrtc/webrtc.go +++ b/webrtc/webrtc.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "math/rand" "strconv" + "time" vpxEncoder "github.com/giongto35/cloud-game/vpx-encoder" "github.com/pions/webrtc" @@ -92,8 +93,8 @@ func Decode(in string, obj interface{}) { // NewWebRTC create func NewWebRTC() *WebRTC { w := &WebRTC{ - ImageChannel: make(chan []byte, 100), - InputChannel: make(chan int, 100), + ImageChannel: make(chan []byte, 2), + InputChannel: make(chan int, 2), } return w } @@ -103,7 +104,7 @@ type WebRTC struct { connection *webrtc.PeerConnection encoder *vpxEncoder.VpxEncoder isConnected bool - isClosed bool + isClosed bool // for yuvI420 image ImageChannel chan []byte InputChannel chan int @@ -121,7 +122,7 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e // reset client if w.isConnected { w.StopClient() - //time.Sleep(2 * time.Second) + time.Sleep(2 * time.Second) } encoder, err := vpxEncoder.NewVpxEncoder(width, height, 20, 1200, 5) @@ -146,6 +147,8 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e return "", err } + + // WebRTC state callback w.connection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String()) if connectionState == webrtc.ICEConnectionStateConnected { @@ -161,8 +164,13 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e } }) - //w.listenInputChannel() - // Data channel + // TODO: take a look at this + w.connection.OnICECandidate(func(iceCandidate *webrtc.ICECandidate) { + fmt.Println(iceCandidate) + }) + + + // Data channel callback w.connection.OnDataChannel(func(d *webrtc.DataChannel) { fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID()) @@ -180,22 +188,31 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e }) offer := webrtc.SessionDescription{} + Decode(remoteSession, &offer) - if err != nil { - return "", err - } + err = w.connection.SetRemoteDescription(offer) if err != nil { return "", err } + answer, err := w.connection.CreateAnswer(nil) if err != nil { return "", err } + localSession := Encode(answer) return localSession, nil } +// TODO: Take a look at this +func (w *WebRTC) AddCandidate(candidate webrtc.ICECandidateInit) { + err := w.connection.AddICECandidate(candidate) + if err != nil { + fmt.Println("Cannot add candidate: ", err) + } +} + // StopClient disconnect func (w *WebRTC) StopClient() { fmt.Println("===StopClient===") @@ -233,7 +250,7 @@ func (w *WebRTC) startStreaming(vp8Track *webrtc.Track) { // receive frame buffer go func() { - for i := 0; w.isConnected; i++ { + for w.isConnected { bs := <-w.encoder.Output vp8Track.WriteSample(media.Sample{Data: bs, Samples: 1}) }