diff --git a/index.html b/index.html index 3d7b7f15..ed637b38 100644 --- a/index.html +++ b/index.html @@ -6,13 +6,8 @@ textarea { height: 50px; } -
- https://github.com/poi5305/go-yuv2webRTC -
- https://github.com/pions/webrtc/tree/v1.2.0/examples/gstreamer-send/jsfiddle -
-

+

Browser base64 Session Description

Golang base64 Session Description:

@@ -37,6 +32,7 @@ function postSession(session) { xhttp.open("POST", "/session", true); xhttp.setRequestHeader("Content-type", "text/plain"); xhttp.send(session); + } let pc = new RTCPeerConnection({ @@ -50,6 +46,11 @@ let log = msg => { document.getElementById('div').innerHTML += msg + '
' } +let inputChannel = pc.createDataChannel('foo') +inputChannel.onclose = () => console.log('inputChannel has closed') +inputChannel.onopen = () => console.log('inputChannel has opened') +inputChannel.onmessage = e => log(`Message from DataChannel '${inputChannel.label}' payload '${e.data}'`) + pc.ontrack = function (event) { var el = document.createElement(event.track.kind) el.srcObject = event.streams[0] @@ -70,6 +71,14 @@ pc.onicecandidate = event => { pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: true}).then(d => pc.setLocalDescription(d)).catch(log) +document.body.onkeydown = function(e){ + inputChannel.send(e.keyCode) +}; + +document.body.onkeyup = function(e){ + inputChannel.send(-e.keyCode) +}; + window.startSession = () => { let sd = document.getElementById('remoteSessionDescription').value if (sd === '') { @@ -84,4 +93,4 @@ window.startSession = () => { } - \ No newline at end of file + diff --git a/main.go b/main.go index 2e8ffb8e..e6a71a0a 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,9 @@ import ( "time" "github.com/giongto35/game-online/ui" + "github.com/giongto35/game-online/webrtc" "github.com/gorilla/mux" "github.com/poi5305/go-yuv2webRTC/screenshot" - "github.com/poi5305/go-yuv2webRTC/webrtc" ) var webRTC *webrtc.WebRTC @@ -23,11 +23,12 @@ var height = 240 func init() { } -func startGame(path string, imageChannel chan *image.RGBA) { - ui.Run([]string{path}, imageChannel) +func startGame(path string, imageChannel chan *image.RGBA, inputChannel chan int) { + ui.Run([]string{path}, imageChannel, inputChannel) } func main() { + imageChannel := make(chan *image.RGBA, 2) fmt.Println("http://localhost:8000") webRTC = webrtc.NewWebRTC() @@ -38,9 +39,8 @@ func main() { go http.ListenAndServe(":8000", router) // start screenshot loop, wait for connection - imageChannel := make(chan *image.RGBA, 2) go screenshotLoop(imageChannel) - startGame("games/supermariobros.rom", imageChannel) + startGame("games/supermariobros.rom", imageChannel, webRTC.InputChannel) time.Sleep(time.Minute) } diff --git a/ui/director.go b/ui/director.go index 680664f2..8aafeb46 100644 --- a/ui/director.go +++ b/ui/director.go @@ -22,13 +22,15 @@ type Director struct { menuView View timestamp float64 imageChannel chan *image.RGBA + inputChannel chan int } -func NewDirector(window *glfw.Window, audio *Audio, imageChannel chan *image.RGBA) *Director { +func NewDirector(window *glfw.Window, audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director { director := Director{} director.window = window director.audio = audio director.imageChannel = imageChannel + director.inputChannel = inputChannel return &director } @@ -85,7 +87,7 @@ func (d *Director) PlayGame(path string) { if err != nil { log.Fatalln(err) } - d.SetView(NewGameView(d, console, path, hash, d.imageChannel)) + d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel)) } func (d *Director) ShowMenu() { diff --git a/ui/gameview.go b/ui/gameview.go index a796a495..2c1150e5 100644 --- a/ui/gameview.go +++ b/ui/gameview.go @@ -11,19 +11,37 @@ import ( const padding = 0 type GameView struct { - director *Director - console *nes.Console - title string - hash string - texture uint32 - record bool - frames []image.Image + director *Director + console *nes.Console + title string + hash string + texture uint32 + record bool + frames []image.Image + + keyPressed []bool + imageChannel chan *image.RGBA + inputChannel chan int } -func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA) View { +func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View { texture := createTexture() - return &GameView{director, console, title, hash, texture, false, nil, imageChannel} + gameview := &GameView{director, console, title, hash, texture, false, nil, make([]bool, 255), imageChannel, inputChannel} + go gameview.ListenToInputChannel() + return gameview +} + +func (view *GameView) ListenToInputChannel() { + for { + key := <-view.inputChannel + if key < 0 { + view.keyPressed[-key] = false + } else { + view.keyPressed[key] = true + } + + } } func (view *GameView) Enter() { @@ -75,7 +93,8 @@ func (view *GameView) Update(t, dt float64) { if readKey(window, glfw.KeyEscape) { view.director.ShowMenu() } - updateControllers(window, console) + //updateControllers(window, console) + view.updateControllers() console.StepSeconds(dt) gl.BindTexture(gl.TEXTURE_2D, view.texture) setTexture(console.Buffer()) @@ -140,3 +159,17 @@ func updateControllers(window *glfw.Window, console *nes.Console) { console.SetButtons1(combineButtons(k1, j1)) console.SetButtons2(j2) } + +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) +} diff --git a/ui/run.go b/ui/run.go index d20fe88e..af9c382a 100644 --- a/ui/run.go +++ b/ui/run.go @@ -25,7 +25,7 @@ func init() { runtime.LockOSThread() } -func Run(paths []string, imageChannel chan *image.RGBA) { +func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int) { // initialize audio portaudio.Initialize() defer portaudio.Terminate() @@ -58,6 +58,6 @@ func Run(paths []string, imageChannel chan *image.RGBA) { gl.Enable(gl.TEXTURE_2D) // run director - director := NewDirector(window, audio, imageChannel) + director := NewDirector(window, audio, imageChannel, inputChannel) director.Start(paths) } diff --git a/webrtc/utils.go b/webrtc/utils.go deleted file mode 100644 index a6d69371..00000000 --- a/webrtc/utils.go +++ /dev/null @@ -1,77 +0,0 @@ -package webrtc - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "io/ioutil" -) - -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) - } -} diff --git a/webrtc/webrtc.go b/webrtc/webrtc.go index 5f351d82..67dc67c1 100644 --- a/webrtc/webrtc.go +++ b/webrtc/webrtc.go @@ -1,8 +1,14 @@ package webrtc import ( + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" "fmt" + "io/ioutil" "math/rand" + "strconv" "time" "github.com/pions/webrtc" @@ -15,10 +21,84 @@ var config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string // 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 } @@ -30,6 +110,7 @@ type WebRTC struct { isConnected bool // for yuvI420 image ImageChannel chan []byte + InputChannel chan int } // StartClient start webrtc @@ -83,6 +164,23 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e w.StopClient() } }) + //w.listenInputChannel() + // Data channel + 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)