mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 18:46:11 +00:00
299 lines
6.6 KiB
HTML
299 lines
6.6 KiB
HTML
<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="Mega Man.nes">Mega Man.nes</option>
|
|
<option value="Metal Gear.nes">Metal Gear.nes</option>
|
|
<option value="Mike Tyson.nes">Mike Tyson.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()" disabled>Play Mario</button-->
|
|
<button id="play" onclick="window.startGame()">Play</button>
|
|
|
|
<br/>
|
|
|
|
<div id="remoteVideos" ></div> <br />
|
|
<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>
|
|
|
|
|
|
<div id="div"></div>
|
|
|
|
<div>
|
|
Refresh to retry
|
|
</div>
|
|
<script>
|
|
|
|
// miscs
|
|
DEBUG = true;
|
|
|
|
let log = msg => {
|
|
if (DEBUG) {
|
|
document.getElementById('div').innerHTML += msg + '<br>'
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
|
|
// for http server
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
xhttp.open("POST", "/session", true);
|
|
xhttp.setRequestHeader("Content-type", "text/plain");
|
|
xhttp.send(session);
|
|
|
|
}
|
|
|
|
|
|
// web socket
|
|
|
|
window.startGame = () => {
|
|
conn = new WebSocket(`ws://${location.host}/ws`);
|
|
|
|
conn.onopen = () => {
|
|
log("WebSocket is opened. Send ping");
|
|
conn.send(JSON.stringify({"id": "ping", "data": gameOp.value}));
|
|
}
|
|
|
|
conn.onerror = error => {
|
|
log(`Websocket error: ${error}`);
|
|
}
|
|
|
|
conn.onclose = () => {
|
|
log("Websocket closed");
|
|
// pc.close();
|
|
}
|
|
|
|
conn.onmessage = e => {
|
|
d = JSON.parse(e.data);
|
|
switch (d["id"]) {
|
|
case "pong":
|
|
log("Recv pong. Start webrtc");
|
|
startWebRTC();
|
|
break;
|
|
case "sdp":
|
|
log("Got remote sdp");
|
|
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(d["data"]))));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// webrtc
|
|
let pc = new RTCPeerConnection({iceServers: [{urls: 'stun:stun.l.google.com:19302'}]})
|
|
|
|
var localSessionDescription = ""
|
|
var remoteSessionDescription = ""
|
|
var conn;
|
|
|
|
|
|
// input channel
|
|
let 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}'`);
|
|
}
|
|
|
|
|
|
// Input handler
|
|
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);
|
|
};
|
|
|
|
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(`iceConnectionState: ${pc.iceConnectionState}`);
|
|
|
|
if (pc.iceConnectionState === "connected") {
|
|
conn.send(JSON.stringify({"id": "start", "data": ""}));
|
|
startInput();
|
|
}
|
|
else if (pc.iceConnectionState === "disconnected") {
|
|
// else { // not sure about this =]
|
|
endInput();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// stream channel
|
|
pc.ontrack = function (event) {
|
|
var el = document.createElement(event.track.kind)
|
|
el.srcObject = event.streams[0]
|
|
el.autoplay = true
|
|
el.controls = true
|
|
|
|
document.getElementById('remoteVideos').appendChild(el)
|
|
}
|
|
|
|
|
|
|
|
// candidate packet from STUN
|
|
pc.onicecandidate = event => {
|
|
if (event.candidate === null) {
|
|
// var session = btoa(JSON.stringify(pc.localDescription));
|
|
// localSessionDescription = session;
|
|
// postSession(session)
|
|
} else {
|
|
console.log(JSON.stringify(event.candidate));
|
|
// conn.send(JSON.stringify({"id": "candidate", "data": JSON.stringify(event.candidate)}));
|
|
}
|
|
}
|
|
|
|
|
|
function startWebRTC() {
|
|
// create SDP
|
|
pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: true}).then(d => {
|
|
pc.setLocalDescription(d, () => {
|
|
// send to ws
|
|
session = btoa(JSON.stringify(pc.localDescription));
|
|
localSessionDescription = session;
|
|
log("Send SDP to remote peer");
|
|
conn.send(JSON.stringify({"id": "sdp", "data": session}));
|
|
});
|
|
|
|
}).catch(log);
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
</html>
|