Skip to content

Commit

Permalink
# refactoring project, add cookie auth, add server available check
Browse files Browse the repository at this point in the history
  • Loading branch information
ruocogito committed Jun 23, 2024
1 parent 496c88e commit e31d574
Show file tree
Hide file tree
Showing 12 changed files with 1,103 additions and 963 deletions.
41 changes: 22 additions & 19 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
<html>
<head>
<title>VoIP Test</title>
<!-- <script type="text/javascript" src="js/lib/olm.js"></script> -->
<script type="text/javascript" src="js/lib/bundle.js"></script>
<script type="text/javascript" src="js/consts.js"></script>
<script type="text/javascript" src="js/VideoElementsAppender.js"></script>
<script type="text/javascript" src="js/voip.js"></script>
<script type="module" src="js/matrix-conference.js"></script>
</head>

<body>
You can place and receive calls with this example. Make sure to edit the constants in
<code>browserTest.js</code> first.
You can enjoin matrix video conference with this example. Make sure to edit the constants in
<code>consts.js</code> first.
<div id="config"></div>
<div id="result"></div>
<label for="notesContainer">Notes and Errors: </label>
<div id="notesContainer"></div>
<div>
<label for="username">Username: </label>
<select id="username" name="username" required>
<label for="userIdCombo">User: </label>
<select id="userIdCombo" required>
</select>
<button id="connectBtn" style="display:none">Connect</button>
<label id="myComboBoxLabel" for="roomsComboBox" style="display:none">Room: </label>
<select id="roomsComboBox" onchange="comboBoxChanged()" style="display:none">
<button id="connectBtn" disabled>Connect</button>
<label id="roomsComboBoxLabel" for="roomsComboBox" style="display:none">Room: </label>
<select id="roomsComboBox" style="display:none">
</select>
</div><div class="webcam-container">
<label id="webcamComboLabel" for="webcamCombo" style="display:none">Webcam: </label>
<select id="webcamCombo" onchange="webcamComboChanged()" style="display:none"></select>
<select id="webcamCombo" style="display:none"></select>
</div>
<button id="call">Place Call</button>
<button id="answer">Answer Call</button>
Expand All @@ -45,13 +42,19 @@
justify-content: start;
}

.video-element {
width: 300px;
height: 300px;
}

.webcam-container {
margin-top: 10px;
margin-bottom: 10px;
}

div#notesContainer {
background-color: #e0e0e0; /* Light grey background */
border: 1px solid #bebebe; /* Slightly darker border */
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); /* Inset shadow for depth */
padding: 10px;
transition: box-shadow 0.3s ease;
overflow: auto; /* Adds scrollbar if content overflows */
max-height: 200px; /* Maximum height before showing scrollbar */
max-width: 740px;
}
</style>
55 changes: 55 additions & 0 deletions js/Enums.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export let CallErrorCode = {
UserHangup : "user_hangup",
InviteTimeout : "invite_timeout"
//other not included
}

export let CallEvent = {
Hangup : "hangup",
State : "state",
Error : "error",
Replaced : "replaced",

// The value of isLocalOnHold() has changed
LocalHoldUnhold : "local_hold_unhold",
// The value of isRemoteOnHold() has changed
RemoteHoldUnhold : "remote_hold_unhold",
// backwards compat alias for LocalHoldUnhold: remove in a major version bump
HoldUnhold : "hold_unhold",
// Feeds have changed
FeedsChanged : "feeds_changed",

AssertedIdentityChanged : "asserted_identity_changed",

LengthChanged : "length_changed",

DataChannel : "datachannel",

SendVoipEvent : "send_voip_event",

// When the call instantiates its peer connection
// For apps that want to access the underlying peer connection, eg for debugging
PeerConnectionCreated : "peer_connection_created",
}

let GroupCallEvent = {
GroupCallStateChanged : "group_call_state_changed",
ActiveSpeakerChanged : "active_speaker_changed",
CallsChanged : "calls_changed",
UserMediaFeedsChanged : "user_media_feeds_changed",
ScreenshareFeedsChanged : "screenshare_feeds_changed",
LocalScreenshareStateChanged : "local_screenshare_state_changed",
LocalMuteStateChanged : "local_mute_state_changed",
ParticipantsChanged : "participants_changed",
Error : "group_call_error",
}

export let LogLevel = {
TRACE: 0,
DEBUG: 1,
INFO: 2,
WARN: 3,
ERROR: 4,
SILENT: 5,
}

21 changes: 21 additions & 0 deletions js/StorageInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class StorageInfo {
constructor() {
this.storageItemName = "matrixDemoStorageInfo"
}
load() {
let obj = ((json)=>json && JSON.parse(json))(localStorage.getItem(this.storageItemName))
if(obj && obj.deviceId && obj.token) {
this.deviceId = obj.deviceId
this.token = obj.token
return this
}
return null
}
save(deviceId, token) {
this.deviceId = deviceId || this.deviceId
this.token = token || this.token

if(!this.deviceId || !this.token) return
localStorage.setItem(this.storageItemName, JSON.stringify(this))
}
}
55 changes: 55 additions & 0 deletions js/Utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

export function getCurrentTimeString() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}

export let isCameraOrWebcamName = function (text) {
// Regular expression to match 'camera' or 'webcam' at the start of the string or after a space
// and not after an open brace '{'
const regex = /^(camera|webcam)|(?<!\(.*)\b( camera| webcam)\b/gi;

// Search for the pattern in the text
const matches = text.match(regex);

// Return the matches
return (matches || []).length > 0;
}

export let calculateHashSha256 = function (string) {
const utf8 = new TextEncoder().encode(string);
let cr = typeof crypto === "undefined" || !!(crypto) === false ? crypto2 : crypto
return cr.subtle.digest('SHA-256', utf8).then((hashBuffer) => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((bytes) => bytes.toString(16).padStart(2, '0'))
.join('');
return hashHex;
});
}

export async function sleepRandom(min, max) {
return sleep(getRandomInt(min, max))
}

export async function sleep(ms) {
return (new Promise(resolve => setTimeout(resolve, ms)));
}

export function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

export function isFirstHexHigher(hex1, hex2) {
// Convert hex strings to integers
const num1 = parseInt(hex1, 16);
const num2 = parseInt(hex2, 16);

// Compare the integers
return num1 > num2;
}

export let streamInfo = (name, stream) => `${name} stream: id:${stream.id}, audio:${stream.getAudioTracks().length > 0}, video:${stream.getVideoTracks().length > 0}`
143 changes: 114 additions & 29 deletions js/VideoElementsAppender.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import * as utils from "/js/Utils.js";

class VideoElementsAppender {
constructor(containerId, elementClassName, elmBackgroundColor, minimumSize, parentPadding, videoMargin) {
this.containerId = containerId;
export class VideoElementsAppender {
constructor(container, elementClassName, elmBackgroundColor, minimumSize, parentPadding, videoMargin) {
this.container = container;
this.minimumSize = minimumSize != null ? minimumSize : 150;
this.parentPadding = parentPadding != null ? parentPadding : 15;
this.videoMargin = videoMargin != null ? videoMargin : 10;
this.elmBackgroundColor = elmBackgroundColor != null ? elmBackgroundColor : "green";
this.elementClassName = elementClassName != null ? elementClassName : "videoElement";
this.maxSidePx = 478;
this.parent = document.getElementById(this.containerId)
}

placeVideElements = ()=>{

let parentWidth = this.parent.offsetWidth;
let parentWidth = this.container.offsetWidth;

let n = this.parent.querySelectorAll(`video[class='${this.elementClassName}']`).length
let n = this.container.querySelectorAll(`video[class='${this.elementClassName}']`).length

let space = parentWidth - (2*n)*this.videoMargin - this.parentPadding*2
this.parent.style.padding = `${this.parentPadding}px`
this.container.style.padding = `${this.parentPadding}px`
if(space/n >= this.minimumSize) {
let actualWidth = (space / n)%this.maxSidePx;
let i = 0;
this.parent.querySelectorAll(`video[class='${this.elementClassName}']`).forEach(e=>{
this.container.querySelectorAll(`video[class='${this.elementClassName}']`).forEach(e=>{
this.setElmStyle(e, actualWidth)
e.style.order = `${i++}`;
})
this.parent.style.display = "flex"
this.parent.style.flexDirection = "row"
this.container.style.display = "flex"
this.container.style.flexDirection = "row"
} else {
this.parent.style.display = "flex"
this.parent.style.flexDirection = "column"
this.parent.style.justifyContent = "flex-start"
this.container.style.display = "flex"
this.container.style.flexDirection = "column"
this.container.style.justifyContent = "flex-start"

let rowN = Math.floor(space / this.minimumSize)
let actualElmWidth = Math.floor(space / rowN)
let childs = this.parent.querySelectorAll(`video[class='${this.elementClassName}']`)
let childs = this.container.querySelectorAll(`video[class='${this.elementClassName}']`)
let rowDiv = document.createElement("div")

let appendRowDiv = () => {rowDiv.id = `rowDiv${i++}`; this.parent.appendChild(rowDiv);rowDiv = document.createElement("div");}
let appendRowDiv = () => {rowDiv.id = `rowDiv${i++}`; this.container.appendChild(rowDiv);rowDiv = document.createElement("div");}
document.querySelectorAll("div[id*=rowDiv]").forEach(e=>e.parentNode.removeChild(e))

let i = 0;
Expand All @@ -62,8 +62,100 @@ class VideoElementsAppender {
if(videoIndex === n - 1)
appendRowDiv()
})
}
}

async bornVideoComponent(isLocal, userId, feed, isExistOponent) {

if(userId == null || feed == null || this.container==null) return null

let elmId = await this.getVideoId(isLocal, userId);

let oldElement = this.container.querySelector(`video#${elmId}`)
console.log(`bornVideoComponent oldElement:${oldElement} isLocal:${isLocal} userId:${userId} ts:${Date.now()}`)
if(oldElement != null) {
return this.replaceVideoElementWithNewFeed(oldElement, feed, isExistOponent)
}

const videoElement = document.createElement('video');
videoElement.id = elmId;
videoElement.style.display = 'none'
this.container.appendChild(videoElement);

videoElement.className = this.elementClassName;

videoElement.srcObject = feed.stream;
videoElement.muted = isLocal;

return videoElement.play().then(()=>{
// Append the new video element to 'videoBackground'
this.placeVideElements()
videoElement.style.display = '';
return videoElement
})
}

replaceVideoElementWithNewFeed(element, feed, isExistOponent) {
if(element == null) return null

let newVideoElement = document.createElement('video');
//newLocalElement.style.display = "none";
if(typeof element.isExistOponent != 'undefined') {
newVideoElement.muted = true;
newVideoElement.isExistOponent = isExistOponent;
}

newVideoElement.srcObject = feed.stream;
newVideoElement.className = element.className
return newVideoElement.play().then(()=> {
let lid = element.id
element.id = null;
newVideoElement.id = lid
newVideoElement.style.cssText = element.style && element.style.cssText
//newVideoElement.style.order = element.style.order
element.parentNode.prepend(newVideoElement)
//element.parentNode.replaceChildren(element, newVideoElement)
element.remove()
if(this.isVideoPlaying(element))
element.pause()
})
}

localVideoElementsCount() {
return this.container.querySelectorAll('[id^="local-"]').length
}

getFirstLocalElement() {
return this.container.querySelector('[id^="local-"]')
}

remoteVideoElementsCount() {
return this.container.querySelectorAll('[id^="remote-"]').length
}

removeVideoElement(isLocal, userId) {
if(isLocal==null || !!userId===false) return

return this.getVideoId(isLocal, userId).then(videoId=>{
let videoElement = document.getElementById(videoId);
if (videoElement) {
let removeFunc = ()=>{
videoElement.srcObject = null; // Frees the stream after pausing
let parent = videoElement.parentNode
if(parent) {
videoElement.parentNode.removeChild(videoElement);
//if(remoteVideoElementsCount() === 0 && localVideoElementsCount() === 1)
// return removeVideoElement(true, client.getUserId())
}
};
if(this.isVideoPlaying(videoElement)) {
videoElement.pause()
removeFunc()
}
else
removeFunc()
}
})
}

setElmStyle(e, actualElmWidth) {
Expand All @@ -72,22 +164,15 @@ class VideoElementsAppender {
e.style.backgroundColor = this.elmBackgroundColor
e.style.margin = `${this.videoMargin}px`
}
}

/*
let test = (n) => { fill(n); (new VideoElementsAppender('con')).placeVideElements(); }

let fill = (n) => {
let parent = document.getElementById("con")
parent.innerHTML= ""
isVideoPlaying(videoElement) {
return !!(videoElement.currentTime > 0 && !videoElement.paused && !videoElement.ended && videoElement.readyState > 2);
}

let j = 0;
let countVideos = n == null ? 1+Math.floor(Math.random() * 10) : n
while(j++ < countVideos) {
let vDiv = document.createElement("div")
vDiv.className = "videoElement"
parent.appendChild(vDiv)
getVideoId(isLocal, userId) {
let prefix = isLocal ? 'local' : "remote"
return utils.calculateHashSha256(userId)
.then((idSuffix)=>{return `${prefix}-${idSuffix}`})
}
}
*/

Loading

0 comments on commit e31d574

Please sign in to comment.