diff --git a/Dockerfile b/Dockerfile
index 3d196e71..200d60b8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,16 +1,16 @@
-From golang:1.12
-
-RUN mkdir -p /go/src/github.com/giongto35/game-online
-COPY . /go/src/github.com/giongto35/game-online/
-WORKDIR /go/src/github.com/giongto35/game-online
-
-# Install server dependencies
-RUN apt-get update
-#RUN apt-get install portaudio19-dev -y
-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 install github.com/giongto35/game-online
-
-EXPOSE 8000
+From golang:1.12
+
+RUN mkdir -p /go/src/github.com/giongto35/game-online
+COPY . /go/src/github.com/giongto35/game-online/
+WORKDIR /go/src/github.com/giongto35/game-online
+
+# Install server dependencies
+RUN apt-get update
+#RUN apt-get install portaudio19-dev -y
+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 install github.com/giongto35/game-online
+
+EXPOSE 8000
diff --git a/games/1200-in-1.nes b/games/1200-in-1.nes
new file mode 100644
index 00000000..d660c97c
Binary files /dev/null and b/games/1200-in-1.nes differ
diff --git a/games/Contra.nes b/games/Contra.nes
new file mode 100644
index 00000000..5a154db6
Binary files /dev/null and b/games/Contra.nes differ
diff --git a/games/Kirby's Adventure.nes b/games/Kirby's Adventure.nes
new file mode 100644
index 00000000..f2ac915a
Binary files /dev/null and b/games/Kirby's Adventure.nes differ
diff --git a/games/Mega Man 2.nes b/games/Mega Man 2.nes
new file mode 100644
index 00000000..ea261a3f
Binary files /dev/null and b/games/Mega Man 2.nes differ
diff --git a/games/Mega Man.nes b/games/Mega Man.nes
new file mode 100644
index 00000000..0669d5f4
Binary files /dev/null and b/games/Mega Man.nes differ
diff --git a/games/Metal Gear.nes b/games/Metal Gear.nes
new file mode 100644
index 00000000..0b10e689
Binary files /dev/null and b/games/Metal Gear.nes differ
diff --git a/games/Mike Tyson.nes b/games/Mike Tyson.nes
new file mode 100644
index 00000000..658ba89a
Binary files /dev/null and b/games/Mike Tyson.nes differ
diff --git a/games/Mortal Kombat 4.nes b/games/Mortal Kombat 4.nes
new file mode 100644
index 00000000..ee03f073
Binary files /dev/null and b/games/Mortal Kombat 4.nes differ
diff --git a/games/Super Mario Bros 2.nes b/games/Super Mario Bros 2.nes
new file mode 100644
index 00000000..58a7a3d2
Binary files /dev/null and b/games/Super Mario Bros 2.nes differ
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/games/Super Mario Bros.nes b/games/Super Mario Bros.nes
new file mode 100644
index 00000000..878ef21b
Binary files /dev/null and b/games/Super Mario Bros.nes differ
diff --git a/games/Teenage Mutant Ninja Turtles 3.nes b/games/Teenage Mutant Ninja Turtles 3.nes
new file mode 100644
index 00000000..c57e9454
Binary files /dev/null and b/games/Teenage Mutant Ninja Turtles 3.nes differ
diff --git a/games/VS Super Mario Bros.nes b/games/VS Super Mario Bros.nes
new file mode 100644
index 00000000..55a0dbf5
Binary files /dev/null and b/games/VS Super Mario Bros.nes differ
diff --git a/index.html b/index.html
index 9bc1a4bd..fa7d4806 100644
--- a/index.html
+++ b/index.html
@@ -1,266 +1,299 @@
-
-
-
-
-
-
- Use Up, Down, Left, Right to Move
- Z to jump (A)
- X to sprint (B)
- C is start button
- V is select button
-
- Fullscreen media for better gaming experience
-
-
- Play Mario
-
-
-
- Refresh to retry
-
-
-
+
+
+
+
+
+ Contra.nes
+ Kirby's Adventure.nes
+ Mega Man 2.nes
+ Mega Man.nes
+ Metal Gear.nes
+ Mike Tyson.nes
+ Mortal Kombat 4.nes
+ Super Mario Bros 2.nes
+ Super Mario Bros 3.nes
+ Super Mario Bros.nes
+ Teenage Mutant Ninja Turtles 3.nes
+ VS Super Mario Bros.nes
+ supermariobros.rom
+ zelda.rom
+
+
+Play
+
+
+
+
+
+ Use Up, Down, Left, Right to Move
+ Z to jump (A)
+ X to sprint (B)
+ C is start button
+ V is select button
+
+ Fullscreen media for better gaming experience
+
+
+
+
+
+
+ Refresh to retry
+
+
+
diff --git a/main.go b/main.go
index e2e29a66..c91f2b77 100644
--- a/main.go
+++ b/main.go
@@ -1,182 +1,188 @@
-package main
-
-import (
- pionRTC "github.com/pions/webrtc"
- "fmt"
- "image"
- "io/ioutil"
- "log"
- "net/http"
-
- // "time"
-
- "github.com/giongto35/game-online/ui"
- "github.com/giongto35/game-online/util"
- "github.com/giongto35/game-online/webrtc"
-
- // "github.com/gorilla/mux"
- "github.com/gorilla/websocket"
-
- "encoding/json"
-)
-
-// var webRTC *webrtc.WebRTC
-var width = 256
-var height = 240
-var gameName = "supermariobros.rom"
-
-// var FPS = 60
-
-var upgrader = websocket.Upgrader{}
-
-type WSPacket struct {
- ID string `json:"id"`
- Data string `json:"data"`
-}
-
-func init() {
-}
-
-func startGame(path string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
- ui.Run([]string{path}, imageChannel, inputChannel, webRTC)
-}
-
-func main() {
- fmt.Println("http://localhost:8000")
-
- // 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)
-}
-
-func getWeb(w http.ResponseWriter, r *http.Request) {
- bs, err := ioutil.ReadFile("./index.html")
- 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
-
- 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":
- res.ID = "pong"
-
- case "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 {
- fmt.Println("[!] Cannot parse candidate: ", err)
- } else {
- webRTC.AddCandidate(hi)
- }
- res.ID = "candidate"
-
- case "start":
- 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
- }
-
- }
-}
-
-func postSession(w http.ResponseWriter, r *http.Request) {
- bs, err := ioutil.ReadAll(r.Body)
- if err != nil {
- log.Fatal(err)
- }
- r.Body.Close()
-
- webRTC := webrtc.NewWebRTC()
-
- localSession, err := webRTC.StartClient(string(bs), 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)
-
- w.Write([]byte(localSession))
-}
-
-// func screenshotLoop(imageChannel chan *image.RGBA) {
-func screenshotLoop(imageChannel chan *image.RGBA, webRTC *webrtc.WebRTC) {
- for image := range imageChannel {
- // Client stopped
- if webRTC.IsClosed() {
- break
- }
-
- // encode frame
- if webRTC.IsConnected() {
- yuv := util.RgbaToYuv(image)
- webRTC.ImageChannel <- yuv
- }
- }
-}
+package main
+
+import (
+ pionRTC "github.com/pions/webrtc"
+ "fmt"
+ "image"
+ "io/ioutil"
+ "log"
+ "net/http"
+
+ "time"
+
+ "github.com/giongto35/game-online/ui"
+ "github.com/giongto35/game-online/util"
+ "github.com/giongto35/game-online/webrtc"
+
+ // "github.com/gorilla/mux"
+ "github.com/gorilla/websocket"
+
+ "encoding/json"
+)
+
+// var webRTC *webrtc.WebRTC
+var width = 256
+var height = 240
+// var gameName = "supermariobros.rom"
+var gameName string
+
+// var FPS = 60
+
+var upgrader = websocket.Upgrader{}
+
+type WSPacket struct {
+ ID string `json:"id"`
+ Data string `json:"data"`
+}
+
+func init() {
+}
+
+func startGame(path string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
+ ui.Run([]string{path}, imageChannel, inputChannel, webRTC)
+}
+
+func main() {
+ fmt.Println("http://localhost:8000")
+ fmt.Println(time.Now().UnixNano())
+
+ // 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)
+}
+
+func getWeb(w http.ResponseWriter, r *http.Request) {
+ bs, err := ioutil.ReadFile("./index.html")
+ 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
+
+ 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
+ }
+
+ }
+}
+
+func postSession(w http.ResponseWriter, r *http.Request) {
+ bs, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+ r.Body.Close()
+
+ webRTC := webrtc.NewWebRTC()
+
+ localSession, err := webRTC.StartClient(string(bs), 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)
+
+ w.Write([]byte(localSession))
+}
+
+// func screenshotLoop(imageChannel chan *image.RGBA) {
+func screenshotLoop(imageChannel chan *image.RGBA, webRTC *webrtc.WebRTC) {
+ for image := range imageChannel {
+ // Client stopped
+ if webRTC.IsClosed() {
+ break
+ }
+
+ // encode frame
+ if webRTC.IsConnected() {
+ yuv := util.RgbaToYuv(image)
+ webRTC.ImageChannel <- yuv
+ }
+ }
+}
diff --git a/ui/director.go b/ui/director.go
index 6afcd6f3..30be463b 100644
--- a/ui/director.go
+++ b/ui/director.go
@@ -1,87 +1,87 @@
-package ui
-
-import (
- "image"
- "log"
- "time"
-
- "github.com/giongto35/game-online/nes"
- "github.com/giongto35/game-online/webrtc"
-)
-
-type View interface {
- Enter()
- Exit()
- Update(t, dt float64)
-}
-
-type Director struct {
- // audio *Audio
- view View
- timestamp float64
- imageChannel chan *image.RGBA
- inputChannel chan int
- 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 {
- director := Director{}
- // director.audio = audio
- director.imageChannel = imageChannel
- director.inputChannel = inputChannel
- director.webRTC = webRTC
- return &director
-}
-
-func (d *Director) SetView(view View) {
- if d.view != nil {
- d.view.Exit()
- }
- d.view = view
- if d.view != nil {
- d.view.Enter()
- }
- d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second)
-}
-
-func (d *Director) Step() {
- //gl.Clear(gl.COLOR_BUFFER_BIT)
- timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
- dt := timestamp - d.timestamp
- d.timestamp = timestamp
- if d.view != nil {
- d.view.Update(timestamp, dt)
- }
-}
-
-func (d *Director) Start(paths []string) {
- if len(paths) == 1 {
- d.PlayGame(paths[0])
- }
- d.Run()
-}
-
-func (d *Director) Run() {
- for {
- // quit game
- if d.webRTC.IsClosed() {
- break
- }
-
- d.Step()
- }
- d.SetView(nil)
-}
-
-func (d *Director) PlayGame(path string) {
- hash, err := hashFile(path)
- if err != nil {
- log.Fatalln(err)
- }
- console, err := nes.NewConsole(path)
- if err != nil {
- log.Fatalln(err)
- }
- d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel))
-}
+package ui
+
+import (
+ "image"
+ "log"
+ "time"
+
+ "github.com/giongto35/game-online/nes"
+ "github.com/giongto35/game-online/webrtc"
+)
+
+type View interface {
+ Enter()
+ Exit()
+ Update(t, dt float64)
+}
+
+type Director struct {
+ // audio *Audio
+ view View
+ timestamp float64
+ imageChannel chan *image.RGBA
+ inputChannel chan int
+ 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 {
+ director := Director{}
+ // director.audio = audio
+ director.imageChannel = imageChannel
+ director.inputChannel = inputChannel
+ director.webRTC = webRTC
+ return &director
+}
+
+func (d *Director) SetView(view View) {
+ if d.view != nil {
+ d.view.Exit()
+ }
+ d.view = view
+ if d.view != nil {
+ d.view.Enter()
+ }
+ d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second)
+}
+
+func (d *Director) Step() {
+ //gl.Clear(gl.COLOR_BUFFER_BIT)
+ timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
+ dt := timestamp - d.timestamp
+ d.timestamp = timestamp
+ if d.view != nil {
+ d.view.Update(timestamp, dt)
+ }
+}
+
+func (d *Director) Start(paths []string) {
+ if len(paths) == 1 {
+ d.PlayGame(paths[0])
+ }
+ d.Run()
+}
+
+func (d *Director) Run() {
+ for {
+ // quit game
+ if d.webRTC.IsClosed() {
+ break
+ }
+
+ d.Step()
+ }
+ d.SetView(nil)
+}
+
+func (d *Director) PlayGame(path string) {
+ hash, err := hashFile(path)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ console, err := nes.NewConsole(path)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel))
+}
diff --git a/ui/gameview.go b/ui/gameview.go
index 313b3173..41540296 100644
--- a/ui/gameview.go
+++ b/ui/gameview.go
@@ -1,106 +1,117 @@
-package ui
-
-import (
- "image"
-
- "fmt"
- // "strconv"
-
- "github.com/giongto35/game-online/nes"
-)
-
-const padding = 0
-
-type GameView struct {
- director *Director
- console *nes.Console
- title string
- hash string
- record bool
- frames []image.Image
-
- keyPressed [8]bool
-
- 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 {
- gameview := &GameView{director, console, title, hash, false, nil, [8]bool{false}, imageChannel, inputChannel}
- go gameview.ListenToInputChannel()
- return gameview
-}
-
-func (view *GameView) ListenToInputChannel() {
- for {
- key := <-view.inputChannel
- s := fmt.Sprintf("%.8b", key)
- for i := 0; i < len(s); i++ {
- if s[i] == '1' {
- view.keyPressed[i] = true
- } else {
- view.keyPressed[i] = false
- }
- }
- }
-}
-
-func (view *GameView) Enter() {
- // view.console.SetAudioChannel(view.director.audio.channel)
- // view.console.SetAudioSampleRate(view.director.audio.sampleRate)
- // load state
- if err := view.console.LoadState(savePath(view.hash)); err == nil {
- return
- } else {
- view.console.Reset()
- }
- // load sram
- cartridge := view.console.Cartridge
- if cartridge.Battery != 0 {
- if sram, err := readSRAM(sramPath(view.hash)); err == nil {
- cartridge.SRAM = sram
- }
- }
-}
-
-func (view *GameView) Exit() {
- // view.console.SetAudioChannel(nil)
- // view.console.SetAudioSampleRate(0)
- // save sram
- cartridge := view.console.Cartridge
- if cartridge.Battery != 0 {
- writeSRAM(sramPath(view.hash), cartridge.SRAM)
- }
- // save state
- view.console.SaveState(savePath(view.hash))
-}
-
-func (view *GameView) Update(t, dt float64) {
- if dt > 1 {
- dt = 0
- }
- console := view.console
- //updateControllers(window, console)
- view.updateControllers()
- //fmt.Println(console.Buffer())
- console.StepSeconds(dt)
- view.imageChannel <- console.Buffer()
- if view.record {
- view.frames = append(view.frames, copyImage(console.Buffer()))
- }
-}
-
-func (view *GameView) updateControllers() {
- // TODO: switch case
- // var buttons [8]bool
- // buttons[nes.ButtonLeft] = view.keyPressed[37]
- // buttons[nes.ButtonUp] = view.keyPressed[38]
- // buttons[nes.ButtonRight] = view.keyPressed[39]
- // buttons[nes.ButtonDown] = view.keyPressed[40]
- // buttons[nes.ButtonA] = view.keyPressed[32]
- // buttons[nes.ButtonB] = view.keyPressed[17]
- // buttons[nes.ButtonStart] = view.keyPressed[13]
- // buttons[nes.ButtonSelect] = view.keyPressed[16]
- // view.console.Controller1.SetButtons(buttons)
- view.console.Controller1.SetButtons(view.keyPressed)
-}
+package ui
+
+import (
+ "image"
+
+ "fmt"
+ "time"
+
+ "github.com/giongto35/game-online/nes"
+)
+
+const padding = 0
+
+type GameView struct {
+ director *Director
+ console *nes.Console
+ title string
+ hash string
+ record bool
+ frames []image.Image
+
+ keyPressed [8]bool
+
+ imageChannel chan *image.RGBA
+ inputChannel chan int
+
+ nanotime int64
+}
+
+func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View {
+ gameview := &GameView{director, console, title, hash, false, nil, [8]bool{false}, imageChannel, inputChannel, time.Now().UnixNano()}
+ go gameview.ListenToInputChannel()
+ return gameview
+}
+
+func (view *GameView) ListenToInputChannel() {
+ for {
+ key := <-view.inputChannel
+ s := fmt.Sprintf("%.8b", key)
+ for i := 0; i < len(s); i++ {
+ if s[i] == '1' {
+ view.keyPressed[i] = true
+ } else {
+ view.keyPressed[i] = false
+ }
+ }
+ }
+}
+
+func (view *GameView) Enter() {
+ // view.console.SetAudioChannel(view.director.audio.channel)
+ // view.console.SetAudioSampleRate(view.director.audio.sampleRate)
+ // load state
+ if err := view.console.LoadState(savePath(view.hash)); err == nil {
+ return
+ } else {
+ view.console.Reset()
+ }
+ // load sram
+ cartridge := view.console.Cartridge
+ if cartridge.Battery != 0 {
+ if sram, err := readSRAM(sramPath(view.hash)); err == nil {
+ cartridge.SRAM = sram
+ }
+ }
+}
+
+func (view *GameView) Exit() {
+ // view.console.SetAudioChannel(nil)
+ // view.console.SetAudioSampleRate(0)
+ // save sram
+ cartridge := view.console.Cartridge
+ if cartridge.Battery != 0 {
+ writeSRAM(sramPath(view.hash), cartridge.SRAM)
+ }
+ // save state
+ view.console.SaveState(savePath(view.hash))
+}
+
+func (view *GameView) Update(t, dt float64) {
+ if dt > 1 {
+ dt = 0
+ }
+ console := view.console
+ //updateControllers(window, console)
+ view.updateControllers()
+ //fmt.Println(console.Buffer())
+ console.StepSeconds(dt)
+
+ // fps to set frame
+ n := time.Now().UnixNano()
+ if n - view.nanotime > 1000000000 / 100000 {
+ view.nanotime = n
+ view.imageChannel <- console.Buffer()
+ }
+
+
+
+ if view.record {
+ view.frames = append(view.frames, copyImage(console.Buffer()))
+ }
+}
+
+func (view *GameView) updateControllers() {
+ // TODO: switch case
+ // var buttons [8]bool
+ // buttons[nes.ButtonLeft] = view.keyPressed[37]
+ // buttons[nes.ButtonUp] = view.keyPressed[38]
+ // buttons[nes.ButtonRight] = view.keyPressed[39]
+ // buttons[nes.ButtonDown] = view.keyPressed[40]
+ // buttons[nes.ButtonA] = view.keyPressed[32]
+ // buttons[nes.ButtonB] = view.keyPressed[17]
+ // buttons[nes.ButtonStart] = view.keyPressed[13]
+ // buttons[nes.ButtonSelect] = view.keyPressed[16]
+ // view.console.Controller1.SetButtons(buttons)
+ view.console.Controller1.SetButtons(view.keyPressed)
+}
diff --git a/ui/run.go b/ui/run.go
index 87bea59b..65ea1b9f 100644
--- a/ui/run.go
+++ b/ui/run.go
@@ -1,42 +1,42 @@
-package ui
-
-import (
- "image"
- // "log"
- "runtime"
-
- // "github.com/gordonklaus/portaudio"
- "github.com/giongto35/game-online/webrtc"
-)
-
-const (
- width = 256
- height = 240
- scale = 3
- title = "NES"
-)
-
-func init() {
- // we need a parallel OS thread to avoid audio stuttering
- runtime.GOMAXPROCS(2)
-
- // we need to keep OpenGL calls on a single thread
- runtime.LockOSThread()
-}
-
-func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
- // initialize audio
- // portaudio.Initialize()
- // defer portaudio.Terminate()
-
- // audio := NewAudio()
- // if err := audio.Start(); err != nil {
- // log.Fatalln(err)
- // }
- // defer audio.Stop()
-
- // run director
- director := NewDirector(imageChannel, inputChannel, webRTC)
- // director := NewDirector(audio, imageChannel, inputChannel)
- director.Start(paths)
-}
+package ui
+
+import (
+ "image"
+ // "log"
+ "runtime"
+
+ // "github.com/gordonklaus/portaudio"
+ "github.com/giongto35/game-online/webrtc"
+)
+
+const (
+ width = 256
+ height = 240
+ scale = 3
+ title = "NES"
+)
+
+func init() {
+ // we need a parallel OS thread to avoid audio stuttering
+ // runtime.GOMAXPROCS(2)
+
+ // we need to keep OpenGL calls on a single thread
+ runtime.LockOSThread()
+}
+
+func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
+ // initialize audio
+ // portaudio.Initialize()
+ // defer portaudio.Terminate()
+
+ // audio := NewAudio()
+ // if err := audio.Start(); err != nil {
+ // log.Fatalln(err)
+ // }
+ // defer audio.Stop()
+
+ // run director
+ director := NewDirector(imageChannel, inputChannel, webRTC)
+ // director := NewDirector(audio, imageChannel, inputChannel)
+ director.Start(paths)
+}
diff --git a/ui/util.go b/ui/util.go
index dc0e20eb..f9fca8b8 100644
--- a/ui/util.go
+++ b/ui/util.go
@@ -1,153 +1,153 @@
-package ui
-
-import (
- "crypto/md5"
- "encoding/binary"
- "fmt"
- "image"
- "image/color"
- "image/draw"
- "image/gif"
- "image/png"
- "io/ioutil"
- "log"
- "os"
- "os/user"
- "path"
-
- "github.com/giongto35/game-online/nes"
-)
-
-var homeDir string
-
-func init() {
- u, err := user.Current()
- if err != nil {
- log.Fatalln(err)
- }
- homeDir = u.HomeDir
-}
-
-func thumbnailURL(hash string) string {
- return "http://www.michaelfogleman.com/static/nes/" + hash + ".png"
-}
-
-func thumbnailPath(hash string) string {
- return homeDir + "/.nes/thumbnail/" + hash + ".png"
-}
-
-func sramPath(hash string) string {
- return homeDir + "/.nes/sram/" + hash + ".dat"
-}
-
-func savePath(hash string) string {
- return homeDir + "/.nes/save/" + hash + ".dat"
-}
-
-func combineButtons(a, b [8]bool) [8]bool {
- var result [8]bool
- for i := 0; i < 8; i++ {
- result[i] = a[i] || b[i]
- }
- return result
-}
-
-func hashFile(path string) (string, error) {
- data, err := ioutil.ReadFile(path)
- if err != nil {
- return "", err
- }
- return fmt.Sprintf("%x", md5.Sum(data)), nil
-}
-func copyImage(src image.Image) *image.RGBA {
- dst := image.NewRGBA(src.Bounds())
- draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
- return dst
-}
-
-func loadPNG(path string) (image.Image, error) {
- file, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer file.Close()
- return png.Decode(file)
-}
-
-func savePNG(path string, im image.Image) error {
- file, err := os.Create(path)
- if err != nil {
- return err
- }
- defer file.Close()
- return png.Encode(file, im)
-}
-
-func saveGIF(path string, frames []image.Image) error {
- var palette []color.Color
- for _, c := range nes.Palette {
- palette = append(palette, c)
- }
- g := gif.GIF{}
- for i, src := range frames {
- if i%3 != 0 {
- continue
- }
- dst := image.NewPaletted(src.Bounds(), palette)
- draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
- g.Image = append(g.Image, dst)
- g.Delay = append(g.Delay, 5)
- }
- file, err := os.Create(path)
- if err != nil {
- return err
- }
- defer file.Close()
- return gif.EncodeAll(file, &g)
-}
-
-func screenshot(im image.Image) {
- for i := 0; i < 1000; i++ {
- path := fmt.Sprintf("%03d.png", i)
- if _, err := os.Stat(path); os.IsNotExist(err) {
- savePNG(path, im)
- return
- }
- }
-}
-
-func animation(frames []image.Image) {
- for i := 0; i < 1000; i++ {
- path := fmt.Sprintf("%03d.gif", i)
- if _, err := os.Stat(path); os.IsNotExist(err) {
- saveGIF(path, frames)
- return
- }
- }
-}
-
-func writeSRAM(filename string, sram []byte) error {
- dir, _ := path.Split(filename)
- if err := os.MkdirAll(dir, 0755); err != nil {
- return err
- }
- file, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer file.Close()
- return binary.Write(file, binary.LittleEndian, sram)
-}
-
-func readSRAM(filename string) ([]byte, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer file.Close()
- sram := make([]byte, 0x2000)
- if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
- return nil, err
- }
- return sram, nil
-}
+package ui
+
+import (
+ "crypto/md5"
+ "encoding/binary"
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/gif"
+ "image/png"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/user"
+ "path"
+
+ "github.com/giongto35/game-online/nes"
+)
+
+var homeDir string
+
+func init() {
+ u, err := user.Current()
+ if err != nil {
+ log.Fatalln(err)
+ }
+ homeDir = u.HomeDir
+}
+
+func thumbnailURL(hash string) string {
+ return "http://www.michaelfogleman.com/static/nes/" + hash + ".png"
+}
+
+func thumbnailPath(hash string) string {
+ return homeDir + "/.nes/thumbnail/" + hash + ".png"
+}
+
+func sramPath(hash string) string {
+ return homeDir + "/.nes/sram/" + hash + ".dat"
+}
+
+func savePath(hash string) string {
+ return homeDir + "/.nes/save/" + hash + ".dat"
+}
+
+func combineButtons(a, b [8]bool) [8]bool {
+ var result [8]bool
+ for i := 0; i < 8; i++ {
+ result[i] = a[i] || b[i]
+ }
+ return result
+}
+
+func hashFile(path string) (string, error) {
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%x", md5.Sum(data)), nil
+}
+func copyImage(src image.Image) *image.RGBA {
+ dst := image.NewRGBA(src.Bounds())
+ draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
+ return dst
+}
+
+func loadPNG(path string) (image.Image, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ return png.Decode(file)
+}
+
+func savePNG(path string, im image.Image) error {
+ file, err := os.Create(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return png.Encode(file, im)
+}
+
+func saveGIF(path string, frames []image.Image) error {
+ var palette []color.Color
+ for _, c := range nes.Palette {
+ palette = append(palette, c)
+ }
+ g := gif.GIF{}
+ for i, src := range frames {
+ if i%3 != 0 {
+ continue
+ }
+ dst := image.NewPaletted(src.Bounds(), palette)
+ draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
+ g.Image = append(g.Image, dst)
+ g.Delay = append(g.Delay, 5)
+ }
+ file, err := os.Create(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return gif.EncodeAll(file, &g)
+}
+
+func screenshot(im image.Image) {
+ for i := 0; i < 1000; i++ {
+ path := fmt.Sprintf("%03d.png", i)
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ savePNG(path, im)
+ return
+ }
+ }
+}
+
+func animation(frames []image.Image) {
+ for i := 0; i < 1000; i++ {
+ path := fmt.Sprintf("%03d.gif", i)
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ saveGIF(path, frames)
+ return
+ }
+ }
+}
+
+func writeSRAM(filename string, sram []byte) error {
+ dir, _ := path.Split(filename)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ file, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return binary.Write(file, binary.LittleEndian, sram)
+}
+
+func readSRAM(filename string) ([]byte, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ sram := make([]byte, 0x2000)
+ if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
+ return nil, err
+ }
+ return sram, nil
+}
diff --git a/util/util.go b/util/util.go
index a218574d..43c5f9dc 100644
--- a/util/util.go
+++ b/util/util.go
@@ -1,52 +1,52 @@
-package util
-
-import (
- "image"
- "unsafe"
-)
-
-// https://stackoverflow.com/questions/9465815/rgb-to-yuv420-algorithm-efficiency
-
-/*
-void rgba2yuv(void *destination, void *source, int width, int height, int stride) {
- const int image_size = width * height;
- unsigned char *rgba = source;
- unsigned char *dst_y = destination;
- unsigned char *dst_u = destination + image_size;
- unsigned char *dst_v = destination + image_size + image_size/4;
-
- // Y plane
- for( int y=0; y> 8 ) + 16;
- }
- }
- // U plane
- for( int y=0; y> 8 ) + 128;
- }
- }
- // V plane
- for( int y=0; y> 8 ) + 128;
- }
- }
-}
-*/
-import "C"
-
-// RgbaToYuv convert to yuv from rgba
-func RgbaToYuv(rgba *image.RGBA) []byte {
- w := rgba.Rect.Max.X
- h := rgba.Rect.Max.Y
- size := int(float32(w*h) * 1.5)
- stride := rgba.Stride - w*4
- yuv := make([]byte, size, size)
- C.rgba2yuv(unsafe.Pointer(&yuv[0]), unsafe.Pointer(&rgba.Pix[0]), C.int(w), C.int(h), C.int(stride))
- return yuv
-}
+package util
+
+import (
+ "image"
+ "unsafe"
+)
+
+// https://stackoverflow.com/questions/9465815/rgb-to-yuv420-algorithm-efficiency
+
+/*
+void rgba2yuv(void *destination, void *source, int width, int height, int stride) {
+ const int image_size = width * height;
+ unsigned char *rgba = source;
+ unsigned char *dst_y = destination;
+ unsigned char *dst_u = destination + image_size;
+ unsigned char *dst_v = destination + image_size + image_size/4;
+
+ // Y plane
+ for( int y=0; y> 8 ) + 16;
+ }
+ }
+ // U plane
+ for( int y=0; y> 8 ) + 128;
+ }
+ }
+ // V plane
+ for( int y=0; y> 8 ) + 128;
+ }
+ }
+}
+*/
+import "C"
+
+// RgbaToYuv convert to yuv from rgba
+func RgbaToYuv(rgba *image.RGBA) []byte {
+ w := rgba.Rect.Max.X
+ h := rgba.Rect.Max.Y
+ size := int(float32(w*h) * 1.5)
+ stride := rgba.Stride - w*4
+ yuv := make([]byte, size, size)
+ C.rgba2yuv(unsafe.Pointer(&yuv[0]), unsafe.Pointer(&rgba.Pix[0]), C.int(w), C.int(h), C.int(stride))
+ return yuv
+}
diff --git a/webrtc/webrtc.go b/webrtc/webrtc.go
index 3c53aa0d..1a717ffe 100644
--- a/webrtc/webrtc.go
+++ b/webrtc/webrtc.go
@@ -1,260 +1,260 @@
-package webrtc
-
-import (
- "bytes"
- "compress/gzip"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "math/rand"
- "strconv"
- "time"
-
- vpxEncoder "github.com/giongto35/game-online/vpx-encoder"
- "github.com/pions/webrtc"
- "github.com/pions/webrtc/pkg/media"
-)
-
-var config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
-
-// Allows compressing offer/answer to bypass terminal input limits.
-const compress = false
-
-func init() {
- //api.mediaEngine.RegisterDefaultCodecs()
- //webrtc.RegisterDefaultCodecs()
-}
-
-func zip(in []byte) []byte {
- var b bytes.Buffer
- gz := gzip.NewWriter(&b)
- _, err := gz.Write(in)
- if err != nil {
- panic(err)
- }
- err = gz.Flush()
- if err != nil {
- panic(err)
- }
- err = gz.Close()
- if err != nil {
- panic(err)
- }
- return b.Bytes()
-}
-
-func unzip(in []byte) []byte {
- var b bytes.Buffer
- _, err := b.Write(in)
- if err != nil {
- panic(err)
- }
- r, err := gzip.NewReader(&b)
- if err != nil {
- panic(err)
- }
- res, err := ioutil.ReadAll(r)
- if err != nil {
- panic(err)
- }
- return res
-}
-
-// Encode encodes the input in base64
-// It can optionally zip the input before encoding
-func Encode(obj interface{}) string {
- b, err := json.Marshal(obj)
- if err != nil {
- panic(err)
- }
-
- if compress {
- b = zip(b)
- }
-
- return base64.StdEncoding.EncodeToString(b)
-}
-
-// Decode decodes the input from base64
-// It can optionally unzip the input after decoding
-func Decode(in string, obj interface{}) {
- b, err := base64.StdEncoding.DecodeString(in)
- if err != nil {
- panic(err)
- }
-
- if compress {
- b = unzip(b)
- }
-
- err = json.Unmarshal(b, obj)
- if err != nil {
- panic(err)
- }
-}
-
-// NewWebRTC create
-func NewWebRTC() *WebRTC {
- w := &WebRTC{
- ImageChannel: make(chan []byte, 2),
- InputChannel: make(chan int, 2),
- }
- return w
-}
-
-// WebRTC connection
-type WebRTC struct {
- connection *webrtc.PeerConnection
- encoder *vpxEncoder.VpxEncoder
- isConnected bool
- isClosed bool
- // for yuvI420 image
- ImageChannel chan []byte
- InputChannel chan int
-}
-
-// StartClient start webrtc
-func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, error) {
- defer func() {
- if err := recover(); err != nil {
- fmt.Println(err)
- w.StopClient()
- }
- }()
-
- // reset client
- if w.isConnected {
- w.StopClient()
- time.Sleep(2 * time.Second)
- }
-
- encoder, err := vpxEncoder.NewVpxEncoder(width, height, 20, 1200, 5)
- if err != nil {
- return "", err
- }
- w.encoder = encoder
-
- fmt.Println("=== StartClient ===")
-
- w.connection, err = webrtc.NewPeerConnection(config)
- if err != nil {
- return "", err
- }
-
- vp8Track, err := w.connection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
- if err != nil {
- return "", err
- }
- _, err = w.connection.AddTrack(vp8Track)
- if err != nil {
- 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 {
- go func() {
- w.isConnected = true
- fmt.Println("ConnectionStateConnected")
- w.startStreaming(vp8Track)
- }()
-
- }
- if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed || connectionState == webrtc.ICEConnectionStateDisconnected {
- w.StopClient()
- }
- })
-
- 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())
-
- // Register channel opening handling
- d.OnOpen(func() {
- fmt.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID())
- })
-
- // Register text message handling
- d.OnMessage(func(msg webrtc.DataChannelMessage) {
- //fmt.Printf("Message from DataChannel '%s': '%s' byte '%b'\n", d.Label(), string(msg.Data), msg.Data)
- i, _ := strconv.Atoi(string(msg.Data))
- w.InputChannel <- i
- })
- })
-
- offer := webrtc.SessionDescription{}
-
- Decode(remoteSession, &offer)
-
- 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
-}
-
-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===")
- w.isConnected = false
- if w.encoder != nil {
- w.encoder.Release()
- }
- if w.connection != nil {
- w.connection.Close()
- }
- w.connection = nil
- w.isClosed = true
-}
-
-// IsConnected comment
-func (w *WebRTC) IsConnected() bool {
- return w.isConnected
-}
-
-func (w *WebRTC) IsClosed() bool {
- return w.isClosed
-}
-
-func (w *WebRTC) startStreaming(vp8Track *webrtc.Track) {
- fmt.Println("Start streaming")
- // send screenshot
- go func() {
- for w.isConnected {
- yuv := <-w.ImageChannel
- if len(w.encoder.Input) < cap(w.encoder.Input) {
- w.encoder.Input <- yuv
- }
- }
- }()
-
- // receive frame buffer
- go func() {
- for i := 0; w.isConnected; i++ {
- bs := <-w.encoder.Output
- vp8Track.WriteSample(media.Sample{Data: bs, Samples: 1})
- }
- }()
-}
+package webrtc
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "strconv"
+ "time"
+
+ vpxEncoder "github.com/giongto35/game-online/vpx-encoder"
+ "github.com/pions/webrtc"
+ "github.com/pions/webrtc/pkg/media"
+)
+
+var config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
+
+// Allows compressing offer/answer to bypass terminal input limits.
+const compress = false
+
+func init() {
+ //api.mediaEngine.RegisterDefaultCodecs()
+ //webrtc.RegisterDefaultCodecs()
+}
+
+func zip(in []byte) []byte {
+ var b bytes.Buffer
+ gz := gzip.NewWriter(&b)
+ _, err := gz.Write(in)
+ if err != nil {
+ panic(err)
+ }
+ err = gz.Flush()
+ if err != nil {
+ panic(err)
+ }
+ err = gz.Close()
+ if err != nil {
+ panic(err)
+ }
+ return b.Bytes()
+}
+
+func unzip(in []byte) []byte {
+ var b bytes.Buffer
+ _, err := b.Write(in)
+ if err != nil {
+ panic(err)
+ }
+ r, err := gzip.NewReader(&b)
+ if err != nil {
+ panic(err)
+ }
+ res, err := ioutil.ReadAll(r)
+ if err != nil {
+ panic(err)
+ }
+ return res
+}
+
+// Encode encodes the input in base64
+// It can optionally zip the input before encoding
+func Encode(obj interface{}) string {
+ b, err := json.Marshal(obj)
+ if err != nil {
+ panic(err)
+ }
+
+ if compress {
+ b = zip(b)
+ }
+
+ return base64.StdEncoding.EncodeToString(b)
+}
+
+// Decode decodes the input from base64
+// It can optionally unzip the input after decoding
+func Decode(in string, obj interface{}) {
+ b, err := base64.StdEncoding.DecodeString(in)
+ if err != nil {
+ panic(err)
+ }
+
+ if compress {
+ b = unzip(b)
+ }
+
+ err = json.Unmarshal(b, obj)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// NewWebRTC create
+func NewWebRTC() *WebRTC {
+ w := &WebRTC{
+ ImageChannel: make(chan []byte, 2),
+ InputChannel: make(chan int, 2),
+ }
+ return w
+}
+
+// WebRTC connection
+type WebRTC struct {
+ connection *webrtc.PeerConnection
+ encoder *vpxEncoder.VpxEncoder
+ isConnected bool
+ isClosed bool
+ // for yuvI420 image
+ ImageChannel chan []byte
+ InputChannel chan int
+}
+
+// StartClient start webrtc
+func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, error) {
+ defer func() {
+ if err := recover(); err != nil {
+ fmt.Println(err)
+ w.StopClient()
+ }
+ }()
+
+ // reset client
+ if w.isConnected {
+ w.StopClient()
+ time.Sleep(2 * time.Second)
+ }
+
+ encoder, err := vpxEncoder.NewVpxEncoder(width, height, 20, 1200, 5)
+ if err != nil {
+ return "", err
+ }
+ w.encoder = encoder
+
+ fmt.Println("=== StartClient ===")
+
+ w.connection, err = webrtc.NewPeerConnection(config)
+ if err != nil {
+ return "", err
+ }
+
+ vp8Track, err := w.connection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
+ if err != nil {
+ return "", err
+ }
+ _, err = w.connection.AddTrack(vp8Track)
+ if err != nil {
+ 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 {
+ go func() {
+ w.isConnected = true
+ fmt.Println("ConnectionStateConnected")
+ w.startStreaming(vp8Track)
+ }()
+
+ }
+ if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed || connectionState == webrtc.ICEConnectionStateDisconnected {
+ w.StopClient()
+ }
+ })
+
+ 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())
+
+ // Register channel opening handling
+ d.OnOpen(func() {
+ fmt.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID())
+ })
+
+ // Register text message handling
+ d.OnMessage(func(msg webrtc.DataChannelMessage) {
+ //fmt.Printf("Message from DataChannel '%s': '%s' byte '%b'\n", d.Label(), string(msg.Data), msg.Data)
+ i, _ := strconv.Atoi(string(msg.Data))
+ w.InputChannel <- i
+ })
+ })
+
+ offer := webrtc.SessionDescription{}
+
+ Decode(remoteSession, &offer)
+
+ 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
+}
+
+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===")
+ w.isConnected = false
+ if w.encoder != nil {
+ w.encoder.Release()
+ }
+ if w.connection != nil {
+ w.connection.Close()
+ }
+ w.connection = nil
+ w.isClosed = true
+}
+
+// IsConnected comment
+func (w *WebRTC) IsConnected() bool {
+ return w.isConnected
+}
+
+func (w *WebRTC) IsClosed() bool {
+ return w.isClosed
+}
+
+func (w *WebRTC) startStreaming(vp8Track *webrtc.Track) {
+ fmt.Println("Start streaming")
+ // send screenshot
+ go func() {
+ for w.isConnected {
+ yuv := <-w.ImageChannel
+ if len(w.encoder.Input) < cap(w.encoder.Input) {
+ w.encoder.Input <- yuv
+ }
+ }
+ }()
+
+ // receive frame buffer
+ go func() {
+ for i := 0; w.isConnected; i++ {
+ bs := <-w.encoder.Output
+ vp8Track.WriteSample(media.Sample{Data: bs, Samples: 1})
+ }
+ }()
+}