I trying create simple Multi-player with HTML5 Canvas, JavaScript(too using John Resig simple Inheritance library) and Node.js with Socket.IO.
My client code:
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
var socket = new io.Socket('127.0.0.1', {port: 8080});
var player = null;
var UP = 'UP',
LEFT = 'LEFT',
DOWN = 'DOWN',
RIGHT = 'RIGHT';
socket.connect();
socket.on('connect', function() {socket.send();
console.log('Connected!');
player = new Player(50, 50);
});
socket.on('message', function(msg) {
if(msg == 'UP') {
player.moveUP();
} else if(msg == 'LEFT') {
player.moveLEFT();
} else if(msg == 'DOWN') {
player.moveDOWN();
} else if(msg == 'RIGHT') {
player.moveRIGHT();
} else {
}
});
socket.on('disconnect', function() {
console.log('Disconnected!');
});
var Player = Class.extend({
init : function(x, y) {
this.x = x;
this.y = y;
},
setX : function(x){
this.x = x;
},
getX : function(){
return this.x;
},
setY : function(y){
this.y = y;
},
getY : function(){
return this.y;
},
draw : function(){
context.clearRect(0, 0, 350, 150);
context.fillRect(this.x, this.y, 15, 15);
},
move : function() {
this.x += 1;
this.y += 1;
},
moveUP : function() {
this.y--;
},
moveLEFT : function() {
this.x--;
},
moveDOWN : function() {
this.y++;
},
moveRIGHT : function() {
this.x++;
}
});
function checkKeyCode(event) {
var keyCode;
if(event == null) {
keyCode = window.event.keyCode;
} else {
keyCode = event.keyCode;
}
switch(keyCode) {
case 38: // UP
player.moveUP();
socket.send(UP);
break;
case 37: // LEFT
player.moveLEFT();
socket.send(LEFT);
break;
case 40: //DOWN
player.moveDOWN();
socket.send(DOWN);
break;
case 39: // RIGHT
player.moveRIGHT();
socket.send(RIGHT);
break;
default:
break;
}
}
function update() {
player.draw();
}
var FPS = 30;
setInterval(function() {
update();
player.draw();
}, 1000/FPS);
function init() {
document.onkeydown = checkKeyCode;
}
init();
And server code:
var http = require('http'),
io = require('socket.io'),
buffer = new Array(),
server = http.createServer(function(req, res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>Hello world</h1>');
});
server.listen(8080);
var socket = io.listen(server);
socket.on('connection', function(client){
client.on('message', function(message){
console.log(message);
client.broadcast(message);
})
client.on('disconnect', function(){
})
});
And when I run two client's I with first client can move second client Rect and with second client move first client rect and something like with third client can move first and second client rect's.
I have question how to create real Multi-Player? something like:
Open three client's and first client get rect1, second rect2 and last rect3. First client only can move rect1, client third can move only rect3.
Maybe anyone have idea? I search in Google but don't find answer.
Sorry for my English language.
First, check out http://www.google.com/events/io/2011/sessions/super-browser-2-turbo-hd-remix-introduction-to-html5-game-development.html
it explains how to use requestAnimationFrame among other things.
Second, the game state should exist on the server and be mirrored on the clients.
When a player clicks down, the client should only send a message. The server should then send a message to all the clients, including the client that took the action.
Each player should exist as an object on the server. When a player logs in they should be brought up to date about the status of each player already on the server.
modified client code: http://codr.cc/s/d0154536/js
modified server code: http://codr.cc/s/f96ce1d2/js
Glenn Fiedler's What every programmer needs to know about game networking -article is good read for anyone who wants get into game networking. It explains the basics in very high level so that it is adaptable for JS and Socket.io.
In case anyone stumbles across this question as I have just now, I wanted to add this link as an example.
I was following the same path as the op several months ago and read every article I could find on the authoritative server model (including the one referenced in the answer by #Epeli), and how to implement it with nodejs/socketio.
The result of my research manifested itself in the github project located at the link provided above (there is also a live demo). Hope this helps someone.
There is now an open-source multiplayer realtime javascript server (and client library) called Lance.gg, which provides, as you say,
a real multiplayer experience
It handles client-side prediction, step drift, bending, and basic physics.
Disclaimer: I am one of the contributors
Related
I am interested in building a web application, which will be served by nginx, and that will ask user access to their web camera, record for a given time period, maybe replay it to user, and store it in the server.
I am also thinking of a basic user interface. Could that be pure HTML+PHP?. Could that be python?
Similar questions here do not seem very relevant/helpful.
What would you suggest?
You can use MediaRecorder to record video from webcam.
Record a video and save its data to recordedBlobs:
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
}
}
function startRecording() {
recordedBlobs = [];
let options = { mimeType: 'video/webm;codecs=vp8' };
let types = ['video/webm;codecs=vp9', 'video/webm\;codecs=h264', 'video/webm', 'video/mpeg', ''];
for (let i in types) {
try {
if (MediaRecorder.isTypeSupported(types[i])) {
options = { mimeType: types[i] };
break;
}
}
catch (e) {
console.log('Exception while creating MediaRecorder: ${JSON.stringify(e)}');
}
}
try {
mediaRecorder = new MediaRecorder(window.stream, options);
} catch (e) {
console.error('Exception while creating MediaRecorder: ${JSON.stringify(e)}');
return;
}
mediaRecorder.onstop = (event) => {
console.log('Recorder stopped: ', event);
};
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10); // collect 10ms of data
}
function stopRecording() {
mediaRecorder.stop();
}
Upload the video data to your action page:
function upload() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
var formData = new FormData();
formData.append("video", blob, fileName + ".webm");
var xhr = new XMLHttpRequest();
xhr.open("POST", "upload.aspx");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
}
}
};
xhr.onerror = function () {
alert(`Network Error`);
};
xhr.send(formData);
}
You can implement the action page in any programming language, PHP, Java, Python, or C#.
No. Access to the user's webcam through a browser requires JavaScript and APIs provided to it by the browser.
Serverside programs do not run on the user's computer and thus cannot access its hardware (and Python and PHP cannot run client-side from a webpage).
Trying to use Chrome's API here (nothing fancy, just want to get the permission box up at least) and no matter what example I find I seem to run into problems.
For example this github link here.. Now pretty much all of these examples are so old that the language is different now. RequestFileSystem is Chrome specific now with webkitRequestFileSystem and FileError has been replaced by DOMError. Here's the relevant javascript, with those parts swapped out to their modern equivalents:
window.webkitRequestFileSystem = window.webkitRequestFileSystem || window.webkitRequestFileSystem;
// Handle errors
function errorHandler(e) {
var msg = '';
switch (e.code) {
case DOMError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
break;
case DOMError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case DOMError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case DOMError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case DOMError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
};
console.log('Error: ' + msg);
}
// Init and write some data to a file
function onInitFs(fs) {
fs.root.getFile('log-f-api.txt', {create: true}, function(fileEntry) {
fileEntry.isFile === true;
fileEntry.name == 'log-f-api.txt';
fileEntry.fullPath == '/log-f-api.txt';
// Create a FileWriter object for our FileEntry (log.txt).
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function(e) {
console.log('Write completed.');
};
fileWriter.onerror = function(e) {
console.log('Write failed: ' + e);
};
// Create a new Blob and write it to log.txt.
if (!window.BlobBuilder && window.WebKitBlobBuilder)
window.BlobBuilder = window.WebKitBlobBuilder; // in Chrome 12.
var bb = new BlobBuilder();
bb.append("some stuff");
console.log("bb size:"+bb.size);
bb.append('put some nice text in our file....');
var ourData = bb.getBlob('text/plain');
fileWriter.write(ourData);
}, errorHandler);
}, errorHandler);
}
// start the party
$(function() {
document.getElementById('hello').innerHTML = 'start the tests';
window.webkitRequestFileSystem(window.PERSISTENT, 5*1024*1024, onInitFs,errorHandler);
});
I've tried out a few different examples and no matter what I just "Error: Unknown Error" in console... I know that Chrome won't allow this API over file://, using both Apache over localhost as well as hosting it on a test server produces the same result. Everything I can find says it should work in Chrome 66, the version I'm trying to get this to work on. What am I doing wrong here?
Have an interesting problem ... I am making a BB10 application (I know) and am using the window.webkitRequestFileSystem call to a) provide a list of local files that this application has downloaded and b) read one of those files and display the contents - nothing crazy.
Individually, I can get both calls a) and b) to work; however, when I place them in the same function hoping to both display a list of files and read one of them, it causes the reading one not to fire at all.
In the example below, regardless of whether the listing call comes before or after the reading call, the reading call will not fire, i.e., I will never see the 'fileentry' alert.
What's going on?
function initFS(){
sharedFolder = blackberry.io.sharedFolder;
var path = sharedFolder+'/downloads';
//alert(path);
document.getElementById('plancontents').value="initialized";
window.requestFileSystem = window.requestFileSystem ||
window.webkitRequestFileSystem;
//window.requestFileSystem(window.TEMPORARY, 1024 * 1024,
//window.requestFileSystem(LocalFileSystem.PERSISTENT, 1024*1024*5,
window.webkitRequestFileSystem(window.PERSISTENT, 1024*1024*5,
function (fs) {
//fs.root.getFile(blackberry.io.sharedFolder
//fs.root.getFile('file:///accounts/1000/shared/downloads/filename.xml', {create: false},
fs.root.getFile(sharedFolder+'/downloads/filename.xml', {create: false},
function (fileEntry) {
alert("fileentry");
fileEntry.file(function (file) {
var reader = new FileReader();
//alert("trying to read file");
reader.onloadend = function (e) {
document.getElementById('plancontents').value = this.result;
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);
});
window.webkitRequestFileSystem(window.PERSISTENT, 1024*1024*5,
function (fs) {
alert('FS requested and inside');
fs.root.getDirectory(path, {}, function(dirEntry){
alert('inside getDir');
var dirReader = dirEntry.createReader();
alert('created reader');
dirReader.readEntries(function(entries) {
alert('trying to list dir');
for(var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (entry.isDirectory) {
alert('Directory: ' + entry.fullPath);
}
else if (entry.isFile) {
alert('File: ' + entry.fullPath);
}
}
}, errorHandler);
}, errorHandler);
});
}
To react to specific space handlers I typically do this -
var fooHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
fooHandler.setInputAction(function(movement){
// do stuff
}, Cesium.ScreenSpaceEventType.WHEEL);
This function would be limited to WHEEL inputs. I have a couple of things that I need to do every time the camera changes position or height. I tried creating an event handler for the camera in a fashion similar to the above, and then calling camera.positionCartographic within that function, but to no avail.
Is there an event in Cesium that captures any movement?
You don't want to use ScreenSpaceEventHandler to do this. Instead, you subscribe to the preRender event and compare the camera position from last frame. Here's some sample code for you:
var lastTime = Cesium.getTimestamp();
var lastPosition = viewer.scene.camera.position.clone();
function preRender(scene) {
var time = Cesium.getTimestamp();
var position = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(lastPosition, position, Cesium.Math.EPSILON4)) {
document.getElementById('viewChanged').style.display = 'block';
lastTime = time;
} else if (time - lastTime > 250) {
//hide the 'view changed' message after 250 ms of inactivity
lastTime = time;
document.getElementById('viewChanged').style.display = 'none';
}
lastPosition = position.clone();
}
viewer.scene.preRender.addEventListener(preRender);
We plan on adding a viewChanged event to Cesium some time soon, perhaps with 1.8, but this code will continue to work after that and you'll be able to switch to the event at your leisure.
If you want a live demo of the above code, see this port of the view changed Google Earth demo we did in Cesium: http://analyticalgraphicsinc.github.io/cesium-google-earth-examples/examples/viewchangeEvent.html
Here's what I ended up doing:
_preRender = function (scene) {
var currentPosition = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(_lastPosition, currentPosition, Cesium.Math.EPSILON4)) {
_lastPosition = currentPosition.clone();
if (typeof _positionChangeTimeout !== 'undefined' && _positionChangeTimeout !== null)
{
clearTimeout(_positionChangeTimeout);
}
var currentPositionCartographic = scene.camera.positionCartographic;
_positionChangeTimeout = setTimeout(function() {
if (typeof _positionChangeListener === 'function' && _positionChangeListener !== null)
{
_positionChangeListener({
lat: Cesium.Math.toDegrees(currentPositionCartographic.latitude),
long: Cesium.Math.toDegrees(currentPositionCartographic.longitude),
zoomLevel: _calcZoomForAltitude(currentPositionCartographic.height, currentPositionCartographic.latitude)
});
}
}, 250);
}
}
I'm testing WebRTC procedure step by step for my sake.
I wrote some testing site for server-less WebRTC.
http://webrtcdevelop.appspot.com/
In fact, STUN server by google is used, but no signalling server deployed.
Session Description Protocol (SDP) is exchanged manually by hand that is CopyPaste between browser windows.
So far, here is the result I've got with the code:
'use strict';
var peerCon;
var ch;
$(document)
.ready(function()
{
init();
$('#remotebtn2')
.attr("disabled", "");
$('#localbtn')
.click(function()
{
offerCreate();
$('#localbtn')
.attr("disabled", "");
$('#remotebtn')
.attr("disabled", "");
$('#remotebtn2')
.removeAttr("disabled");
});
$('#remotebtn')
.click(function()
{
answerCreate(
new RTCSessionDescription(JSON.parse($('#remote')
.val())));
$('#localbtn')
.attr("disabled", "");
$('#remotebtn')
.attr("disabled", "");
$('#remotebtn')
.attr("disabled", "");
});
$('#remotebtn2')
.click(function()
{
answerGet(
new RTCSessionDescription(JSON.parse($('#remote')
.val())));
$('#remotebtn2')
.attr("disabled", "");
});
$('#msgbtn')
.click(function()
{
msgSend($('#msg')
.val());
});
});
var init = function()
{
//offer------
peerCon =
new RTCPeerConnection(
{
"iceServers": [
{
"url": "stun:stun.l.google.com:19302"
}]
},
{
"optional": []
});
var localDescriptionOut = function()
{
console.log(JSON.stringify(peerCon.localDescription));
$('#local')
.text(JSON.stringify(peerCon.localDescription));
};
peerCon.onicecandidate = function(e)
{
console.log(e);
if (e.candidate === null)
{
console.log('candidate empty!');
localDescriptionOut();
}
};
ch = peerCon.createDataChannel(
'ch1',
{
reliable: true
});
ch.onopen = function()
{
dlog('ch.onopen');
};
ch.onmessage = function(e)
{
dlog(e.data);
};
ch.onclose = function(e)
{
dlog('closed');
};
ch.onerror = function(e)
{
dlog('error');
};
};
var msgSend = function(msg)
{
ch.send(msg);
}
var offerCreate = function()
{
peerCon
.createOffer(function(description)
{
peerCon
.setLocalDescription(description, function()
{
//wait for complete of peerCon.onicecandidate
}, error);
}, error);
};
var answerCreate = function(descreption)
{
peerCon
.setRemoteDescription(descreption, function()
{
peerCon
.createAnswer(
function(description)
{
peerCon
.setLocalDescription(description, function()
{
//wait for complete of peerCon.onicecandidate
}, error);
}, error);
}, error);
};
var answerGet = function(description)
{
peerCon.setRemoteDescription(description, function()
{ //
console.log(JSON.stringify(description));
dlog('local-remote-setDescriptions complete!');
}, error);
};
var error = function(e)
{
console.log(e);
};
var dlog = function(msg)
{
var content = $('#onmsg')
.html();
$('#onmsg')
.html(content + msg + '<br>');
}
Firefox(26.0):
RtpDataChannels
onopen event is fired successfully, but send fails.
Chrome(31.0):
RtpDataChannels
onopen event is fired successfully, and send also succeeded.
A SDP object by Chrome is as follows:
{"sdp":".................. cname:L5dftYw3P3clhLve
\r\
na=ssrc:2410443476 msid:ch1 ch1
\r\
na=ssrc:2410443476 mslabel:ch1
\r\
na=ssrc:2410443476 label:ch1
\r\n","type":"offer"}
where the ch1 information defined in the code;
ch = peerCon.createDataChannel(
'ch1',
{
reliable: false
});
is bundled properly.
However, a SDP object (local description) by Firefox does not contain DataChannel at all, and moreover, the SDP is much shorter than Chrome, and less information bundled.
What do I miss?
Probably, I guess the reason that send fails on DataChannel is due to this lack of information in the SDP object by firefox.
How could I fix this?
I investigated sources of various working libraries, such as peerJS, easyRTC, simpleWebRTC, but cannot figure out the reason.
Any suggestion and recommendation to read is appreciated.
[not an answer, yet]
I leave this here just trying to help you. I am not much of a WebRTC developer. But, curious i am, this quite new and verry interresting for me.
Have you seen this ?
DataChannels
Supported in Firefox today, you can use DataChannels to send peer-to-peer
information during an audio/video call. There is
currently a bug that requires developers to set up some sort of
audio/video stream (even a “fake” one) in order to initiate a
DataChannel, but we will soon be fixing that.
Also, i found this bug hook, witch seems to be related.
One last point, your version of adapter.js is different from the one served on code.google. And .. alot. the webrtcDetectedVersion part is missing in yours.
https://code.google.com/p/webrtc/source/browse/stable/samples/js/base/adapter.js
Try that, come back to me with good newz. ?
After last updating, i have this line in console after clicking 'get answer'
Object { name="INVALID_STATE", message="Cannot set remote offer in
state HAVE_LOCAL_OFFER", exposedProps={...}, more...}
but this might be useless info ence i copy pasted the same browser offre to answer.
.. witch made me notice you are using jQuery v1.7.1 jquery.com.
Try updating jQuery (before i kill a kitten), and in the meantime, try make sure you use all updated versions of scripts.
Woups, after fast reading this : https://developer.mozilla.org/en-US/docs/Web/Guide/API/WebRTC/WebRTC_basics then comparing your javascripts, i see no SHIM.
Shims
As you can imagine, with such an early API, you must use the browser
prefixes and shim it to a common variable.
> var PeerConnection = window.mozRTCPeerConnection ||
> window.webkitRTCPeerConnection; var IceCandidate =
> window.mozRTCIceCandidate || window.RTCIceCandidate; var
> SessionDescription = window.mozRTCSessionDescription ||
> window.RTCSessionDescription; navigator.getUserMedia =
> navigator.getUserMedia || navigator.mozGetUserMedia ||
> navigator.webkitGetUserMedia;