Files
Foundry-VTT-Docker/resources/app/client/av/settings.js
2025-01-04 00:34:03 +01:00

255 lines
6.7 KiB
JavaScript

/**
* @typedef {object} AVSettingsData
* @property {boolean} [muted] Whether this user has muted themselves.
* @property {boolean} [hidden] Whether this user has hidden their video.
* @property {boolean} [speaking] Whether the user is broadcasting audio.
*/
class AVSettings {
constructor() {
this.initialize();
this._set = foundry.utils.debounce((key, value) => game.settings.set("core", key, value), 100);
this._change = foundry.utils.debounce(this._onSettingsChanged.bind(this), 100);
this.activity[game.userId] = {};
}
/* -------------------------------------------- */
/**
* WebRTC Mode, Disabled, Audio only, Video only, Audio & Video
* @enum {number}
*/
static AV_MODES = {
DISABLED: 0,
AUDIO: 1,
VIDEO: 2,
AUDIO_VIDEO: 3
};
/* -------------------------------------------- */
/**
* Voice modes: Always-broadcasting, voice-level triggered, push-to-talk.
* @enum {string}
*/
static VOICE_MODES = {
ALWAYS: "always",
ACTIVITY: "activity",
PTT: "ptt"
};
/* -------------------------------------------- */
/**
* Displayed nameplate options: Off entirely, animate between player and character name, player name only, character
* name only.
* @enum {number}
*/
static NAMEPLATE_MODES = {
OFF: 0,
BOTH: 1,
PLAYER_ONLY: 2,
CHAR_ONLY: 3
};
/* -------------------------------------------- */
/**
* AV dock positions.
* @enum {string}
*/
static DOCK_POSITIONS = {
TOP: "top",
RIGHT: "right",
BOTTOM: "bottom",
LEFT: "left"
};
/* -------------------------------------------- */
/**
* Default client AV settings.
* @type {object}
*/
static DEFAULT_CLIENT_SETTINGS = {
videoSrc: "default",
audioSrc: "default",
audioSink: "default",
dockPosition: AVSettings.DOCK_POSITIONS.LEFT,
hidePlayerList: false,
hideDock: false,
muteAll: false,
disableVideo: false,
borderColors: false,
dockWidth: 240,
nameplates: AVSettings.NAMEPLATE_MODES.BOTH,
voice: {
mode: AVSettings.VOICE_MODES.PTT,
pttName: "`",
pttDelay: 100,
activityThreshold: -45
},
users: {}
};
/* -------------------------------------------- */
/**
* Default world-level AV settings.
* @type {object}
*/
static DEFAULT_WORLD_SETTINGS = {
mode: AVSettings.AV_MODES.DISABLED,
turn: {
type: "server",
url: "",
username: "",
password: ""
}
};
/* -------------------------------------------- */
/**
* Default client settings for each connected user.
* @type {object}
*/
static DEFAULT_USER_SETTINGS = {
popout: false,
x: 100,
y: 100,
z: 0,
width: 320,
volume: 1.0,
muted: false,
hidden: false,
blocked: false
};
/* -------------------------------------------- */
/**
* Stores the transient AV activity data received from other users.
* @type {Record<string, AVSettingsData>}
*/
activity = {};
/* -------------------------------------------- */
initialize() {
this.client = game.settings.get("core", "rtcClientSettings");
this.world = game.settings.get("core", "rtcWorldSettings");
this._original = foundry.utils.deepClone({client: this.client, world: this.world});
const {muted, hidden} = this._getUserSettings(game.user);
game.user.broadcastActivity({av: {muted, hidden}});
}
/* -------------------------------------------- */
changed() {
return this._change();
}
/* -------------------------------------------- */
get(scope, setting) {
return foundry.utils.getProperty(this[scope], setting);
}
/* -------------------------------------------- */
getUser(userId) {
const user = game.users.get(userId);
if ( !user ) return null;
return this._getUserSettings(user);
}
/* -------------------------------------------- */
set(scope, setting, value) {
foundry.utils.setProperty(this[scope], setting, value);
this._set(`rtc${scope.titleCase()}Settings`, this[scope]);
}
/* -------------------------------------------- */
/**
* Return a mapping of AV settings for each game User.
* @type {object}
*/
get users() {
const users = {};
for ( let u of game.users ) {
users[u.id] = this._getUserSettings(u);
}
return users;
}
/* -------------------------------------------- */
/**
* A helper to determine if the dock is configured in a vertical position.
*/
get verticalDock() {
const positions = this.constructor.DOCK_POSITIONS;
return [positions.LEFT, positions.RIGHT].includes(this.client.dockPosition ?? positions.LEFT);
}
/* -------------------------------------------- */
/**
* Prepare a standardized object of user settings data for a single User
* @private
*/
_getUserSettings(user) {
const clientSettings = this.client.users[user.id] || {};
const activity = this.activity[user.id] || {};
const settings = foundry.utils.mergeObject(AVSettings.DEFAULT_USER_SETTINGS, clientSettings, {inplace: false});
settings.canBroadcastAudio = user.can("BROADCAST_AUDIO");
settings.canBroadcastVideo = user.can("BROADCAST_VIDEO");
if ( user.isSelf ) {
settings.muted ||= !game.webrtc?.client.isAudioEnabled();
settings.hidden ||= !game.webrtc?.client.isVideoEnabled();
} else {
// Either we have muted or hidden them, or they have muted or hidden themselves.
settings.muted ||= !!activity.muted;
settings.hidden ||= !!activity.hidden;
}
settings.speaking = activity.speaking;
return settings;
}
/* -------------------------------------------- */
/**
* Handle setting changes to either rctClientSettings or rtcWorldSettings.
* @private
*/
_onSettingsChanged() {
const original = this._original;
this.initialize();
const changed = foundry.utils.diffObject(original, this._original);
game.webrtc.onSettingsChanged(changed);
Hooks.callAll("rtcSettingsChanged", this, changed);
}
/* -------------------------------------------- */
/**
* Handle another connected user changing their AV settings.
* @param {string} userId
* @param {AVSettingsData} settings
*/
handleUserActivity(userId, settings) {
const current = this.activity[userId] || {};
this.activity[userId] = foundry.utils.mergeObject(current, settings, {inplace: false});
if ( !ui.webrtc ) return;
const hiddenChanged = ("hidden" in settings) && (current.hidden !== settings.hidden);
const mutedChanged = ("muted" in settings) && (current.muted !== settings.muted);
if ( (hiddenChanged || mutedChanged) && ui.webrtc.getUserVideoElement(userId) ) ui.webrtc._refreshView(userId);
if ( "speaking" in settings ) ui.webrtc.setUserIsSpeaking(userId, settings.speaking);
}
}