Merge pull request #106 from UniversityRadioYork/markspolakovs-pro-mode-trim

Add Pro Mode™️ and trim control
This commit is contained in:
Marks Polakovs 2020-05-09 15:57:32 +02:00 committed by GitHub
commit b67479b5f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 2 deletions

View file

@ -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) {

View file

@ -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

View 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&trade;
<br />
This mode enables some advanced features. Don't enable it unless you know what you're doing!
</label>
</div>
</>
);
}

View file

@ -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&trade;
</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>

View file

@ -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>(

View file

@ -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",

View file

@ -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 }) {
&nbsp; Repeat {playerState.repeat}
</button>
</div>
{proMode && (
<ProModeButtons channel={id} />
)}
<div className="card-body p-0">
<span className="card-title">
<strong>

View 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>
</>
)
}