Skip to content

Commit

Permalink
Implement volume overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
Zerthox committed Mar 20, 2024
1 parent 0350348 commit 30cb78a
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 26 deletions.
2 changes: 1 addition & 1 deletion packages/dium/src/modules/flux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface ActionHandlers {
export interface Dispatcher {
_currentDispatchActionType: any;
_actionHandlers: ActionHandlers;
_subscriptions: Record<string, any>;
_subscriptions: Record<string, Set<ActionHandler>>;
_processingWaitQueue: boolean;
_waitQueue: any[];
_interceptor: (arg: any) => any;
Expand Down
3 changes: 3 additions & 0 deletions src/BetterVolume/experiment.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// legacy code for disabling audio experiment

import {Logger, React, Utils, getMeta} from "dium";
import {ExperimentStore, ExperimentTreatment} from "@dium/modules";
import {Text} from "@dium/components";
Expand Down Expand Up @@ -27,6 +29,7 @@ const onLoadExperiments = (): void => {

if (hasExperiment()) {
const {disableExperiment} = Settings.current;
Logger.log("Experiment setting:", disableExperiment);
// check if we have to disable
if (disableExperiment) {
// simply setting this should be fine, seems to be only changed on connect etc.
Expand Down
34 changes: 11 additions & 23 deletions src/BetterVolume/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import {createPlugin, Finder, Filters, Patcher, React} from "dium";
import {Snowflake, MediaEngineStore, MediaEngineActions, MediaEngineContext, AudioConvert} from "@dium/modules";
import {MenuItem, FormSwitch} from "@dium/components";
import {createPlugin, Finder, Filters, Patcher, React, Logger} from "dium";
import {Snowflake, MediaEngineStore, MediaEngineContext, AudioConvert, MediaEngineActions, Dispatcher} from "@dium/modules";
import {Settings} from "./settings";
import {NumberInput} from "./input";
import {handleExperiment, hasExperiment, resetExperiment} from "./experiment";
import {css} from "./styles.module.scss";
import {MenuItem} from "@dium/components";
import {NumberInput} from "./input";
import {handleVolumeSync, resetVolumeSync} from "./sync";

type UseUserVolumeItem = (userId: Snowflake, context: MediaEngineContext) => JSX.Element;

const useUserVolumeItemFilter = Filters.bySource("user-volume");

export default createPlugin({
start() {
// handle audio experiment
handleExperiment();
// handle volume override sync
handleVolumeSync();

// add number input to user volume item
const useUserVolumeItemFilter = Filters.bySource("user-volume");
Finder.waitFor(useUserVolumeItemFilter, {resolve: false}).then((result: Record<string, UseUserVolumeItem>) => {
const useUserVolumeItem = Finder.resolveKey(result, useUserVolumeItemFilter);
Patcher.after(...useUserVolumeItem, ({args: [userId, context], result}) => {
Expand Down Expand Up @@ -49,21 +50,8 @@ export default createPlugin({
});
},
stop() {
resetExperiment();
resetVolumeSync();
},
styles: css,
Settings,
SettingsPanel: () => {
const [{disableExperiment}, setSettings] = Settings.useState();

return (
<FormSwitch
note="Force disable experiment interfering with volumes greater than 200%."
hideBorder
value={disableExperiment}
disabled={hasExperiment()}
onChange={(checked) => setSettings({disableExperiment: checked})}
>Disable Audio experiment</FormSwitch>
);
}
Settings
});
2 changes: 1 addition & 1 deletion src/BetterVolume/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "BetterVolume",
"author": "Zerthox",
"version": "2.5.0",
"version": "3.0.0",
"description": "Set user volume values manually instead of using a slider. Allows setting volumes higher than 200%.",
"dependencies": {
"dium": "*"
Expand Down
33 changes: 32 additions & 1 deletion src/BetterVolume/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
import {createSettings} from "dium";
import {Snowflake} from "@dium/modules";

export const Settings = createSettings({
export interface Settings {
volumeOverrides: Record<Snowflake, number>;

/** @deprecated legacy */
disableExperiment: null | boolean;
}

export const Settings = createSettings<Settings>({
volumeOverrides: {},
disableExperiment: null
});

export const hasOverride = (userId: Snowflake): boolean => userId in Settings.current.volumeOverrides;

export const updateVolumeOverride = (userId: Snowflake, volume: number): boolean => {
const isNew = !hasOverride(userId);
Settings.update(({volumeOverrides}) => {
volumeOverrides[userId] = volume;
return {volumeOverrides};
});
return isNew;
};

export const tryResetVolumeOverride = (userId: Snowflake): boolean => {
if (hasOverride(userId)) {
Settings.update(({volumeOverrides}) => {
delete volumeOverrides[userId];
return {volumeOverrides};
});
return true;
}
return false;
};
97 changes: 97 additions & 0 deletions src/BetterVolume/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {Finder, Filters, Flux, Logger} from "dium";
import {Snowflake, Dispatcher, MediaEngineContext, AudioConvert, MediaEngineContextType} from "@dium/modules";
import {Settings, updateVolumeOverride as updateVolumeOverride, tryResetVolumeOverride} from "./settings";

const enum ActionType {
AUDIO_SET_LOCAL_VOLUME = "AUDIO_SET_LOCAL_VOLUME",
USER_SETTINGS_PROTO_UPDATE = "USER_SETTINGS_PROTO_UPDATE"
}

const MAX_VOLUME_PERC = 200;

const MAX_VOLUME_AMP = AudioConvert.perceptualToAmplitude(MAX_VOLUME_PERC);

export const dispatchVolumeOverrides = () => {
for (const [userId, volume] of Object.entries(Settings.current.volumeOverrides)) {
Dispatcher.dispatch<SetVolumeAction>({
type: ActionType.AUDIO_SET_LOCAL_VOLUME,
context: MediaEngineContextType.DEFAULT,
userId,
volume
});
}
};

interface SetVolumeAction extends Flux.Action {
type: ActionType.AUDIO_SET_LOCAL_VOLUME;
userId: Snowflake;
volume: number;
context: MediaEngineContext;
}

const settingsUpdateHandler = (action: Flux.Action) => dispatchVolumeOverrides();

interface AudioSettingsManager {
actions: Record<string, Flux.ActionHandler> & {
AUDIO_SET_LOCAL_VOLUME: Flux.ActionHandler<SetVolumeAction>
};
initializedCount: number;
stores: Map<any, any>;
}

const wrappedSettingsManagerHandler: Flux.ActionHandler<SetVolumeAction> = (action) => {
const isOverCap = action.volume > MAX_VOLUME_AMP;
if (isOverCap) {
const isNew = updateVolumeOverride(action.userId, action.volume);
if (isNew) {
Logger.log(`New volume override ${AudioConvert.amplitudeToPerceptual(action.volume)} for user ${action.userId}`);
originalHandler({...action, volume: MAX_VOLUME_AMP});
}
} else {
const wasRemoved = tryResetVolumeOverride(action.userId);
if (wasRemoved) {
Logger.log(`Removed volume override for user ${action.userId}`);
}
originalHandler(action);
}
};

const trySwapHandler = <A extends Flux.Action>(action: Flux.Action["type"], prev: Flux.ActionHandler<A>, next: Flux.ActionHandler<A>): boolean => {
const isPresent = Dispatcher._subscriptions[action].has(prev);
if (isPresent) {
Dispatcher.unsubscribe(action, prev);
Dispatcher.subscribe(action, next);
}
return isPresent;
};

let originalHandler = null;

const hasSetVolume = Filters.byKeys(ActionType.AUDIO_SET_LOCAL_VOLUME);

export const handleVolumeSync = () => {
Dispatcher.subscribe(ActionType.USER_SETTINGS_PROTO_UPDATE, settingsUpdateHandler);
Logger.log(`Subscribed to ${ActionType.USER_SETTINGS_PROTO_UPDATE} events`);

dispatchVolumeOverrides();

Finder.waitFor((exported) => exported.actions && hasSetVolume(exported.actions)).then((AudioSettingsManager: AudioSettingsManager) => {
originalHandler = AudioSettingsManager.actions[ActionType.AUDIO_SET_LOCAL_VOLUME];
const swapped = trySwapHandler(ActionType.AUDIO_SET_LOCAL_VOLUME, originalHandler, wrappedSettingsManagerHandler);
if (swapped) {
Logger.log(`Replaced ${ActionType.AUDIO_SET_LOCAL_VOLUME} handler`);
} else {
Logger.warn(`${ActionType.AUDIO_SET_LOCAL_VOLUME} handler not present`);
}
});
};

export const resetVolumeSync = () => {
Dispatcher.unsubscribe(ActionType.USER_SETTINGS_PROTO_UPDATE, settingsUpdateHandler);
Logger.log(`Unsubscribed from ${ActionType.USER_SETTINGS_PROTO_UPDATE} events`);

const swapped = trySwapHandler(ActionType.AUDIO_SET_LOCAL_VOLUME, wrappedSettingsManagerHandler, originalHandler);
if (swapped) {
Logger.log(`Reset ${ActionType.AUDIO_SET_LOCAL_VOLUME} handler`);
}
};

0 comments on commit 30cb78a

Please sign in to comment.