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>
......
......@@ -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",
......
......@@ -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
......@@ -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