Commit 7a216f4c by Christoph

V0.984 rc.

* Cleanup callapp for easier use * Support for async init to have video devices available from the first frame * better work with recent Chrome changes and if loaded via http / file: url’s (mediaDevices null) * Will fail now during init some WebRTC features aren’t available to avoid cryptic error messages during runtime * Added GetBufferedAmount
parent 062d45ee
......@@ -16,7 +16,7 @@
<input type="checkbox" name="audio" class="callapp_send_audio" checked autocomplete="off"> Audio
<input type="checkbox" name="video" class="callapp_send_video" checked autocomplete="off"> Video
<input type= "text" class="callapp_address" autocomplete="off">
<button class="callapp_button"> Start / Stop </button>
<button class="callapp_button"> Join </button>
<div class="callapp_local_video">local video</div>
<div class="callapp_remote_video">remote video</div>
</div>
......
......@@ -16,14 +16,15 @@
<input type="checkbox" name="audio" class="callapp_send_audio" checked autocomplete="off"> Audio
<input type="checkbox" name="video" class="callapp_send_video" checked autocomplete="off"> Video
<input type= "text" class="callapp_address" autocomplete="off">
<button class="callapp_button"> Start / Stop </button>
<button class="callapp_button"> Join </button>
<div class="callapp_local_video">local video</div>
<div class="callapp_remote_video">remote video</div>
</div>
<script>
//everything below is just the callapp.ts compiled to js without the "export" key word
//everything below is just the callapp.ts compiled to js via "npm run tsc -- -p src/apps"
//but without the "export" key word
//the inclusion of the ./bundle/awrtc.js above will make the interface available via
//the global variable "awrtc"
class CallApp {
......@@ -35,7 +36,7 @@ class CallApp {
this.mLocalVideo = null;
this.mRemoteVideo = {};
this.mIsRunning = false;
this.OnStartStopButtonClicked = () => {
this.Ui_OnStartStopButtonClicked = () => {
if (this.mIsRunning) {
this.Stop();
}
......@@ -43,7 +44,7 @@ class CallApp {
this.Start(this.mAddress, this.mAudio, this.mVideo);
}
};
this.OnUiUpdate = () => {
this.Ui_OnUpdate = () => {
console.debug("OnUiUpdate");
this.mAddress = this.mUiAddress.value;
this.mAudio = this.mUiAudio.checked;
......@@ -60,46 +61,6 @@ class CallApp {
this.mNetConfig.IsConference = false;
this.mNetConfig.SignalingUrl = "wss://signaling.because-why-not.com/callapp";
}
setupUi(parent) {
this.mUiAddress = parent.querySelector(".callapp_address");
this.mUiAudio = parent.querySelector(".callapp_send_audio");
this.mUiVideo = parent.querySelector(".callapp_send_video");
this.mUiUrl = parent.querySelector(".callapp_url");
this.mUiButton = parent.querySelector(".callapp_button");
this.mUiLocalVideoParent = parent.querySelector(".callapp_local_video");
this.mUiRemoteVideoParent = parent.querySelector(".callapp_remote_video");
this.mUiAudio.onclick = this.OnUiUpdate;
this.mUiVideo.onclick = this.OnUiUpdate;
this.mUiAddress.onkeyup = this.OnUiUpdate;
this.mUiButton.onclick = this.OnStartStopButtonClicked;
//set default value + make string "true"/"false" to proper booleans
this.mAudio = this.GetParameterByName("audio");
this.mAudio = this.tobool(this.mAudio, true);
this.mVideo = this.GetParameterByName("video");
this.mVideo = this.tobool(this.mVideo, true);
this.mAutostart = this.GetParameterByName("autostart");
this.mAutostart = this.tobool(this.mAutostart, false);
this.mAddress = this.GetParameterByName("a");
//if autostart is set but no address is given -> create one and reopen the page
if (this.mAddress === null && this.mAutostart == true) {
this.mAddress = this.GenerateRandomKey();
window.location.href = this.GetUrlParams();
}
else {
if (this.mAddress === null)
this.mAddress = this.GenerateRandomKey();
this.UpdateUi();
}
//used for interacting with the Unity CallApp
//current hack to get the html element delivered. by default this
//just the image is copied and given as array
//Lazy frames will be the default soon though
if (this.mAutostart) {
console.log("Starting automatically ... ");
this.Start(this.mAddress, this.mAudio, this.mVideo);
}
console.log("address: " + this.mAddress + " audio: " + this.mAudio + " video: " + this.mVideo + " autostart: " + this.mAutostart);
}
GetParameterByName(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
......@@ -117,29 +78,11 @@ class CallApp {
return false;
return defaultval;
}
GenerateRandomKey() {
var result = "";
for (var i = 0; i < 7; i++) {
result += String.fromCharCode(65 + Math.round(Math.random() * 25));
}
return result;
}
GetUrlParams() {
return "?a=" + this.mAddress + "&audio=" + this.mAudio + "&video=" + this.mVideo + "&" + "autostart=" + true;
}
GetUrl() {
return location.protocol + '//' + location.host + location.pathname + this.GetUrlParams();
}
UpdateUi() {
console.log("UpdateUi");
this.mUiAddress.value = this.mAddress;
this.mUiAudio.checked = this.mAudio;
this.mUiVideo.checked = this.mVideo;
this.mUiUrl.innerHTML = this.GetUrl();
}
Start(address, audio, video) {
if (this.mCall != null)
this.Stop();
this.mIsRunning = true;
this.Ui_OnStart();
console.log("start");
console.log("Using signaling server url: " + this.mNetConfig.SignalingUrl);
//create media configuration
......@@ -149,12 +92,14 @@ class CallApp {
config.IdealWidth = 640;
config.IdealHeight = 480;
config.IdealFps = 30;
config.FrameUpdates = false;
//For usage in HTML set FrameUpdates to false and wait for MediaUpdate to
//get the VideoElement. By default awrtc would deliver frames individually
//for use in Unity WebGL
console.log("requested config:" + JSON.stringify(config));
//setup our high level call class.
this.mCall = new awrtc.BrowserWebRtcCall(this.mNetConfig);
//handle events (get triggered after Configure / Listen call)
//+ugly lahmda to avoid loosing "this" reference
//+ugly lambda to avoid loosing "this" reference
this.mCall.addEventListener((sender, args) => {
this.OnNetworkEvent(sender, args);
});
......@@ -173,91 +118,89 @@ class CallApp {
this.mCall.Listen(address);
}
Stop() {
this.Cleanup();
}
Cleanup() {
if (this.mCall != null) {
this.mCall.Dispose();
this.mCall = null;
clearInterval(this.mIntervalId);
this.mIntervalId = -1;
this.mIsRunning = false;
this.mLocalVideo = null;
this.mRemoteVideo = {};
}
this.Ui_OnCleanup();
}
Update() {
if (this.mCall != null)
this.mCall.Update();
}
OnNetworkEvent(sender, args) {
//User gave access to requiested camera/ micrphone
//User gave access to requested camera/ microphone
if (args.Type == awrtc.CallEventType.ConfigurationComplete) {
console.log("configuration complete");
}
/*
else if (args.Type == awrtc.CallEventType.FrameUpdate) {
//frame updated. For unity this needs to be called every frame to deliver
//the byte array but for the browser we get the internally used video element
//for now. (this system will probably change in the future)
let frameUpdateArgs = args as awrtc.FrameUpdateEventArgs;
var lazyFrame = frameUpdateArgs.Frame as LazyFrame;
if (this.mLocalVideo == null && frameUpdateArgs.ConnectionId == awrtc.ConnectionId.INVALID) {
let videoElement = lazyFrame.FrameGenerator.VideoElement;
this.mLocalVideo = videoElement;
this.mUiLocalVideoParent.appendChild(videoElement);
console.log("local video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
else if (frameUpdateArgs.ConnectionId != awrtc.ConnectionId.INVALID && this.mRemoteVideo[frameUpdateArgs.ConnectionId.id] == null) {
var videoElement = lazyFrame.FrameGenerator.VideoElement;
this.mRemoteVideo[frameUpdateArgs.ConnectionId.id] = videoElement;
this.mUiRemoteVideoParent.appendChild(videoElement);
console.log("remote video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
}
*/
else if (args.Type == awrtc.CallEventType.MediaUpdate) {
let margs = args;
if (this.mLocalVideo == null && margs.ConnectionId == awrtc.ConnectionId.INVALID) {
var videoElement = margs.VideoElement;
this.mLocalVideo = videoElement;
this.mUiLocalVideoParent.appendChild(videoElement);
this.Ui_OnLocalVideo(videoElement);
console.log("local video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
else if (margs.ConnectionId != awrtc.ConnectionId.INVALID && this.mRemoteVideo[margs.ConnectionId.id] == null) {
var videoElement = margs.VideoElement;
this.mRemoteVideo[margs.ConnectionId.id] = videoElement;
this.mUiRemoteVideoParent.appendChild(videoElement);
this.Ui_OnRemoteVideo(videoElement, margs.ConnectionId);
console.log("remote video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
}
else if (args.Type == awrtc.CallEventType.ListeningFailed) {
//First attempt of this example is to try to listen on a certain address
//for confernce calls this should always work (expect the internet is dead)
//for conference calls this should always work (expect the internet is dead)
if (this.mNetConfig.IsConference == false) {
//no conference call and listening failed? someone might have claimed the address.
//Try to connect to existing call
this.mCall.Call(this.mAddress);
}
else {
console.error("Listening failed. Server dead?");
let errorMsg = "Listening failed. Offline? Server dead?";
console.error(errorMsg);
this.Ui_OnError(errorMsg);
this.Cleanup();
return;
}
}
else if (args.Type == awrtc.CallEventType.ConnectionFailed) {
//Outgoing call failed entirely. This can mean there is no address to connect to,
//server is offline, internet is dead, firewall blocked access, ...
alert("connection failed");
let errorMsg = "Connection failed. Offline? Server dead? ";
console.error(errorMsg);
this.Ui_OnError(errorMsg);
this.Cleanup();
return;
}
else if (args.Type == awrtc.CallEventType.CallEnded) {
//call endet or was disconnected
//call ended or was disconnected
var callEndedEvent = args;
console.log("call ended with id " + callEndedEvent.ConnectionId.id);
//document.body.removeChild(mRemoteVideo[callEndedEvent.ConnectionId.id]);
//remove properly
this.mRemoteVideo[callEndedEvent.ConnectionId.id] = null;
delete this.mRemoteVideo[callEndedEvent.ConnectionId.id];
this.Ui_OnLog("Disconnected from user with id " + callEndedEvent.ConnectionId.id);
//check if this was the last user
if (this.mNetConfig.IsConference == false && Object.keys(this.mRemoteVideo).length == 0) {
//1 to 1 call and only user left -> quit
this.Cleanup();
return;
}
}
else if (args.Type == awrtc.CallEventType.Message) {
//no ui for this yet. simply echo messages for testing
let messageArgs = args;
this.mCall.Send(messageArgs.Content, messageArgs.Reliable, messageArgs.ConnectionId);
}
else if (args.Type == awrtc.CallEventType.DataMessage) {
//no ui for this yet. simply echo messages for testing
let messageArgs = args;
this.mCall.SendData(messageArgs.Content, messageArgs.Reliable, messageArgs.ConnectionId);
}
......@@ -265,6 +208,92 @@ class CallApp {
console.log("Unhandled event: " + args.Type);
}
}
setupUi(parent) {
this.mUiAddress = parent.querySelector(".callapp_address");
this.mUiAudio = parent.querySelector(".callapp_send_audio");
this.mUiVideo = parent.querySelector(".callapp_send_video");
this.mUiUrl = parent.querySelector(".callapp_url");
this.mUiButton = parent.querySelector(".callapp_button");
this.mUiLocalVideoParent = parent.querySelector(".callapp_local_video");
this.mUiRemoteVideoParent = parent.querySelector(".callapp_remote_video");
this.mUiAudio.onclick = this.Ui_OnUpdate;
this.mUiVideo.onclick = this.Ui_OnUpdate;
this.mUiAddress.onkeyup = this.Ui_OnUpdate;
this.mUiButton.onclick = this.Ui_OnStartStopButtonClicked;
//set default value + make string "true"/"false" to proper booleans
this.mAudio = this.GetParameterByName("audio");
this.mAudio = this.tobool(this.mAudio, true);
this.mVideo = this.GetParameterByName("video");
this.mVideo = this.tobool(this.mVideo, true);
this.mAutostart = this.GetParameterByName("autostart");
this.mAutostart = this.tobool(this.mAutostart, false);
this.mAddress = this.GetParameterByName("a");
//if autostart is set but no address is given -> create one and reopen the page
if (this.mAddress === null && this.mAutostart == true) {
this.mAddress = this.GenerateRandomKey();
window.location.href = this.GetUrlParams();
}
else {
if (this.mAddress === null)
this.mAddress = this.GenerateRandomKey();
this.Ui_Update();
}
//used for interacting with the Unity CallApp
//current hack to get the html element delivered. by default this
//just the image is copied and given as array
//Lazy frames will be the default soon though
if (this.mAutostart) {
console.log("Starting automatically ... ");
this.Start(this.mAddress, this.mAudio, this.mVideo);
}
console.log("address: " + this.mAddress + " audio: " + this.mAudio + " video: " + this.mVideo + " autostart: " + this.mAutostart);
}
Ui_OnStart() {
this.mUiButton.textContent = "Stop";
}
Ui_OnCleanup() {
this.mUiButton.textContent = "Join";
while (this.mUiLocalVideoParent.hasChildNodes()) {
this.mUiLocalVideoParent.removeChild(this.mUiLocalVideoParent.firstChild);
}
while (this.mUiRemoteVideoParent.hasChildNodes()) {
this.mUiRemoteVideoParent.removeChild(this.mUiRemoteVideoParent.firstChild);
}
}
Ui_OnLog(msg) {
}
Ui_OnError(msg) {
}
Ui_OnLocalVideo(video) {
this.mUiLocalVideoParent.appendChild(document.createElement("br"));
this.mUiLocalVideoParent.appendChild(video);
}
Ui_OnRemoteVideo(video, id) {
this.mUiRemoteVideoParent.appendChild(document.createElement("br"));
this.mUiRemoteVideoParent.appendChild(new Text("connection " + id.id));
this.mUiRemoteVideoParent.appendChild(document.createElement("br"));
this.mUiRemoteVideoParent.appendChild(video);
}
Ui_Update() {
console.log("UpdateUi");
this.mUiAddress.value = this.mAddress;
this.mUiAudio.checked = this.mAudio;
this.mUiVideo.checked = this.mVideo;
this.mUiUrl.innerHTML = this.GetUrl();
}
GenerateRandomKey() {
var result = "";
for (var i = 0; i < 7; i++) {
result += String.fromCharCode(65 + Math.round(Math.random() * 25));
}
return result;
}
GetUrlParams() {
return "?a=" + this.mAddress + "&audio=" + this.mAudio + "&video=" + this.mVideo + "&" + "autostart=" + true;
}
GetUrl() {
return location.protocol + '//' + location.host + location.pathname + this.GetUrlParams();
}
}
function callapp(parent) {
let callApp;
......@@ -277,9 +306,8 @@ function callapp(parent) {
callApp = new CallApp();
callApp.setupUi(parent);
}
callapp(document.querySelector("#callapp1"));
//examples.CAPIWebRtcNetwork_minimal();
callapp(document.querySelector("#callapp1"));
</script>
</body>
......
......@@ -9,9 +9,8 @@
<button onclick="apps.BrowserMediaNetwork_frameaccess()">BrowserMediaNetwork_frameaccess</button><br>
<button onclick="apps.WebsocketNetwork_sharedaddress()">WebsocketNetwork_sharedaddress</button><br>
<button onclick="apps.WebsocketNetwork_test1()">WebsocketNetwork_test1</button><br>
<button onclick="apps.CAPIWebRtcNetwork_testapp()">CAPIWebRtcNetwork_testapp</button><br>
<button onclick="apps.CAPIMediaNetwork_testapp()">CAPIMediaNetwork_testapp</button><br>
<button onclick="apps.CAPIMediaStreamAPI()">CAPIMediaStreamAPI</button><br>
<button onclick="apps.CAPI_WebRtcNetwork_testapp()">CAPI_WebRtcNetwork_testapp</button><br>
<button onclick="apps.CAPI_MediaNetwork_testapp()">CAPI_MediaNetwork_testapp</button><br>
</head>
<body>
<script>
......
{
"name": "awrtc_browser",
"version": "0.98.3",
"version": "0.98.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -1389,7 +1389,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
......@@ -1410,12 +1411,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
......@@ -1430,17 +1433,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
......@@ -1557,7 +1563,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
......@@ -1569,6 +1576,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
......@@ -1583,6 +1591,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
......@@ -1590,12 +1599,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
......@@ -1614,6 +1625,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
......@@ -1694,7 +1706,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
......@@ -1706,6 +1719,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
......@@ -1791,7 +1805,8 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
......@@ -1827,6 +1842,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
......@@ -1846,6 +1862,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
......@@ -1889,12 +1906,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
......
{
"name": "awrtc_browser",
"version": "0.98.3",
"version": "0.98.4",
"description": "",
"author": "because-why-not.com Limited",
"license": "BSD-3-Clause",
......
......@@ -29,29 +29,37 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as awrtc from "../awrtc/index"
import { ConnectionId } from "../awrtc/index";
/**
* Main (and most complicated) example for using BrowserWebRtcCall.
* Have a look at examples.html for easier scenarios.
*
*
*
* Features:
* - Build a "Join" system on top of the regular Listen / Call model to make it easier to use.
* - basic user interface (This is for easy testing not for use as a final application!!! Write your own using the API)
* - setup to be compatible with the Unity Asset's CallApp (but without TURN server!)
* - Get parameters from the address line to configure the call
* - autostart the call (this might not work in all browsers. Mostly used for testing)
* Todo:
* - text message system (so far it sends back the same message)
* - conference call support
*
*
*/
export class CallApp
{
private mAddress;
private mAudio;
private mVideo;
private mAutostart;
private mNetConfig = new awrtc.NetworkConfig();
private mCall : awrtc.BrowserWebRtcCall = null;
//update loop
private mIntervalId:any = -1;
private mLocalVideo = null;
private mRemoteVideo = {};
private mUiAddress: HTMLInputElement;
private mUiAudio: HTMLInputElement;
private mUiVideo: HTMLInputElement;
private mUiButton: HTMLButtonElement;
private mUiUrl: HTMLElement;
private mUiLocalVideoParent: HTMLElement;
private mUiRemoteVideoParent: HTMLElement;
private mLocalVideo: HTMLVideoElement = null;
private mRemoteVideo = {};
private mIsRunning = false;
......@@ -68,59 +76,6 @@ export class CallApp
this.mNetConfig.SignalingUrl = "wss://signaling.because-why-not.com/callapp";
}
public setupUi(parent : HTMLElement)
{
this.mUiAddress = parent.querySelector<HTMLInputElement>(".callapp_address");
this.mUiAudio = parent.querySelector<HTMLInputElement>(".callapp_send_audio");
this.mUiVideo = parent.querySelector<HTMLInputElement>(".callapp_send_video");
this.mUiUrl = parent.querySelector<HTMLParagraphElement>(".callapp_url");
this.mUiButton = parent.querySelector<HTMLInputElement>(".callapp_button");
this.mUiLocalVideoParent = parent.querySelector<HTMLParagraphElement>(".callapp_local_video");
this.mUiRemoteVideoParent = parent.querySelector<HTMLParagraphElement>(".callapp_remote_video");
this.mUiAudio.onclick = this.OnUiUpdate;
this.mUiVideo.onclick = this.OnUiUpdate;
this.mUiAddress.onkeyup = this.OnUiUpdate;
this.mUiButton.onclick = this.OnStartStopButtonClicked;
//set default value + make string "true"/"false" to proper booleans
this.mAudio = this.GetParameterByName("audio");
this.mAudio = this.tobool(this.mAudio , true)
this.mVideo = this.GetParameterByName("video");
this.mVideo = this.tobool(this.mVideo , true);
this.mAutostart = this.GetParameterByName("autostart");
this.mAutostart = this.tobool(this.mAutostart, false);
this.mAddress = this.GetParameterByName("a");
//if autostart is set but no address is given -> create one and reopen the page
if (this.mAddress === null && this.mAutostart == true) {
this.mAddress = this.GenerateRandomKey();
window.location.href = this.GetUrlParams();
}
else
{
if(this.mAddress === null)
this.mAddress = this.GenerateRandomKey();
this.UpdateUi();
}
//used for interacting with the Unity CallApp
//current hack to get the html element delivered. by default this
//just the image is copied and given as array
//Lazy frames will be the default soon though
if(this.mAutostart)
{
console.log("Starting automatically ... ")
this.Start(this.mAddress, this.mAudio , this.mVideo );
}
console.log("address: " + this.mAddress + " audio: " + this.mAudio + " video: " + this.mVideo + " autostart: " + this.mAutostart);
}
private GetParameterByName(name) {
......@@ -145,54 +100,15 @@ export class CallApp
private GenerateRandomKey() {
var result = "";
for (var i = 0; i < 7; i++) {
result += String.fromCharCode(65 + Math.round(Math.random() * 25));
}
return result;
}
private GetUrlParams() {
return "?a=" + this.mAddress + "&audio=" + this.mAudio + "&video=" + this.mVideo + "&" + "autostart=" + true;
}
private GetUrl() {
return location.protocol + '//' + location.host + location.pathname + this.GetUrlParams();
}
public OnStartStopButtonClicked = ()=>{
if(this.mIsRunning) {
this.Stop();
}else{
this.Start(this.mAddress, this.mAudio, this.mVideo);
}
}
public OnUiUpdate = ()=>
{
console.debug("OnUiUpdate");
this.mAddress = this.mUiAddress.value;
this.mAudio = this.mUiAudio.checked;
this.mVideo = this.mUiVideo.checked;
this.mUiUrl.innerHTML = this.GetUrl();
}
public UpdateUi() : void
{
console.log("UpdateUi");
this.mUiAddress.value = this.mAddress;
this.mUiAudio.checked = this.mAudio ;
this.mUiVideo.checked = this.mVideo ;
this.mUiUrl.innerHTML = this.GetUrl();
}
public Start(address, audio, video) : void
{
if(this.mCall != null)
this.Stop();
this.mIsRunning = true;
this.Ui_OnStart()
console.log("start");
console.log("Using signaling server url: " + this.mNetConfig.SignalingUrl);
......@@ -203,14 +119,16 @@ export class CallApp
config.IdealWidth = 640;
config.IdealHeight = 480;
config.IdealFps = 30;
config.FrameUpdates = false;
//For usage in HTML set FrameUpdates to false and wait for MediaUpdate to
//get the VideoElement. By default awrtc would deliver frames individually
//for use in Unity WebGL
console.log("requested config:" + JSON.stringify(config));
//setup our high level call class.
this.mCall = new awrtc.BrowserWebRtcCall(this.mNetConfig);
//handle events (get triggered after Configure / Listen call)
//+ugly lahmda to avoid loosing "this" reference
//+ugly lambda to avoid loosing "this" reference
this.mCall.addEventListener((sender, args)=>{
this.OnNetworkEvent(sender, args);
});
......@@ -240,13 +158,22 @@ export class CallApp
public Stop(): void
{
this.Cleanup();
}
private Cleanup():void{
if(this.mCall != null)
{
this.mCall.Dispose();
this.mCall = null;
clearInterval(this.mIntervalId);
this.mIntervalId = -1;
this.mIsRunning = false;
this.mLocalVideo = null;
this.mRemoteVideo = {};
}
this.Ui_OnCleanup();
}
private Update():void
......@@ -257,34 +184,10 @@ export class CallApp
private OnNetworkEvent(sender: any, args: awrtc.CallEventArgs):void{
//User gave access to requiested camera/ micrphone
//User gave access to requested camera/ microphone
if (args.Type == awrtc.CallEventType.ConfigurationComplete){
console.log("configuration complete");
}
/*
else if (args.Type == awrtc.CallEventType.FrameUpdate) {
//frame updated. For unity this needs to be called every frame to deliver
//the byte array but for the browser we get the internally used video element
//for now. (this system will probably change in the future)
let frameUpdateArgs = args as awrtc.FrameUpdateEventArgs;
var lazyFrame = frameUpdateArgs.Frame as LazyFrame;
if (this.mLocalVideo == null && frameUpdateArgs.ConnectionId == awrtc.ConnectionId.INVALID) {
let videoElement = lazyFrame.FrameGenerator.VideoElement;
this.mLocalVideo = videoElement;
this.mUiLocalVideoParent.appendChild(videoElement);
console.log("local video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
else if (frameUpdateArgs.ConnectionId != awrtc.ConnectionId.INVALID && this.mRemoteVideo[frameUpdateArgs.ConnectionId.id] == null) {
var videoElement = lazyFrame.FrameGenerator.VideoElement;
this.mRemoteVideo[frameUpdateArgs.ConnectionId.id] = videoElement;
this.mUiRemoteVideoParent.appendChild(videoElement);
console.log("remote video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
}
*/
else if (args.Type == awrtc.CallEventType.MediaUpdate) {
let margs = args as awrtc.MediaUpdatedEventArgs;
......@@ -292,50 +195,63 @@ export class CallApp
var videoElement = margs.VideoElement;
this.mLocalVideo = videoElement;
this.mUiLocalVideoParent.appendChild(videoElement);
this.Ui_OnLocalVideo(videoElement);
console.log("local video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
else if (margs.ConnectionId != awrtc.ConnectionId.INVALID && this.mRemoteVideo[margs.ConnectionId.id] == null) {
var videoElement = margs.VideoElement;
this.mRemoteVideo[margs.ConnectionId.id] = videoElement;
this.mUiRemoteVideoParent.appendChild(videoElement);
this.Ui_OnRemoteVideo(videoElement, margs.ConnectionId);
console.log("remote video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
}
else if (args.Type == awrtc.CallEventType.ListeningFailed) {
//First attempt of this example is to try to listen on a certain address
//for confernce calls this should always work (expect the internet is dead)
//for conference calls this should always work (expect the internet is dead)
if (this.mNetConfig.IsConference == false) {
//no conference call and listening failed? someone might have claimed the address.
//Try to connect to existing call
this.mCall.Call(this.mAddress);
}
else {
console.error("Listening failed. Server dead?");
let errorMsg = "Listening failed. Offline? Server dead?";
console.error(errorMsg);
this.Ui_OnError(errorMsg);
this.Cleanup();
return;
}
}
else if (args.Type == awrtc.CallEventType.ConnectionFailed) {
//Outgoing call failed entirely. This can mean there is no address to connect to,
//server is offline, internet is dead, firewall blocked access, ...
alert("connection failed");
let errorMsg = "Connection failed. Offline? Server dead? ";
console.error(errorMsg);
this.Ui_OnError(errorMsg);
this.Cleanup();
return;
}
else if (args.Type == awrtc.CallEventType.CallEnded) {
//call endet or was disconnected
//call ended or was disconnected
var callEndedEvent = args as awrtc.CallEndedEventArgs;
console.log("call ended with id " + callEndedEvent.ConnectionId.id);
//document.body.removeChild(mRemoteVideo[callEndedEvent.ConnectionId.id]);
//remove properly
this.mRemoteVideo[callEndedEvent.ConnectionId.id] = null;
delete this.mRemoteVideo[callEndedEvent.ConnectionId.id];
this.Ui_OnLog("Disconnected from user with id " + callEndedEvent.ConnectionId.id);
//check if this was the last user
if(this.mNetConfig.IsConference == false && Object.keys(this.mRemoteVideo).length == 0)
{
//1 to 1 call and only user left -> quit
this.Cleanup();
return;
}
}
else if (args.Type == awrtc.CallEventType.Message) {
//no ui for this yet. simply echo messages for testing
let messageArgs = args as awrtc.MessageEventArgs;
this.mCall.Send(messageArgs.Content, messageArgs.Reliable, messageArgs.ConnectionId);
}
else if (args.Type == awrtc.CallEventType.DataMessage) {
//no ui for this yet. simply echo messages for testing
let messageArgs = args as awrtc.DataMessageEventArgs;
this.mCall.SendData(messageArgs.Content, messageArgs.Reliable, messageArgs.ConnectionId);
}
......@@ -343,7 +259,146 @@ export class CallApp
console.log("Unhandled event: " + args.Type);
}
}
//UI calls. should be moved out into its own class later
private mAudio;
private mVideo;
private mAutostart;
private mUiAddress: HTMLInputElement;
private mUiAudio: HTMLInputElement;
private mUiVideo: HTMLInputElement;
private mUiButton: HTMLButtonElement;
private mUiUrl: HTMLElement;
private mUiLocalVideoParent: HTMLElement;
private mUiRemoteVideoParent: HTMLElement;
public setupUi(parent : HTMLElement)
{
this.mUiAddress = parent.querySelector<HTMLInputElement>(".callapp_address");
this.mUiAudio = parent.querySelector<HTMLInputElement>(".callapp_send_audio");
this.mUiVideo = parent.querySelector<HTMLInputElement>(".callapp_send_video");
this.mUiUrl = parent.querySelector<HTMLParagraphElement>(".callapp_url");
this.mUiButton = parent.querySelector<HTMLInputElement>(".callapp_button");
this.mUiLocalVideoParent = parent.querySelector<HTMLParagraphElement>(".callapp_local_video");
this.mUiRemoteVideoParent = parent.querySelector<HTMLParagraphElement>(".callapp_remote_video");
this.mUiAudio.onclick = this.Ui_OnUpdate;
this.mUiVideo.onclick = this.Ui_OnUpdate;
this.mUiAddress.onkeyup = this.Ui_OnUpdate;
this.mUiButton.onclick = this.Ui_OnStartStopButtonClicked;
//set default value + make string "true"/"false" to proper booleans
this.mAudio = this.GetParameterByName("audio");
this.mAudio = this.tobool(this.mAudio , true)
this.mVideo = this.GetParameterByName("video");
this.mVideo = this.tobool(this.mVideo , true);
this.mAutostart = this.GetParameterByName("autostart");
this.mAutostart = this.tobool(this.mAutostart, false);
this.mAddress = this.GetParameterByName("a");
//if autostart is set but no address is given -> create one and reopen the page
if (this.mAddress === null && this.mAutostart == true) {
this.mAddress = this.GenerateRandomKey();
window.location.href = this.GetUrlParams();
}
else
{
if(this.mAddress === null)
this.mAddress = this.GenerateRandomKey();
this.Ui_Update();
}
//used for interacting with the Unity CallApp
//current hack to get the html element delivered. by default this
//just the image is copied and given as array
//Lazy frames will be the default soon though
if(this.mAutostart)
{
console.log("Starting automatically ... ")
this.Start(this.mAddress, this.mAudio , this.mVideo );
}
console.log("address: " + this.mAddress + " audio: " + this.mAudio + " video: " + this.mVideo + " autostart: " + this.mAutostart);
}
private Ui_OnStart(){
this.mUiButton.textContent = "Stop";
}
private Ui_OnCleanup()
{
this.mUiButton.textContent = "Join";
while (this.mUiLocalVideoParent.hasChildNodes()) {
this.mUiLocalVideoParent.removeChild(this.mUiLocalVideoParent.firstChild);
}
while (this.mUiRemoteVideoParent.hasChildNodes()) {
this.mUiRemoteVideoParent.removeChild(this.mUiRemoteVideoParent.firstChild);
}
}
private Ui_OnLog(msg:string){
}
private Ui_OnError(msg:string){
}
private Ui_OnLocalVideo(video : HTMLVideoElement){
this.mUiLocalVideoParent.appendChild( document.createElement("br"));
this.mUiLocalVideoParent.appendChild(video);
}
private Ui_OnRemoteVideo(video : HTMLVideoElement, id: ConnectionId){
this.mUiRemoteVideoParent.appendChild( document.createElement("br"));
this.mUiRemoteVideoParent.appendChild(new Text("connection " + id.id));
this.mUiRemoteVideoParent.appendChild( document.createElement("br"));
this.mUiRemoteVideoParent.appendChild(video);
}
public Ui_OnStartStopButtonClicked = ()=>{
if(this.mIsRunning) {
this.Stop();
}else{
this.Start(this.mAddress, this.mAudio, this.mVideo);
}
}
public Ui_OnUpdate = ()=>
{
console.debug("OnUiUpdate");
this.mAddress = this.mUiAddress.value;
this.mAudio = this.mUiAudio.checked;
this.mVideo = this.mUiVideo.checked;
this.mUiUrl.innerHTML = this.GetUrl();
}
public Ui_Update() : void
{
console.log("UpdateUi");
this.mUiAddress.value = this.mAddress;
this.mUiAudio.checked = this.mAudio ;
this.mUiVideo.checked = this.mVideo ;
this.mUiUrl.innerHTML = this.GetUrl();
}
private GenerateRandomKey() {
var result = "";
for (var i = 0; i < 7; i++) {
result += String.fromCharCode(65 + Math.round(Math.random() * 25));
}
return result;
}
private GetUrlParams() {
return "?a=" + this.mAddress + "&audio=" + this.mAudio + "&video=" + this.mVideo + "&" + "autostart=" + true;
}
private GetUrl() {
return location.protocol + '//' + location.host + location.pathname + this.GetUrlParams();
}
}
......
......@@ -125,7 +125,7 @@ interface IRemoteVideoDict {
class MinimalCall
{
//just a number we give each local call to
//identify the output of each indivudal call
//identify the output of each individual call
mId:number = -1;
mCall: awrtc.BrowserWebRtcCall = null;
mLocalVideo: HTMLVideoElement = null;
......@@ -168,11 +168,12 @@ class MinimalCall
private OnCallEvent(sender: any, args: awrtc.CallEventArgs)
{
if (args.Type == awrtc.CallEventType.ConfigurationComplete) {
console.log("configuration complete");
this.mCall.Listen(this.mAddress);
} else if (args.Type == awrtc.CallEventType.FrameUpdate) {
}/* Old system. not used anymore
else if (args.Type == awrtc.CallEventType.FrameUpdate) {
let frameUpdateArgs = args as awrtc.FrameUpdateEventArgs;
if (this.mLocalVideo == null && frameUpdateArgs.ConnectionId == awrtc.ConnectionId.INVALID) {
......@@ -190,7 +191,28 @@ class MinimalCall
this.mRemoteVideo[frameUpdateArgs.ConnectionId.id] = lazyFrame.FrameGenerator.VideoElement;
this.mDiv.appendChild(this.mRemoteVideo[frameUpdateArgs.ConnectionId.id]);
}
} else if (args.Type == awrtc.CallEventType.ListeningFailed) {
}*/
else if (args.Type == awrtc.CallEventType.MediaUpdate) {
let margs = args as awrtc.MediaUpdatedEventArgs;
if (this.mLocalVideo == null && margs.ConnectionId == awrtc.ConnectionId.INVALID) {
var videoElement = margs.VideoElement;
this.mLocalVideo = videoElement;
this.mDiv.innerHTML += "local video: " + "<br>";
this.mDiv.appendChild(videoElement);
console.log("local video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
else if (margs.ConnectionId != awrtc.ConnectionId.INVALID && this.mRemoteVideo[margs.ConnectionId.id] == null) {
var videoElement = margs.VideoElement;
this.mRemoteVideo[margs.ConnectionId.id] = videoElement;
this.mDiv.innerHTML += "remote " + this.mId + "<br>";
this.mDiv.appendChild(videoElement);
console.log("remote video added resolution:" + videoElement.videoWidth + videoElement.videoHeight + " fps: ??");
}
}else if (args.Type == awrtc.CallEventType.ListeningFailed) {
if (this.mNetConfig.IsConference == false) {
//in 1 to 1 calls there is a listener and a caller
......@@ -242,9 +264,11 @@ export function BrowserWebRtcCall_minimal() {
let mediaConfigSender = new awrtc.MediaConfig();
mediaConfigSender.Video = true;
mediaConfigSender.Audio = true;
mediaConfigSender.FrameUpdates = false;
let mediaConfigReceiver = new awrtc.MediaConfig();
mediaConfigReceiver.Video = false;
mediaConfigReceiver.Audio = false;
mediaConfigReceiver.FrameUpdates = false;
//random key so we don't mistakenly connect
//to another user
......
......@@ -37,7 +37,7 @@ import { DeviceApi, DeviceInfo } from "../awrtc/index";
//testapp to run a full connection test using the CAPI
//which is used by the unity WebGL plugin
export function CAPIWebRtcNetwork_testapp() {
export function CAPI_WebRtcNetwork_testapp() {
console.log("test1");
var testMessage = "test1234";
......@@ -46,26 +46,26 @@ export function CAPIWebRtcNetwork_testapp() {
//var configuration = "{ \"signaling\" : { \"class\": \"WebsocketNetwork\", \"param\" : \"ws://localhost:12776\"}, \"iceServers\":[\"stun:stun.l.google.com:19302\"]}";
var configuration = "{ \"signaling\" : { \"class\": \"LocalNetwork\", \"param\" : null}, \"iceServers\":[{\"urls\": \"stun:stun.l.google.com:19302\"}]}";
var srv = awrtc.CAPIWebRtcNetworkCreate(configuration);
awrtc.CAPIWebRtcNetworkStartServer(srv, "Room1");
var srv = awrtc.CAPI_WebRtcNetwork_Create(configuration);
awrtc.CAPI_WebRtcNetwork_StartServer(srv, "Room1");
var clt = awrtc.CAPIWebRtcNetworkCreate(configuration);
var clt = awrtc.CAPI_WebRtcNetwork_Create(configuration);
setInterval(() => {
awrtc.CAPIWebRtcNetworkUpdate(srv);
awrtc.CAPI_WebRtcNetwork_Update(srv);
var evt = null;
while (evt = awrtc.CAPIWebRtcNetworkDequeue(srv)) {
while (evt = awrtc.CAPI_WebRtcNetwork_Dequeue(srv)) {
console.log("server inc: " + evt.toString());
if (evt.Type == awrtc.NetEventType.ServerInitialized) {
console.log("server started. Address " + evt.Info);
awrtc.CAPIWebRtcNetworkConnect(clt, evt.Info);
awrtc.CAPI_WebRtcNetwork_Connect(clt, evt.Info);
} else if (evt.Type == awrtc.NetEventType.ServerInitFailed) {
console.error("server start failed");
......@@ -74,21 +74,21 @@ export function CAPIWebRtcNetwork_testapp() {
} else if (evt.Type == awrtc.NetEventType.Disconnected) {
console.log("server peer disconnected");
console.log("server shutdown");
awrtc.CAPIWebRtcNetworkShutdown(srv);
awrtc.CAPI_WebRtcNetwork_Shutdown(srv);
} else if (evt.Type == awrtc.NetEventType.ReliableMessageReceived) {
//srv.SendData(evt.ConnectionId, evt.MessageData, true);
awrtc.CAPIWebRtcNetworkSendData(srv, evt.ConnectionId.id, evt.MessageData, true);
awrtc.CAPI_WebRtcNetwork_SendData(srv, evt.ConnectionId.id, evt.MessageData, true);
} else if (evt.Type == awrtc.NetEventType.UnreliableMessageReceived) {
//srv.SendData(evt.ConnectionId, evt.MessageData, false);
awrtc.CAPIWebRtcNetworkSendData(srv, evt.ConnectionId.id, evt.MessageData, false);
awrtc.CAPI_WebRtcNetwork_SendData(srv, evt.ConnectionId.id, evt.MessageData, false);
}
}
//srv.Flush();
awrtc.CAPIWebRtcNetworkFlush(srv);
awrtc.CAPI_WebRtcNetwork_Flush(srv);
//clt.Update();
awrtc.CAPIWebRtcNetworkUpdate(clt);
while (evt = awrtc.CAPIWebRtcNetworkDequeue(clt)) {
awrtc.CAPI_WebRtcNetwork_Update(clt);
while (evt = awrtc.CAPI_WebRtcNetwork_Dequeue(clt)) {
console.log("client inc: " + evt.toString());
......@@ -97,7 +97,7 @@ export function CAPIWebRtcNetwork_testapp() {
let buff = awrtc.Encoding.UTF16.GetBytes(testMessage);
//clt.SendData(evt.ConnectionId, buff, true);
awrtc.CAPIWebRtcNetworkSendData(clt, evt.ConnectionId.id, buff, true);
awrtc.CAPI_WebRtcNetwork_SendData(clt, evt.ConnectionId.id, buff, true);
} else if (evt.Type == awrtc.NetEventType.ReliableMessageReceived) {
//check last message
......@@ -110,7 +110,7 @@ export function CAPIWebRtcNetwork_testapp() {
let buff = awrtc.Encoding.UTF16.GetBytes(testMessage);
//clt.SendData(evt.ConnectionId, buff, false);
awrtc.CAPIWebRtcNetworkSendData(clt, evt.ConnectionId.id, buff, false);
awrtc.CAPI_WebRtcNetwork_SendData(clt, evt.ConnectionId.id, buff, false);
} else if (evt.Type == awrtc.NetEventType.UnreliableMessageReceived) {
let str = awrtc.Encoding.UTF16.GetString(evt.MessageData);
if (str != testMessage) {
......@@ -119,49 +119,49 @@ export function CAPIWebRtcNetwork_testapp() {
console.log("client disconnecting");
//clt.Disconnect(evt.ConnectionId);
awrtc.CAPIWebRtcNetworkDisconnect(clt, evt.ConnectionId.id);
awrtc.CAPI_WebRtcNetwork_Disconnect(clt, evt.ConnectionId.id);
console.log("client shutting down");
//clt.Shutdown();
awrtc.CAPIWebRtcNetworkShutdown(clt);
awrtc.CAPI_WebRtcNetwork_Shutdown(clt);
}
}
//clt.Flush();
awrtc.CAPIWebRtcNetworkFlush(clt);
awrtc.CAPI_WebRtcNetwork_Flush(clt);
}, 100);
}
//for testing the media API used by the unity plugin
export function CAPIMediaNetwork_testapp()
export function CAPI_MediaNetwork_testapp()
{
awrtc.BrowserMediaStream.DEBUG_SHOW_ELEMENTS = true;
var signalingUrl : string = DefaultValues.Signaling;
let lIndex = awrtc.CAPIMediaNetwork_Create("{\"IceUrls\":[\"stun:stun.l.google.com:19302\"], \"SignalingUrl\":\"ws://because-why-not.com:12776\"}");
let lIndex = awrtc.CAPI_MediaNetwork_Create("{\"IceUrls\":[\"stun:stun.l.google.com:19302\"], \"SignalingUrl\":\"ws://because-why-not.com:12776\"}");
let configDone = false;
awrtc.CAPIMediaNetwork_Configure(lIndex, true, true, 160, 120, 640, 480, 640, 480, -1, -1, -1);
console.log(awrtc.CAPIMediaNetwork_GetConfigurationState(lIndex));
awrtc.CAPI_MediaNetwork_Configure(lIndex, true, true, 160, 120, 640, 480, 640, 480, -1, -1, -1);
console.log(awrtc.CAPI_MediaNetwork_GetConfigurationState(lIndex));
let startTime = new Date().getTime();
let mainLoop = function () {
awrtc.CAPIWebRtcNetworkUpdate(lIndex);
if (awrtc.CAPIMediaNetwork_GetConfigurationState(lIndex) == (awrtc.MediaConfigurationState.Successful as number) && configDone == false) {
awrtc.CAPI_WebRtcNetwork_Update(lIndex);
if (awrtc.CAPI_MediaNetwork_GetConfigurationState(lIndex) == (awrtc.MediaConfigurationState.Successful as number) && configDone == false) {
configDone = true;
console.log("configuration done");
}
if (awrtc.CAPIMediaNetwork_GetConfigurationState(lIndex) == (awrtc.MediaConfigurationState.Failed as number)) {
if (awrtc.CAPI_MediaNetwork_GetConfigurationState(lIndex) == (awrtc.MediaConfigurationState.Failed as number)) {
alert("configuration failed");
}
if (configDone == false)
console.log(awrtc.CAPIMediaNetwork_GetConfigurationState(lIndex));
console.log(awrtc.CAPI_MediaNetwork_GetConfigurationState(lIndex));
if ((new Date().getTime() - startTime) < 15000) {
window.requestAnimationFrame(mainLoop);
} else {
console.log("shutting down");
awrtc.CAPIWebRtcNetworkRelease(lIndex);
awrtc.CAPI_WebRtcNetwork_Release(lIndex);
}
}
window.requestAnimationFrame(mainLoop);
......@@ -412,7 +412,7 @@ class FpsCounter
}
//Sends video data between two peers within the same browser window
//and accesses the resultung frame data directly
//and accesses the resulting frame data directly
export function BrowserMediaNetwork_frameaccess() {
......
......@@ -138,7 +138,7 @@ export class BrowserMediaNetwork extends WebRtcNetwork implements IMediaNetwork
//user requested specific device? get it now to properly add it to the
//constraints alter
//constraints later
let deviceId:string = null;
if(config.Video && config.VideoDeviceName && config.VideoDeviceName !== "")
{
......@@ -184,30 +184,39 @@ export class BrowserMediaNetwork extends WebRtcNetwork implements IMediaNetwork
constraints.video = video;
SLog.L("calling GetUserMedia. Media constraints: " + JSON.stringify(constraints));
let promise = navigator.mediaDevices.getUserMedia(constraints);
promise.then((stream) => { //user gave permission
//totally unrelated -> user gave access to devices. use this
//to get the proper names for our DeviceApi
DeviceApi.Update();
//call worked -> setup a frame buffer that deals with the rest
this.mLocalStream = new BrowserMediaStream(stream as MediaStream);
this.mLocalStream.InternalStreamAdded = (stream)=>{
this.EnqueueMediaEvent(MediaEventType.StreamAdded, ConnectionId.INVALID, this.mLocalStream.VideoElement);
};
//unlike native version this one will happily play the local sound causing an echo
//set to mute
this.mLocalStream.SetMute(true);
this.OnConfigurationSuccess();
});
promise.catch((err)=> {
//failed due to an error or user didn't give permissions
SLog.LE(err.name + ": " + err.message);
this.OnConfigurationFailed(err.message);
});
if(navigator && navigator.mediaDevices)
{
let promise = navigator.mediaDevices.getUserMedia(constraints);
promise.then((stream) => { //user gave permission
//totally unrelated -> user gave access to devices. use this
//to get the proper names for our DeviceApi
DeviceApi.Update();
//call worked -> setup a frame buffer that deals with the rest
this.mLocalStream = new BrowserMediaStream(stream as MediaStream);
this.mLocalStream.InternalStreamAdded = (stream)=>{
this.EnqueueMediaEvent(MediaEventType.StreamAdded, ConnectionId.INVALID, this.mLocalStream.VideoElement);
};
//unlike native version this one will happily play the local sound causing an echo
//set to mute
this.mLocalStream.SetMute(true);
this.OnConfigurationSuccess();
});
promise.catch((err)=> {
//failed due to an error or user didn't give permissions
SLog.LE(err.name + ": " + err.message);
this.OnConfigurationFailed(err.message);
});
}else{
//no access to media device -> fail
let error = "Configuration failed. navigator.mediaDevices is unedfined. The browser might not allow media access." +
"Is the page loaded via http or file URL? Some browsers only support https!";
SLog.LE(error);
this.OnConfigurationFailed(error);
}
} else {
this.OnConfigurationSuccess();
}
......
......@@ -53,6 +53,17 @@ export class DeviceApi
return DeviceApi.sLastUpdate > 0;
}
private static sIsPending = false;
public static get IsPending(){
return DeviceApi.sIsPending;
}
private static sLastError:string = null;
private static get LastError()
{
return this.sLastError;
}
private static sDeviceInfo: { [id: string] : DeviceInfo; } = {};
private static sVideoDeviceCounter = 1;
......@@ -87,6 +98,7 @@ export class DeviceApi
private static InternalOnEnum = (devices:MediaDeviceInfo[])=>
{
DeviceApi.sIsPending = false;
DeviceApi.sLastUpdate = new Date().getTime();
let newDeviceInfo: { [id: string] : DeviceInfo; } = {};
......@@ -141,7 +153,10 @@ export class DeviceApi
if(DeviceApi.sAccessStream)
{
DeviceApi.sAccessStream.stop();
var tracks = DeviceApi.sAccessStream.getTracks();
for (var i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
DeviceApi.sAccessStream = null;
}
DeviceApi.TriggerChangedEvent();
......@@ -158,11 +173,21 @@ export class DeviceApi
DeviceApi.sDeviceInfo = {};
DeviceApi.sVideoDeviceCounter = 1;
DeviceApi.sAccessStream = null;
DeviceApi.sLastError = null;
DeviceApi.sIsPending = false;
}
private static InternalOnError = (err:DOMError)=>
private static InternalOnErrorCatch = (err:DOMError)=>
{
let txt :string = err.toString();
DeviceApi.InternalOnErrorString(txt);
}
private static InternalOnErrorString = (err:string)=>
{
DeviceApi.sIsPending = false;
DeviceApi.sLastError = err;
SLog.LE(err);
DeviceApi.TriggerChangedEvent();
}
private static InternalOnStream = (stream:MediaStream)=>
......@@ -173,24 +198,48 @@ export class DeviceApi
/**Updates the device list based on the current
* access. Given devices numbers if the name isn't known.
* access. Gives the devices numbers if the name isn't known.
*/
public static Update():void
{
navigator.mediaDevices.enumerateDevices()
.then(DeviceApi.InternalOnEnum)
.catch(DeviceApi.InternalOnError);
DeviceApi.sLastError = null;
if(DeviceApi.IsApiAvailable())
{
DeviceApi.sIsPending = true;
navigator.mediaDevices.enumerateDevices()
.then(DeviceApi.InternalOnEnum)
.catch(DeviceApi.InternalOnErrorCatch);
}else{
DeviceApi.InternalOnErrorString("Can't access mediaDevices or enumerateDevices");
}
}
/**Checks if the API is available in the browser.
* false - browser doesn't support this API
* true - browser supports the API (might still refuse to give
* us access later on)
*/
public static IsApiAvailable():boolean
{
if(navigator && navigator.mediaDevices && navigator.mediaDevices.enumerateDevices)
return true;
return false;
}
/**Asks the user for access first to get the full
* device names.
*/
public static RequestUpdate():void
{
let constraints = {video:true};
navigator.mediaDevices.getUserMedia(constraints)
.then(DeviceApi.InternalOnStream)
.catch(DeviceApi.InternalOnError);
DeviceApi.sLastError = null;
if(DeviceApi.IsApiAvailable())
{
DeviceApi.sIsPending = true;
let constraints = {video:true};
navigator.mediaDevices.getUserMedia(constraints)
.then(DeviceApi.InternalOnStream)
.catch(DeviceApi.InternalOnErrorCatch);
}else{
DeviceApi.InternalOnErrorString("Can't access mediaDevices or enumerateDevices");
}
}
......
......@@ -127,7 +127,9 @@ export class MediaPeer extends WebRtcDataPeer
}
else{
for(let v of stream.getTracks())
{
this.mPeer.addTrack(v, stream);
}
}
}
......
......@@ -196,27 +196,39 @@ export class SLog {
{
SLog.sLogLevel = level;
}
public static RequestLogLevel(level: SLogLevel)
{
if(level > SLog.sLogLevel)
SLog.sLogLevel = level;
}
public static L(msg: any): void {
SLog.Log(msg);
public static L(msg: any, tag?:string): void {
SLog.Log(msg, tag);
}
public static LW(msg: any): void {
SLog.LogWarning(msg);
public static LW(msg: any, tag?:string): void {
SLog.LogWarning(msg, tag);
}
public static LE(msg: any): void {
SLog.LogError(msg);
public static LE(msg: any, tag?:string): void {
SLog.LogError(msg, tag);
}
public static Log(msg: any): void {
public static Log(msg: any, tag?:string): void {
if(!tag)
tag = "";
if(SLog.sLogLevel >= SLogLevel.Info)
console.log(msg);
console.log(msg, tag);
}
public static LogWarning(msg: any): void {
public static LogWarning(msg: any, tag?:string): void {
if(!tag)
tag = "";
if(SLog.sLogLevel >= SLogLevel.Warnings)
console.warn(msg);
console.warn(msg, tag);
}
public static LogError(msg: any) {
public static LogError(msg: any, tag?:string) {
if(!tag)
tag = "";
if(SLog.sLogLevel >= SLogLevel.Errors)
console.error(msg);
console.error(msg, tag);
}
}
\ No newline at end of file
......@@ -49,7 +49,23 @@ export enum NetEventType {
Disconnected = 8,//a connection was disconnected
FatalError = 100, //not yet used
Warning = 101,//not yet used
Log = 102 //not yet used
Log = 102, //not yet used
/// <summary>
/// This value and higher are reserved for other uses.
/// Should never get to the user and should be filtered out.
/// </summary>
ReservedStart = 200,
/// <summary>
/// Reserved.
/// Used by protocols that forward NetworkEvents
/// </summary>
MetaVersion = 201,
/// <summary>
/// Reserved.
/// Used by protocols that forward NetworkEvents.
/// </summary>
MetaHeartbeat = 202
}
export enum NetEventDataType {
Null = 0,
......@@ -330,6 +346,7 @@ export interface IBasicNetwork extends INetwork {
Connect(address: string): ConnectionId;
}
export interface IWebRtcNetwork extends IBasicNetwork {
GetBufferedAmount(id: ConnectionId, reliable:boolean): number;
}
//export {NetEventType, NetworkEvent, ConnectionId, INetwork, IBasicNetwork};
......@@ -151,8 +151,18 @@ export class WebRtcNetwork implements IBasicNetwork {
SLog.LogWarning("unknown connection id");
return false;
}
}
public GetBufferedAmount(id: ConnectionId, reliable: boolean): number {
let peer = this.mIdToConnection[id.id];
if (peer) {
return peer.GetBufferedAmount(reliable);
} else {
SLog.LogWarning("unknown connection id");
return -1;
}
}
public Disconnect(id: ConnectionId): void {
let peer = this.mIdToConnection[id.id];
......
......@@ -551,6 +551,27 @@ export class WebRtcDataPeer extends AWebRtcPeer {
}
return sentSuccessfully;
}
public GetBufferedAmount(reliable: boolean): number {
let result = -1;
try {
if (reliable) {
if (this.mReliableDataChannel.readyState === "open")
{
result = this.mReliableDataChannel.bufferedAmount;
}
}
else {
if (this.mUnreliableDataChannel.readyState === "open")
{
result = this.mUnreliableDataChannel.bufferedAmount;
}
}
} catch (e) {
SLog.LogError("Exception while trying to access GetBufferedAmount: " + e);
}
return result;
}
public DequeueEvent(/*out*/ ev: Output<NetworkEvent>): boolean {
//lock(mEvents)
......
......@@ -45,10 +45,13 @@ export enum WebsocketServerStatus {
ShuttingDown
}
//TODO: handle errors if the socket connection failed
//+ send back failed events for connected / serverstart events that are buffered
export class WebsocketNetwork implements IBasicNetwork {
public static readonly LOGTAG = "WebsocketNetwork";
//websocket.
private mSocket: WebSocket;
......@@ -73,13 +76,40 @@ export class WebsocketNetwork implements IBasicNetwork {
//next free connection id
private mNextOutgoingConnectionId = new ConnectionId(1);
/// <summary>
/// Version of the protocol implemented here
/// </summary>
public static readonly PROTOCOL_VERSION = 2;
/// <summary>
/// Minimal protocol version that is still supported.
/// V 1 servers won't understand heartbeat and version
/// messages but would just log an unknown message and
/// continue normally.
/// </summary>
public static readonly PROTOCOL_VERSION_MIN = 1;
/// <summary>
/// Assume 1 until message received
/// </summary>
private mRemoteProtocolVersion = 1;
private mUrl: string = null;
private mConfig: WebsocketNetwork.Configuration;
private mLastHeartbeat: number;
private mHeartbeatReceived = true;
private mIsDisposed = false;
public constructor(url: string) {
public constructor(url: string, configuration?:WebsocketNetwork.Configuration) {
this.mUrl = url;
this.mStatus = WebsocketConnectionStatus.NotConnected;
this.mConfig = configuration;
if(!this.mConfig)
this.mConfig = new WebsocketNetwork.Configuration();
this.mConfig.Lock();
}
private WebsocketConnect(): void {
......@@ -90,7 +120,6 @@ export class WebsocketNetwork implements IBasicNetwork {
this.mSocket.onerror = (error) => { this.OnWebsocketOnError(error); };
this.mSocket.onmessage = (e) => { this.OnWebsocketOnMessage(e); };
this.mSocket.onclose = (e) => { this.OnWebsocketOnClose(e); };
//js websockets connect automatically after creation?
}
private WebsocketCleanup() : void {
this.mSocket.onopen = null;
......@@ -114,7 +143,33 @@ export class WebsocketNetwork implements IBasicNetwork {
this.WebsocketConnect();
}
}
private UpdateHeartbeat():void{
if(this.mStatus == WebsocketConnectionStatus.Connected && this.mConfig.Heartbeat > 0)
{
let diff = Date.now() - this.mLastHeartbeat;
if(diff > (this.mConfig.Heartbeat * 1000))
{
//We trigger heatbeat timeouts only for protocol V2
//protocol 1 can receive the heatbeats but
//won't send a reply
//(still helpful to trigger TCP ACK timeout)
if(this.mRemoteProtocolVersion > 1
&& this.mHeartbeatReceived == false)
{
this.TriggerHeartbeatTimeout();
return;
}
this.mLastHeartbeat = Date.now();
this.mHeartbeatReceived = false;
this.SendHeartbeat();
}
}
}
private TriggerHeartbeatTimeout(){
SLog.L("Closing due to heartbeat timeout. Server didn't respond in time.", WebsocketNetwork.LOGTAG);
this.Cleanup();
}
private CheckSleep() : void
{
if (this.mStatus == WebsocketConnectionStatus.Connected
......@@ -131,11 +186,13 @@ export class WebsocketNetwork implements IBasicNetwork {
private OnWebsocketOnOpen() {
SLog.L('onWebsocketOnOpen');
SLog.L('onWebsocketOnOpen', WebsocketNetwork.LOGTAG);
this.mStatus = WebsocketConnectionStatus.Connected;
this.mLastHeartbeat = Date.now();
this.SendVersion();
}
private OnWebsocketOnClose(event: CloseEvent) {
SLog.L('Closed: ' + JSON.stringify(event));
SLog.L('Closed: ' + JSON.stringify(event), WebsocketNetwork.LOGTAG);
if(event.code != 1000)
{
......@@ -154,10 +211,9 @@ export class WebsocketNetwork implements IBasicNetwork {
|| this.mStatus == WebsocketConnectionStatus.NotConnected)
return;
//browsers will have ArrayBuffer in event.data -> change to byte array
let evt = NetworkEvent.fromByteArray(new Uint8Array(event.data));
this.HandleIncomingEvent(evt);
let msg = new Uint8Array(event.data);
this.ParseMessage(msg);
}
private OnWebsocketOnError(error) {
//the error event doesn't seem to have any useful information?
//browser is expected to call OnClose after this
......@@ -244,6 +300,31 @@ export class WebsocketNetwork implements IBasicNetwork {
this.mConnections.splice(index, 1);
}
}
private ParseMessage(msg:Uint8Array):void{
if(msg.length == 0)
{
}else if(msg[0] == NetEventType.MetaVersion)
{
if (msg.length > 1)
{
this.mRemoteProtocolVersion = msg[1];
}
else
{
SLog.LW("Received an invalid MetaVersion header without content.");
}
}else if(msg[0] == NetEventType.MetaHeartbeat)
{
this.mHeartbeatReceived = true;
}else
{
let evt = NetworkEvent.fromByteArray(msg);
this.HandleIncomingEvent(evt);
}
}
private HandleIncomingEvent(evt: NetworkEvent) {
if (evt.Type == NetEventType.NewConnection) {
......@@ -283,13 +364,38 @@ export class WebsocketNetwork implements IBasicNetwork {
while (this.mOutgoingQueue.length > 0) {
var evt = this.mOutgoingQueue.shift();
//var msg = NetworkEvent.toString(evt);
var msg = NetworkEvent.toByteArray(evt);
this.mSocket.send(msg);
this.SendNetworkEvent(evt);
}
}
private SendHeartbeat() : void
{
let msg = new Uint8Array(1);
msg[0] = NetEventType.MetaHeartbeat;
this.InternalSend(msg);
}
private SendVersion() :void
{
let msg = new Uint8Array(2);
msg[0] = NetEventType.MetaVersion;
msg[1] = WebsocketNetwork.PROTOCOL_VERSION;
this.InternalSend(msg);
}
private SendNetworkEvent(evt: NetworkEvent):void
{
var msg = NetworkEvent.toByteArray(evt);
this.InternalSend(msg);
}
private InternalSend(msg: Uint8Array): void
{
this.mSocket.send(msg);
}
private NextConnectionId(): ConnectionId {
var result = this.mNextOutgoingConnectionId;
this.mNextOutgoingConnectionId = new ConnectionId(this.mNextOutgoingConnectionId.id + 1);
......@@ -320,9 +426,11 @@ export class WebsocketNetwork implements IBasicNetwork {
return null;
}
public Update(): void {
this.UpdateHeartbeat();
this.CheckSleep();
}
public Flush(): void {
//ideally we buffer everything and then flush when it is connected as
//websockets aren't suppose to be used for realtime communication anyway
......@@ -395,6 +503,34 @@ export class WebsocketNetwork implements IBasicNetwork {
}
}
export namespace WebsocketNetwork{
export class Configuration{
mHeartbeat:number = 30;
get Heartbeat():number
{
return this.mHeartbeat;
}
set Heartbeat(value:number){
if(this.mLocked)
{
throw new Error("Can't change configuration once used.");
}
this.mHeartbeat = value;
}
mLocked = false;
Lock():void
{
this.mLocked = true;
}
}
}
//Below tests only. Move out later
function bufferToString(buffer: Uint8Array): string {
......@@ -411,4 +547,4 @@ function stringToBuffer(str: string): Uint8Array {
let result = new Uint8Array(buf);
return result;
}
}
\ No newline at end of file
......@@ -32,23 +32,107 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import {SLog, WebRtcNetwork, SignalingConfig, NetworkEvent, ConnectionId, LocalNetwork, WebsocketNetwork} from "../network/index"
import { MediaConfigurationState, NetworkConfig, MediaConfig } from "../media/index";
import { BrowserMediaStream, BrowserMediaNetwork, DeviceApi } from "../media_browser/index";
import { BrowserMediaStream, BrowserMediaNetwork, DeviceApi, BrowserWebRtcCall } from "../media_browser/index";
var CAPI_InitMode = {
//Original mode. Devices will be unknown after startup
Default: 0,
//Waits for the desvice info to come in
//names might be missing though (browser security thing)
WaitForDevices: 1,
//Asks the user for camera / audio access to be able to
//get accurate device information
RequestAccess: 2
};
var CAPI_InitState = {
Uninitialized: 0,
Initializing: 1,
Initialized: 2,
Failed: 3
};
var gCAPI_InitState = CAPI_InitState.Uninitialized;
export function CAPI_InitAsync(initmode)
{
console.debug("CAPI_InitAsync mode: " + initmode);
gCAPI_InitState = CAPI_InitState.Initializing;
let hasDevApi = DeviceApi.IsApiAvailable();
if( hasDevApi && initmode == CAPI_InitMode.WaitForDevices)
{
DeviceApi.Update();
}else if(hasDevApi && initmode == CAPI_InitMode.RequestAccess)
{
DeviceApi.RequestUpdate();
}else{
//either no device access available or not requested. Switch
//to init state immediately without device info
gCAPI_InitState = CAPI_InitState.Initialized;
if(hasDevApi == false)
{
console.debug("Initialized without accessible DeviceAPI");
}
}
}
export function CAPI_PollInitState()
{
//keep checking if the DeviceApi left pending state
//Once completed init is finished.
//Later we might do more here
if(DeviceApi.IsPending == false && gCAPI_InitState == CAPI_InitState.Initializing)
{
gCAPI_InitState = CAPI_InitState.Initialized;
console.debug("Init completed.");
}
return gCAPI_InitState;
}
/**
*
* @param loglevel
* None = 0,
* Errors = 1,
* Warnings = 2,
* Verbose = 3
*/
export function CAPI_SLog_SetLogLevel(loglevel:number)
{
if(loglevel < 0 || loglevel > 3)
{
SLog.LogError("Invalid log level " + loglevel);
return;
}
SLog.SetLogLevel(loglevel);
}
var gCAPIWebRtcNetworkInstances: { [id: number]: WebRtcNetwork }= {};
var gCAPIWebRtcNetworkInstancesNextIndex = 1;
var gCAPI_WebRtcNetwork_Instances: { [id: number]: WebRtcNetwork }= {};
var gCAPI_WebRtcNetwork_InstancesNextIndex = 1;
export function CAPIWebRtcNetworkIsAvailable() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
export function CAPI_WebRtcNetwork_IsAvailable() {
//used by C# component to check if this plugin is loaded.
//can only go wrong due to programming error / packaging
if(WebRtcNetwork && WebsocketNetwork)
return true;
return false;
}
export function CAPI_WebRtcNetwork_IsBrowserSupported()
{
if (RTCPeerConnection && RTCDataChannel)
return true;
SLog.LE("Browser doesn't seem to support a modern WebRTC API");
return false;
}
export function CAPIWebRtcNetworkCreate(lConfiguration: string) {
var lIndex = gCAPIWebRtcNetworkInstancesNextIndex;
gCAPIWebRtcNetworkInstancesNextIndex++;
export function CAPI_WebRtcNetwork_Create(lConfiguration: string) {
var lIndex = gCAPI_WebRtcNetwork_InstancesNextIndex;
gCAPI_WebRtcNetwork_InstancesNextIndex++;
var signaling_class = "LocalNetwork";
var signaling_param: any = null;
......@@ -90,83 +174,87 @@ export function CAPIWebRtcNetworkCreate(lConfiguration: string) {
let rtcConfiguration: RTCConfiguration = { iceServers: iceServers };
gCAPIWebRtcNetworkInstances[lIndex] = new WebRtcNetwork(signalingConfig, rtcConfiguration);
gCAPI_WebRtcNetwork_Instances[lIndex] = new WebRtcNetwork(signalingConfig, rtcConfiguration);
} else {
SLog.LogWarning("Parsing configuration failed. Configuration: " + lConfiguration);
return -1;
}
}
//gCAPIWebRtcNetworkInstances[lIndex].OnLog = function (lMsg) {
//gCAPI_WebRtcNetwork_Instances[lIndex].OnLog = function (lMsg) {
// console.debug(lMsg);
//};
return lIndex;
}
export function CAPIWebRtcNetworkRelease(lIndex: number) {
if (lIndex in gCAPIWebRtcNetworkInstances) {
gCAPIWebRtcNetworkInstances[lIndex].Dispose();
delete gCAPIWebRtcNetworkInstances[lIndex];
export function CAPI_WebRtcNetwork_Release(lIndex: number) {
if (lIndex in gCAPI_WebRtcNetwork_Instances) {
gCAPI_WebRtcNetwork_Instances[lIndex].Dispose();
delete gCAPI_WebRtcNetwork_Instances[lIndex];
}
}
export function CAPIWebRtcNetworkConnect(lIndex: number, lRoom: string) {
return gCAPIWebRtcNetworkInstances[lIndex].Connect(lRoom);
export function CAPI_WebRtcNetwork_Connect(lIndex: number, lRoom: string) {
return gCAPI_WebRtcNetwork_Instances[lIndex].Connect(lRoom);
}
export function CAPIWebRtcNetworkStartServer(lIndex: number, lRoom: string) {
gCAPIWebRtcNetworkInstances[lIndex].StartServer(lRoom);
export function CAPI_WebRtcNetwork_StartServer(lIndex: number, lRoom: string) {
gCAPI_WebRtcNetwork_Instances[lIndex].StartServer(lRoom);
}
export function CAPIWebRtcNetworkStopServer(lIndex: number) {
gCAPIWebRtcNetworkInstances[lIndex].StopServer();
export function CAPI_WebRtcNetwork_StopServer(lIndex: number) {
gCAPI_WebRtcNetwork_Instances[lIndex].StopServer();
}
export function CAPIWebRtcNetworkDisconnect(lIndex: number, lConnectionId: number) {
gCAPIWebRtcNetworkInstances[lIndex].Disconnect(new ConnectionId(lConnectionId));
export function CAPI_WebRtcNetwork_Disconnect(lIndex: number, lConnectionId: number) {
gCAPI_WebRtcNetwork_Instances[lIndex].Disconnect(new ConnectionId(lConnectionId));
}
export function CAPIWebRtcNetworkShutdown(lIndex: number) {
gCAPIWebRtcNetworkInstances[lIndex].Shutdown();
export function CAPI_WebRtcNetwork_Shutdown(lIndex: number) {
gCAPI_WebRtcNetwork_Instances[lIndex].Shutdown();
}
export function CAPIWebRtcNetworkUpdate(lIndex: number) {
gCAPIWebRtcNetworkInstances[lIndex].Update();
export function CAPI_WebRtcNetwork_Update(lIndex: number) {
gCAPI_WebRtcNetwork_Instances[lIndex].Update();
}
export function CAPIWebRtcNetworkFlush(lIndex: number) {
gCAPIWebRtcNetworkInstances[lIndex].Flush();
export function CAPI_WebRtcNetwork_Flush(lIndex: number) {
gCAPI_WebRtcNetwork_Instances[lIndex].Flush();
}
export function CAPIWebRtcNetworkSendData(lIndex: number, lConnectionId: number, lUint8ArrayData: Uint8Array, lReliable: boolean) {
gCAPIWebRtcNetworkInstances[lIndex].SendData(new ConnectionId(lConnectionId), lUint8ArrayData, lReliable);
export function CAPI_WebRtcNetwork_SendData(lIndex: number, lConnectionId: number, lUint8ArrayData: Uint8Array, lReliable: boolean) {
gCAPI_WebRtcNetwork_Instances[lIndex].SendData(new ConnectionId(lConnectionId), lUint8ArrayData, lReliable);
}
//helper for emscripten
export function CAPIWebRtcNetworkSendDataEm(lIndex: number, lConnectionId: number, lUint8ArrayData: Uint8Array, lUint8ArrayDataOffset: number, lUint8ArrayDataLength: number, lReliable: boolean) {
export function CAPI_WebRtcNetwork_SendDataEm(lIndex: number, lConnectionId: number, lUint8ArrayData: Uint8Array, lUint8ArrayDataOffset: number, lUint8ArrayDataLength: number, lReliable: boolean) {
//console.debug("SendDataEm: " + lReliable + " length " + lUint8ArrayDataLength + " to " + lConnectionId);
var arrayBuffer = new Uint8Array(lUint8ArrayData.buffer, lUint8ArrayDataOffset, lUint8ArrayDataLength);
return gCAPIWebRtcNetworkInstances[lIndex].SendData(new ConnectionId(lConnectionId), arrayBuffer, lReliable);
return gCAPI_WebRtcNetwork_Instances[lIndex].SendData(new ConnectionId(lConnectionId), arrayBuffer, lReliable);
}
export function CAPI_WebRtcNetwork_GetBufferedAmount(lIndex: number, lConnectionId: number, lReliable: boolean) {
return gCAPI_WebRtcNetwork_Instances[lIndex].GetBufferedAmount(new ConnectionId(lConnectionId), lReliable);
}
export function CAPIWebRtcNetworkDequeue(lIndex: number): NetworkEvent {
return gCAPIWebRtcNetworkInstances[lIndex].Dequeue();
export function CAPI_WebRtcNetwork_Dequeue(lIndex: number): NetworkEvent {
return gCAPI_WebRtcNetwork_Instances[lIndex].Dequeue();
}
export function CAPIWebRtcNetworkPeek(lIndex: number): NetworkEvent {
return gCAPIWebRtcNetworkInstances[lIndex].Peek();
export function CAPI_WebRtcNetwork_Peek(lIndex: number): NetworkEvent {
return gCAPI_WebRtcNetwork_Instances[lIndex].Peek();
}
/**Allows to peek into the next event to figure out its length and allocate
* the memory needed to store it before calling
* CAPIWebRtcNetworkDequeueEm
* CAPI_WebRtcNetwork_DequeueEm
*
* @param {type} lIndex
* @returns {Number}
*/
export function CAPIWebRtcNetworkPeekEventDataLength(lIndex) {
var lNetEvent = gCAPIWebRtcNetworkInstances[lIndex].Peek();
return CAPIWebRtcNetworkCheckEventLength(lNetEvent);
export function CAPI_WebRtcNetwork_PeekEventDataLength(lIndex) {
var lNetEvent = gCAPI_WebRtcNetwork_Instances[lIndex].Peek();
return CAPI_WebRtcNetwork_CheckEventLength(lNetEvent);
}
//helper
export function CAPIWebRtcNetworkCheckEventLength(lNetEvent: NetworkEvent) {
export function CAPI_WebRtcNetwork_CheckEventLength(lNetEvent: NetworkEvent) {
if (lNetEvent == null) {
//invalid event
return -1;
......@@ -185,7 +273,7 @@ export function CAPIWebRtcNetworkCheckEventLength(lNetEvent: NetworkEvent) {
return lNetEvent.RawData.length;
}
}
export function CAPIWebRtcNetworkEventDataToUint8Array(data: any, dataUint8Array: Uint8Array, dataOffset: number, dataLength: number) {
export function CAPI_WebRtcNetwork_EventDataToUint8Array(data: any, dataUint8Array: Uint8Array, dataOffset: number, dataLength: number) {
//data can be null, string or Uint8Array
//return value will be the length of data we used
if (data == null) {
......@@ -211,8 +299,8 @@ export function CAPIWebRtcNetworkEventDataToUint8Array(data: any, dataUint8Array
//Version for emscripten or anything that doesn't have a garbage collector.
// The memory for everything needs to be allocated before the call.
export function CAPIWebRtcNetworkDequeueEm(lIndex: number, lTypeIntArray: Int32Array, lTypeIntIndex: number, lConidIntArray: Int32Array, lConidIndex: number, lDataUint8Array: Uint8Array, lDataOffset: number, lDataLength: number, lDataLenIntArray: Int32Array, lDataLenIntIndex: number) {
var nEvt = CAPIWebRtcNetworkDequeue(lIndex);
export function CAPI_WebRtcNetwork_DequeueEm(lIndex: number, lTypeIntArray: Int32Array, lTypeIntIndex: number, lConidIntArray: Int32Array, lConidIndex: number, lDataUint8Array: Uint8Array, lDataOffset: number, lDataLength: number, lDataLenIntArray: Int32Array, lDataLenIntIndex: number) {
var nEvt = CAPI_WebRtcNetwork_Dequeue(lIndex);
if (nEvt == null)
return false;
......@@ -220,13 +308,13 @@ export function CAPIWebRtcNetworkDequeueEm(lIndex: number, lTypeIntArray: Int32A
lConidIntArray[lConidIndex] = nEvt.ConnectionId.id;
//console.debug("event" + nEvt.netEventType);
var length = CAPIWebRtcNetworkEventDataToUint8Array(nEvt.RawData, lDataUint8Array, lDataOffset, lDataLength);
var length = CAPI_WebRtcNetwork_EventDataToUint8Array(nEvt.RawData, lDataUint8Array, lDataOffset, lDataLength);
lDataLenIntArray[lDataLenIntIndex] = length; //return the length if so the user knows how much of the given array is used
return true;
}
export function CAPIWebRtcNetworkPeekEm(lIndex: number, lTypeIntArray: Int32Array, lTypeIntIndex: number, lConidIntArray: Int32Array, lConidIndex: number, lDataUint8Array: Uint8Array, lDataOffset: number, lDataLength: number, lDataLenIntArray: Int32Array, lDataLenIntIndex: number) {
var nEvt = CAPIWebRtcNetworkPeek(lIndex);
export function CAPI_WebRtcNetwork_PeekEm(lIndex: number, lTypeIntArray: Int32Array, lTypeIntIndex: number, lConidIntArray: Int32Array, lConidIndex: number, lDataUint8Array: Uint8Array, lDataOffset: number, lDataLength: number, lDataLenIntArray: Int32Array, lDataLenIntIndex: number) {
var nEvt = CAPI_WebRtcNetwork_Peek(lIndex);
if (nEvt == null)
return false;
......@@ -234,7 +322,7 @@ export function CAPIWebRtcNetworkPeekEm(lIndex: number, lTypeIntArray: Int32Arra
lConidIntArray[lConidIndex] = nEvt.ConnectionId.id;
//console.debug("event" + nEvt.netEventType);
var length = CAPIWebRtcNetworkEventDataToUint8Array(nEvt.RawData, lDataUint8Array, lDataOffset, lDataLength);
var length = CAPI_WebRtcNetwork_EventDataToUint8Array(nEvt.RawData, lDataUint8Array, lDataOffset, lDataLength);
lDataLenIntArray[lDataLenIntIndex] = length; //return the length if so the user knows how much of the given array is used
return true;
......@@ -242,22 +330,30 @@ export function CAPIWebRtcNetworkPeekEm(lIndex: number, lTypeIntArray: Int32Arra
export function CAPIMediaNetwork_IsAvailable() : boolean{
export function CAPI_MediaNetwork_IsAvailable() : boolean{
return true;
if(BrowserMediaNetwork && BrowserWebRtcCall)
return true;
return false;
}
export function CAPI_MediaNetwork_HasUserMedia() : boolean{
if(navigator && navigator.mediaDevices)
return true;
return false;
}
export function CAPIMediaNetwork_Create(lJsonConfiguration):number {
export function CAPI_MediaNetwork_Create(lJsonConfiguration):number {
let config = new NetworkConfig();
config = JSON.parse(lJsonConfiguration);
let mediaNetwork = new BrowserMediaNetwork(config);
var lIndex = gCAPIWebRtcNetworkInstancesNextIndex;
gCAPIWebRtcNetworkInstancesNextIndex++;
var lIndex = gCAPI_WebRtcNetwork_InstancesNextIndex;
gCAPI_WebRtcNetwork_InstancesNextIndex++;
gCAPIWebRtcNetworkInstances[lIndex] = mediaNetwork;
gCAPI_WebRtcNetwork_Instances[lIndex] = mediaNetwork;
return lIndex;
}
......@@ -268,7 +364,7 @@ export function CAPIMediaNetwork_Create(lJsonConfiguration):number {
//Configure(config: MediaConfig): void;
export function CAPIMediaNetwork_Configure(lIndex:number, audio: boolean, video: boolean,
export function CAPI_MediaNetwork_Configure(lIndex:number, audio: boolean, video: boolean,
minWidth: number, minHeight: number,
maxWidth: number, maxHeight: number,
idealWidth: number, idealHeight: number,
......@@ -292,38 +388,38 @@ export function CAPIMediaNetwork_Configure(lIndex:number, audio: boolean, video:
config.FrameUpdates = true;
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
mediaNetwork.Configure(config);
}
//GetConfigurationState(): MediaConfigurationState;
export function CAPIMediaNetwork_GetConfigurationState(lIndex: number): number{
export function CAPI_MediaNetwork_GetConfigurationState(lIndex: number): number{
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.GetConfigurationState() as number;
}
//Note: not yet glued to the C# version!
//GetConfigurationError(): string;
export function CAPIMediaNetwork_GetConfigurationError(lIndex: number): string {
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
export function CAPI_MediaNetwork_GetConfigurationError(lIndex: number): string {
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.GetConfigurationError();
}
//ResetConfiguration(): void;
export function CAPIMediaNetwork_ResetConfiguration(lIndex: number) : void {
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
export function CAPI_MediaNetwork_ResetConfiguration(lIndex: number) : void {
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.ResetConfiguration();
}
//TryGetFrame(id: ConnectionId): RawFrame;
export function CAPIMediaNetwork_TryGetFrame(lIndex: number, lConnectionId: number,
export function CAPI_MediaNetwork_TryGetFrame(lIndex: number, lConnectionId: number,
lWidthInt32Array: Int32Array, lWidthIntArrayIndex: number,
lHeightInt32Array: Int32Array, lHeightIntArrayIndex: number,
lBufferUint8Array: Uint8Array, lBufferUint8ArrayOffset: number, lBufferUint8ArrayLength: number): boolean
{
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
let frame = mediaNetwork.TryGetFrame(new ConnectionId(lConnectionId));
if (frame == null || frame.Buffer == null) {
......@@ -343,8 +439,8 @@ export function CAPIMediaNetwork_TryGetFrame(lIndex: number, lConnectionId: numb
}
//Returns the frame buffer size or -1 if no frame is available
export function CAPIMediaNetwork_TryGetFrameDataLength(lIndex: number, connectionId: number) : number {
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
export function CAPI_MediaNetwork_TryGetFrameDataLength(lIndex: number, connectionId: number) : number {
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
let frame = mediaNetwork.PeekFrame(new ConnectionId(connectionId));
let length: number = -1;
......@@ -358,32 +454,32 @@ export function CAPIMediaNetwork_TryGetFrameDataLength(lIndex: number, connectio
//SLog.L("data length:" + length);
return length;
}
export function CAPIMediaNetwork_SetVolume(lIndex: number, volume: number, connectionId: number) : void {
export function CAPI_MediaNetwork_SetVolume(lIndex: number, volume: number, connectionId: number) : void {
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
mediaNetwork.SetVolume(volume, new ConnectionId(connectionId));
}
export function CAPIMediaNetwork_HasAudioTrack(lIndex: number, connectionId: number): boolean
export function CAPI_MediaNetwork_HasAudioTrack(lIndex: number, connectionId: number): boolean
{
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.HasAudioTrack(new ConnectionId(connectionId));
}
export function CAPIMediaNetwork_HasVideoTrack(lIndex: number, connectionId: number): boolean {
export function CAPI_MediaNetwork_HasVideoTrack(lIndex: number, connectionId: number): boolean {
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.HasVideoTrack(new ConnectionId(connectionId));
}
export function CAPIMediaNetwork_SetMute(lIndex: number, value: boolean)
export function CAPI_MediaNetwork_SetMute(lIndex: number, value: boolean)
{
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
mediaNetwork.SetMute(value);
}
export function CAPIMediaNetwork_IsMute(lIndex: number)
export function CAPI_MediaNetwork_IsMute(lIndex: number)
{
let mediaNetwork = gCAPIWebRtcNetworkInstances[lIndex] as BrowserMediaNetwork;
let mediaNetwork = gCAPI_WebRtcNetwork_Instances[lIndex] as BrowserMediaNetwork;
return mediaNetwork.IsMute();
}
......@@ -416,21 +512,4 @@ export function CAPI_DeviceApi_Devices_Get(index:number):string{
SLog.LE("Requested device with index " + index + " does not exist.");
return "";
}
}
/**
*
* @param loglevel
* None = 0,
* Errors = 1,
* Warnings = 2,
* Verbose = 3
*/
export function CAPI_SLog_SetLogLevel(loglevel:number)
{
if(loglevel < 0 || loglevel > 3)
{
SLog.LogError("Invalid log level " + loglevel);
return;
}
SLog.SetLogLevel(loglevel);
}
\ No newline at end of file
......@@ -74,7 +74,9 @@ describe("BrowserApiTest_MediaStreamApi", () => {
console.log(device.kind + ": " + device.label +
" id = " + device.deviceId);
});
gStream.stop();
gStream.getTracks().forEach(t => {
t.stop();
});
done();
})
.catch(function(err) {
......@@ -109,7 +111,9 @@ describe("BrowserApiTest_MediaStreamApi", () => {
console.log(device.kind + ": " + device.label +
" id = " + device.deviceId);
});
gStream.stop();
gStream.getTracks().forEach(t => {
t.stop();
});
done();
})
.catch(function(err) {
......
......@@ -31,6 +31,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import {DeviceApi, CAPI_DeviceApi_Update,
CAPI_DeviceApi_RequestUpdate, CAPI_DeviceApi_Devices_Length,
CAPI_DeviceApi_Devices_Get} from "../awrtc/index"
export function DeviceApiTest_export()
{
......@@ -97,7 +98,7 @@ describe("DeviceApiTest", () => {
//should have original label now
expect(devices2[key1].label).not.toBe("videodevice 1");
//and not be guessed anymore
expect(devices2[key1].isLabelGuessed).toBe(false);
expect(devices2[key1].isLabelGuessed).toBe(false, "Chrome fails this now. Likely due to file://. Check for better test setup");
update2complete = true;
DeviceApi.Reset();
......
......@@ -30,7 +30,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import { WebsocketTest } from "WebsocketNetworkTest";
import { IBasicNetworkTest } from "helper/IBasicNetworkTest";
import { NetworkEvent, IBasicNetwork, NetEventType, WebsocketNetwork,
ConnectionId, SignalingConfig, LocalNetwork, WebRtcNetwork }
ConnectionId, SignalingConfig, LocalNetwork, WebRtcNetwork, IWebRtcNetwork }
from "../awrtc/index";
export class WebRtcNetworkTest extends IBasicNetworkTest {
......@@ -56,6 +56,46 @@ export class WebRtcNetworkTest extends IBasicNetworkTest {
this.mUrl = WebsocketTest.sUrl;
this.mUseWebsockets = WebRtcNetworkTest.mAlwaysUseWebsockets;
})
it("GetBufferedAmount", (done) => {
var srv: IWebRtcNetwork;
var address: string;
var srvToCltId: ConnectionId;
var clt: IWebRtcNetwork;
var cltToSrvId: ConnectionId;
var evt: NetworkEvent;
this.thenAsync((finished) => {
this._CreateServerClient((rsrv, raddress, rsrvToCltId, rclt, rcltToSrvId) => {
srv = rsrv as IWebRtcNetwork;
address = raddress;
srvToCltId = rsrvToCltId;
clt = rclt as IWebRtcNetwork;
cltToSrvId = rcltToSrvId;
finished();
});
});
this.then(() => {
//TODO: more detailed testing by actually triggering the buffer to fill?
//might be tricky as this is very system dependent
let buf:number;
buf = srv.GetBufferedAmount(srvToCltId, false);
expect(buf).toBe(0);
buf = srv.GetBufferedAmount(srvToCltId, true);
expect(buf).toBe(0);
buf = clt.GetBufferedAmount(cltToSrvId, false);
expect(buf).toBe(0);
buf = clt.GetBufferedAmount(cltToSrvId, true);
expect(buf).toBe(0);
done();
});
this.start();
});
it("SharedAddress", (done) => {
//turn off websockets and use shared websockets for this test as local network doesn't support shared mode
......
......@@ -40,6 +40,7 @@ export class WebsocketTest extends IBasicNetworkTest {
//public static sUrl = 'ws://localhost:12776/test';
//public static sUrlShared = 'ws://localhost:12776/testshared';
public static sUrl = 'ws://signaling.because-why-not.com';
//public static sUrl = 'ws://192.168.1.3:12776';
public static sUrlShared = 'ws://signaling.because-why-not.com/testshared';
//any url to simulate offline server
public static sBadUrl = 'ws://localhost:13776';
......@@ -54,6 +55,42 @@ export class WebsocketTest extends IBasicNetworkTest {
this.mUrl = WebsocketTest.sUrl;
});
//can only be done manually so far
xit("Timeout", (done) => {
//this needs to be a local test server
//that can be disconnected to test the timeout
this.mUrl = "ws://192.168.1.3:12776";
var evt: NetworkEvent;
var srv: WebsocketNetwork;
var address;
this.thenAsync((finished) => {
this._CreateServerNetwork((rsrv, raddress) => {
srv = rsrv;
address = raddress;
finished();
});
});
this.thenAsync((finished) => {
console.log("Server ready at " + address);
expect(srv).not.toBeNull();
expect(address).not.toBeNull();
console.debug("Waiting for timeout");
this.waitForEvent(srv, finished, 120000);
});
this.then(() => {
console.log("Timeout over");
evt = srv.Dequeue();
expect(evt).not.toBeNull();
expect(evt.Type).toBe(NetEventType.ServerClosed);
expect(srv.getStatus()).toBe(WebsocketConnectionStatus.NotConnected);
done();
});
this.start();
}, 130000);
it("SharedAddress", (done) => {
this.mUrl = WebsocketTest.sUrlShared;
......
......@@ -28,7 +28,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { BasicNetworkTestBase, Task } from "./BasicNetworkTestBase";
import { IBasicNetwork, NetworkEvent, NetEventType, ConnectionId, Encoding } from "../../awrtc/network/index";
import { IBasicNetwork, NetworkEvent, NetEventType, ConnectionId, Encoding, SLog, SLogLevel } from "../../awrtc/network/index";
export abstract class IBasicNetworkTest extends BasicNetworkTestBase {
......@@ -36,10 +36,12 @@ export abstract class IBasicNetworkTest extends BasicNetworkTestBase {
super.setup();
let originalTimeout = 5000;
beforeEach(() => {
SLog.RequestLogLevel(SLogLevel.Info);
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = this.mDefaultWaitTimeout + 5000;
});
afterEach(() => {
console.debug("Test shutting down ...");
this.ShutdownAll();
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = this.mDefaultWaitTimeout + 5000;
......
......@@ -18,6 +18,8 @@
"WebRtcNetworkTest.ts",
"CallTest.ts",
"LocalNetworkTest.ts",
"MediaNetworkTest.ts"
"MediaNetworkTest.ts",
"DeviceApiTest.ts",
"BrowserApiTest.ts"
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment