UNPKG

awrtc_browser

Version:

Compatible browser implementation to the Unity asset WebRTC Video Chat. Try examples in build folder

533 lines 23 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import { MediaConfigurationState } from "./IMediaNetwork"; import { CallAcceptedEventArgs, CallEndedEventArgs, ErrorEventArgs, CallEventType, WaitForIncomingCallEventArgs, CallErrorType, DataMessageEventArgs, FrameUpdateEventArgs, CallEventArgs, MessageEventArgs, MediaUpdatedEventArgs } from "./CallEventArgs"; import { SLog, Encoding } from "../network/Helper"; import { NetworkConfig } from "./NetworkConfig"; import { MediaConfig } from "./MediaConfig"; import { ConnectionId, NetEventType } from "../network/index"; var CallException = /** @class */ (function () { function CallException(errorMsg) { this.mErrorMsg = errorMsg; } CallException.prototype.ErrorMsg = function () { }; return CallException; }()); var InvalidOperationException = /** @class */ (function (_super) { __extends(InvalidOperationException, _super); function InvalidOperationException() { return _super !== null && _super.apply(this, arguments) || this; } return InvalidOperationException; }(CallException)); /// <summary> /// State of the call. Mainly used to check for bugs / invalid states. /// </summary> var CallState; (function (CallState) { /// <summary> /// Not yet initialized / bug /// </summary> CallState[CallState["Invalid"] = 0] = "Invalid"; /// <summary> /// Object is initialized but local media not yet configured /// </summary> CallState[CallState["Initialized"] = 1] = "Initialized"; /// <summary> /// In process of accessing the local media devices. /// </summary> CallState[CallState["Configuring"] = 2] = "Configuring"; /// <summary> /// Configured. Video/Audio can be accessed and call is ready to start /// </summary> CallState[CallState["Configured"] = 3] = "Configured"; /// <summary> /// In process of requesting an address from the server to then listen and wait for /// an incoming call. /// </summary> CallState[CallState["RequestingAddress"] = 4] = "RequestingAddress"; /// <summary> /// Call is listening on an address and waiting for an incoming call /// </summary> CallState[CallState["WaitingForIncomingCall"] = 5] = "WaitingForIncomingCall"; /// <summary> /// Call is in the process of connecting to another call object. /// </summary> CallState[CallState["WaitingForOutgoingCall"] = 6] = "WaitingForOutgoingCall"; /// <summary> /// Indicating that the call object is at least connected to another object /// </summary> CallState[CallState["InCall"] = 7] = "InCall"; //CallAcceptedIncoming, //CallAcceptedOutgoing, /// <summary> /// Call ended / conference room closed /// </summary> CallState[CallState["Closed"] = 8] = "Closed"; })(CallState || (CallState = {})); /* class ConnectionMetaData { } */ var ConnectionInfo = /** @class */ (function () { function ConnectionInfo() { this.mConnectionIds = new Array(); //public GetMeta(id:ConnectionId) : ConnectionMetaData //{ // return this.mConnectionMeta[id.id]; //} } //private mConnectionMeta: { [id: number]: ConnectionMetaData } = {}; ConnectionInfo.prototype.AddConnection = function (id, incoming) { this.mConnectionIds.push(id.id); //this.mConnectionMeta[id.id] = new ConnectionMetaData(); }; ConnectionInfo.prototype.RemConnection = function (id) { var index = this.mConnectionIds.indexOf(id.id); if (index >= 0) { this.mConnectionIds.splice(index, 1); } else { SLog.LE("tried to remove an unknown connection with id " + id.id); } //delete this.mConnectionMeta[id.id]; }; ConnectionInfo.prototype.HasConnection = function (id) { return this.mConnectionIds.indexOf(id.id) != -1; }; ConnectionInfo.prototype.GetIds = function () { return this.mConnectionIds; }; return ConnectionInfo; }()); /**This class wraps an implementation of * IMediaStream and converts its polling system * to an easier to use event based system. * * Ideally use only features defined by * ICall to avoid having to deal with internal changes * in future updates. */ var AWebRtcCall = /** @class */ (function () { function AWebRtcCall(config) { if (config === void 0) { config = null; } this.MESSAGE_TYPE_INVALID = 0; this.MESSAGE_TYPE_DATA = 1; this.MESSAGE_TYPE_STRING = 2; this.MESSAGE_TYPE_CONTROL = 3; this.mNetworkConfig = new NetworkConfig(); this.mMediaConfig = null; this.mCallEventHandlers = []; this.mNetwork = null; this.mConnectionInfo = new ConnectionInfo(); this.mConferenceMode = false; this.mState = CallState.Invalid; this.mIsDisposed = false; this.mServerInactive = true; this.mPendingListenCall = false; this.mPendingCallCall = false; this.mPendingAddress = null; if (config != null) { this.mNetworkConfig = config; this.mConferenceMode = config.IsConference; } } AWebRtcCall.prototype.addEventListener = function (listener) { this.mCallEventHandlers.push(listener); }; AWebRtcCall.prototype.removeEventListener = function (listener) { this.mCallEventHandlers = this.mCallEventHandlers.filter(function (h) { return h !== listener; }); }; Object.defineProperty(AWebRtcCall.prototype, "State", { get: function () { return this.mState; }, enumerable: true, configurable: true }); AWebRtcCall.prototype.Initialize = function (network) { this.mNetwork = network; this.mState = CallState.Initialized; }; AWebRtcCall.prototype.Configure = function (config) { this.CheckDisposed(); if (this.mState != CallState.Initialized) { throw new InvalidOperationException("Method can't be used in state " + this.mState); } this.mState = CallState.Configuring; SLog.Log("Enter state CallState.Configuring"); this.mMediaConfig = config; this.mNetwork.Configure(this.mMediaConfig); }; AWebRtcCall.prototype.Call = function (address) { this.CheckDisposed(); if (this.mState != CallState.Initialized && this.mState != CallState.Configuring && this.mState != CallState.Configured) { throw new InvalidOperationException("Method can't be used in state " + this.mState); } if (this.mConferenceMode) { throw new InvalidOperationException("Method can't be used in conference calls."); } SLog.Log("Call to " + address); this.EnsureConfiguration(); if (this.mState == CallState.Configured) { this.ProcessCall(address); } else { this.PendingCall(address); } }; AWebRtcCall.prototype.Listen = function (address) { this.CheckDisposed(); if (this.mState != CallState.Initialized && this.mState != CallState.Configuring && this.mState != CallState.Configured) { throw new InvalidOperationException("Method can't be used in state " + this.mState); } this.EnsureConfiguration(); if (this.mState == CallState.Configured) { this.ProcessListen(address); } else { this.PendingListen(address); } }; AWebRtcCall.prototype.Send = function (message, reliable, id) { this.CheckDisposed(); if (reliable == null) reliable = true; if (id) { this.InternalSendTo(message, reliable, id); } else { this.InternalSendToAll(message, reliable); } }; AWebRtcCall.prototype.InternalSendToAll = function (message, reliable) { var data = this.PackStringMsg(message); ; for (var _i = 0, _a = this.mConnectionInfo.GetIds(); _i < _a.length; _i++) { var id = _a[_i]; SLog.L("Send message to " + id + "! " + message); this.InternalSendRawTo(data, new ConnectionId(id), reliable); } }; AWebRtcCall.prototype.InternalSendTo = function (message, reliable, id) { var data = this.PackStringMsg(message); this.InternalSendRawTo(data, id, reliable); }; AWebRtcCall.prototype.SendData = function (message, reliable, id) { this.CheckDisposed(); var data = this.PackDataMsg(message); this.InternalSendRawTo(data, id, reliable); }; AWebRtcCall.prototype.PackStringMsg = function (message) { var data = Encoding.UTF16.GetBytes(message); var buff = new Uint8Array(data.length + 1); buff[0] = this.MESSAGE_TYPE_STRING; for (var i = 0; i < data.length; i++) { buff[i + 1] = data[i]; } return buff; }; AWebRtcCall.prototype.UnpackStringMsg = function (message) { var buff = new Uint8Array(message.length - 1); for (var i = 0; i < buff.length; i++) { buff[i] = message[i + 1]; } var res = Encoding.UTF16.GetString(buff); return res; }; AWebRtcCall.prototype.PackDataMsg = function (data) { var buff = new Uint8Array(data.length + 1); buff[0] = this.MESSAGE_TYPE_DATA; for (var i = 0; i < data.length; i++) { buff[i + 1] = data[i]; } return buff; }; AWebRtcCall.prototype.UnpackDataMsg = function (message) { var buff = new Uint8Array(message.length - 1); for (var i = 0; i < buff.length; i++) { buff[i] = message[i + 1]; } return buff; }; AWebRtcCall.prototype.InternalSendRawTo = function (rawdata, id, reliable) { this.mNetwork.SendData(id, rawdata, reliable); }; AWebRtcCall.prototype.Update = function () { if (this.mIsDisposed) return; if (this.mNetwork == null) return; this.mNetwork.Update(); //waiting for the media configuration? if (this.mState == CallState.Configuring) { var configState = this.mNetwork.GetConfigurationState(); if (configState == MediaConfigurationState.Failed) { this.OnConfigurationFailed(this.mNetwork.GetConfigurationError()); //bugfix: user might dispose the call during the event above if (this.mIsDisposed) return; if (this.mNetwork != null) this.mNetwork.ResetConfiguration(); } else if (configState == MediaConfigurationState.Successful) { this.OnConfigurationComplete(); if (this.mIsDisposed) return; } } var evt; while ((evt = this.mNetwork.Dequeue()) != null) { switch (evt.Type) { case NetEventType.NewConnection: if (this.mState == CallState.WaitingForIncomingCall || (this.mConferenceMode && this.mState == CallState.InCall)) //keep accepting connections after { //remove ability to accept incoming connections if (this.mConferenceMode == false) this.mNetwork.StopServer(); this.mState = CallState.InCall; this.mConnectionInfo.AddConnection(evt.ConnectionId, true); this.TriggerCallEvent(new CallAcceptedEventArgs(evt.ConnectionId)); if (this.mIsDisposed) return; } else if (this.mState == CallState.WaitingForOutgoingCall) { this.mConnectionInfo.AddConnection(evt.ConnectionId, false); //only possible in 1 on 1 calls this.mState = CallState.InCall; this.TriggerCallEvent(new CallAcceptedEventArgs(evt.ConnectionId)); if (this.mIsDisposed) return; } else { //Debug.Assert(mState == CallState.WaitingForIncomingCall || mState == CallState.WaitingForOutgoingCall); SLog.LogWarning("Received incoming connection during invalid state " + this.mState); } break; case NetEventType.ConnectionFailed: //call failed if (this.mState == CallState.WaitingForOutgoingCall) { this.TriggerCallEvent(new ErrorEventArgs(CallEventType.ConnectionFailed)); if (this.mIsDisposed) return; this.mState = CallState.Configured; } else { //Debug.Assert(mState == CallState.WaitingForOutgoingCall); SLog.LogError("Received ConnectionFailed during " + this.mState); } break; case NetEventType.Disconnected: if (this.mConnectionInfo.HasConnection(evt.ConnectionId)) { this.mConnectionInfo.RemConnection(evt.ConnectionId); //call ended if (this.mConferenceMode == false && this.mConnectionInfo.GetIds().length == 0) { this.mState = CallState.Closed; } this.TriggerCallEvent(new CallEndedEventArgs(evt.ConnectionId)); if (this.mIsDisposed) return; } break; case NetEventType.ServerInitialized: //incoming calls possible this.mServerInactive = false; this.mState = CallState.WaitingForIncomingCall; this.TriggerCallEvent(new WaitForIncomingCallEventArgs(evt.Info)); if (this.mIsDisposed) return; break; case NetEventType.ServerInitFailed: this.mServerInactive = true; //reset state to the earlier state which is Configured (as without configuration no //listening possible). Local camera/audio will keep running this.mState = CallState.Configured; this.TriggerCallEvent(new ErrorEventArgs(CallEventType.ListeningFailed)); if (this.mIsDisposed) return; break; case NetEventType.ServerClosed: this.mServerInactive = true; //no incoming calls possible anymore if (this.mState == CallState.WaitingForIncomingCall || this.mState == CallState.RequestingAddress) { this.mState = CallState.Configured; //might need to be handled as a special timeout event? this.TriggerCallEvent(new ErrorEventArgs(CallEventType.ListeningFailed, CallErrorType.Unknown, "Server closed the connection while waiting for incoming calls.")); if (this.mIsDisposed) return; } else { //event is normal during other states as the server connection will be closed after receiving a call } break; case NetEventType.ReliableMessageReceived: case NetEventType.UnreliableMessageReceived: var reliable = evt.Type === NetEventType.ReliableMessageReceived; //chat message received if (evt.MessageData.length >= 2) { if (evt.MessageData[0] == this.MESSAGE_TYPE_STRING) { var message = this.UnpackStringMsg(evt.MessageData); this.TriggerCallEvent(new MessageEventArgs(evt.ConnectionId, message, reliable)); } else if (evt.MessageData[0] == this.MESSAGE_TYPE_DATA) { var message = this.UnpackDataMsg(evt.MessageData); this.TriggerCallEvent(new DataMessageEventArgs(evt.ConnectionId, message, reliable)); } else { //invalid message? } } else { //invalid message? } if (this.mIsDisposed) return; break; } } var handleLocalFrames = true; var handleRemoteFrames = true; if (this.mMediaConfig.FrameUpdates && handleLocalFrames) { var localFrame = this.mNetwork.TryGetFrame(ConnectionId.INVALID); if (localFrame != null) { this.FrameToCallEvent(ConnectionId.INVALID, localFrame); if (this.mIsDisposed) return; } } if (this.mMediaConfig.FrameUpdates && handleRemoteFrames) { for (var _i = 0, _a = this.mConnectionInfo.GetIds(); _i < _a.length; _i++) { var id = _a[_i]; var conId = new ConnectionId(id); var remoteFrame = this.mNetwork.TryGetFrame(conId); if (remoteFrame != null) { this.FrameToCallEvent(conId, remoteFrame); if (this.mIsDisposed) return; } } } var mediaEvent = null; while ((mediaEvent = this.mNetwork.DequeueMediaEvent()) != null) { this.MediaEventToCallEvent(mediaEvent); } this.mNetwork.Flush(); }; AWebRtcCall.prototype.FrameToCallEvent = function (id, frame) { var args = new FrameUpdateEventArgs(id, frame); this.TriggerCallEvent(args); }; AWebRtcCall.prototype.MediaEventToCallEvent = function (evt) { var videoElement = null; if (evt.EventType == evt.EventType) { var args = new MediaUpdatedEventArgs(evt.ConnectionId, evt.Args); this.TriggerCallEvent(args); } }; AWebRtcCall.prototype.PendingCall = function (address) { this.mPendingAddress = address; this.mPendingCallCall = true; this.mPendingListenCall = false; }; AWebRtcCall.prototype.ProcessCall = function (address) { this.mState = CallState.WaitingForOutgoingCall; this.mNetwork.Connect(address); this.ClearPending(); }; AWebRtcCall.prototype.PendingListen = function (address) { this.mPendingAddress = address; this.mPendingCallCall = false; this.mPendingListenCall = true; }; AWebRtcCall.prototype.ProcessListen = function (address) { SLog.Log("Listen at " + address); this.mServerInactive = false; this.mState = CallState.RequestingAddress; this.mNetwork.StartServer(address); this.ClearPending(); }; AWebRtcCall.prototype.DoPending = function () { if (this.mPendingCallCall) { this.ProcessCall(this.mPendingAddress); } else if (this.mPendingListenCall) { this.ProcessListen(this.mPendingAddress); } this.ClearPending(); }; AWebRtcCall.prototype.ClearPending = function () { this.mPendingAddress = null; this.mPendingCallCall = null; this.mPendingListenCall = null; }; AWebRtcCall.prototype.CheckDisposed = function () { if (this.mIsDisposed) throw new InvalidOperationException("Object is disposed. No method calls possible."); }; AWebRtcCall.prototype.EnsureConfiguration = function () { if (this.mState == CallState.Initialized) { SLog.Log("Use default configuration"); this.Configure(new MediaConfig()); } else { } }; AWebRtcCall.prototype.TriggerCallEvent = function (args) { var arr = this.mCallEventHandlers.slice(); for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) { var callback = arr_1[_i]; callback(this, args); } }; AWebRtcCall.prototype.OnConfigurationComplete = function () { if (this.mIsDisposed) return; this.mState = CallState.Configured; SLog.Log("Enter state CallState.Configured"); this.TriggerCallEvent(new CallEventArgs(CallEventType.ConfigurationComplete)); if (this.mIsDisposed == false) this.DoPending(); }; AWebRtcCall.prototype.OnConfigurationFailed = function (error) { SLog.LogWarning("Configuration failed: " + error); if (this.mIsDisposed) return; this.mState = CallState.Initialized; this.TriggerCallEvent(new ErrorEventArgs(CallEventType.ConfigurationFailed, CallErrorType.Unknown, error)); //bugfix: user might dispose the call during the event above if (this.mIsDisposed == false) this.ClearPending(); }; AWebRtcCall.prototype.DisposeInternal = function (disposing) { //nothing to dispose but subclasses overwrite this if (!this.mIsDisposed) { if (disposing) { } this.mIsDisposed = true; } }; AWebRtcCall.prototype.Dispose = function () { this.DisposeInternal(true); }; return AWebRtcCall; }()); export { AWebRtcCall }; //# sourceMappingURL=AWebRtcCall.js.map