From 44476d9b56c66e3b551872955844c71b6e3707c8 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 8 Sep 2019 12:02:59 +0800 Subject: [PATCH 1/5] Retouch readme (#79) * Update help overlay screen * Update REAME * Fix table column --- README.md | 40 ++++++++++++++++++------------------ static/img/help_overlay.png | Bin 15516 -> 20106 bytes 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d3c238a3..000d9c20 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,31 @@ # Web-based Cloud Gaming Service - [http://cloudretro.io](http://cloudretro.io) -- [**Game Instruction**](document/instruction/) +CloudRetro, Open-source Cloud Gaming Service For Retro Games + +This project aims to bring the most modern and convenient gaming experience to user as well as experiement the performance of Cloud-gaming technology. You can play any retro games on your browser directly, which is fully compatible on multi-platform like Desktop, Android, IOS. This flexibility also enables online gaming experience to retro games. + +\*Because there are limited servers in US East, US West, Eu, Singapore, you may experience some latency issues in particular regions. You can try hosting your own service following the instruction the next section to have a better sense of smoothness. **Video demo**: https://www.youtube.com/watch?v=koqWB1VKflo ---- - -CloudRetro, Open source Web-based Cloud Gaming Service building on [WebRTC](https://github.com/pion) and [LibRetro](https://retroarch.com/). - -This project aims to bring the most modern and convenient gaming experience to user. You can play any retro games on your browser directly, which is fully compatible on multi-platform like Desktop, Android, IOS. This flexibility also enables online gaming experience to retro games. - -Note: **Due to the high cost of hosting, I will Hibernate the servers for a while. I'm working on a big change and will turn on hosting again. Sorry for that :(** -You can try hosting your own service following the instruction in the next session. - Screenshot | Screenshot :-------------------------:|:-------------------------: ![screenshot](document/img/landing-page-ps-hm.png)|![screenshot](document/img/landing-page-ps-x4.png) ![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page-front.png) ## Feature -1. Cloud gaming: Game logic is hosted on a remote server. User doesn't have to install or setup anything. Images and audio are streamed to user in the most optimal way. -2. Cross-platform compatibility: The game is run on webbrowser, the most universal built-in app. No console, plugin, external app or devices are needed. The device must support webRTC to perform streaming. Joystick is also supported. -4. Emulator agnostic: The game can be play directly without emulator selection and initialization as long as the its cores are supported by RetroArch. -3. Vertically scaled + Load balancing: We can add more machines to handle more traffic. The closest server with highest free resource will be assigned to user. +1. Cloud gaming: Game logic and storage is hosted on cloud service. It reduces the cumbersome of game initialization. Images and audio are streamed to user in the most optimal way. +2. Cross-platform compatibility: The game is run on web browser, the most universal built-in app. No console, plugin, external app or devices are needed. Chrome with the latest version and fully WebRTC support is recommended for the game. +3. Emulator agnostic: The game can be played directly without any extra effort to set up the gaming emulator or platform. +4. Vertically scaled: The infrastructure is designed to be able to scale under high traffic by adding more instances. 5. Cloud storage: Game state is storing on online storage, so you can come back to continue playing in a game. +6. Online multiplayer: Bring online multiplayer gaming to retro games. (In Road map) +7. Collaborate gameplay: Follow the idea of "Twitch Plays Pokemon", multiple players can play the same game together (In Road map) ## Run on local by Docker -You try hosting the server yourself by running `./run_local_docker.sh`. It will spawn a docker environment and you can access the emulator on `localhost:8000`. +You try running the server yourself by running `make run-docker`. It will spawn a docker environment and you can access the service on `localhost:8000`. ## Development environment @@ -48,13 +45,16 @@ brew install libvpx pkg-config opus opusfile ... not tested yet ... ``` -And run - * `./run_local.sh` +Because coordinator and workers needs to run simulateneously. Workers connects to coordinator. +1. Script + * `make run` * The scripts includes build the binary using Go module +2. Manual + * `go run cmd/main.go -overlordhost overlord` - spawn coordinator + * `go run cmd/main.go -overlordhost ws://localhost:8000/wso` - spawn workers connecting to coordinator -## Documentation -- ✏ [Design Doc](document/designdoc/) -- 💿 [Implementation Doc](document/implementation/) +## Wiki +- [Wiki](https://github.com/giongto35/cloud-game/wiki) ## FAQ - [FAQ](https://github.com/giongto35/cloud-game/wiki/3.-FAQ) diff --git a/static/img/help_overlay.png b/static/img/help_overlay.png index e4b7ef27d219f1dcb253c962d6b25c8036dc9111..a4c6a5b9e9f3b9b5a377d4dd58b12c6cebeb47ec 100644 GIT binary patch literal 20106 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 literal 15516 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 Date: Sun, 8 Sep 2019 12:04:21 +0800 Subject: [PATCH 2/5] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 000d9c20..ce81adf2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Web-based Cloud Gaming Service +# CloudRetro, Open-source Cloud Gaming Service For Retro Games - [http://cloudretro.io](http://cloudretro.io) -CloudRetro, Open-source Cloud Gaming Service For Retro Games - This project aims to bring the most modern and convenient gaming experience to user as well as experiement the performance of Cloud-gaming technology. You can play any retro games on your browser directly, which is fully compatible on multi-platform like Desktop, Android, IOS. This flexibility also enables online gaming experience to retro games. \*Because there are limited servers in US East, US West, Eu, Singapore, you may experience some latency issues in particular regions. You can try hosting your own service following the instruction the next section to have a better sense of smoothness. From 13f9e2e1d1d326b1a8df3bfe36c37155039dffa0 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 8 Sep 2019 12:06:49 +0800 Subject: [PATCH 3/5] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce81adf2..f41dcd9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# CloudRetro, Open-source Cloud Gaming Service For Retro Games -- [http://cloudretro.io](http://cloudretro.io) +# CloudRetro +**Open-source Cloud Gaming Service For Retro Games [http://cloudretro.io](http://cloudretro.io)** This project aims to bring the most modern and convenient gaming experience to user as well as experiement the performance of Cloud-gaming technology. You can play any retro games on your browser directly, which is fully compatible on multi-platform like Desktop, Android, IOS. This flexibility also enables online gaming experience to retro games. From 39fbc5f04c5b69642e25876fcdde0cb39efc45ac Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 8 Sep 2019 12:08:39 +0800 Subject: [PATCH 4/5] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f41dcd9f..a87d4538 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # CloudRetro -**Open-source Cloud Gaming Service For Retro Games [http://cloudretro.io](http://cloudretro.io)** +**Open-source Cloud Gaming Service For Retro Games** +**[http://cloudretro.io](http://cloudretro.io)** +## Introduction This project aims to bring the most modern and convenient gaming experience to user as well as experiement the performance of Cloud-gaming technology. You can play any retro games on your browser directly, which is fully compatible on multi-platform like Desktop, Android, IOS. This flexibility also enables online gaming experience to retro games. \*Because there are limited servers in US East, US West, Eu, Singapore, you may experience some latency issues in particular regions. You can try hosting your own service following the instruction the next section to have a better sense of smoothness. From d8f57a3c584a42c452141c02ba0773b9904bf1e8 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Sun, 8 Sep 2019 13:18:12 +0800 Subject: [PATCH 5/5] Encoder based on platform (#80) * Update help overlay screen * Encoder based on mobile or desktop * Update TODO note to use useragent for video encoding --- README.md | 2 +- cws/cws.go | 3 ++- static/js/ws.js | 7 ++++++- worker/handlers.go | 4 ++-- worker/overlord.go | 29 ++++++++++++++++++++++++++--- worker/room/media.go | 5 +++-- worker/room/room.go | 4 ++-- 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a87d4538..6aad25ac 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Because coordinator and workers needs to run simulateneously. Workers connects t ## Credits * *Pion* Webrtc team for the incredible Golang Webrtc library and their supports https://github.com/pion/webrtc/. -* *Nanoarch* Golang RetroArch https://github.com/libretro/go-nanoarch and https://retroarch.com. +* *Nanoarch/Retroarch* Golang RetroArch https://github.com/libretro/go-nanoarch and https://retroarch.com. * *gen2brain* for the h264 go encoder https://github.com/gen2brain/x264-go * *poi5305* for the video encoding https://github.com/poi5305/go-yuv2webRTC. * *fogleman* for the NES emulator https://github.com/fogleman/nes. diff --git a/cws/cws.go b/cws/cws.go index 9b1dd50b..fb26d25e 100644 --- a/cws/cws.go +++ b/cws/cws.go @@ -28,7 +28,8 @@ type Client struct { } type WSPacket struct { - ID string `json:"id"` + ID string `json:"id"` + // TODO: Make Data generic: map[string]interface{} for more usecases Data string `json:"data"` RoomID string `json:"room_id"` diff --git a/static/js/ws.js b/static/js/ws.js index 35b54860..9c6d1eec 100644 --- a/static/js/ws.js +++ b/static/js/ws.js @@ -270,7 +270,7 @@ function startGame() { log("Starting game screen"); screenState = "game"; - conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx], "room_id": roomID != null ? roomID : '', "player_index": 1})); + conn.send(JSON.stringify({"id": "start", "data": JSON.stringify({"game_name": gameList[gameIdx], "is_mobile": isMobileDevice()}), "room_id": roomID != null ? roomID : '', "player_index": 1})); // clear menu screen stopGameInputTimer(); @@ -283,3 +283,8 @@ function startGame() { return true } + +// Check mobile type because different mobile can accept different video encoder. +function isMobileDevice() { + return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1); +}; diff --git a/worker/handlers.go b/worker/handlers.go index 81fba6f9..aea237ed 100644 --- a/worker/handlers.go +++ b/worker/handlers.go @@ -131,12 +131,12 @@ func (h *Handler) detachRoom(roomID string) { // createNewRoom creates a new room // Return nil in case of room is existed -func (h *Handler) createNewRoom(gameName string, roomID string, playerIndex int) *room.Room { +func (h *Handler) createNewRoom(gameName string, roomID string, playerIndex int, videoEncoderType string) *room.Room { // If the roomID is empty, // or the roomID doesn't have any running sessions (room was closed) // we spawn a new room if roomID == "" || !h.isRoomRunning(roomID) { - room := room.NewRoom(roomID, gameName, h.onlineStorage) + room := room.NewRoom(roomID, gameName, videoEncoderType, h.onlineStorage) // TODO: Might have race condition h.rooms[room.ID] = room return room diff --git a/worker/overlord.go b/worker/overlord.go index e1f67377..ba05e026 100644 --- a/worker/overlord.go +++ b/worker/overlord.go @@ -1,8 +1,10 @@ package worker import ( + "encoding/json" "log" + "github.com/giongto35/cloud-game/config" "github.com/giongto35/cloud-game/cws" "github.com/giongto35/cloud-game/webrtc" "github.com/giongto35/cloud-game/worker/room" @@ -79,7 +81,18 @@ func (h *Handler) RouteOverlord() { session, _ := h.sessions[resp.SessionID] peerconnection := session.peerconnection - room := h.startGameHandler(resp.Data, resp.RoomID, resp.PlayerIndex, peerconnection) + // TODO: Standardize for all types of packet. Make WSPacket generic + var startPacket struct { + GameName string `json:"game_name"` + IsMobile bool `json:"is_mobile"` + } + + err := json.Unmarshal([]byte(resp.Data), &startPacket) + if err != nil { + panic(err) + } + + room := h.startGameHandler(startPacket.GameName, resp.RoomID, resp.PlayerIndex, peerconnection, getVideoEncoder(startPacket.IsMobile)) session.RoomID = room.ID // TODO: can data race h.rooms[room.ID] = room @@ -193,7 +206,17 @@ func getServerIDOfRoom(oc *OverlordClient, roomID string) string { return packet.Data } -func (h *Handler) startGameHandler(gameName, roomID string, playerIndex int, peerconnection *webrtc.WebRTC) *room.Room { +// getVideoEncoder returns video encoder based on some qualification. +// Actually Android is only supporting VP8 but H264 has better encoding performance +// TODO: Better use useragent attribute from frontend +func getVideoEncoder(isMobile bool) string { + if isMobile == true { + return config.CODEC_VP8 + } + return config.CODEC_H264 +} + +func (h *Handler) startGameHandler(gameName, roomID string, playerIndex int, peerconnection *webrtc.WebRTC, videoEncoderType string) *room.Room { log.Println("Starting game", gameName) // If we are connecting to overlord, request corresponding serverID based on roomID // TODO: check if roomID is in the current server @@ -202,7 +225,7 @@ func (h *Handler) startGameHandler(gameName, roomID string, playerIndex int, pee // If room is not running if room == nil { // Create new room - room = h.createNewRoom(gameName, roomID, playerIndex) + room = h.createNewRoom(gameName, roomID, playerIndex, videoEncoderType) // Wait for done signal from room go func() { <-room.Done diff --git a/worker/room/media.go b/worker/room/media.go index 0c9af3c6..124cb2d8 100644 --- a/worker/room/media.go +++ b/worker/room/media.go @@ -98,11 +98,12 @@ func (r *Room) startAudio(sampleRate int) { } } -func (r *Room) startVideo(width, height int) { +func (r *Room) startVideo(width, height int, videoEncoderType string) { var encoder encoder.Encoder var err error - if config.Codec == config.CODEC_H264 { + log.Println("Video Encoder: ", videoEncoderType) + if videoEncoderType == config.CODEC_H264 { encoder, err = h264encoder.NewH264Encoder(width, height, 1) } else { encoder, err = vpxencoder.NewVpxEncoder(width, height, 20, 1200, 5) diff --git a/worker/room/room.go b/worker/room/room.go index 2781df87..4ff7f598 100644 --- a/worker/room/room.go +++ b/worker/room/room.go @@ -50,7 +50,7 @@ type Room struct { } // NewRoom creates a new room -func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Room { +func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStorage *storage.Client) *Room { // If no roomID is given, generate it from gameName // If the is roomID, get gameName from roomID if roomID == "" { @@ -101,7 +101,7 @@ func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Roo room.director = getEmulator(emuName, roomID, imageChannel, audioChannel, inputChannel) gameMeta := room.director.LoadMeta(game.Path) - go room.startVideo(gameMeta.Width, gameMeta.Height) + go room.startVideo(gameMeta.Width, gameMeta.Height, videoEncoderType) go room.startAudio(gameMeta.AudioSampleRate) room.director.Start()