Merge pull request #106 from UniversityRadioYork/markspolakovs-pro-mode-trim
Add Pro Mode™️ and trim control
This commit is contained in:
commit
b67479b5f9
8 changed files with 113 additions and 2 deletions
|
@ -21,6 +21,8 @@ const PlayerEmitter: StrictEmitter<
|
|||
> = EventEmitter as any;
|
||||
|
||||
class Player extends ((PlayerEmitter as unknown) as { new (): EventEmitter }) {
|
||||
private volume = 1;
|
||||
private trim = 1;
|
||||
private constructor(
|
||||
private readonly engine: AudioEngine,
|
||||
private wavesurfer: WaveSurfer,
|
||||
|
@ -64,7 +66,13 @@ class Player extends ((PlayerEmitter as unknown) as { new (): EventEmitter }) {
|
|||
}
|
||||
|
||||
setVolume(val: number) {
|
||||
this.wavesurfer.setVolume(val);
|
||||
this.volume = val;
|
||||
this.wavesurfer.setVolume(this.volume * this.trim);
|
||||
}
|
||||
|
||||
setTrim(val: number) {
|
||||
this.trim = val;
|
||||
this.wavesurfer.setVolume(this.volume * this.trim);
|
||||
}
|
||||
|
||||
public static create(engine: AudioEngine, player: number, url: string) {
|
||||
|
|
|
@ -35,6 +35,7 @@ interface PlayerState {
|
|||
state: PlayerStateEnum;
|
||||
volume: number;
|
||||
gain: number;
|
||||
trim: number;
|
||||
timeCurrent: number;
|
||||
timeRemaining: number;
|
||||
timeLength: number;
|
||||
|
@ -63,6 +64,7 @@ const BasePlayerState: PlayerState = {
|
|||
state: "stopped",
|
||||
volume: 1,
|
||||
gain: 1,
|
||||
trim: 1,
|
||||
timeCurrent: 0,
|
||||
timeRemaining: 0,
|
||||
timeLength: 0,
|
||||
|
@ -139,6 +141,15 @@ const mixerState = createSlice({
|
|||
) {
|
||||
state.players[action.payload.player].gain = action.payload.gain;
|
||||
},
|
||||
setPlayerTrim(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
player: number;
|
||||
trim: number;
|
||||
}>
|
||||
) {
|
||||
state.players[action.payload.player].trim = action.payload.trim;
|
||||
},
|
||||
setMicError(state, action: PayloadAction<null | MicErrorEnum>) {
|
||||
state.mic.openError = action.payload;
|
||||
},
|
||||
|
@ -549,6 +560,11 @@ export const setVolume = (
|
|||
};
|
||||
};
|
||||
|
||||
export const setChannelTrim = (player: number, val: number): AppThunk => async dispatch => {
|
||||
dispatch(mixerState.actions.setPlayerTrim({ player, trim: val }));
|
||||
audioEngine.players[player]?.setTrim(val);
|
||||
};
|
||||
|
||||
export const openMicrophone = (micID: string): AppThunk => async (
|
||||
dispatch,
|
||||
getState
|
||||
|
|
35
src/optionsMenu/ProModeTab.tsx
Normal file
35
src/optionsMenu/ProModeTab.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { streamer } from "../broadcast/state";
|
||||
import { WebRTCStreamer } from "../broadcast/rtc_streamer";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "../rootReducer";
|
||||
import {changeSetting} from "./settingsState";
|
||||
|
||||
export function ProModeTab() {
|
||||
const settings = useSelector((state: RootState) => state.settings);
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<>
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={settings.proMode}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
changeSetting({
|
||||
key: "proMode",
|
||||
val: e.target.checked,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label className="form-check-label">
|
||||
Enable WebStudio Pro Mode™
|
||||
<br />
|
||||
This mode enables some advanced features. Don't enable it unless you know what you're doing!
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -10,6 +10,7 @@ import { AboutTab } from "./AboutTab";
|
|||
import { StatsTab } from "./StatsTab";
|
||||
import { AdvancedTab } from "./AdvancedTab";
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
import {ProModeTab} from "./ProModeTab";
|
||||
|
||||
export function OptionsMenu() {
|
||||
const state = useSelector((state: RootState) => state.optionsMenu);
|
||||
|
@ -36,6 +37,14 @@ export function OptionsMenu() {
|
|||
Stream Statistics
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={state.currentTab === "pro" ? "active" : ""}
|
||||
onClick={() => dispatch(OptionsState.changeTab("pro"))}
|
||||
>
|
||||
Pro Mode™
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={state.currentTab === "advanced" ? "active" : ""}
|
||||
|
@ -68,6 +77,9 @@ export function OptionsMenu() {
|
|||
<TabPane tabId="stats">
|
||||
<StatsTab />
|
||||
</TabPane>
|
||||
<TabPane tabId="pro">
|
||||
<ProModeTab />
|
||||
</TabPane>
|
||||
<TabPane tabId="advanced">
|
||||
<AdvancedTab />
|
||||
</TabPane>
|
||||
|
|
|
@ -5,6 +5,7 @@ interface Settings {
|
|||
enableRecording: boolean;
|
||||
tracklist: "always" | "while_live" | "never";
|
||||
doTheNews: "always" | "while_live" | "never";
|
||||
proMode: boolean;
|
||||
}
|
||||
|
||||
const settingsState = createSlice({
|
||||
|
@ -14,6 +15,7 @@ const settingsState = createSlice({
|
|||
enableRecording: false,
|
||||
tracklist: "while_live",
|
||||
doTheNews: "while_live",
|
||||
proMode: false
|
||||
} as Settings,
|
||||
reducers: {
|
||||
changeSetting<K extends keyof Settings>(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
export type OptionsTabIDsEnum = "mic" | "about" | "advanced" | "stats";
|
||||
export type OptionsTabIDsEnum = "mic" | "about" | "pro" | "advanced" | "stats";
|
||||
|
||||
const optionsMenuState = createSlice({
|
||||
name: "optionsMenu",
|
||||
|
|
|
@ -7,10 +7,12 @@ import {
|
|||
FaPlay,
|
||||
FaPause,
|
||||
FaStop,
|
||||
FaTachometerAlt
|
||||
} from "react-icons/fa";
|
||||
import { RootState } from "../rootReducer";
|
||||
import * as MixerState from "../mixer/state";
|
||||
import { secToHHMM, timestampToHHMM } from "../lib/utils";
|
||||
import ProModeButtons from "./ProModeButtons";
|
||||
|
||||
export const USE_REAL_GAIN_VALUE = false;
|
||||
|
||||
|
@ -18,6 +20,7 @@ export function Player({ id }: { id: number }) {
|
|||
const playerState = useSelector(
|
||||
(state: RootState) => state.mixer.players[id]
|
||||
);
|
||||
const proMode = useSelector((state: RootState) => state.settings.proMode);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [now, setNow] = useState<Date>(new Date());
|
||||
|
@ -75,6 +78,9 @@ export function Player({ id }: { id: number }) {
|
|||
Repeat {playerState.repeat}
|
||||
</button>
|
||||
</div>
|
||||
{proMode && (
|
||||
<ProModeButtons channel={id} />
|
||||
)}
|
||||
<div className="card-body p-0">
|
||||
<span className="card-title">
|
||||
<strong>
|
||||
|
|
32
src/showplanner/ProModeButtons.tsx
Normal file
32
src/showplanner/ProModeButtons.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import React, {useState} from "react";
|
||||
import {FaTachometerAlt} from "react-icons/fa";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "../rootReducer";
|
||||
import {setChannelTrim} from "../mixer/state";
|
||||
|
||||
type ButtonIds = "trim";
|
||||
|
||||
export default function ProModeButtons({channel}: { channel: number }) {
|
||||
const [activeButton, setActiveButton] = useState<ButtonIds | null>(null);
|
||||
const trimVal = useSelector((state: RootState) => state.mixer.players[channel]?.trim);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row m-0 p-1 card-header channelButtons proMode">
|
||||
{(activeButton === null || activeButton === "trim") && (
|
||||
<button className="btn btn-warning" title="Trim">
|
||||
<FaTachometerAlt onClick={() => setActiveButton(activeButton === "trim" ? null : "trim")}/>
|
||||
</button>
|
||||
)}
|
||||
{activeButton === "trim" && (
|
||||
<>
|
||||
<input type="range" min={0.01} max={5} step={0.2} value={trimVal.toFixed(1)}
|
||||
onChange={e => dispatch(setChannelTrim(channel, parseFloat(e.target.value)))}/>
|
||||
<b>{trimVal}</b>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue