Remove HTTP

This commit is contained in:
giongto35 2019-04-09 23:05:04 +08:00
parent f27f03144f
commit 9bde5b14dd
5 changed files with 25 additions and 447 deletions

137
main.go
View file

@ -1,31 +1,25 @@
package main
import (
"html/template"
"math/rand"
"os"
"strconv"
"time"
pionRTC "github.com/pion/webrtc"
"encoding/json"
"fmt"
"image"
"io/ioutil"
"log"
"math/rand"
"net/http"
_ "net/http/pprof"
"strconv"
"time"
"github.com/giongto35/cloud-game/ui"
"github.com/giongto35/cloud-game/util"
"github.com/giongto35/cloud-game/webrtc"
"github.com/gorilla/websocket"
"encoding/json"
pionRTC "github.com/pion/webrtc"
)
const gameboyIndex = "./static/gameboy.html"
const httpIndex = "./static/index_http.html"
const wsIndex = "./static/index_ws.html"
var width = 256
@ -50,12 +44,6 @@ type WSPacket struct {
PlayerIndex int `json:"player_index"`
}
type SessionPacket struct {
Game string `json:"game"`
RoomID string `json:"room_id"`
SDP string `json:"sdp"`
}
type Room struct {
imageChannel chan *image.RGBA
inputChannel chan int
@ -64,54 +52,33 @@ type Room struct {
var rooms map[string]*Room
func init() {
}
func startGame(path string, roomID string, imageChannel chan *image.RGBA, inputChannel chan int) {
ui.Run([]string{path}, roomID, imageChannel, inputChannel)
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
fmt.Printf("Usage: %s [ws]\n", os.Args[0])
fmt.Println("http://localhost:8000")
rooms = map[string]*Room{}
if len(os.Args) > 1 {
service = "ws"
log.Println("Using websocket")
// ignore origin
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
http.HandleFunc("/ws", ws)
} else {
log.Println("Using http")
http.HandleFunc("/session", postSession)
}
// ignore origin
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
http.HandleFunc("/ws", ws)
http.HandleFunc("/", getWeb)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.ListenAndServe(":8000", nil)
}
func getWeb(w http.ResponseWriter, r *http.Request) {
if indexFN != gameboyIndex {
bs, err := ioutil.ReadFile(indexFN)
if err != nil {
log.Fatal(err)
}
w.Write(bs)
} else {
// gameboy index
tmpl := template.Must(template.ParseFiles(indexFN))
data := IndexPageData{
Service: service,
}
tmpl.Execute(w, data)
}
func startGame(path string, roomID string, imageChannel chan *image.RGBA, inputChannel chan int) {
ui.Run([]string{path}, roomID, imageChannel, inputChannel)
}
// initRoom initilize room returns roomID
func getWeb(w http.ResponseWriter, r *http.Request) {
bs, err := ioutil.ReadFile(indexFN)
if err != nil {
log.Fatal(err)
}
w.Write(bs)
}
// init initilize room returns roomID
func initRoom(roomID, gameName string) string {
roomID = generateRoomID()
imageChannel := make(chan *image.RGBA, 100)
@ -127,7 +94,9 @@ func initRoom(roomID, gameName string) string {
return roomID
}
// startSession handles one session call
func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string, playerIndex int) string {
// If the roomID is empty, we spawn a new room
if roomID == "" {
roomID = initRoom(roomID, gameName)
}
@ -168,15 +137,15 @@ func ws(w http.ResponseWriter, r *http.Request) {
}
req := WSPacket{}
res := 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
@ -228,64 +197,6 @@ func ws(w http.ResponseWriter, r *http.Request) {
}
}
func postSession(w http.ResponseWriter, r *http.Request) {
bs, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
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(postPacket.SDP, width, height)
if err != nil {
log.Fatalln(err)
}
roomID := postPacket.RoomID
if roomID == "" {
fmt.Println("Init Room")
//generate new room
roomID = generateRoomID()
imageChannel := make(chan *image.RGBA, 100)
inputChannel := make(chan int, 100)
rooms[roomID] = &Room{
imageChannel: imageChannel,
inputChannel: inputChannel,
rtcSessions: []*webrtc.WebRTC{},
}
go fanoutScreen(imageChannel, roomID)
go startGame("games/"+postPacket.Game, roomID, imageChannel, inputChannel)
// fanin input channel
// fanout output channel
} else {
// if there is room, reuse image channel, add webRTC session
}
rooms[roomID].rtcSessions = append(rooms[roomID].rtcSessions, webRTC)
faninInput(rooms[roomID].inputChannel, webRTC, 1)
res := SessionPacket{
SDP: localSession,
RoomID: roomID,
}
stRes, err := json.Marshal(res)
if err != nil {
log.Println("json marshal:", err)
}
//w.Write([]byte(localSession))
w.Write(stRes)
}
// generateRoomID generate a unique room ID containing 16 digits
func generateRoomID() string {
roomID := strconv.FormatInt(rand.Int63(), 16)

View file

@ -176,5 +176,5 @@
<script src="/static/js/gameboy_loader.js"></script>
<script src="/static/js/gameboy_controller.js"></script>
<script src="/static/js/{{.Service}}.js"></script>
<script src="/static/js/ws.js"></script>
</body>

View file

@ -1,245 +0,0 @@
<html>
<style>
textarea {
width: 60%;
height: 50px;
}
</style>
<select id="gameOp">
<option value="Contra.nes">Contra.nes</option>
<option value="Kirby's Adventure.nes">Kirby's Adventure.nes</option>
<option value="Mega Man 2.nes">Mega Man 2.nes</option>
<option value="Metal Gear.nes">Metal Gear.nes</option>
<option value="Mortal Kombat 4.nes">Mortal Kombat 4.nes</option>
<option value="Super Mario Bros 2.nes">Super Mario Bros 2.nes</option>
<option value="Super Mario Bros 3.nes">Super Mario Bros 3.nes</option>
<option value="Super Mario Bros.nes">Super Mario Bros.nes</option>
<option value="Teenage Mutant Ninja Turtles 3.nes">Teenage Mutant Ninja Turtles 3.nes</option>
<option value="VS Super Mario Bros.nes">VS Super Mario Bros.nes</option>
<option value="supermariobros.rom">supermariobros.rom</option>
<option value="zelda.rom">zelda.rom</option>
</select>
<button id="playGame" onclick="window.startSession()">Play Mario</button>
Room ID: <input type="text" id="roomID"><br>
<br/><br/>
<body scroll="no" style="overflow: hidden">
<div id="remoteVideos" ></div> <br />
<h3>Instruction</h3>
<div>
Use Up, Down, Left, Right to Move <br />
Z to jump (A) <br />
X to sprint (B) <br />
C is start button <br />
V is select button <br />
<!--Fullscreen media for better gaming experience<br /-->
</div><br>
<h3>Log:</h3>
<pre id="div"></pre>
<div>
🎮<u><i>Refresh to retry when checking is too long</i></u>
</div>
<script>
// miscs
DEBUG = true;
let log = msg => {
if (DEBUG) {
document.getElementById('div').innerHTML += msg + '<br>'
console.log(msg);
}
}
var localSessionDescription = ""
var remoteSessionDescription = ""
var roomID = ""
// Register with server the session description
function postSession(session) {
if (session == "") {
return;
}
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
d = JSON.parse(this.responseText);
remoteSessionDescription = d["sdp"]
document.getElementById('roomID').value = d["room_id"]
document.getElementById('playGame').disabled = false;
}
};
roomID = document.getElementById('roomID').value
xhttp.open("POST", "/session", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify({"game": gameOp.value, "room_id": roomID, "sdp": session}));
}
let pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
})
const dataChannelOptions = {
ordered: false, // do not guarantee order
maxPacketLifeTime: 3000, // in milliseconds
};
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]
el.autoplay = true
el.width = 800;
el.height = 600;
el.poster = new URL("https://orig00.deviantart.net/cdcd/f/2017/276/a/a/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif");
document.getElementById('remoteVideos').appendChild(el)
}
pc.onicecandidate = event => {
if (event.candidate === null) {
var session = btoa(JSON.stringify(pc.localDescription));
localSessionDescription = session;
console.log("ice candidate")
postSession(session)
}
}
pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: true}).then(d => pc.setLocalDescription(d)).catch(log)
// Key controller
keyState = {
// controllers
a: false,
b: false,
start: false,
select: false,
// navigators
up: false,
down: false,
left: false,
right: false,
}
keyMap = {
37: "left",
38: "up",
39: "right",
40: "down",
90: "a",
88: "b",
67: "start",
86: "select",
}
INPUT_FPS = 100;
INPUT_STATE_PACKET = 5;
stateUnchange = true;
unchangePacket = INPUT_STATE_PACKET;
function setState(e, bo) {
if (e.keyCode in keyMap) {
keyState[keyMap[e.keyCode]] = bo;
stateUnchange = false;
unchangePacket = INPUT_STATE_PACKET;
}
}
document.body.onkeydown = function(e){
setState(e, true);
};
document.body.onkeyup = function(e){
setState(e, false);
};
window.startSession = () => {
console.log("Start session")
let sd = remoteSessionDescription
if (sd === '') {
return alert('Session Description must not be empty')
}
try {
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(sd))));
} catch (e) {
alert(e);
}
}
var timer = null;
function sendInput() {
// prepare key
/*
const (
ButtonA = iota
ButtonB
ButtonSelect
ButtonStart
ButtonUp
ButtonDown
ButtonLeft
ButtonRight
)
*/
if (stateUnchange || unchangePacket > 0) {
st = "";
["a", "b", "select", "start", "up", "down", "left", "right"].forEach(elem => {
st += keyState[elem]?1:0;
});
ss = parseInt(st, 2);
console.log(`Key state string: ${st} ==> ${ss}`);
// send
inputChannel.send(ss);
stateUnchange = false;
unchangePacket--;
}
}
function startInput() {
if (timer == null) {
timer = setInterval(sendInput, 1000 / INPUT_FPS)
}
}
function endInput() {
clearInterval(timer);
timer = null;
}
pc.oniceconnectionstatechange = e => {
log(pc.iceConnectionState);
if (pc.iceConnectionState === "connected") {
startInput();
}
else if (pc.iceConnectionState === "disconnected") {
endInput();
}
}
</script>
</html>

View file

@ -1,86 +0,0 @@
// http signal server
function startGame() {
log("Starting game screen")
// clear
endInput();
document.getElementById('div').innerHTML = "";
$("#loading-screen").show();
$("#menu-screen").fadeOut();
// end clear
// Register with server the session description
function postSession(session) {
if (session == "") {
return;
}
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
remoteSessionDescription = this.responseText;
// document.getElementById('playGame').disabled = false;
// by original design, we would click to start.
startSession();
}
};
xhttp.open("POST", "/session", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify({ "game": GAME_LIST[gameIdx].nes, "sdp": session }));
}
let pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
})
inputChannel = pc.createDataChannel('foo')
inputChannel.onclose = () => log('inputChannel has closed')
inputChannel.onopen = () => log('inputChannel has opened')
inputChannel.onmessage = e => log(`Message from DataChannel '${inputChannel.label}' payload '${e.data}'`)
pc.ontrack = function (event) {
log("New stream, yay!");
document.getElementById("loading-screen").srcObject = event.streams[0];
// document.getElementById('remoteVideos').appendChild(el)
}
pc.onicecandidate = event => {
if (event.candidate === null) {
var session = btoa(JSON.stringify(pc.localDescription));
localSessionDescription = session;
postSession(session)
}
}
pc.createOffer({ offerToReceiveVideo: true, offerToReceiveAudio: true }).then(d => pc.setLocalDescription(d)).catch(log)
window.startSession = () => {
let sd = remoteSessionDescription
if (sd === '') {
return alert('Session Description must not be empty')
}
try {
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(sd))));
} catch (e) {
alert(e);
}
}
pc.oniceconnectionstatechange = e => {
log(`iceConnectionState: ${pc.iceConnectionState}`);
if (pc.iceConnectionState === "connected") {
startInput();
screenState = "game";
}
else if (pc.iceConnectionState === "disconnected") {
endInput();
}
}
}

View file

@ -81,8 +81,6 @@ function startGame() {
pc.ontrack = function (event) {
log("New stream, yay!");
document.getElementById("loading-screen").srcObject = event.streams[0];
// document.getElementById("loading-screen").width = 270;
// document.getElementById("loading-screen").height = 240;
}