mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-25 19:13:52 +00:00
Merge branch 'master' of github.com:giongto35/cloud-game
This commit is contained in:
commit
6e91a891d7
7 changed files with 64 additions and 34 deletions
46
README.md
vendored
46
README.md
vendored
|
|
@ -1,34 +1,31 @@
|
|||
# Web-based Cloud Gaming Service
|
||||
- [http://cloudretro.io](http://cloudretro.io)
|
||||
# CloudRetro
|
||||
**Open-source Cloud Gaming Service For Retro Games**
|
||||
**[http://cloudretro.io](http://cloudretro.io)**
|
||||
|
||||
- [**Game Instruction**](document/instruction/)
|
||||
## 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.
|
||||
|
||||
**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
|
||||
:-------------------------:|:-------------------------:
|
||||
|
|
||||
|
|
||||
|
||||
## 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)
|
||||
|
|
@ -62,7 +62,7 @@ And run
|
|||
## 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.
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
7
static/js/ws.js
vendored
7
static/js/ws.js
vendored
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue