Skip to content

Commit

Permalink
Merge pull request #186 from ctrlaltdavid/dev/users-interface
Browse files Browse the repository at this point in the history
Add DomainServer.users interface for working with users in the domain
  • Loading branch information
ctrlaltdavid authored Aug 29, 2022
2 parents bb3acca + fdb8fed commit 22f87cb
Show file tree
Hide file tree
Showing 40 changed files with 1,708 additions and 57 deletions.
10 changes: 8 additions & 2 deletions example/interface.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,16 @@ <h3>Avatar List</h3>
<input id="avatarsCount" type="number" class="narrow" readonly="readonly" />
</div>

<div class="row">
<span>Show ignored:</span>
<input id="showExtraData" type="checkbox" class="checkbox" />
</div>

<table id="avatarList">
<thead>
<tr><th>Session ID</th><th>Session name</th><th colspan="3">Position</th><th>Yaw</th><th>Model</th>
<th>Scale</th></th><th>Joints</th><th>Head Pitch</th><th>Echo</th></tr>
<tr><th>Session ID</th><th>Session name</th><th>Loudness</th><th>Gain</th><th>Mute</th><th>Ignore</th>
<th colspan="3">Position</th><th>Yaw</th><th>Model</th><th>Scale</th></th><th>Joints</th>
<th>Head Pitch</th><th>Echo</th></tr>
</thead>
<tbody>
</tbody>
Expand Down
108 changes: 94 additions & 14 deletions example/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,18 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer,
const POS_DECIMAL_PLACES = 3;
const YAW_DECIMAL_PLACES = 1;
const SESSION_DISPLAY_NAME_INDEX = 1;
const X_INDEX = 2;
const Y_INDEX = 3;
const Z_INDEX = 4;
const YAW_INDEX = 5;
const SKELETON_MODEL_URL_INDEX = 6;
const SKELETON_SCALE_INDEX = 7;
const SKELETON_JOINTS_COUNT_INDEX = 8;
const HEAD_PITCH_INDEX = 9;
const AUDIO_LOUDNESS_INDEX = 2;
// const AUDIO_GAIN_INDEX = 3;
// const AVATAR_MUTE_INDEX = 4;
// const AVATAR_IGNORE_INDEX = 5;
const X_INDEX = 6;
const Y_INDEX = 7;
const Z_INDEX = 8;
const YAW_INDEX = 9;
const SKELETON_MODEL_URL_INDEX = 10;
const SKELETON_SCALE_INDEX = 11;
const SKELETON_JOINTS_COUNT_INDEX = 12;
const HEAD_PITCH_INDEX = 13;

const RAD_TO_DEG = 180.0 / Math.PI; // eslint-disable-line @typescript-eslint/no-magic-numbers

Expand Down Expand Up @@ -385,19 +389,46 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer,

// Avatar List

const showExtraDataCheckbox = document.getElementById("showExtraData");

const avatarsCount = document.getElementById("avatarsCount");
avatarsCount.value = avatarMixer.avatarList.count;

const avatarListBody = document.querySelector("#avatarList > tbody");

const avatars = new Map(); // <sessionID, { avatar, tr, headJoint }>

domainServer.users.wantIgnored = showExtraDataCheckbox.checked;
function onShowExtraDataClick() {
domainServer.users.wantIgnored = showExtraDataCheckbox.checked;
}
showExtraDataCheckbox.addEventListener("click", onShowExtraDataClick);

function onEchoClicked(checkbox, sessionID) {
if (doppelganger !== null) {
doppelganger.toggle(checkbox, sessionID);
}
}

function onGainChanged(input, sessionID) {
const MIN_GAIN = -60.0;
const MAX_GAIN = 20.0;
const BASE_10 = 10;
const gain = Math.max(MIN_GAIN, Math.min(parseFloat(input.value, BASE_10), MAX_GAIN));
input.value = gain;
domainServer.users.setAvatarGain(sessionID, gain);
}

function onMuteClicked(checkbox, sessionID) {
domainServer.users.setPersonalMute(sessionID, checkbox.checked);
}

function onIgnoreClicked(checkbox, sessionID, muteCheckbox) {
domainServer.users.setPersonalIgnore(sessionID, checkbox.checked);
muteCheckbox.checked = domainServer.users.getPersonalMute(sessionID);
muteCheckbox.disabled = checkbox.checked;
}

function onAvatarAdded(sessionID) {

avatarsCount.value = avatarMixer.avatarList.count;
Expand All @@ -411,6 +442,54 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer,
td = document.createElement("td");
td.innerHTML = avatar.sessionDisplayName;
tr.appendChild(td);
td = document.createElement("td");
td.className = "number";
td.innerHTML = avatar.audioLoudness.toFixed(0);
tr.appendChild(td);
td = document.createElement("td");
td.className = "input";
const gain = document.createElement("input");
gain.type = "number";
gain.className = "narrow align-right";
if (sessionID.value() === Uuid.AVATAR_SELF_ID) {
gain.disabled = true;
} else {
gain.value = domainServer.users.getAvatarGain(sessionID);
gain.onblur = (event) => {
onGainChanged(event.target, sessionID);
};
}
td.appendChild(gain);
tr.appendChild(td);
td = document.createElement("td");
const mute = document.createElement("input");
mute.type = "checkbox";
mute.className = "checkbox";
if (sessionID.value() === Uuid.AVATAR_SELF_ID) {
mute.disabled = true;
} else {
mute.checked = domainServer.users.getPersonalMute(sessionID);
mute.disabled = domainServer.users.getPersonalIgnore(sessionID);
mute.onclick = (event) => {
onMuteClicked(event.target, sessionID);
};
}
td.appendChild(mute);
tr.appendChild(td);
td = document.createElement("td");
const ignore = document.createElement("input");
ignore.type = "checkbox";
ignore.className = "checkbox";
if (sessionID.value() === Uuid.AVATAR_SELF_ID) {
ignore.disabled = true;
} else {
ignore.checked = domainServer.users.getPersonalIgnore(sessionID);
ignore.onclick = (event) => {
onIgnoreClicked(event.target, sessionID, mute);
};
}
td.appendChild(ignore);
tr.appendChild(td);
const position = avatar.position;
td = document.createElement("td");
td.className = "number";
Expand Down Expand Up @@ -441,17 +520,17 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer,
td.className = "number";
tr.appendChild(td);
td = document.createElement("td");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.className = "checkbox";
const echo = document.createElement("input");
echo.type = "checkbox";
echo.className = "checkbox";
if (sessionID.value() === Uuid.AVATAR_SELF_ID) {
cb.disabled = true;
echo.disabled = true;
} else {
cb.onclick = (event) => {
echo.onclick = (event) => {
onEchoClicked(event.target, sessionID);
};
}
td.appendChild(cb);
td.appendChild(echo);
tr.appendChild(td);
avatarListBody.appendChild(tr);

Expand Down Expand Up @@ -495,6 +574,7 @@ import { Vircadia, DomainServer, Camera, AudioMixer, AvatarMixer, EntityServer,
avatarMixerGameLoop = () => {
avatarMixer.update();
for (const value of avatars.values()) {
value.tr.childNodes[AUDIO_LOUDNESS_INDEX].innerHTML = value.avatar.audioLoudness.toFixed(0);
const position = value.avatar.position;
value.tr.childNodes[X_INDEX].innerHTML = position.x.toFixed(POS_DECIMAL_PLACES);
value.tr.childNodes[Y_INDEX].innerHTML = position.y.toFixed(POS_DECIMAL_PLACES);
Expand Down
12 changes: 12 additions & 0 deletions src/DomainServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

import UsersInterface from "./domain/interfaces/UsersInterface";
import AddressManager from "./domain/networking/AddressManager";
import Node from "./domain/networking/Node";
import NodeList from "./domain/networking/NodeList";
Expand Down Expand Up @@ -86,6 +87,9 @@ type OnStateChanged = (state: ConnectionState, info: string) => void;
* @property {DomainServer~onStateChanged|null} onStateChanged - Sets a single function to be called when the state of the
* connection to the domain server changes. Set to <code>null</code> to remove the callback.
* <em>Write-only.</em>
*
* @property {UsersInterface} users - Properties and methods for working with users in the domain.
* <em>Read-only.</em>
*/
class DomainServer {
// C++ Application.cpp
Expand Down Expand Up @@ -143,6 +147,8 @@ class DomainServer {
#_sessionUUID = new Uuid();
#_sessionUUIDChanged = new SignalEmitter();

#_usersInterface: UsersInterface;

#_DEBUG = false;


Expand Down Expand Up @@ -197,6 +203,7 @@ class DomainServer {

// WEBRTC TODO: Address further C++ code.

this.#_usersInterface = new UsersInterface(contextID);
}


Expand Down Expand Up @@ -250,6 +257,11 @@ class DomainServer {
}


get users(): UsersInterface {
return this.#_usersInterface;
}


/*@sdkdoc
* Initiates connection of the user client to a domain server.
* <p>The following types of address are supported:</p>
Expand Down
38 changes: 33 additions & 5 deletions src/domain/AvatarManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import Uuid from "./shared/Uuid";


/*@devdoc
* The <code>AvatarManager</code> class is concerned with operations on that the user client knows about in the domain (has
* been sent data on by the avatar mixer).
* The <code>AvatarManager</code> class is concerned with operations on avatars that the user client knows about in the domain
* (has been sent data on by the avatar mixer).
* <p>C++: <code>AvatarManager</code></p>
* @class AvatarManager
* @extends AvatarHashMap
Expand Down Expand Up @@ -55,6 +55,17 @@ class AvatarManager extends AvatarHashMap {
// Context
this.#_nodeList = ContextManager.get(contextID, NodeList) as NodeList;

// When we hear that the user has ignored an avatar by session UUID, immediately remove that avatar instead of waiting
// for the absence of packets from avatar mixer.
this.#_nodeList.ignoredNode.connect((nodeID: Uuid, ignored: boolean) => {
if (ignored) {
this.removeAvatar(nodeID, KillAvatarReason.AvatarIgnored);
}

// WEBRTC TODO: Address further C++ code. Avatar orb.

});

this.#_nodeList.nodeActivated.connect(this.nodeActivated);

this.#_myAvatar = new MyAvatar(contextID);
Expand Down Expand Up @@ -89,6 +100,12 @@ class AvatarManager extends AvatarHashMap {
updateMyAvatar(/* deltaTime: number */): void {
// C++ void AvatarManager::updateMyAvatar(float deltaTime)

// WebRTC TODO: Address further C++ code.

this.#_myAvatar.update(/* deltaTime */);

// WebRTC TODO: Address further C++ code.

const now = Date.now();
const deltaTime = now - this.#_lastSendAvatarDataTime;

Expand Down Expand Up @@ -184,7 +201,7 @@ class AvatarManager extends AvatarHashMap {
protected override newSharedAvatar(sessionUUID: Uuid): AvatarData {
// C++ AvatarData* newSharedAvatar(const QUuid& sessionUUID)

// WEBRTC TODO: Address further C++ code - use OtherAvatar instead of Avatar?
// WEBRTC TODO: Address further C++ code. Use OtherAvatar instead of Avatar.

const otherAvatar = new Avatar(this.#_contextID);
otherAvatar.setSessionUUID(sessionUUID);
Expand All @@ -207,11 +224,22 @@ class AvatarManager extends AvatarHashMap {
// C++ void handleRemovedAvatar(const Avatar* removedAvatar,
// KillAvatarReason removalReason = KillAvatarReason::NoReason);

// WEBRTC TODO: Address further C++ code. Cast removedAvatar to OtherAvatar.

super.handleRemovedAvatar(removedAvatar, removalReason);

// WEBRTC TODO: Address further C++ code - grabs, die, physics, orb.
// WEBRTC TODO: Address further C++ code. Grabs, die, physics, orb.

if (removalReason !== KillAvatarReason.AvatarDisconnected) {

// WEBRTC TOOD: Address further C++ code. Ignore radius, remove from scene.

// WEBRTC TODO: Address further C++ code - remove avatar entities from tree and scene.
} else {
this.#_nodeList.removeFromIgnoreMuteSets(removedAvatar.getSessionUUID());

// WEBRTC TODO: Address further C++ code. Disconnected API signal. Remove from scene.

}

}

Expand Down
45 changes: 44 additions & 1 deletion src/domain/audio-client/AudioClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ class AudioClient {
static readonly #RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;


static #computeLoudness(pcmData: Int16Array | null): number {
// C++ float computeLoudness(int16_t* samples, int numSamples)

if (pcmData === null || pcmData.length === 0) {
return 0;
}

let loudness = 0;
for (const value of pcmData) {
loudness += Math.abs(value);
}
return loudness / pcmData.length;
}


// Context
#_nodeList;
#_packetReceiver;
Expand All @@ -71,6 +86,8 @@ class AudioClient {
#_isStereoInput = false;
#_isMuted = false;
#_inputDevice: MediaStream | null = null; // Web SDK-specific member.
#_lastInputLoudness = 0.0;
#_lastRawInputLoudness = 0.0;

#_dummyAudioInputTimer: ReturnType<typeof setTimeout> | null = null;
#_outgoingAvatarAudioSequenceNumber = 0;
Expand Down Expand Up @@ -134,6 +151,8 @@ class AudioClient {
* @function AudioClient.switchInputDevice
* @param {MediaStream|null} inputDevice - The audio input stream from the user client to be sent to the audio mixer.
* <code>null</code> for no input device.
* @returns {Promise<boolean>} <code>true</code> if successfully switched to the given input device, <code>false</code> if
* unsuccessful.
*/
async switchInputDevice(inputDevice: MediaStream | null): Promise<boolean> {
// C++ bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo)
Expand All @@ -142,7 +161,11 @@ class AudioClient {
// The method has been renamed accordingly.
// The deviceInfo parameter has been repurposed to be an input MediaStream or null.
this.#_inputDevice = inputDevice;
return this.#switchInputToAudioDevice(inputDevice);
const success = await this.#switchInputToAudioDevice(inputDevice);
if (this.#_isMuted) {
void this.#switchInputToAudioDevice(null);
}
return success;
}


Expand Down Expand Up @@ -193,6 +216,15 @@ class AudioClient {
this.#_positionGetter = positionGetter;
}

/*@devdoc
* Gets the last audio input loudness.
* @returns {number} The last audio input loudness.
*/
getLastInputLoudness(): number {
// C++ float getLastInputLoudness() const
return this.#_lastInputLoudness;
}


#start(): void {
// C++ void AudioClient::start()
Expand Down Expand Up @@ -332,6 +364,11 @@ class AudioClient {
// The inputReceived signal is solely used for avatar recordings, the scripting API, and the audioscope.

// WEBRTC TODO: Address further C++ code.
const audioGateOpen = true;

// Loudness after mute/gate.
this.#_lastInputLoudness = this.#_isMuted || !audioGateOpen ? 0.0 : this.#_lastRawInputLoudness;


const packetType = audioBuffer === null || this.#_isMuted
? PacketType.SilentAudioFrame
Expand Down Expand Up @@ -512,6 +549,12 @@ class AudioClient {

// WEBRTC TODO: Address further C++ code.

if (!this.#_audioInput.hasPendingFrame()) { // Shouldn't hit this condition but in case we're bogged down.
this.#_lastRawInputLoudness = AudioClient.#computeLoudness(pcmData);
}

// WEBRTC TODO: Address further C++ code.

this.#handleAudioInput(pcmData);
}
};
Expand Down
Loading

0 comments on commit 22f87cb

Please sign in to comment.