Initial
This commit is contained in:
254
resources/app/client/av/settings.js
Normal file
254
resources/app/client/av/settings.js
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user