Mixing and UI tweaks, mebbe.

This commit is contained in:
Marks Polakovs 2020-03-19 17:49:55 +00:00
parent 254ce13b88
commit a6b24072b8
9 changed files with 795 additions and 189 deletions

View file

@ -13,6 +13,7 @@
"@types/react-dom": "16.9.4",
"@types/react-redux": "^7.1.5",
"@types/webpack-env": "^1.14.1",
"between.js": "^0.1.2-fix.2",
"lodash": "^4.17.15",
"qs": "^6.9.1",
"react": "^0.0.0-experimental-38dd17ab9",

View file

@ -38,13 +38,15 @@
}
.sp-col {
display: block;
height: 80%;
display: flex;
height: 60%;
overflow-y: scroll;
border: 1px solid black;
margin-left: .2vw;
margin-right: .2vw;
padding: .2vw;
flex-direction: column;
flex-wrap: nowrap;
}
.sp-main-col{
@ -52,7 +54,7 @@
}
.sp-col-inner {
height: 99%;
flex: 1;
}
.sp-track {
@ -70,7 +72,7 @@
}
.sp-track-active {
background-color: #10c998;
background-color: #10c998 !important;
}
html, body, #root {
@ -120,6 +122,31 @@ button{
}
.player div {
height: 16%;
overflow-x: hidden;
}
}
.sp-player-button {
height: 2em;
}
.sp-mixer-buttons {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
position: relative;
}
.sp-mixer-buttons-backdrop {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: #78acf1;
}
.sp-mixer-buttons button {
flex: 1;
background: transparent;
border: 1px solid black;
z-index: 100;
}

424
src/lib/between.d.ts vendored Normal file
View file

@ -0,0 +1,424 @@
// Stolen from https://github.com/sasha240100/between.js/blob/master/types/Between.d.ts
// Declare your modules properly, people!
declare module "between.js" {
/**
* Loop mode
*/
export type LoopMode = "repeat" | "bounce";
export type EventEmmit = "start" | "update" | "complete";
/**
* A collection of easing methods defining ease-in ease-out curves.
*/
export interface Easing extends Function {
/**
* Linear easing.
*
* @class Easing.Linear
*/
Linear: {
/**
* Ease-in.
*
* @method Easing.Linear#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
None: Function;
};
/**
* Quadratic easing.
*
* @class Easing.Quadratic
*/
Quadratic: {
/**
* Ease-in.
*
* @method Easing.Quadratic#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
In: Function;
/**
* Ease-out.
*
* @method Easing.Quadratic#Out
* @param {number} k - The value to be tweened.
* @returns {number} k* (2-k).
*/
Out: Function;
/**
* Ease-in/out.
*
* @method Easing.Quadratic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Cubic easing.
*
* @class Easing.Cubic
*/
Cubic: {
/**
* Cubic ease-in.
*
* @method Easing.Cubic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Cubic ease-out.
*
* @method Easing.Cubic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Cubic ease-in/out.
*
* @method Easing.Cubic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Quartic easing.
*
* @class Easing.Quartic
*/
Quartic: {
/**
* Quartic ease-in.
*
* @method Easing.Quartic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quartic ease-out.
*
* @method Easing.Quartic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Quartic ease-in/out.
*
* @method Easing.Quartic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Quintic easing.
*
* @class Easing.Quintic
*/
Quintic: {
/**
* Quintic ease-in.
*
* @method Easing.Quintic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Quintic ease-out.
*
* @method Easing.Quintic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Quintic ease-in/out.
*
* @method Easing.Quintic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Number;
};
/**
* Sinusoidal easing.
*
* @class Easing.Sinusoidal
*/
Sinusoidal: {
/**
* Sinusoidal ease-in.
*
* @method Easing.Sinusoidal#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Sinusoidal ease-out.
*
* @method Easing.Sinusoidal#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Sinusoidal ease-in/out.
*
* @method Easing.Sinusoidal#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Exponential easing.
*
* @class Easing.Exponential
*/
Exponential: {
/**
* Exponential ease-in.
*
* @method Easing.Exponential#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Exponential ease-out.
*
* @method Easing.Exponential#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Exponential ease-in/out.
*
* @method Easing.Exponential#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Circular easing.
*
* @class Easing.Circular
*/
Circular: {
/**
* Circular ease-in.
*
* @method Easing.Circular#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Circular ease-out.
*
* @method Easing.Circular#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Circular ease-in/out.
*
* @method Easing.Circular#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Elastic easing.
*
* @class Easing.Elastic
*/
Elastic: {
/**
* Elastic ease-in.
*
* @method Easing.Elastic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Elastic ease-out.
*
* @method Easing.Elastic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Elastic ease-in/out.
*
* @method Easing.Elastic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Back easing.
*
* @class Easing.Back
*/
Back: {
/**
* Back ease-in.
*
* @method Easing.Back#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Back ease-out.
*
* @method Easing.Back#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Back ease-in/out.
*
* @method Easing.Back#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
/**
* Bounce easing.
*
* @class Easing.Bounce
*/
Bounce: {
/**
* Bounce ease-in.
*
* @method Easing.Bounce#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
In: Function;
/**
* Bounce ease-out.
*
* @method Easing.Bounce#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
Out: Function;
/**
* Bounce ease-in/out.
*
* @method Easing.Bounce#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
InOut: Function;
};
}
export class Between {
/**
* Creates a new Between instance
* @param from start
* @param to end
*/
constructor(
from: Number | Object | [Number | Object],
to: Number | Object | [Number | Object]
);
/**
* Sets duration
* @param duration duration in ms
*/
time(duration: Number): this;
/**
* Set loop mode and repeat times
* @param mode the loop mode
* @param repeatTime if not defined, then treats as endless
*/
loop(mode: LoopMode, repeatTime?: Number): this;
/**
* Set easing function
* @param easing Easing type
*/
easing(easing: Easing | Function): this;
/**
* Adds event listener
* @param eventName
* @param callback
*/
on(eventName: EventEmmit, callback: Function): this;
/**
* Pauses
*/
pause(): this;
/**
* Starts (if was paused)
*/
play(): this;
/**
* Returns `true` if paused
*/
isPaused: boolean;
}
}

209
src/mixer/state.ts Normal file
View file

@ -0,0 +1,209 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import * as Between from "between.js";
import { PlanItem } from "../showplanner/state";
import { Track, MYRADIO_NON_API_BASE } from "../api";
import { AppThunk } from "../store";
console.log(Between);
const audioContext = new AudioContext();
const playerSources: MediaElementAudioSourceNode[] = [];
const playerGains: GainNode[] = [];
const playerGainTweens: Between.Between[] = [];
// TODO
// const destination = audioContext.createWebcastSource(4096, 2);
const destination = audioContext.createDynamicsCompressor();
destination.connect(audioContext.destination);
type PlayerStateEnum = "playing" | "paused" | "stopped";
type VolumePresetEnum = "off" | "bed" | "full";
interface PlayerState {
loadedItem: PlanItem | Track | null;
loading: boolean;
state: PlayerStateEnum;
volume: number;
}
interface MixerState {
players: PlayerState[];
}
const mixerState = createSlice({
name: "Player",
initialState: {
players: [
{
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
},
{
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
},
{
loadedItem: null,
loading: false,
state: "stopped",
volume: 1
}
]
} as MixerState,
reducers: {
loadItem(
state,
action: PayloadAction<{ player: number; item: PlanItem | Track }>
) {
state.players[action.payload.player].loadedItem =
action.payload.item;
state.players[action.payload.player].loading = true;
},
itemLoadComplete(state, action: PayloadAction<{ player: number }>) {
state.players[action.payload.player].loading = false;
},
setPlayerState(
state,
action: PayloadAction<{ player: number; state: PlayerStateEnum }>
) {
state.players[action.payload.player].state = action.payload.state;
},
setPlayerVolume(
state,
action: PayloadAction<{ player: number; volume: number }>
) {
state.players[action.payload.player].volume = action.payload.volume;
}
}
});
export default mixerState.reducer;
export const load = (player: number, item: PlanItem | Track): AppThunk => (
dispatch,
getState
) => {
if (typeof playerSources[player] !== "undefined") {
if (!playerSources[player].mediaElement.paused) {
// already playing, don't kill playback
return;
}
}
dispatch(mixerState.actions.loadItem({ player, item }));
const el = new Audio();
el.crossOrigin = "use-credentials";
if ("album" in item) {
// track
el.src =
MYRADIO_NON_API_BASE +
"/NIPSWeb/secure_play?recordid=" +
item.album.recordid +
"&trackid=" +
item.trackid;
} else if ("type" in item && item.type == "aux") {
el.src =
MYRADIO_NON_API_BASE +
"/NIPSWeb/managed_play?managedid=" +
item.managedid;
} else {
throw new Error(
"Unsure how to handle this!\r\n\r\n" + JSON.stringify(item)
);
}
el.oncanplay = () => {
dispatch(mixerState.actions.itemLoadComplete({ player }));
};
el.load();
const sauce = audioContext.createMediaElementSource(el);
const gain = audioContext.createGain();
gain.gain.value = getState().mixer.players[player].volume;
sauce.connect(gain);
gain.connect(destination);
console.log("Connected to", destination);
playerSources[player] = sauce;
playerGains[player] = gain;
};
export const play = (player: number): AppThunk => dispatch => {
try {
console.log("PLAY YOU FASCIST")
playerSources[player].mediaElement.play();
dispatch(
mixerState.actions.setPlayerState({ player, state: "playing" })
);
playerSources[player].mediaElement.addEventListener(
"ended",
function() {
dispatch(
mixerState.actions.setPlayerState({
player,
state: "stopped"
})
);
}
);
} catch {
console.log("nothing selected/loaded");
}
};
export const pause = (player: number): AppThunk => dispatch => {
try {
if (playerSources[player].mediaElement.paused) {
playerSources[player].mediaElement.play();
dispatch(
mixerState.actions.setPlayerState({ player, state: "playing" })
);
} else {
playerSources[player].mediaElement.pause();
dispatch(
mixerState.actions.setPlayerState({ player, state: "paused" })
);
}
} catch {
console.log("nothing selected/loaded");
}
};
export const stop = (player: number): AppThunk => dispatch => {
try {
playerSources[player].mediaElement.pause();
playerSources[player].mediaElement.currentTime = 0;
dispatch(
mixerState.actions.setPlayerState({ player, state: "stopped" })
);
} catch {
console.log("nothing selected/loaded");
}
};
export const setVolume = (
player: number,
level: VolumePresetEnum
): AppThunk => (dispatch, getState) => {
let volume: number;
switch (level) {
case "off":
volume = 0;
break;
case "bed":
volume = 0.25;
break;
case "full":
volume = 1;
break;
}
const currentLevel = getState().mixer.players[player].volume;
playerGainTweens[player] = new (Between as any)(currentLevel, volume)
.on("update", (value: number) => {
console.log(value);
dispatch(mixerState.actions.setPlayerVolume({ player, volume }));
if (playerGains[player]) {
playerGains[player].gain.value = value;
}
})
.time(1000);
};

View file

@ -1,11 +1,11 @@
import { combineReducers } from "@reduxjs/toolkit";
import ShowplanReducer from "./showplanner/state";
import PlayerReducer from "./showplanner/player/state";
import MixerReducer from "./mixer/state";
const rootReducer = combineReducers({
showplan: ShowplanReducer,
player: PlayerReducer
mixer: MixerReducer
});
export type RootState = ReturnType<typeof rootReducer>;

View file

@ -31,23 +31,33 @@ import {
removeItem
} from "./state";
import * as PlayerState from "./player/state";
import * as PlayerState from "../mixer/state";
import playLogo from '../assets/icons/play.svg'
import pauseLogo from '../assets/icons/pause.svg'
import stopLogo from '../assets/icons/stop.svg'
import playLogo from "../assets/icons/play.svg";
import pauseLogo from "../assets/icons/pause.svg";
import stopLogo from "../assets/icons/stop.svg";
const CML_CACHE: { [recordid_trackid: string]: Track } = {};
const TS_ITEM_MENU_ID = "SongMenu";
function Item({ item: x, index, column }: { item: PlanItem | Track; index: number; column: number }) {
function Item({
item: x,
index,
column
}: {
item: PlanItem | Track;
index: number;
column: number;
}) {
const dispatch = useDispatch();
const id = itemId(x);
const isReal = "timeslotitemid" in x;
const isGhost = "ghostid" in x;
const playerState = useSelector((state: RootState) => state.player.players[column]);
const playerState = useSelector(
(state: RootState) => state.mixer.players[column]
);
function triggerClick() {
if (column > -1) {
@ -61,12 +71,20 @@ function Item({ item: x, index, column }: { item: PlanItem | Track; index: numbe
<div
ref={provided.innerRef}
key={id}
className={`sp-track ${(playerState.loadedItem !== null && itemId(playerState.loadedItem) === id) ? "sp-track-active" : ""}`}
className={`sp-track ${
playerState.loadedItem !== null &&
itemId(playerState.loadedItem) === id
? "sp-track-active"
: ""
}`}
onClick={triggerClick}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ContextMenuTrigger id={isReal ? TS_ITEM_MENU_ID : ""} collect={() => ({ id })}>
<ContextMenuTrigger
id={isReal ? TS_ITEM_MENU_ID : ""}
collect={() => ({ id })}
>
{x.title}
{"artist" in x && " - " + x.artist}
<code>
@ -80,33 +98,43 @@ function Item({ item: x, index, column }: { item: PlanItem | Track; index: numbe
}
function Player({ id }: { id: number }) {
const playerState = useSelector((state: RootState) => state.player.players[id]);
const playerState = useSelector(
(state: RootState) => state.mixer.players[id]
);
const dispatch = useDispatch();
return (
<div style={{height:"16%"}} className="player">
{playerState.loadedItem == null && (<div>No Media Selected</div>)}
{(playerState.loadedItem !== null && playerState.loading == false) && (<div>{playerState.loadedItem.title}</div>)}
<div className="player">
{playerState.loadedItem == null && <div>No Media Selected</div>}
{playerState.loadedItem !== null && playerState.loading == false && (
<div>{playerState.loadedItem.title}</div>
)}
{playerState.loading && <b>LOADING</b>}
<div className="mediaButtons" style={{height:"100%"}}>
<button
onClick={() => dispatch(PlayerState.play(id))}
className={playerState.state === "playing" ? "sp-state-playing" : ""}
>
<img src={playLogo} style={{height:"10vh"}}/>
</button>
<button
onClick={() => dispatch(PlayerState.pause(id))}
className={playerState.state === "paused" ? "sp-state-paused" : ""}
>
<img src={pauseLogo} style={{height:"10vh"}}/>
</button>
<button
onClick={() => dispatch(PlayerState.stop(id))}
className={playerState.state === "stopped" ? "sp-state-stopped" : ""}
>
<img src={stopLogo} style={{height:"10vh"}}/>
</button>
<div className="mediaButtons">
<button
onClick={() => dispatch(PlayerState.play(id))}
className={playerState.state === "playing" ? "sp-state-playing" : ""}
>
<img src={playLogo} className="sp-player-button" />
</button>
<button
onClick={() => dispatch(PlayerState.pause(id))}
className={playerState.state === "paused" ? "sp-state-paused" : ""}
>
<img src={pauseLogo} className="sp-player-button" />
</button>
<button
onClick={() => dispatch(PlayerState.stop(id))}
className={playerState.state === "stopped" ? "sp-state-stopped" : ""}
>
<img src={stopLogo} className="sp-player-button" />
</button>
</div>
<div className="sp-mixer-buttons">
<div className="sp-mixer-buttons-backdrop" style={{ width: playerState.volume * 100 + "%" }}></div>
<button onClick={() => dispatch(PlayerState.setVolume(id, "off"))}>Off</button>
<button onClick={() => dispatch(PlayerState.setVolume(id, "bed"))}>Bed</button>
<button onClick={() => dispatch(PlayerState.setVolume(id, "full"))}>Full</button>
</div>
</div>
);
@ -129,7 +157,12 @@ function Column({ id, data }: { id: number; data: PlanItem[] }) {
.filter(x => x.channel === id)
.sort((a, b) => a.weight - b.weight)
.map((x, index) => (
<Item key={itemId(x)} item={x} index={index} column={id} />
<Item
key={itemId(x)}
item={x}
index={index}
column={id}
/>
))}
{provided.placeholder}
</div>
@ -184,28 +217,28 @@ function CentralMusicLibrary() {
function LibraryColumn() {
const [sauce, setSauce] = useState("None");
return (
<div className="sp-col" style={{height:"48%", marginBottom:"1%"}}>
<select
style={{ width: "100%" }}
value={sauce}
onChange={e => setSauce(e.target.value)}
>
<option value={"None"} disabled>
Choose a library
</option>
<option value={"CentralMusicLibrary"}>Central Music Library</option>
</select>
{sauce === "CentralMusicLibrary" && <CentralMusicLibrary />}
</div>
<div className="sp-col" style={{ height: "48%", marginBottom: "1%" }}>
<select
style={{ width: "100%" }}
value={sauce}
onChange={e => setSauce(e.target.value)}
>
<option value={"None"} disabled>
Choose a library
</option>
<option value={"CentralMusicLibrary"}>Central Music Library</option>
</select>
{sauce === "CentralMusicLibrary" && <CentralMusicLibrary />}
</div>
);
}
function MixingInterface(){
function MixingInterface() {
const [sauce, setSauce] = useState("None");
return (
<div className="sp-col" style={{height:"48%", overflowY:"visible"}}>
<h1>Mixing Interface</h1>
</div>
<div className="sp-col" style={{ height: "48%", overflowY: "visible" }}>
<h1>Mixing Interface</h1>
</div>
);
}
@ -275,9 +308,12 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
}
return (
<div className="sp-container">
<div style={{height:"10%"}}>
<div style={{ height: "10%" }}>
<h1>baps3 ayy lmao</h1>
<img src="https://ury.org.uk/images/logo.png" style={{height:"6%", right:"2%", position:"absolute", top:"2%"}}/>
<img
src="https://ury.org.uk/images/logo.png"
style={{ height: "6%", right: "2%", position: "absolute", top: "2%" }}
/>
</div>
<div className="sp-status">
{planSaving && <em>Plan saving...</em>}
@ -292,9 +328,9 @@ const Showplanner: React.FC<{ timeslotId: number }> = function({ timeslotId }) {
<Column id={0} data={showplan} />
<Column id={1} data={showplan} />
<Column id={2} data={showplan} />
<div className="sp-main-col" style={{marginRight:".2%"}}>
<LibraryColumn />
<MixingInterface />
<div className="sp-main-col" style={{ marginRight: ".2%" }}>
<LibraryColumn />
<MixingInterface />
</div>
</DragDropContext>
</div>

View file

@ -1,126 +0,0 @@
import "../../lib/webcast";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { PlanItem } from "../state";
import { Track, MYRADIO_NON_API_BASE } from "../../api";
import { AppThunk } from "../../store";
/// <reference path="webcast.d.ts" />
const audioContext = new AudioContext();
const playerSources: MediaElementAudioSourceNode[] = [];
const playerGains: GainNode[] = [];
// TODO
// const destination = audioContext.createWebcastSource(4096, 2);
const destination = audioContext.createDynamicsCompressor();
destination.connect(audioContext.destination);
type PlayerStateEnum = "playing" | "paused" | "stopped";
interface SinglePlayerState {
loadedItem: PlanItem | Track | null
loading: boolean;
state: PlayerStateEnum;
}
interface PlayerState {
players: SinglePlayerState[];
}
const playerState = createSlice({
name: "Player",
initialState: {
players: [{
loadedItem: null,
loading: false,
state: "stopped"
}, {
loadedItem: null,
loading: false,
state: "stopped"
}, {
loadedItem: null,
loading: false,
state: "stopped"
}]
} as PlayerState,
reducers: {
loadItem(state, action: PayloadAction<{ player: number, item: PlanItem | Track }>) {
state.players[action.payload.player].loadedItem = action.payload.item
state.players[action.payload.player].loading = true;
},
itemLoadComplete(state, action: PayloadAction<{ player: number}>) {
state.players[action.payload.player].loading = false;
},
setPlayerState(state, action: PayloadAction<{ player: number, state: PlayerStateEnum }>) {
state.players[action.payload.player].state = action.payload.state;
}
}
});
export default playerState.reducer;
export const load = (player: number, item: PlanItem | Track): AppThunk => dispatch => {
if (typeof playerSources[player] !== "undefined") {
if (!playerSources[player].mediaElement.paused) {
// already playing, don't kill playback
return;
}
}
dispatch(playerState.actions.loadItem({ player, item }));
const el = new Audio();
el.crossOrigin = "use-credentials";
if ("album" in item) {
// track
el.src = MYRADIO_NON_API_BASE + "/NIPSWeb/secure_play?recordid=" + item.album.recordid + "&trackid=" + item.trackid;
} else if ("type" in item && item.type == "aux") {
el.src = MYRADIO_NON_API_BASE + "/NIPSWeb/managed_play?managedid=" + item.managedid;
} else {
throw new Error("Unsure how to handle this!\r\n\r\n" + JSON.stringify(item));
}
el.oncanplay = () => {
dispatch(playerState.actions.itemLoadComplete({ player }));
}
el.load();
const sauce = audioContext.createMediaElementSource(el);
const gain = audioContext.createGain();
sauce.connect(gain);
gain.connect(destination);
console.log("Connected to", destination);
playerSources[player] = sauce;
playerGains[player] = gain;
}
export const play = (player: number): AppThunk => dispatch => {
try{
playerSources[player].mediaElement.play();
dispatch(playerState.actions.setPlayerState({ player, state: "playing" }));
playerSources[player].mediaElement.addEventListener("ended", function(){dispatch(playerState.actions.setPlayerState({ player, state: "stopped" }));})
} catch {
console.log("nothing selected/loaded");
}
};
export const pause = (player: number): AppThunk => dispatch => {
try{
if (playerSources[player].mediaElement.paused) {
playerSources[player].mediaElement.play();
dispatch(playerState.actions.setPlayerState({ player, state: "playing" }));
} else {
playerSources[player].mediaElement.pause();
dispatch(playerState.actions.setPlayerState({ player, state: "paused" }));
}
} catch {
console.log("nothing selected/loaded");
}
};
export const stop = (player: number): AppThunk => dispatch => {
try{
playerSources[player].mediaElement.pause();
playerSources[player].mediaElement.currentTime = 0;
dispatch(playerState.actions.setPlayerState({ player, state: "stopped" }));
} catch {
console.log("nothing selected/loaded");
}
};

View file

@ -2138,6 +2138,19 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
between.js@^0.1.2-fix.2:
version "0.1.2-fix.2"
resolved "https://registry.yarnpkg.com/between.js/-/between.js-0.1.2-fix.2.tgz#3cdc2b728decf7fc1c3c66042afe98cbaedb3259"
integrity sha512-9rkmzYDM6fW6LV99qD9cF17IjRRIQrOFKnZYuR1W2vqPGJtc9Y3UGs+UH6FJ0xqNYv0i9wRu+ZSqG6gM1xX1eg==
dependencies:
color "^3.0.0"
color-string "^1.5.2"
easing-functions "^1.0.1"
lerp "^1.0.3"
minivents "^2.2.0"
raf "^3.4.0"
rollup-plugin-alias "^1.4.0"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -3519,6 +3532,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
easing-functions@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/easing-functions/-/easing-functions-1.0.1.tgz#66eaadafb4593212b700e867553d876009848ef4"
integrity sha512-5eHZ+InobokmzyYTs3BtL+bNoKVQiDF1iQoh1FQ0pWNAhhgqA2/o9z4LX/G6fq0jWFu16LdAxidxQi/fnRqfdw==
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -5991,6 +6009,11 @@ left-pad@^1.3.0:
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==
lerp@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/lerp/-/lerp-1.0.3.tgz#a18c8968f917896de15ccfcc28d55a6b731e776e"
integrity sha1-oYyJaPkXiW3hXM/MKNVaa3Med24=
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@ -6364,6 +6387,11 @@ minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
safe-buffer "^5.1.2"
yallist "^3.0.0"
minivents@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/minivents/-/minivents-2.2.0.tgz#559492edc04bc00b62168850af5b8ca06d5e088b"
integrity sha1-VZSS7cBLwAtiFohQr1uMoG1eCIs=
minizlib@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
@ -8091,7 +8119,7 @@ raf-schd@^4.0.2:
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0"
integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==
raf@3.4.1:
raf@3.4.1, raf@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
@ -8721,6 +8749,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-plugin-alias@^1.4.0:
version "1.5.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-alias/-/rollup-plugin-alias-1.5.2.tgz#f15a1cc8ee0debf74ab5c2bb68a944a66b568411"
integrity sha512-ODeZXhTxpD48sfcYLAFc1BGrsXKDj7o1CSNH3uYbdK3o0NxyMmaQPTNgW+ko+am92DLC8QSTe4kyxTuEkI5S5w==
dependencies:
slash "^3.0.0"
rsvp@^4.8.4:
version "4.8.5"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"