Skip to content

Commit fcfe591

Browse files
committed
Adds a dj interface at /dj
1 parent 616d837 commit fcfe591

File tree

7 files changed

+334
-8
lines changed

7 files changed

+334
-8
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"react-router": "^1.0.2",
7474
"react-spinner": "^0.2.3",
7575
"react-test-tree": "^1.0.0",
76-
"react-wavesurfer": "^0.2.3",
76+
"react-wavesurfer": "^0.7.3",
7777
"redux": "^3.0.4",
7878
"redux-devtools": "^3.0.2",
7979
"redux-logger": "^2.2.1",
@@ -83,6 +83,7 @@
8383
"style-loader": "^0.13.0",
8484
"url-loader": "^0.5.6",
8585
"victory": "^0.4.0",
86+
"wavesurfer.js": "^1.1.13",
8687
"webpack": "^1.12.9",
8788
"webpack-dev-middleware": "^1.4.0",
8889
"webpack-hot-middleware": "^2.6.0",

src/components/MomentPlayer/index.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Wavesurfer from "react-wavesurfer";
44
import "./index.scss";
55
import {throttle} from "lodash";
66

7-
function wrapAudioPlayerElements(child) {
7+
export function wrapAudioPlayerElements(child) {
88
const PlayButtonName = PlayButton.name;
99
const WavesurferName = Wavesurfer.name;
1010
const height = "128px";
@@ -75,22 +75,27 @@ export default class MomentPlayer extends Component {
7575
time,
7676
loadAudio,
7777
onEnd,
78-
autoplay
78+
autoplay,
79+
title,
80+
titleEl,
81+
volume
7982
} = this.props;
8083
const surferOptions = {
8184
normalize: true
8285
};
86+
8387
return (<div className="moment-player-panel">
84-
<h1 className="text-center">
85-
Now Playing: {this.props.title}
86-
</h1>
88+
{titleEl ? titleEl(title) : <h1 className="text-center">
89+
Now Playing: {title}
90+
</h1>}
8791
<AudioPlayer>
8892
<PlayButton
89-
isPlaying={this.props.playing}
93+
isPlaying={playing}
9094
play={setPlaying.bind(this, loadAudio, true)}
9195
pause={setPlaying.bind(this, loadAudio, false)} />
9296
<Wavesurfer
9397
audioFile={url}
98+
volume={volume}
9499
pos={time}
95100
onPosChange={onPositionChange.bind(this, loadAudio)}
96101
playing={playing}

src/containers/DJ/index-test.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// yo

src/containers/DJ/index.js

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import React, {Component} from "react";
2+
import { connect } from "react-redux";
3+
import {get} from "lodash";
4+
5+
import "./index.scss";
6+
7+
import Spinner from "react-spinner";
8+
9+
import Wavesurfer from "react-wavesurfer";
10+
import {PlayButton, wrapAudioPlayerElements, AudioPlayer} from "../../components/MomentPlayer";
11+
12+
import {
13+
loadMoments,
14+
loadTranscripts,
15+
loadAudio,
16+
loadMetrics
17+
} from "../../actions";
18+
19+
import {
20+
MomentPlayer,
21+
Timeline
22+
} from "../../components";
23+
24+
import getActiveIndex from "../MomentViewer/getActiveIndex";
25+
26+
class DJ extends Component {
27+
28+
constructor(props) {
29+
super(props);
30+
this.state = {};
31+
}
32+
33+
34+
fetch(props) {
35+
props.loadAudio({
36+
time: 0,
37+
momentId: props.currentMomentId,
38+
playing: false
39+
});
40+
props.loadMoments({momentId: props.currentMomentId});
41+
props.loadTranscripts({momentId: props.currentMomentId});
42+
props.loadMetrics({momentId: props.currentMomentId});
43+
}
44+
45+
componentWillMount() {
46+
this.fetch(this.props);
47+
}
48+
49+
componentWillReceiveProps(nextProps) {
50+
if (nextProps.currentMomentId !== this.props.currentMomentId) {
51+
this.fetch(nextProps);
52+
}
53+
}
54+
55+
scPlay() {
56+
this.setState({scPlaying: true});
57+
}
58+
59+
scPause() {
60+
this.setState({scPlaying: false});
61+
}
62+
63+
scPosChange(e) {
64+
const scTime = e.originalArgs[0];
65+
this.setState({scTime});
66+
}
67+
68+
masterPlay() {
69+
this.scPlay();
70+
this.props.loadAudio({playing: true});
71+
}
72+
73+
masterPause() {
74+
this.scPause();
75+
this.props.loadAudio({playing: false});
76+
}
77+
78+
setVolumeFactory(playerKey) {
79+
return (e) => {
80+
this.setState({
81+
[playerKey]: +e.target.value
82+
});
83+
};
84+
}
85+
86+
crossFadeFactory() {
87+
return (e) => {
88+
this.setState({
89+
crossFade: +e.target.value
90+
});
91+
};
92+
}
93+
94+
calculateVolume(lr, crossFade, volume) {
95+
if (crossFade > 0 && lr === "left") {
96+
return (1-crossFade) * volume;
97+
}
98+
if (crossFade < 0 && lr === "right") {
99+
return (1+crossFade) * volume;
100+
}
101+
return volume;
102+
}
103+
104+
render() {
105+
const {
106+
currentMoment,
107+
currentMission,
108+
loading,
109+
currentTranscripts,
110+
loadAudio,
111+
metrics,
112+
onEnd,
113+
autoplay
114+
} = this.props;
115+
116+
if (loading) {
117+
return <div className="text-center lead">
118+
<p>Loading moment...</p>
119+
<Spinner />
120+
</div>;
121+
}
122+
123+
if (!currentMoment) {
124+
return <div>
125+
Error fetching moment.
126+
</div>;
127+
}
128+
129+
const {time, playing} = this.props.currentAudio;
130+
let {transcripts} = currentTranscripts;
131+
132+
//this is bad, but necessary until I can think of a clever solution
133+
transcripts = transcripts.map(function(i) {
134+
return i.set("active", false);
135+
});
136+
137+
const momentMetStart = this.props.currentMoment.metStart;
138+
const currentMissionTime = momentMetStart + (time * 1000);
139+
140+
const activeIndex = getActiveIndex(
141+
transcripts,
142+
currentMissionTime
143+
);
144+
145+
if(activeIndex >= 0) {
146+
const activeMessage = transcripts.get(activeIndex).set("active", true);
147+
transcripts = transcripts.set(activeIndex, activeMessage);
148+
}
149+
150+
const timelineClickEvent = function(startTime) {
151+
const seekTime = (startTime - metStart) / 1000;
152+
if(metStart) {
153+
loadAudio({
154+
time: seekTime
155+
});
156+
}
157+
};
158+
159+
const {
160+
title,
161+
audioUrl,
162+
metStart,
163+
metEnd
164+
} = currentMoment;
165+
const missionLength = currentMission.length;
166+
167+
const {
168+
scPlaying = false,
169+
scTime = 0,
170+
scVolume = 1,
171+
apolloVolume = 1,
172+
crossFade = 0
173+
} = this.state;
174+
175+
return (
176+
<div>
177+
<h1 className="text-center">
178+
Apollo 11 DJ
179+
</h1>
180+
<div className="dj-viewer-container">
181+
<div className="dj-viewer">
182+
<h2>Moe Shop - Crosstalk</h2>
183+
<AudioPlayer>
184+
<PlayButton
185+
isPlaying={scPlaying}
186+
play={this.scPlay.bind(this)}
187+
pause={this.scPause.bind(this)} />
188+
<Wavesurfer
189+
audioFile={"https://api.soundcloud.com/tracks/255043058/stream?client_id=7535f0fce0d2cb28070cdb86b8746f77"}
190+
pos={scTime}
191+
playing={scPlaying}
192+
volume={this.calculateVolume("left", crossFade, scVolume)}
193+
onPosChange={this.scPosChange.bind(this)}
194+
/>
195+
</AudioPlayer>
196+
<div>
197+
<small>
198+
Streamed from <a href="https://soundcloud.com">soundcloud.com</a>.
199+
Song Courtesy of <a href="https://moeshop.bandcamp.com/">Moe Shop</a>.
200+
</small>
201+
</div>
202+
</div>
203+
<div className="dj-volume-control panel panel-info">
204+
<div className="panel-body">
205+
<div className="dj-play-button-container">
206+
<PlayButton isPlaying={scPlaying || playing}
207+
pause={this.masterPause.bind(this)}
208+
play={this.masterPlay.bind(this)} />
209+
</div>
210+
<div className="dj-volume-control-sliders">
211+
<input type="range" min={0} max={1} step={0.01} onChange={this.setVolumeFactory("scVolume")} value={scVolume} className="dj-vertical" />
212+
<input type="range" min={-1} max={1} step={0.01} onChange={this.crossFadeFactory()} className="dj-horizontal" />
213+
<input type="range" min={0} max={1} step={0.01} onChange={this.setVolumeFactory("apolloVolume")} value={apolloVolume} className="dj-vertical" />
214+
</div>
215+
</div>
216+
</div>
217+
<div className="moment-viewer-container">
218+
<MomentPlayer
219+
volume={this.calculateVolume("right", crossFade, apolloVolume)}
220+
title={title}
221+
titleEl={(title) => <h2>{title}</h2>}
222+
url={audioUrl}
223+
start={metStart}
224+
end={metEnd}
225+
time={time}
226+
playing={playing}
227+
loadAudio={loadAudio}
228+
autoplay={autoplay}
229+
onEnd={onEnd}
230+
missionLength={missionLength} />
231+
<div style={{marginTop: "0.5em"}} className="timeline-panel row">
232+
<Timeline
233+
timeline={transcripts}
234+
clickEvent={timelineClickEvent}/>
235+
</div>
236+
</div>
237+
</div>
238+
</div>
239+
);
240+
}
241+
}
242+
243+
function mapStateToProps(state) {
244+
const {audio, metrics} = state;
245+
const momentId = 1;
246+
const { loading, entities } = state.moments;
247+
const { moments, missions } = entities;
248+
const moment = get(moments, momentId);
249+
if (loading || !moment) {
250+
return {
251+
currentMomentId: momentId,
252+
loading: true,
253+
currentAudio: audio
254+
};
255+
}
256+
const transcripts = state.transcripts;
257+
const mission = get(missions, moment.mission);
258+
259+
return {
260+
currentMomentId: momentId,
261+
loading,
262+
currentMission: mission,
263+
currentMoment: moment,
264+
currentTranscripts: transcripts,
265+
currentAudio: audio,
266+
metrics
267+
};
268+
}
269+
270+
export default connect(mapStateToProps, {
271+
loadMoments,
272+
loadTranscripts,
273+
loadAudio,
274+
loadMetrics
275+
})(DJ);
276+

src/containers/DJ/index.scss

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
$dj-width: 100%;
2+
$dj-volume-width: 20%;
3+
$dj-player-width: ($dj-width - $dj-volume-width) / 2;
4+
.dj-viewer-container {
5+
display: flex;
6+
flex-direction: row;
7+
flex-grow: 1;
8+
.moment-viewer-container, .dj-viewer {
9+
width: $dj-player-width;
10+
position: relative;
11+
padding: 0 15px;
12+
}
13+
.transcript-panel {
14+
width: 100%;
15+
}
16+
.transcript-panel .list-group {
17+
max-height: 35em;
18+
}
19+
}
20+
21+
.dj-volume-control {
22+
width: $dj-volume-width;
23+
&-sliders {
24+
display: flex;
25+
}
26+
}
27+
28+
.dj-play-button-container {
29+
margin: 0 auto;
30+
width: 150px;
31+
height: 150px;
32+
}
33+
34+
.dj-vertical {
35+
writing-mode: vertical-lr; /* IE */
36+
-webkit-appearance: slider-vertical; /* WebKit */
37+
width: 8px;
38+
height: 175px;
39+
padding: 0 5px;
40+
}

src/containers/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export StoryViewer from "./StoryViewer";
77
export NoMatch from "./NoMatch";
88
export Settings from "./Settings";
99
export PlaylistViewer from "./PlaylistViewer";
10+
export DJ from "./DJ";

0 commit comments

Comments
 (0)