Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
sfu
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Alain Takoudjou
sfu
Commits
0aa77441
Commit
0aa77441
authored
Sep 20, 2020
by
Juliusz Chroboczek
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve typing of javascript code.
We now enable typing of sfu.js.
parent
4e14c29f
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
266 additions
and
118 deletions
+266
-118
static/protocol.js
static/protocol.js
+70
-49
static/sfu.js
static/sfu.js
+188
-67
static/tsconfig.json
static/tsconfig.json
+8
-2
No files found.
static/protocol.js
View file @
0aa77441
...
@@ -7,7 +7,8 @@
...
@@ -7,7 +7,8 @@
/**
/**
* toHex formats an array as a hexadecimal string.
* toHex formats an array as a hexadecimal string.
* @returns {string}
* @param {number[]|Uint8Array} array - the array to format
* @returns {string} - the hexadecimal representation of array
*/
*/
function
toHex
(
array
)
{
function
toHex
(
array
)
{
let
a
=
new
Uint8Array
(
array
);
let
a
=
new
Uint8Array
(
array
);
...
@@ -37,43 +38,52 @@ function randomid() {
...
@@ -37,43 +38,52 @@ function randomid() {
function
ServerConnection
()
{
function
ServerConnection
()
{
/**
/**
* The id of this connection.
* The id of this connection.
*
* @type {string}
* @type {string}
* @const
*/
*/
this
.
id
=
randomid
();
this
.
id
=
randomid
();
/**
/**
* The group that we have joined, or nil if we haven't joined yet.
* The group that we have joined, or nil if we haven't joined yet.
*
* @type {string}
* @type {string}
*/
*/
this
.
group
=
null
;
this
.
group
=
null
;
/**
/**
* The underlying websocket.
* The underlying websocket.
*
* @type {WebSocket}
* @type {WebSocket}
*/
*/
this
.
socket
=
null
;
this
.
socket
=
null
;
/**
/**
* The set of all up streams, indexed by their id.
* The set of all up streams, indexed by their id.
* @type {Object.<string,Stream>}
*
* @type {Object<string,Stream>}
*/
*/
this
.
up
=
{};
this
.
up
=
{};
/**
/**
* The set of all down streams, indexed by their id.
* The set of all down streams, indexed by their id.
* @type {Object.<string,Stream>}
*
* @type {Object<string,Stream>}
*/
*/
this
.
down
=
{};
this
.
down
=
{};
/**
/**
* The ICE configuration used by all associated streams.
* The ICE configuration used by all associated streams.
* @type {Array.<RTCIceServer>}
*
* @type {RTCIceServer[]}
*/
*/
this
.
iceServers
=
null
;
this
.
iceServers
=
null
;
/**
/**
* The permissions granted to this connection.
* The permissions granted to this connection.
* @type {Object.<string,boolean>}
*
* @type {Object<string,boolean>}
*/
*/
this
.
permissions
=
{};
this
.
permissions
=
{};
/**
/**
* clientdata is a convenient place to attach data to a ServerConnection.
* clientdata is a convenient place to attach data to a ServerConnection.
* It is not used by the library.
* It is not used by the library.
* @type{Object.<string,any>}
*
* @type{Object<any,any>}
*/
*/
this
.
userdata
=
{};
this
.
userdata
=
{};
...
@@ -81,46 +91,54 @@ function ServerConnection() {
...
@@ -81,46 +91,54 @@ function ServerConnection() {
/**
/**
* onconnected is called when the connection has been established
* onconnected is called when the connection has been established
* @type{function(): any}
*
* @type{(this: ServerConnection) => any}
*/
*/
this
.
onconnected
=
null
;
this
.
onconnected
=
null
;
/**
/**
* onclose is called when the connection is closed
* onclose is called when the connection is closed
* @type{function(number, string): any}
*
* @type{(this: ServerConnection, code: number, reason: string) => any}
*/
*/
this
.
onclose
=
null
;
this
.
onclose
=
null
;
/**
/**
* onuser is called whenever a user is added or removed from the group
* onuser is called whenever a user is added or removed from the group
* @type{function(string, string, string): any}
*
* @type{(this: ServerConnection, id: string, kind: string, username: string) => any}
*/
*/
this
.
onuser
=
null
;
this
.
onuser
=
null
;
/**
/**
* onpermissions is called whenever the current user's permissions change
* onpermissions is called whenever the current user's permissions change
* @type{function(Object.<string,boolean>): any}
*
* @type{(this: ServerConnection, permissions: Object<string,boolean>) => any}
*/
*/
this
.
onpermissions
=
null
;
this
.
onpermissions
=
null
;
/**
/**
* ondownstream is called whenever a new down stream is added. It
* ondownstream is called whenever a new down stream is added. It
* should set up the stream's callbacks; actually setting up the UI
* should set up the stream's callbacks; actually setting up the UI
* should be done in the stream's ondowntrack callback.
* should be done in the stream's ondowntrack callback.
* @type{function(Stream): any}
*
* @type{(this: ServerConnection, stream: Stream) => any}
*/
*/
this
.
ondownstream
=
null
;
this
.
ondownstream
=
null
;
/**
/**
* onchat is called whenever a new chat message is received.
* onchat is called whenever a new chat message is received.
* @type {function(string, string, string, string): any}
*
* @type {(this: ServerConnection, id: string, username: string, kind: string, message: string) => any}
*/
*/
this
.
onchat
=
null
;
this
.
onchat
=
null
;
/**
/**
* onclearchat is called whenever the server requests that the chat
* onclearchat is called whenever the server requests that the chat
* be cleared.
* be cleared.
* @type{function(): any}
*
* @type{(this: ServerConnection) => any}
*/
*/
this
.
onclearchat
=
null
;
this
.
onclearchat
=
null
;
/**
/**
* onusermessage is called when the server sends an error or warning
* onusermessage is called when the server sends an error or warning
* message that should be displayed to the user.
* message that should be displayed to the user.
* @type{function(string, string): any}
*
* @type{(this: ServerConnection, kind: string, message: string) => any}
*/
*/
this
.
onusermessage
=
null
;
this
.
onusermessage
=
null
;
}
}
...
@@ -132,14 +150,14 @@ function ServerConnection() {
...
@@ -132,14 +150,14 @@ function ServerConnection() {
* @property {string} [id]
* @property {string} [id]
* @property {string} [username]
* @property {string} [username]
* @property {string} [password]
* @property {string} [password]
* @property {Object
.
<string,boolean>} [permissions]
* @property {Object<string,boolean>} [permissions]
* @property {string} [group]
* @property {string} [group]
* @property {string} [value]
* @property {string} [value]
* @property {RTCSessionDescriptionInit} [offer]
* @property {RTCSessionDescriptionInit} [offer]
* @property {RTCSessionDescriptionInit} [answer]
* @property {RTCSessionDescriptionInit} [answer]
* @property {RTCIceCandidate} [candidate]
* @property {RTCIceCandidate} [candidate]
* @property {Object
.
<string,string>} [labels]
* @property {Object<string,string>} [labels]
* @property {Object
.
<string,(boolean|number)>} [request]
* @property {Object<string,(boolean|number)>} [request]
*/
*/
/**
/**
...
@@ -157,17 +175,18 @@ ServerConnection.prototype.close = function() {
...
@@ -157,17 +175,18 @@ ServerConnection.prototype.close = function() {
*/
*/
ServerConnection
.
prototype
.
send
=
function
(
m
)
{
ServerConnection
.
prototype
.
send
=
function
(
m
)
{
if
(
!
this
.
socket
||
this
.
socket
.
readyState
!==
this
.
socket
.
OPEN
)
{
if
(
!
this
.
socket
||
this
.
socket
.
readyState
!==
this
.
socket
.
OPEN
)
{
// send on a closed
connection
doesn't throw
// send on a closed
socket
doesn't throw
throw
(
new
Error
(
'
Connection is not open
'
));
throw
(
new
Error
(
'
Connection is not open
'
));
}
}
return
this
.
socket
.
send
(
JSON
.
stringify
(
m
));
return
this
.
socket
.
send
(
JSON
.
stringify
(
m
));
}
}
/** getIceServers fetches an ICE configuration from the server and
/**
* getIceServers fetches an ICE configuration from the server and
* populates the iceServers field of a ServerConnection. It is called
* populates the iceServers field of a ServerConnection. It is called
* lazily by connect.
* lazily by connect.
*
*
* @returns {Promise<
Array.<Object>
>}
* @returns {Promise<
RTCIceServer[]
>}
* @function
* @function
*/
*/
ServerConnection
.
prototype
.
getIceServers
=
async
function
()
{
ServerConnection
.
prototype
.
getIceServers
=
async
function
()
{
...
@@ -294,8 +313,8 @@ ServerConnection.prototype.connect = async function(url) {
...
@@ -294,8 +313,8 @@ ServerConnection.prototype.connect = async function(url) {
/**
/**
* login authenticates with the server.
* login authenticates with the server.
*
*
* @param {string} username
* @param {string} username
- the username to login as.
* @param {string} password
* @param {string} password
- the password.
*/
*/
ServerConnection
.
prototype
.
login
=
function
(
username
,
password
)
{
ServerConnection
.
prototype
.
login
=
function
(
username
,
password
)
{
this
.
send
({
this
.
send
({
...
@@ -324,7 +343,7 @@ ServerConnection.prototype.join = function(group) {
...
@@ -324,7 +343,7 @@ ServerConnection.prototype.join = function(group) {
* @param {string} what - One of '', 'audio', 'screenshare' or 'everything'.
* @param {string} what - One of '', 'audio', 'screenshare' or 'everything'.
*/
*/
ServerConnection
.
prototype
.
request
=
function
(
what
)
{
ServerConnection
.
prototype
.
request
=
function
(
what
)
{
/** @type {Object
.
<string,boolean>} */
/** @type {Object<string,boolean>} */
let
request
=
{};
let
request
=
{};
switch
(
what
)
{
switch
(
what
)
{
case
''
:
case
''
:
...
@@ -422,7 +441,7 @@ ServerConnection.prototype.chat = function(username, kind, message) {
...
@@ -422,7 +441,7 @@ ServerConnection.prototype.chat = function(username, kind, message) {
*
*
* @param {string} kind - One of "clearchat", "lock", "unlock", "record or
* @param {string} kind - One of "clearchat", "lock", "unlock", "record or
* "unrecord".
* "unrecord".
* @param {string} [message]
* @param {string} [message]
- An optional user-readable message.
*/
*/
ServerConnection
.
prototype
.
groupAction
=
function
(
kind
,
message
)
{
ServerConnection
.
prototype
.
groupAction
=
function
(
kind
,
message
)
{
this
.
send
({
this
.
send
({
...
@@ -436,8 +455,8 @@ ServerConnection.prototype.groupAction = function(kind, message) {
...
@@ -436,8 +455,8 @@ ServerConnection.prototype.groupAction = function(kind, message) {
* userAction sends a request to act on a user.
* userAction sends a request to act on a user.
*
*
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
* @param {string} id
* @param {string} id
- The id of the user to act upon.
* @param {string} [message]
* @param {string} [message]
- An optional user-readable message.
*/
*/
ServerConnection
.
prototype
.
userAction
=
function
(
kind
,
id
,
message
)
{
ServerConnection
.
prototype
.
userAction
=
function
(
kind
,
id
,
message
)
{
this
.
send
({
this
.
send
({
...
@@ -452,7 +471,7 @@ ServerConnection.prototype.userAction = function(kind, id, message) {
...
@@ -452,7 +471,7 @@ ServerConnection.prototype.userAction = function(kind, id, message) {
* Called when we receive an offer from the server. Don't call this.
* Called when we receive an offer from the server. Don't call this.
*
*
* @param {string} id
* @param {string} id
* @param {Object
.
<string, string>} labels
* @param {Object<string, string>} labels
* @param {RTCSessionDescriptionInit} offer
* @param {RTCSessionDescriptionInit} offer
* @param {boolean} renegotiate
* @param {boolean} renegotiate
* @function
* @function
...
@@ -656,13 +675,15 @@ function Stream(sc, id, pc) {
...
@@ -656,13 +675,15 @@ function Stream(sc, id, pc) {
* The associated ServerConnection.
* The associated ServerConnection.
*
*
* @type {ServerConnection}
* @type {ServerConnection}
*/
* @const
*/
this
.
sc
=
sc
;
this
.
sc
=
sc
;
/**
/**
* The id of this stream.
* The id of this stream.
*
*
* @type {string}
* @type {string}
*/
* @const
*/
this
.
id
=
id
;
this
.
id
=
id
;
/**
/**
* For up streams, one of "local" or "screenshare".
* For up streams, one of "local" or "screenshare".
...
@@ -693,20 +714,20 @@ function Stream(sc, id, pc) {
...
@@ -693,20 +714,20 @@ function Stream(sc, id, pc) {
/**
/**
* Track labels, indexed by track id.
* Track labels, indexed by track id.
*
*
* @type {Object
.
<string,string>}
* @type {Object<string,string>}
*/
*/
this
.
labels
=
{};
this
.
labels
=
{};
/**
/**
* Track labels, indexed by mid.
* Track labels, indexed by mid.
*
*
* @type {Object
.
<string,string>}
* @type {Object<string,string>}
*/
*/
this
.
labelsByMid
=
{};
this
.
labelsByMid
=
{};
/**
/**
* Buffered ICE candidates. This will be flushed by flushIceCandidates
* Buffered ICE candidates. This will be flushed by flushIceCandidates
* when the PC becomes stable.
* when the PC becomes stable.
*
*
* @type {
Array.<RTCIceCandidate>
}
* @type {
RTCIceCandidate[]
}
*/
*/
this
.
iceCandidates
=
[];
this
.
iceCandidates
=
[];
/**
/**
...
@@ -721,7 +742,7 @@ function Stream(sc, id, pc) {
...
@@ -721,7 +742,7 @@ function Stream(sc, id, pc) {
* a dictionary indexed by track id, with each value a disctionary of
* a dictionary indexed by track id, with each value a disctionary of
* statistics.
* statistics.
*
*
* @type {Object
.
<string,any>}
* @type {Object<string,any>}
*/
*/
this
.
stats
=
{};
this
.
stats
=
{};
/**
/**
...
@@ -734,7 +755,7 @@ function Stream(sc, id, pc) {
...
@@ -734,7 +755,7 @@ function Stream(sc, id, pc) {
/**
/**
* clientdata is a convenient place to attach data to a Stream.
* clientdata is a convenient place to attach data to a Stream.
* It is not used by the library.
* It is not used by the library.
* @type{Object
.<string
,any>}
* @type{Object
<any
,any>}
*/
*/
this
.
userdata
=
{};
this
.
userdata
=
{};
...
@@ -743,21 +764,21 @@ function Stream(sc, id, pc) {
...
@@ -743,21 +764,21 @@ function Stream(sc, id, pc) {
/**
/**
* onclose is called when the stream is closed.
* onclose is called when the stream is closed.
*
*
* @type{
function():
any}
* @type{
(this: Stream) =>
any}
*/
*/
this
.
onclose
=
null
;
this
.
onclose
=
null
;
/**
/**
* onerror is called whenever an error occurs. If the error is
* onerror is called whenever an error occurs. If the error is
* fatal, then onclose will be called afterwards.
* fatal, then onclose will be called afterwards.
*
*
* @type{
function(any):
any}
* @type{
(this: Stream, error: any) =>
any}
*/
*/
this
.
onerror
=
null
;
this
.
onerror
=
null
;
/**
/**
* onnegotiationcompleted is called whenever negotiation or
* onnegotiationcompleted is called whenever negotiation or
* renegotiation has completed.
* renegotiation has completed.
*
*
* @type{
function():
any}
* @type{
(this: Stream) =>
any}
*/
*/
this
.
onnegotiationcompleted
=
null
;
this
.
onnegotiationcompleted
=
null
;
/**
/**
...
@@ -765,32 +786,32 @@ function Stream(sc, id, pc) {
...
@@ -765,32 +786,32 @@ function Stream(sc, id, pc) {
* If the stream parameter differs from its previous value, then it
* If the stream parameter differs from its previous value, then it
* indicates that the old stream has been discarded.
* indicates that the old stream has been discarded.
*
*
* @type{
function(MediaStreamTrack, RTCRtpTransceiver, string, MediaStream):
any}
* @type{
(this: Stream, track: MediaStreamTrack, transceiver: RTCRtpTransceiver, label: string, stream: MediaStream) =>
any}
*/
*/
this
.
ondowntrack
=
null
;
this
.
ondowntrack
=
null
;
/**
/**
* onlabel is called whenever the server sets a new label for the stream.
* onlabel is called whenever the server sets a new label for the stream.
*
*
* @type{
function(string):
any}
* @type{
(this: Stream, label: string) =>
any}
*/
*/
this
.
onlabel
=
null
;
this
.
onlabel
=
null
;
/**
/**
* onstatus is called whenever the status of the stream changes.
* onstatus is called whenever the status of the stream changes.
*
*
* @type{
function(string):
any}
* @type{
(this: Stream, status: string) =>
any}
*/
*/
this
.
onstatus
=
null
;
this
.
onstatus
=
null
;
/**
/**
* onabort is called when the server requested that an up stream be
* onabort is called when the server requested that an up stream be
* closed. It is the resposibility of the client to close the stream.
* closed. It is the resposibility of the client to close the stream.
*
*
* @type{
function():
any}
* @type{
(this: Stream) =>
any}
*/
*/
this
.
onabort
=
null
;
this
.
onabort
=
null
;
/**
/**
* onstats is called when we have new statistics about the connection
* onstats is called when we have new statistics about the connection
*
*
* @type{
function(Object.<string,any>):
any}
* @type{
(this: Stream, stats: Object<any,any>) =>
any}
*/
*/
this
.
onstats
=
null
;
this
.
onstats
=
null
;
}
}
...
@@ -834,6 +855,7 @@ Stream.prototype.close = function(sendclose) {
...
@@ -834,6 +855,7 @@ Stream.prototype.close = function(sendclose) {
* @function
* @function
*/
*/
Stream
.
prototype
.
flushIceCandidates
=
async
function
()
{
Stream
.
prototype
.
flushIceCandidates
=
async
function
()
{
/** @type {Promise<any>[]} */
let
promises
=
[];
let
promises
=
[];
this
.
iceCandidates
.
forEach
(
c
=>
{
this
.
iceCandidates
.
forEach
(
c
=>
{
promises
.
push
(
this
.
pc
.
addIceCandidate
(
c
).
catch
(
console
.
warn
));
promises
.
push
(
this
.
pc
.
addIceCandidate
(
c
).
catch
(
console
.
warn
));
...
@@ -845,16 +867,16 @@ Stream.prototype.flushIceCandidates = async function () {
...
@@ -845,16 +867,16 @@ Stream.prototype.flushIceCandidates = async function () {
/**
/**
* negotiate negotiates or renegotiates an up stream. It is called
* negotiate negotiates or renegotiates an up stream. It is called
* automatically when required. If the client requires renegotiation, it
* automatically when required. If the client requires renegotiation, it
* is probably
more effective to call restartIce on the underlying PC
* is probably
better to call restartIce which will cause negotiate to be
*
rather than invoking this function direct
ly.
*
called asynchronous
ly.
*
*
* @function
* @function
* @param {boolean} [restartIce]
* @param {boolean} [restartIce]
- Whether to restart ICE.
*/
*/
Stream
.
prototype
.
negotiate
=
async
function
(
restartIce
)
{
Stream
.
prototype
.
negotiate
=
async
function
(
restartIce
)
{
let
c
=
this
;
let
c
=
this
;
let
options
=
null
;
let
options
=
{}
;
if
(
restartIce
)
if
(
restartIce
)
options
=
{
iceRestart
:
true
};
options
=
{
iceRestart
:
true
};
let
offer
=
await
c
.
pc
.
createOffer
(
options
);
let
offer
=
await
c
.
pc
.
createOffer
(
options
);
...
@@ -892,8 +914,7 @@ Stream.prototype.negotiate = async function (restartIce) {
...
@@ -892,8 +914,7 @@ Stream.prototype.negotiate = async function (restartIce) {
Stream
.
prototype
.
restartIce
=
function
()
{
Stream
.
prototype
.
restartIce
=
function
()
{
let
c
=
this
;
let
c
=
this
;
/** @ts-ignore */
if
(
'
restartIce
'
in
c
.
pc
)
{
if
(
typeof
c
.
pc
.
restartIce
===
'
function
'
)
{
try
{
try
{
/** @ts-ignore */
/** @ts-ignore */
c
.
pc
.
restartIce
();
c
.
pc
.
restartIce
();
...
@@ -988,7 +1009,7 @@ Stream.prototype.updateStats = async function() {
...
@@ -988,7 +1009,7 @@ Stream.prototype.updateStats = async function() {
* setStatsInterval sets the interval in milliseconds at which the onstats
* setStatsInterval sets the interval in milliseconds at which the onstats
* handler will be called. This is only useful for up streams.
* handler will be called. This is only useful for up streams.
*
*
* @param {number} ms
* @param {number} ms
- The interval in milliseconds.
*/
*/
Stream
.
prototype
.
setStatsInterval
=
function
(
ms
)
{
Stream
.
prototype
.
setStatsInterval
=
function
(
ms
)
{
let
c
=
this
;
let
c
=
this
;
...
...
static/sfu.js
View file @
0aa77441
...
@@ -103,9 +103,8 @@ function storeSettings(settings) {
...
@@ -103,9 +103,8 @@ function storeSettings(settings) {
*
*
* @returns {settings}
* @returns {settings}
*/
*/
function
getSettings
()
{
function
getSettings
()
{
/** @type{settings} */
/** @type
{settings} */
let
settings
;
let
settings
;
try
{
try
{
let
json
=
window
.
sessionStorage
.
getItem
(
'
settings
'
);
let
json
=
window
.
sessionStorage
.
getItem
(
'
settings
'
);
...
@@ -127,22 +126,50 @@ function updateSettings(settings) {
...
@@ -127,22 +126,50 @@ function updateSettings(settings) {
storeSettings
(
s
);
storeSettings
(
s
);
}
}
/**
* @param {string} id
*/
function
getSelectElement
(
id
)
{
let
elt
=
document
.
getElementById
(
id
);
if
(
!
elt
||
!
(
elt
instanceof
HTMLSelectElement
))
throw
new
Error
(
`Couldn't find
${
id
}
`
);
return
elt
;
}
/**
* @param {string} id
*/
function
getInputElement
(
id
)
{
let
elt
=
document
.
getElementById
(
id
);
if
(
!
elt
||
!
(
elt
instanceof
HTMLInputElement
))
throw
new
Error
(
`Couldn't find
${
id
}
`
);
return
elt
;
}
/**
* @param {string} id
*/
function
getButtonElement
(
id
)
{
let
elt
=
document
.
getElementById
(
id
);
if
(
!
elt
||
!
(
elt
instanceof
HTMLButtonElement
))
throw
new
Error
(
`Couldn't find
${
id
}
`
);
return
elt
;
}
function
reflectSettings
()
{
function
reflectSettings
()
{
let
settings
=
getSettings
();
let
settings
=
getSettings
();
let
store
=
false
;
let
store
=
false
;
setLocalMute
(
settings
.
localMute
);
setLocalMute
(
settings
.
localMute
);
let
videoselect
=
let
videoselect
=
getSelectElement
(
'
videoselect
'
);
/** @type {HTMLSelectElement} */
(
document
.
getElementById
(
'
videoselect
'
));
if
(
!
settings
.
video
||
!
selectOptionAvailable
(
videoselect
,
settings
.
video
))
{
if
(
!
settings
.
video
||
!
selectOptionAvailable
(
videoselect
,
settings
.
video
))
{
settings
.
video
=
selectOptionDefault
(
videoselect
);
settings
.
video
=
selectOptionDefault
(
videoselect
);
store
=
true
;
store
=
true
;
}
}
videoselect
.
value
=
settings
.
video
;
videoselect
.
value
=
settings
.
video
;
let
audioselect
=
let
audioselect
=
getSelectElement
(
'
audioselect
'
);
/** @type {HTMLSelectElement} */
(
document
.
getElementById
(
'
audioselect
'
));
if
(
!
settings
.
audio
||
!
selectOptionAvailable
(
audioselect
,
settings
.
audio
))
{
if
(
!
settings
.
audio
||
!
selectOptionAvailable
(
audioselect
,
settings
.
audio
))
{
settings
.
audio
=
selectOptionDefault
(
audioselect
);
settings
.
audio
=
selectOptionDefault
(
audioselect
);
store
=
true
;
store
=
true
;
...
@@ -150,24 +177,24 @@ function reflectSettings() {
...
@@ -150,24 +177,24 @@ function reflectSettings() {
audioselect
.
value
=
settings
.
audio
;
audioselect
.
value
=
settings
.
audio
;
if
(
settings
.
request
)
if
(
settings
.
request
)
document
.
getElementById
(
'
requestselect
'
).
value
=
settings
.
request
;
getSelectElement
(
'
requestselect
'
).
value
=
settings
.
request
;
else
{
else
{
settings
.
request
=
document
.
getElementById
(
'
requestselect
'
).
value
;
settings
.
request
=
getSelectElement
(
'
requestselect
'
).
value
;
store
=
true
;
store
=
true
;
}
}
if
(
settings
.
send
)
if
(
settings
.
send
)
document
.
getElementById
(
'
sendselect
'
).
value
=
settings
.
send
;
getSelectElement
(
'
sendselect
'
).
value
=
settings
.
send
;
else
{
else
{
settings
.
send
=
document
.
getElementById
(
'
sendselect
'
).
value
;
settings
.
send
=
getSelectElement
(
'
sendselect
'
).
value
;
store
=
true
;
store
=
true
;
}
}
document
.
getElementById
(
'
activitybox
'
).
checked
=
settings
.
activityDetection
;
getInputElement
(
'
activitybox
'
).
checked
=
settings
.
activityDetection
;
document
.
getElementById
(
'
blackboardbox
'
).
checked
=
settings
.
blackboardMode
;
getInputElement
(
'
blackboardbox
'
).
checked
=
settings
.
blackboardMode
;
document
.
getElementById
(
'
studiobox
'
).
checked
=
settings
.
studioMode
;
getInputElement
(
'
studiobox
'
).
checked
=
settings
.
studioMode
;
if
(
store
)
if
(
store
)
storeSettings
(
settings
);
storeSettings
(
settings
);
...
@@ -218,9 +245,9 @@ function setConnected(connected) {
...
@@ -218,9 +245,9 @@ function setConnected(connected) {
}
else
{
}
else
{
resetUsers
();
resetUsers
();
let
userpass
=
getUserPass
();
let
userpass
=
getUserPass
();
document
.
getElementById
(
'
username
'
).
value
=
getInputElement
(
'
username
'
).
value
=
userpass
?
userpass
.
username
:
''
;
userpass
?
userpass
.
username
:
''
;
document
.
getElementById
(
'
password
'
).
value
=
getInputElement
(
'
password
'
).
value
=
userpass
?
userpass
.
password
:
''
;
userpass
?
userpass
.
password
:
''
;
statspan
.
textContent
=
'
Disconnected
'
;
statspan
.
textContent
=
'
Disconnected
'
;
statspan
.
classList
.
remove
(
'
connected
'
);
statspan
.
classList
.
remove
(
'
connected
'
);
...
@@ -234,6 +261,7 @@ function setConnected(connected) {
...
@@ -234,6 +261,7 @@ function setConnected(connected) {
}
}
}
}
/** @this {ServerConnection} */
function
gotConnected
()
{
function
gotConnected
()
{
setConnected
(
true
);
setConnected
(
true
);
let
up
=
getUserPass
();
let
up
=
getUserPass
();
...
@@ -243,6 +271,7 @@ function gotConnected() {
...
@@ -243,6 +271,7 @@ function gotConnected() {
}
}
/**
/**
* @this {ServerConnection}
* @param {number} code
* @param {number} code
* @param {string} reason
* @param {string} reason
*/
*/
...
@@ -255,6 +284,7 @@ function gotClose(code, reason) {
...
@@ -255,6 +284,7 @@ function gotClose(code, reason) {
}
}
/**
/**
* @this {ServerConnection}
* @param {Stream} c
* @param {Stream} c
*/
*/
function
gotDownStream
(
c
)
{
function
gotDownStream
(
c
)
{
...
@@ -291,12 +321,12 @@ setViewportHeight();
...
@@ -291,12 +321,12 @@ setViewportHeight();
addEventListener
(
'
resize
'
,
setViewportHeight
);
addEventListener
(
'
resize
'
,
setViewportHeight
);
addEventListener
(
'
orientationchange
'
,
setViewportHeight
);
addEventListener
(
'
orientationchange
'
,
setViewportHeight
);
document
.
getElementById
(
'
presentbutton
'
).
onclick
=
function
(
e
)
{
getButtonElement
(
'
presentbutton
'
).
onclick
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
addLocalMedia
();
addLocalMedia
();
};
};
document
.
getElementById
(
'
unpresentbutton
'
).
onclick
=
function
(
e
)
{
getButtonElement
(
'
unpresentbutton
'
).
onclick
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
delUpMediaKind
(
'
local
'
);
delUpMediaKind
(
'
local
'
);
resizePeers
();
resizePeers
();
...
@@ -353,26 +383,34 @@ function setLocalMute(mute) {
...
@@ -353,26 +383,34 @@ function setLocalMute(mute) {
}
}
}
}
document
.
getElementById
(
'
videoselect
'
).
onchange
=
function
(
e
)
{
getSelectElement
(
'
videoselect
'
).
onchange
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLSelectElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
video
:
this
.
value
});
updateSettings
({
video
:
this
.
value
});
changePresentation
();
changePresentation
();
};
};
document
.
getElementById
(
'
audioselect
'
).
onchange
=
function
(
e
)
{
getSelectElement
(
'
audioselect
'
).
onchange
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLSelectElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
audio
:
this
.
value
});
updateSettings
({
audio
:
this
.
value
});
changePresentation
();
changePresentation
();
};
};
document
.
getElementById
(
'
blackboardbox
'
).
onchange
=
function
(
e
)
{
getInputElement
(
'
blackboardbox
'
).
onchange
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLInputElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
blackboardMode
:
this
.
checked
});
updateSettings
({
blackboardMode
:
this
.
checked
});
changePresentation
();
changePresentation
();
}
}
document
.
getElementById
(
'
studiobox
'
).
onchange
=
function
(
e
)
{
getInputElement
(
'
studiobox
'
).
onchange
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLInputElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
studioMode
:
this
.
checked
});
updateSettings
({
studioMode
:
this
.
checked
});
changePresentation
();
changePresentation
();
}
}
...
@@ -413,7 +451,9 @@ function getMaxVideoThroughput() {
...
@@ -413,7 +451,9 @@ function getMaxVideoThroughput() {
}
}
}
}
document
.
getElementById
(
'
sendselect
'
).
onchange
=
async
function
(
e
)
{
getSelectElement
(
'
sendselect
'
).
onchange
=
async
function
(
e
)
{
if
(
!
(
this
instanceof
HTMLSelectElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
send
:
this
.
value
});
updateSettings
({
send
:
this
.
value
});
let
t
=
getMaxVideoThroughput
();
let
t
=
getMaxVideoThroughput
();
for
(
let
id
in
serverConnection
.
up
)
{
for
(
let
id
in
serverConnection
.
up
)
{
...
@@ -423,8 +463,10 @@ document.getElementById('sendselect').onchange = async function(e) {
...
@@ -423,8 +463,10 @@ document.getElementById('sendselect').onchange = async function(e) {
}
}
}
}
document
.
getElementById
(
'
requestselect
'
).
onchange
=
function
(
e
)
{
getSelectElement
(
'
requestselect
'
).
onchange
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLSelectElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
request
:
this
.
value
});
updateSettings
({
request
:
this
.
value
});
serverConnection
.
request
(
this
.
value
);
serverConnection
.
request
(
this
.
value
);
};
};
...
@@ -433,7 +475,9 @@ const activityDetectionInterval = 200;
...
@@ -433,7 +475,9 @@ const activityDetectionInterval = 200;
const
activityDetectionPeriod
=
700
;
const
activityDetectionPeriod
=
700
;
const
activityDetectionThreshold
=
0.2
;
const
activityDetectionThreshold
=
0.2
;
document
.
getElementById
(
'
activitybox
'
).
onchange
=
function
(
e
)
{
getInputElement
(
'
activitybox
'
).
onchange
=
function
(
e
)
{
if
(
!
(
this
instanceof
HTMLInputElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
updateSettings
({
activityDetection
:
this
.
checked
});
updateSettings
({
activityDetection
:
this
.
checked
});
for
(
let
id
in
serverConnection
.
down
)
{
for
(
let
id
in
serverConnection
.
down
)
{
let
c
=
serverConnection
.
down
[
id
];
let
c
=
serverConnection
.
down
[
id
];
...
@@ -446,6 +490,10 @@ document.getElementById('activitybox').onchange = function(e) {
...
@@ -446,6 +490,10 @@ document.getElementById('activitybox').onchange = function(e) {
}
}
}
}
/**
* @this {Stream}
* @param {Object<string,any>} stats
*/
function
gotUpStats
(
stats
)
{
function
gotUpStats
(
stats
)
{
let
c
=
this
;
let
c
=
this
;
...
@@ -477,8 +525,12 @@ function setActive(c, value) {
...
@@ -477,8 +525,12 @@ function setActive(c, value) {
peer
.
classList
.
remove
(
'
peer-active
'
);
peer
.
classList
.
remove
(
'
peer-active
'
);
}
}
/**
* @this {Stream}
* @param {Object<string,any>} stats
*/
function
gotDownStats
(
stats
)
{
function
gotDownStats
(
stats
)
{
if
(
!
document
.
getElementById
(
'
activitybox
'
).
checked
)
if
(
!
getInputElement
(
'
activitybox
'
).
checked
)
return
;
return
;
let
c
=
this
;
let
c
=
this
;
...
@@ -514,7 +566,11 @@ function addSelectOption(select, label, value) {
...
@@ -514,7 +566,11 @@ function addSelectOption(select, label, value) {
if
(
!
value
)
if
(
!
value
)
value
=
label
;
value
=
label
;
for
(
let
i
=
0
;
i
<
select
.
children
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
select
.
children
.
length
;
i
++
)
{
let
child
=
/** @type {HTMLOptionElement} */
(
select
.
children
[
i
]);
let
child
=
select
.
children
[
i
];
if
(
!
(
child
instanceof
HTMLOptionElement
))
{
console
.
warn
(
'
Unexpected select child
'
);
continue
;
}
if
(
child
.
value
===
value
)
{
if
(
child
.
value
===
value
)
{
if
(
child
.
label
!==
label
)
{
if
(
child
.
label
!==
label
)
{
child
.
label
=
label
;
child
.
label
=
label
;
...
@@ -536,10 +592,13 @@ function addSelectOption(select, label, value) {
...
@@ -536,10 +592,13 @@ function addSelectOption(select, label, value) {
function
selectOptionAvailable
(
select
,
value
)
{
function
selectOptionAvailable
(
select
,
value
)
{
let
children
=
select
.
children
;
let
children
=
select
.
children
;
for
(
let
i
=
0
;
i
<
children
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
children
.
length
;
i
++
)
{
let
child
=
/** @type {HTMLOptionElement} */
(
select
.
children
[
i
]);
let
child
=
select
.
children
[
i
];
if
(
child
.
value
===
value
)
{
if
(
!
(
child
instanceof
HTMLOptionElement
))
{
return
true
;
console
.
warn
(
'
Unexpected select child
'
);
continue
;
}
}
if
(
child
.
value
===
value
)
return
true
;
}
}
return
false
;
return
false
;
}
}
...
@@ -551,7 +610,11 @@ function selectOptionAvailable(select, value) {
...
@@ -551,7 +610,11 @@ function selectOptionAvailable(select, value) {
function
selectOptionDefault
(
select
)
{
function
selectOptionDefault
(
select
)
{
/* First non-empty option. */
/* First non-empty option. */
for
(
let
i
=
0
;
i
<
select
.
children
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
select
.
children
.
length
;
i
++
)
{
let
child
=
/** @type {HTMLOptionElement} */
(
select
.
children
[
i
]);
let
child
=
select
.
children
[
i
];
if
(
!
(
child
instanceof
HTMLOptionElement
))
{
console
.
warn
(
'
Unexpected select child
'
);
continue
;
}
if
(
child
.
value
)
if
(
child
.
value
)
return
child
.
value
;
return
child
.
value
;
}
}
...
@@ -587,13 +650,13 @@ async function setMediaChoices(done) {
...
@@ -587,13 +650,13 @@ async function setMediaChoices(done) {
if
(
d
.
kind
===
'
videoinput
'
)
{
if
(
d
.
kind
===
'
videoinput
'
)
{
if
(
!
label
)
if
(
!
label
)
label
=
`Camera
${
cn
}
`
;
label
=
`Camera
${
cn
}
`
;
addSelectOption
(
document
.
getElementById
(
'
videoselect
'
),
addSelectOption
(
getSelectElement
(
'
videoselect
'
),
label
,
d
.
deviceId
);
label
,
d
.
deviceId
);
cn
++
;
cn
++
;
}
else
if
(
d
.
kind
===
'
audioinput
'
)
{
}
else
if
(
d
.
kind
===
'
audioinput
'
)
{
if
(
!
label
)
if
(
!
label
)
label
=
`Microphone
${
mn
}
`
;
label
=
`Microphone
${
mn
}
`
;
addSelectOption
(
document
.
getElementById
(
'
audioselect
'
),
addSelectOption
(
getSelectElement
(
'
audioselect
'
),
label
,
d
.
deviceId
);
label
,
d
.
deviceId
);
mn
++
;
mn
++
;
}
}
...
@@ -693,6 +756,7 @@ async function addLocalMedia(id) {
...
@@ -693,6 +756,7 @@ async function addLocalMedia(id) {
stopUpMedia
(
old
);
stopUpMedia
(
old
);
let
constraints
=
{
audio
:
audio
,
video
:
video
};
let
constraints
=
{
audio
:
audio
,
video
:
video
};
/** @type {MediaStream} */
let
stream
=
null
;
let
stream
=
null
;
try
{
try
{
stream
=
await
navigator
.
mediaDevices
.
getUserMedia
(
constraints
);
stream
=
await
navigator
.
mediaDevices
.
getUserMedia
(
constraints
);
...
@@ -717,11 +781,11 @@ async function addLocalMedia(id) {
...
@@ -717,11 +781,11 @@ async function addLocalMedia(id) {
t
.
enabled
=
false
;
t
.
enabled
=
false
;
}
else
if
(
t
.
kind
==
'
video
'
)
{
}
else
if
(
t
.
kind
==
'
video
'
)
{
if
(
settings
.
blackboardMode
)
{
if
(
settings
.
blackboardMode
)
{
if
(
'
contentHint
'
in
t
)
/** @ts-ignore */
t
.
contentHint
=
'
detail
'
;
t
.
contentHint
=
'
detail
'
;
}
}
}
}
let
sender
=
c
.
pc
.
addTrack
(
t
,
stream
);
c
.
pc
.
addTrack
(
t
,
stream
);
});
});
c
.
onstats
=
gotUpStats
;
c
.
onstats
=
gotUpStats
;
...
@@ -730,12 +794,16 @@ async function addLocalMedia(id) {
...
@@ -730,12 +794,16 @@ async function addLocalMedia(id) {
setButtonsVisibility
();
setButtonsVisibility
();
}
}
async
function
addShareMedia
(
setup
)
{
async
function
addShareMedia
()
{
if
(
!
getUserPass
())
if
(
!
getUserPass
())
return
;
return
;
/** @type {MediaStream} */
let
stream
=
null
;
let
stream
=
null
;
try
{
try
{
if
(
!
(
'
getDisplayMedia
'
in
navigator
.
mediaDevices
))
throw
new
Error
(
'
Your browser does not support screen sharing
'
);
/** @ts-ignore */
stream
=
await
navigator
.
mediaDevices
.
getDisplayMedia
({
video
:
true
});
stream
=
await
navigator
.
mediaDevices
.
getDisplayMedia
({
video
:
true
});
}
catch
(
e
)
{
}
catch
(
e
)
{
console
.
error
(
e
);
console
.
error
(
e
);
...
@@ -747,7 +815,7 @@ async function addShareMedia(setup) {
...
@@ -747,7 +815,7 @@ async function addShareMedia(setup) {
c
.
kind
=
'
screenshare
'
;
c
.
kind
=
'
screenshare
'
;
c
.
stream
=
stream
;
c
.
stream
=
stream
;
stream
.
getTracks
().
forEach
(
t
=>
{
stream
.
getTracks
().
forEach
(
t
=>
{
let
sender
=
c
.
pc
.
addTrack
(
t
,
stream
);
c
.
pc
.
addTrack
(
t
,
stream
);
t
.
onended
=
e
=>
{
t
.
onended
=
e
=>
{
delUpMedia
(
c
);
delUpMedia
(
c
);
};
};
...
@@ -807,6 +875,9 @@ function delUpMediaKind(kind) {
...
@@ -807,6 +875,9 @@ function delUpMediaKind(kind) {
hideVideo
();
hideVideo
();
}
}
/**
* @param {string} kind
*/
function
findUpMedia
(
kind
)
{
function
findUpMedia
(
kind
)
{
for
(
let
id
in
serverConnection
.
up
)
{
for
(
let
id
in
serverConnection
.
up
)
{
if
(
serverConnection
.
up
[
id
].
kind
===
kind
)
if
(
serverConnection
.
up
[
id
].
kind
===
kind
)
...
@@ -815,6 +886,9 @@ function findUpMedia(kind) {
...
@@ -815,6 +886,9 @@ function findUpMedia(kind) {
return
null
;
return
null
;
}
}
/**
* @param {boolean} mute
*/
function
muteLocalTracks
(
mute
)
{
function
muteLocalTracks
(
mute
)
{
if
(
!
serverConnection
)
if
(
!
serverConnection
)
return
;
return
;
...
@@ -846,12 +920,14 @@ function setMedia(c, isUp) {
...
@@ -846,12 +920,14 @@ function setMedia(c, isUp) {
peersdiv
.
appendChild
(
div
);
peersdiv
.
appendChild
(
div
);
}
}
let
media
=
document
.
getElementById
(
'
media-
'
+
c
.
id
);
let
media
=
/** @type {HTMLVideoElement} */
(
document
.
getElementById
(
'
media-
'
+
c
.
id
));
if
(
!
media
)
{
if
(
!
media
)
{
media
=
document
.
createElement
(
'
video
'
);
media
=
document
.
createElement
(
'
video
'
);
media
.
id
=
'
media-
'
+
c
.
id
;
media
.
id
=
'
media-
'
+
c
.
id
;
media
.
classList
.
add
(
'
media
'
);
media
.
classList
.
add
(
'
media
'
);
media
.
autoplay
=
true
;
media
.
autoplay
=
true
;
/** @ts-ignore */
media
.
playsinline
=
true
;
media
.
playsinline
=
true
;
media
.
controls
=
true
;
media
.
controls
=
true
;
if
(
isUp
)
if
(
isUp
)
...
@@ -883,7 +959,9 @@ function delMedia(id) {
...
@@ -883,7 +959,9 @@ function delMedia(id) {
let
peer
=
document
.
getElementById
(
'
peer-
'
+
id
);
let
peer
=
document
.
getElementById
(
'
peer-
'
+
id
);
if
(
!
peer
)
if
(
!
peer
)
throw
new
Error
(
'
Removing unknown media
'
);
throw
new
Error
(
'
Removing unknown media
'
);
let
media
=
document
.
getElementById
(
'
media-
'
+
id
);
let
media
=
/** @type{HTMLVideoElement} */
(
document
.
getElementById
(
'
media-
'
+
id
));
media
.
srcObject
=
null
;
media
.
srcObject
=
null
;
mediadiv
.
removeChild
(
peer
);
mediadiv
.
removeChild
(
peer
);
...
@@ -941,14 +1019,19 @@ function resizePeers() {
...
@@ -941,14 +1019,19 @@ function resizePeers() {
if
(
!
count
)
if
(
!
count
)
// No video, nothing to resize.
// No video, nothing to resize.
return
;
return
;
let
size
=
100
/
columns
;
let
container
=
document
.
getElementById
(
"
video-container
"
)
let
container
=
document
.
getElementById
(
"
video-container
"
)
// Peers div has total padding of 30px, we remove 30 on offsetHeight
// Peers div has total padding of 30px, we remove 30 on offsetHeight
let
max_video_height
=
Math
.
trunc
((
peers
.
offsetHeight
-
30
)
/
columns
);
let
max_video_height
=
Math
.
trunc
((
peers
.
offsetHeight
-
30
)
/
columns
);
let
media_list
=
document
.
getElementsByClassName
(
"
media
"
);
let
media_list
=
document
.
getElementsByClassName
(
"
media
"
);
for
(
let
i
=
0
;
i
<
media_list
.
length
;
i
++
)
for
(
let
i
=
0
;
i
<
media_list
.
length
;
i
++
)
{
media_list
[
i
].
style
[
'
max_height
'
]
=
max_video_height
+
"
px
"
;
let
media
=
media_list
[
i
];
if
(
!
(
media
instanceof
HTMLMediaElement
))
{
console
.
warn
(
'
Unexpected media
'
);
continue
;
}
media
.
style
[
'
max_height
'
]
=
max_video_height
+
"
px
"
;
}
if
(
count
<=
2
&&
container
.
offsetHeight
>
container
.
offsetWidth
)
{
if
(
count
<=
2
&&
container
.
offsetHeight
>
container
.
offsetWidth
)
{
peers
.
style
[
'
grid-template-columns
'
]
=
"
repeat(1, 1fr)
"
;
peers
.
style
[
'
grid-template-columns
'
]
=
"
repeat(1, 1fr)
"
;
...
@@ -957,7 +1040,7 @@ function resizePeers() {
...
@@ -957,7 +1040,7 @@ function resizePeers() {
}
}
}
}
/** @type{Object
.
<string,string>} */
/** @type{Object<string,string>} */
let
users
=
{};
let
users
=
{};
/**
/**
...
@@ -1051,7 +1134,7 @@ function clearUsername() {
...
@@ -1051,7 +1134,7 @@ function clearUsername() {
}
}
/**
/**
* @param {Object
.
<string,boolean>} perms
* @param {Object<string,boolean>} perms
*/
*/
function
gotPermissions
(
perms
)
{
function
gotPermissions
(
perms
)
{
displayUsername
();
displayUsername
();
...
@@ -1064,7 +1147,7 @@ const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\\+~#=?]+[-a-zA-Z0-9@:%/_\\+~#=]/
...
@@ -1064,7 +1147,7 @@ const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\\+~#=?]+[-a-zA-Z0-9@:%/_\\+~#=]/
/**
/**
* @param {string} line
* @param {string} line
* @returns {
Array.<Text|HTMLElement>
}
* @returns {
(Text|HTMLElement)[]
}
*/
*/
function
formatLine
(
line
)
{
function
formatLine
(
line
)
{
let
r
=
new
RegExp
(
urlRegexp
);
let
r
=
new
RegExp
(
urlRegexp
);
...
@@ -1088,7 +1171,7 @@ function formatLine(line) {
...
@@ -1088,7 +1171,7 @@ function formatLine(line) {
}
}
/**
/**
* @param {
Array.<string>
} lines
* @param {
string[]
} lines
* @returns {HTMLElement}
* @returns {HTMLElement}
*/
*/
function
formatLines
(
lines
)
{
function
formatLines
(
lines
)
{
...
@@ -1113,6 +1196,12 @@ function formatLines(lines) {
...
@@ -1113,6 +1196,12 @@ function formatLines(lines) {
/** @type {lastMessage} */
/** @type {lastMessage} */
let
lastMessage
=
{};
let
lastMessage
=
{};
/**
* @param {string} peerId
* @param {string} nick
* @param {string} kind
* @param {string} message
*/
function
addToChatbox
(
peerId
,
nick
,
kind
,
message
){
function
addToChatbox
(
peerId
,
nick
,
kind
,
message
){
let
userpass
=
getUserPass
();
let
userpass
=
getUserPass
();
let
row
=
document
.
createElement
(
'
div
'
);
let
row
=
document
.
createElement
(
'
div
'
);
...
@@ -1174,7 +1263,7 @@ function clearChat() {
...
@@ -1174,7 +1263,7 @@ function clearChat() {
* part may be quoted and may include backslash escapes.
* part may be quoted and may include backslash escapes.
*
*
* @param {string} line
* @param {string} line
* @returns {
Array.<string>
}
* @returns {
string[]
}
*/
*/
function
parseCommand
(
line
)
{
function
parseCommand
(
line
)
{
let
i
=
0
;
let
i
=
0
;
...
@@ -1204,7 +1293,8 @@ function parseCommand(line) {
...
@@ -1204,7 +1293,8 @@ function parseCommand(line) {
}
}
function
handleInput
()
{
function
handleInput
()
{
let
input
=
document
.
getElementById
(
'
input
'
);
let
input
=
/** @type {HTMLTextAreaElement} */
(
document
.
getElementById
(
'
input
'
));
let
data
=
input
.
value
;
let
data
=
input
.
value
;
input
.
value
=
''
;
input
.
value
=
''
;
...
@@ -1356,39 +1446,66 @@ function chatResizer(e) {
...
@@ -1356,39 +1446,66 @@ function chatResizer(e) {
document
.
getElementById
(
'
resizer
'
).
addEventListener
(
'
mousedown
'
,
chatResizer
,
false
);
document
.
getElementById
(
'
resizer
'
).
addEventListener
(
'
mousedown
'
,
chatResizer
,
false
);
/** @enum {string} */
const
MessageLevel
=
{
info
:
'
info
'
,
warning
:
'
warning
'
,
error
:
'
error
'
,
}
function
displayError
(
message
,
level
,
position
,
gravity
)
{
/**
var
background
=
"
linear-gradient(to right, #e20a0a, #df2d2d)
"
;
* @param {string} message
if
(
level
===
"
info
"
)
{
* @param {MessageLevel} [level]
background
=
"
linear-gradient(to right, #529518, #96c93d)
"
;
*/
}
function
displayError
(
message
,
level
)
{
if
(
level
===
"
warning
"
)
{
if
(
!
level
)
background
=
"
linear-gradient(to right, #edd800, #c9c200)
"
;
level
=
MessageLevel
.
error
;
var
background
=
'
linear-gradient(to right, #e20a0a, #df2d2d)
'
;
var
position
=
'
center
'
;
var
gravity
=
'
top
'
;
switch
(
level
)
{
case
MessageLevel
.
info
:
background
=
'
linear-gradient(to right, #529518, #96c93d)
'
;
position
=
'
right
'
;
gravity
=
'
bottom
'
;
break
;
case
MessageLevel
.
warning
:
background
=
"
linear-gradient(to right, #edd800, #c9c200)
"
;
break
;
}
}
/** @ts-ignore */
Toastify
({
Toastify
({
text
:
message
,
text
:
message
,
duration
:
4000
,
duration
:
4000
,
close
:
true
,
close
:
true
,
position
:
position
?
position
:
'
center
'
,
position
:
position
,
gravity
:
gravity
?
gravity
:
'
top
'
,
gravity
:
gravity
,
backgroundColor
:
background
,
backgroundColor
:
background
,
className
:
level
,
className
:
level
,
}).
showToast
();
}).
showToast
();
}
}
/**
* @param {string} message
*/
function
displayWarning
(
message
)
{
function
displayWarning
(
message
)
{
let
level
=
"
warning
"
;
return
displayError
(
message
,
MessageLevel
.
warning
);
return
displayError
(
message
,
level
);
}
}
/**
* @param {string} message
*/
function
displayMessage
(
message
)
{
function
displayMessage
(
message
)
{
return
displayError
(
message
,
"
info
"
,
"
right
"
,
"
bottom
"
);
return
displayError
(
message
,
MessageLevel
.
info
);
}
}
document
.
getElementById
(
'
userform
'
).
onsubmit
=
function
(
e
)
{
document
.
getElementById
(
'
userform
'
).
onsubmit
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
let
username
=
document
.
getElementById
(
'
username
'
).
value
.
trim
();
let
username
=
getInputElement
(
'
username
'
).
value
.
trim
();
let
password
=
document
.
getElementById
(
'
password
'
).
value
;
let
password
=
getInputElement
(
'
password
'
).
value
;
storeUserPass
(
username
,
password
);
storeUserPass
(
username
,
password
);
serverConnect
();
serverConnect
();
};
};
...
@@ -1438,6 +1555,8 @@ document.getElementById('clodeside').onclick = function(e) {
...
@@ -1438,6 +1555,8 @@ document.getElementById('clodeside').onclick = function(e) {
document
.
getElementById
(
'
collapse-video
'
).
onclick
=
function
(
e
)
{
document
.
getElementById
(
'
collapse-video
'
).
onclick
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
let
width
=
window
.
innerWidth
;
let
width
=
window
.
innerWidth
;
if
(
width
<=
768
)
{
if
(
width
<=
768
)
{
let
user_box
=
document
.
getElementById
(
'
userDropdown
'
);
let
user_box
=
document
.
getElementById
(
'
userDropdown
'
);
...
@@ -1453,6 +1572,8 @@ document.getElementById('collapse-video').onclick = function(e) {
...
@@ -1453,6 +1572,8 @@ document.getElementById('collapse-video').onclick = function(e) {
document
.
getElementById
(
'
switch-video
'
).
onclick
=
function
(
e
)
{
document
.
getElementById
(
'
switch-video
'
).
onclick
=
function
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
(
this
instanceof
HTMLElement
))
throw
new
Error
(
'
Unexpected type for this
'
);
showVideo
();
showVideo
();
this
.
style
.
display
=
""
;
this
.
style
.
display
=
""
;
document
.
getElementById
(
'
collapse-video
'
).
style
.
display
=
"
block
"
;
document
.
getElementById
(
'
collapse-video
'
).
style
.
display
=
"
block
"
;
...
...
static/tsconfig.json
View file @
0aa77441
...
@@ -4,10 +4,16 @@
...
@@ -4,10 +4,16 @@
"allowJs"
:
true
,
"allowJs"
:
true
,
"checkJs"
:
true
,
"checkJs"
:
true
,
"declaration"
:
true
,
"declaration"
:
true
,
"noImplicitThis"
:
true
,
"emitDeclarationOnly"
:
true
,
"emitDeclarationOnly"
:
true
,
"strictBindCallApply"
:
true
"strictFunctionTypes"
:
true
,
"strictBindCallApply"
:
true
,
"noFallthroughCasesInSwitch"
:
true
,
"noImplicitReturns"
:
true
,
"noUnusedLocals"
:
true
},
},
"files"
:
[
"files"
:
[
"protocol.js"
"protocol.js"
,
"sfu.js"
]
]
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment