@ldelia/react-media 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -4
- package/package.json +2 -1
- package/src/components/reproduction-widget/README.md +27 -0
- package/src/components/reproduction-widget/ReproductionWidget.js +29 -0
- package/src/components/reproduction-widget/ReproductionWidget.tsx +68 -0
- package/src/components/reproduction-widget/index.js +1 -0
- package/src/components/reproduction-widget/index.tsx +1 -0
- package/src/components/reproduction-widget/inner-players/PlayAlongInnerPlayer.js +5 -0
- package/src/components/reproduction-widget/inner-players/PlayAlongInnerPlayer.tsx +10 -0
- package/src/components/reproduction-widget/inner-players/YouTubeInnerPlayer.js +21 -0
- package/src/components/reproduction-widget/inner-players/YouTubeInnerPlayer.tsx +36 -0
- package/src/components/reproduction-widget/models/Player/PlayAlongPlayer.js +97 -0
- package/src/components/reproduction-widget/models/Player/PlayAlongPlayer.ts +129 -0
- package/src/components/reproduction-widget/models/Player/PlayerEvents.js +4 -0
- package/src/components/reproduction-widget/models/Player/PlayerEvents.ts +4 -0
- package/src/components/reproduction-widget/models/Player/YouTubePlayer.js +126 -0
- package/src/components/reproduction-widget/models/Player/YouTubePlayer.ts +171 -0
- package/src/components/reproduction-widget/models/Reproduction.js +214 -0
- package/src/components/reproduction-widget/models/Reproduction.ts +272 -0
- package/src/components/reproduction-widget/models/ReproductionBuilder.js +51 -0
- package/src/components/reproduction-widget/models/ReproductionBuilder.ts +70 -0
- package/src/stories/reproduction-widget.stories.js +16 -0
- package/src/stories/reproduction-widget.stories.tsx +23 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { YouTubePlayer as InnerYouTubePlayer } from 'react-youtube';
|
|
2
|
+
import { PlayAlongPlayer } from './PlayAlongPlayer';
|
|
3
|
+
import { PLAYER_EVENTS } from './PlayerEvents';
|
|
4
|
+
|
|
5
|
+
// https://developers.google.com/youtube/iframe_api_reference
|
|
6
|
+
enum INNER_YOUTUBE_PLAYER_EVENTS {
|
|
7
|
+
VIDEO_UNSTARTED = -1,
|
|
8
|
+
VIDEO_ENDED = 0,
|
|
9
|
+
VIDEO_PLAYING = 1,
|
|
10
|
+
VIDEO_PAUSED = 2,
|
|
11
|
+
VIDEO_BUFFERING = 3,
|
|
12
|
+
VIDEO_CUED = 5,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface INNER_YOUTUBE_PLAYER_StateChangeEvent {
|
|
16
|
+
data: INNER_YOUTUBE_PLAYER_EVENTS;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dispatchOnReadyHandlers = Symbol();
|
|
20
|
+
const dispatchOnFinishHandlers = Symbol();
|
|
21
|
+
|
|
22
|
+
export class YouTubePlayer {
|
|
23
|
+
private currentTime: number;
|
|
24
|
+
private isRunning: boolean;
|
|
25
|
+
private innerPlayer: InnerYouTubePlayer | number;
|
|
26
|
+
private [dispatchOnReadyHandlers]: (() => void)[];
|
|
27
|
+
private [dispatchOnFinishHandlers]: (() => void)[];
|
|
28
|
+
|
|
29
|
+
static get EVENTS() {
|
|
30
|
+
return PLAYER_EVENTS;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
constructor(innerPlayer: InnerYouTubePlayer) {
|
|
34
|
+
this[dispatchOnFinishHandlers] = [];
|
|
35
|
+
this[dispatchOnReadyHandlers] = [];
|
|
36
|
+
|
|
37
|
+
this.currentTime = 0;
|
|
38
|
+
this.isRunning = false;
|
|
39
|
+
|
|
40
|
+
this.setInnerPlayer(innerPlayer);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private setInnerPlayer(innerPlayer: InnerYouTubePlayer) {
|
|
44
|
+
this.innerPlayer = innerPlayer;
|
|
45
|
+
this.dispatch(YouTubePlayer.EVENTS.READY);
|
|
46
|
+
|
|
47
|
+
// this is necessary for avoiding the state video cued.
|
|
48
|
+
// When a video is in this state, when user seek to X, the song is played
|
|
49
|
+
this.innerPlayer.playVideo();
|
|
50
|
+
this.innerPlayer.pauseVideo();
|
|
51
|
+
|
|
52
|
+
this.innerPlayer.addEventListener(
|
|
53
|
+
'onStateChange',
|
|
54
|
+
(videoState: INNER_YOUTUBE_PLAYER_StateChangeEvent) => {
|
|
55
|
+
switch (videoState.data) {
|
|
56
|
+
case INNER_YOUTUBE_PLAYER_EVENTS.VIDEO_ENDED:
|
|
57
|
+
this.dispatch(YouTubePlayer.EVENTS.FINISH);
|
|
58
|
+
this.isRunning = false;
|
|
59
|
+
this.currentTime = 0;
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getInnerPlayer() {
|
|
69
|
+
return this.innerPlayer;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
play() {
|
|
73
|
+
const videoPlayer = this.getInnerPlayer();
|
|
74
|
+
videoPlayer.playVideo();
|
|
75
|
+
|
|
76
|
+
this.isRunning = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pause() {
|
|
80
|
+
this.isRunning = false;
|
|
81
|
+
|
|
82
|
+
this.getInnerPlayer().pauseVideo();
|
|
83
|
+
|
|
84
|
+
this.currentTime = this.getInnerPlayer().getCurrentTime();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
stop() {
|
|
88
|
+
this.isRunning = false;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Hay un issue al llamar a getDuration del video luego de reanudar una canción pausada (devuelve siempre 0)
|
|
92
|
+
* Para evitar que se pierda la información y tener que cargarla de nuevo, se simula un stop pausando y llevando al comienzo
|
|
93
|
+
* videoPlayer.stopVideo();
|
|
94
|
+
*/
|
|
95
|
+
this.getInnerPlayer().pauseVideo();
|
|
96
|
+
|
|
97
|
+
this.seekTo(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
seekTo(seconds: number) {
|
|
101
|
+
const videoPlayer = this.getInnerPlayer();
|
|
102
|
+
this.currentTime = seconds;
|
|
103
|
+
|
|
104
|
+
videoPlayer.seekTo(this.currentTime);
|
|
105
|
+
|
|
106
|
+
if (this.isRunning) {
|
|
107
|
+
this.play();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getCurrentTime() {
|
|
112
|
+
return this.isRunning
|
|
113
|
+
? this.getInnerPlayer().getCurrentTime()
|
|
114
|
+
: this.currentTime;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getDuration() {
|
|
118
|
+
if (this.isAvailable()) {
|
|
119
|
+
return this.getInnerPlayer().getDuration();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getAvailablePlaybackRates() {
|
|
124
|
+
return this.getInnerPlayer().getAvailablePlaybackRates();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setPlaybackRate(playbackRate: number) {
|
|
128
|
+
if (!this.getAvailablePlaybackRates().includes(playbackRate)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`The PlayAlongPlayer doesn't support a playbackRate with value ${playbackRate}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
this.getInnerPlayer().setPlaybackRate(playbackRate);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
isAvailable() {
|
|
137
|
+
return this.getInnerPlayer() !== null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
on(eventName: keyof typeof PlayAlongPlayer.EVENTS, handler: () => void) {
|
|
141
|
+
switch (eventName) {
|
|
142
|
+
case PlayAlongPlayer.EVENTS.READY:
|
|
143
|
+
return this[dispatchOnReadyHandlers].push(handler);
|
|
144
|
+
case PlayAlongPlayer.EVENTS.FINISH:
|
|
145
|
+
return this[dispatchOnFinishHandlers].push(handler);
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
dispatch(eventName: keyof typeof PlayAlongPlayer.EVENTS) {
|
|
152
|
+
let handler, i, len;
|
|
153
|
+
let ref: (() => void)[] = [];
|
|
154
|
+
|
|
155
|
+
switch (eventName) {
|
|
156
|
+
case YouTubePlayer.EVENTS.READY:
|
|
157
|
+
ref = this[dispatchOnReadyHandlers];
|
|
158
|
+
break;
|
|
159
|
+
case YouTubePlayer.EVENTS.FINISH:
|
|
160
|
+
ref = this[dispatchOnFinishHandlers];
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
|
167
|
+
handler = ref[i];
|
|
168
|
+
setTimeout(handler, 0);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { PLAYER_EVENTS } from './Player/PlayerEvents';
|
|
2
|
+
import { ReproductionBuilder } from './ReproductionBuilder';
|
|
3
|
+
const STATES = {
|
|
4
|
+
STOPPED: 0,
|
|
5
|
+
COUNTING_IN: 1,
|
|
6
|
+
PLAYING: 2,
|
|
7
|
+
PAUSED: 3,
|
|
8
|
+
};
|
|
9
|
+
const EVENTS = {
|
|
10
|
+
READY: 'READY',
|
|
11
|
+
START: 'START',
|
|
12
|
+
COUNTING_IN: 'COUNTING_IN',
|
|
13
|
+
PLAY: 'PLAY',
|
|
14
|
+
PLAYING: 'PLAYING',
|
|
15
|
+
PAUSED: 'PAUSED',
|
|
16
|
+
FINISH: 'FINISH',
|
|
17
|
+
};
|
|
18
|
+
const dispatchOnReadyHandlers = Symbol();
|
|
19
|
+
const dispatchOnSongStartHandlers = Symbol();
|
|
20
|
+
const dispatchOnCountingInHandlers = Symbol();
|
|
21
|
+
const dispatchOnPlayHandlers = Symbol();
|
|
22
|
+
const dispatchOnPlayingHandlers = Symbol();
|
|
23
|
+
const dispatchOnPausedHandlers = Symbol();
|
|
24
|
+
const dispatchOnFinishHandlers = Symbol();
|
|
25
|
+
export class Reproduction {
|
|
26
|
+
static get EVENTS() {
|
|
27
|
+
return EVENTS;
|
|
28
|
+
}
|
|
29
|
+
static get STATES() {
|
|
30
|
+
return STATES;
|
|
31
|
+
}
|
|
32
|
+
constructor(player, requiresCountingIn, songTempo) {
|
|
33
|
+
this[dispatchOnReadyHandlers] = [];
|
|
34
|
+
this[dispatchOnSongStartHandlers] = [];
|
|
35
|
+
this[dispatchOnCountingInHandlers] = [];
|
|
36
|
+
this[dispatchOnPlayHandlers] = [];
|
|
37
|
+
this[dispatchOnPlayingHandlers] = [];
|
|
38
|
+
this[dispatchOnPausedHandlers] = [];
|
|
39
|
+
this[dispatchOnFinishHandlers] = [];
|
|
40
|
+
this.songTempo = songTempo;
|
|
41
|
+
this.player = player;
|
|
42
|
+
this.ready = false;
|
|
43
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
44
|
+
this.interval = null;
|
|
45
|
+
this.requiresCountingIn = requiresCountingIn;
|
|
46
|
+
this.countingInCounter = 0;
|
|
47
|
+
this.player.on(PLAYER_EVENTS.READY, () => {
|
|
48
|
+
this.ready = true;
|
|
49
|
+
this.dispatch(Reproduction.EVENTS.READY);
|
|
50
|
+
});
|
|
51
|
+
this.player.on(PLAYER_EVENTS.FINISH, () => {
|
|
52
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
53
|
+
clearInterval(this.interval);
|
|
54
|
+
this.dispatch(Reproduction.EVENTS.FINISH);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
on(eventName, handler) {
|
|
58
|
+
switch (eventName) {
|
|
59
|
+
case Reproduction.EVENTS.READY:
|
|
60
|
+
return this[dispatchOnReadyHandlers].push(handler);
|
|
61
|
+
case Reproduction.EVENTS.START:
|
|
62
|
+
return this[dispatchOnSongStartHandlers].push(handler);
|
|
63
|
+
case Reproduction.EVENTS.COUNTING_IN:
|
|
64
|
+
return this[dispatchOnCountingInHandlers].push(handler);
|
|
65
|
+
case Reproduction.EVENTS.PLAY:
|
|
66
|
+
return this[dispatchOnPlayHandlers].push(handler);
|
|
67
|
+
case Reproduction.EVENTS.PLAYING:
|
|
68
|
+
return this[dispatchOnPlayingHandlers].push(handler);
|
|
69
|
+
case Reproduction.EVENTS.PAUSED:
|
|
70
|
+
return this[dispatchOnPausedHandlers].push(handler);
|
|
71
|
+
case Reproduction.EVENTS.FINISH:
|
|
72
|
+
return this[dispatchOnFinishHandlers].push(handler);
|
|
73
|
+
default:
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
dispatch(eventName) {
|
|
78
|
+
let handler, i, len;
|
|
79
|
+
let ref = [];
|
|
80
|
+
switch (eventName) {
|
|
81
|
+
case Reproduction.EVENTS.READY:
|
|
82
|
+
ref = this[dispatchOnReadyHandlers];
|
|
83
|
+
break;
|
|
84
|
+
case Reproduction.EVENTS.START:
|
|
85
|
+
ref = this[dispatchOnSongStartHandlers];
|
|
86
|
+
break;
|
|
87
|
+
case Reproduction.EVENTS.COUNTING_IN:
|
|
88
|
+
ref = this[dispatchOnCountingInHandlers];
|
|
89
|
+
break;
|
|
90
|
+
case Reproduction.EVENTS.PLAY:
|
|
91
|
+
ref = this[dispatchOnPlayHandlers];
|
|
92
|
+
break;
|
|
93
|
+
case Reproduction.EVENTS.PLAYING:
|
|
94
|
+
ref = this[dispatchOnPlayingHandlers];
|
|
95
|
+
break;
|
|
96
|
+
case Reproduction.EVENTS.PAUSED:
|
|
97
|
+
ref = this[dispatchOnPausedHandlers];
|
|
98
|
+
break;
|
|
99
|
+
case Reproduction.EVENTS.FINISH:
|
|
100
|
+
ref = this[dispatchOnFinishHandlers];
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
|
106
|
+
handler = ref[i];
|
|
107
|
+
handler();
|
|
108
|
+
// setTimeout(handler, 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
countIn(timeout, limit) {
|
|
112
|
+
// the initial count starts instantly, no waiting
|
|
113
|
+
this.countingInCounter++;
|
|
114
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN);
|
|
115
|
+
const interval = setInterval(() => {
|
|
116
|
+
this.countingInCounter++;
|
|
117
|
+
if (this.countingInCounter === limit) {
|
|
118
|
+
clearInterval(interval);
|
|
119
|
+
this.countingInCounter = 0;
|
|
120
|
+
if (limit !== 5) {
|
|
121
|
+
this.countIn(this.getBPMInterval(), 5);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.play();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN);
|
|
129
|
+
}
|
|
130
|
+
}, timeout);
|
|
131
|
+
}
|
|
132
|
+
start() {
|
|
133
|
+
if (this.state === Reproduction.STATES.STOPPED) {
|
|
134
|
+
this.dispatch(Reproduction.EVENTS.START);
|
|
135
|
+
}
|
|
136
|
+
if (this.requiresCountingIn && this.getCurrentTime() === 0) {
|
|
137
|
+
this.state = Reproduction.STATES.COUNTING_IN;
|
|
138
|
+
this.countIn(this.getBPMInterval() * 2, 3);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this.play();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
play() {
|
|
145
|
+
this.state = Reproduction.STATES.PLAYING;
|
|
146
|
+
this.dispatch(Reproduction.EVENTS.PLAY);
|
|
147
|
+
this.player.play();
|
|
148
|
+
const intervalTimeout = 200;
|
|
149
|
+
this.interval = setInterval(() => {
|
|
150
|
+
if (this.isPlaying()) {
|
|
151
|
+
this.dispatch(Reproduction.EVENTS.PLAYING);
|
|
152
|
+
}
|
|
153
|
+
}, intervalTimeout);
|
|
154
|
+
}
|
|
155
|
+
pause() {
|
|
156
|
+
this.state = Reproduction.STATES.PAUSED;
|
|
157
|
+
this.player.pause();
|
|
158
|
+
clearInterval(this.interval);
|
|
159
|
+
this.dispatch(Reproduction.EVENTS.PAUSED);
|
|
160
|
+
}
|
|
161
|
+
stop() {
|
|
162
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
163
|
+
this.player.stop();
|
|
164
|
+
clearInterval(this.interval);
|
|
165
|
+
this.dispatch(Reproduction.EVENTS.FINISH);
|
|
166
|
+
}
|
|
167
|
+
isReady() {
|
|
168
|
+
// It's necessary to avoid play the reproduction-widget when the player is not ready
|
|
169
|
+
return this.ready;
|
|
170
|
+
}
|
|
171
|
+
isPlaying() {
|
|
172
|
+
return this.state === Reproduction.STATES.PLAYING;
|
|
173
|
+
}
|
|
174
|
+
isStopped() {
|
|
175
|
+
return this.state === Reproduction.STATES.STOPPED;
|
|
176
|
+
}
|
|
177
|
+
isPaused() {
|
|
178
|
+
return this.state === Reproduction.STATES.PAUSED;
|
|
179
|
+
}
|
|
180
|
+
isCountingIn() {
|
|
181
|
+
return this.state === Reproduction.STATES.COUNTING_IN;
|
|
182
|
+
}
|
|
183
|
+
getPlayer() {
|
|
184
|
+
return this.player;
|
|
185
|
+
}
|
|
186
|
+
getTempo() {
|
|
187
|
+
return this.songTempo;
|
|
188
|
+
}
|
|
189
|
+
getCurrentTime() {
|
|
190
|
+
// in seconds with milliseconds.
|
|
191
|
+
return this.player.getCurrentTime();
|
|
192
|
+
}
|
|
193
|
+
getDuration() {
|
|
194
|
+
return this.player.getDuration();
|
|
195
|
+
}
|
|
196
|
+
seekTo(seconds) {
|
|
197
|
+
this.player.seekTo(seconds);
|
|
198
|
+
}
|
|
199
|
+
getAvailablePlaybackRates() {
|
|
200
|
+
return this.player.getAvailablePlaybackRates();
|
|
201
|
+
}
|
|
202
|
+
setPlaybackRate(playbackRate) {
|
|
203
|
+
this.player.setPlaybackRate(playbackRate);
|
|
204
|
+
}
|
|
205
|
+
isAvailable() {
|
|
206
|
+
return this.player.isAvailable();
|
|
207
|
+
}
|
|
208
|
+
getBPMInterval() {
|
|
209
|
+
return 60000 / this.getTempo();
|
|
210
|
+
}
|
|
211
|
+
static newBuilder() {
|
|
212
|
+
return new ReproductionBuilder();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { PLAYER_EVENTS } from './Player/PlayerEvents';
|
|
2
|
+
import { ReproductionBuilder } from './ReproductionBuilder';
|
|
3
|
+
import { PlayAlongPlayer } from './Player/PlayAlongPlayer';
|
|
4
|
+
import { YouTubePlayer } from './Player/YouTubePlayer';
|
|
5
|
+
|
|
6
|
+
type Player = PlayAlongPlayer | YouTubePlayer;
|
|
7
|
+
|
|
8
|
+
const STATES = {
|
|
9
|
+
STOPPED: 0,
|
|
10
|
+
COUNTING_IN: 1,
|
|
11
|
+
PLAYING: 2,
|
|
12
|
+
PAUSED: 3,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const EVENTS = {
|
|
16
|
+
READY: 'READY',
|
|
17
|
+
START: 'START',
|
|
18
|
+
COUNTING_IN: 'COUNTING_IN',
|
|
19
|
+
PLAY: 'PLAY',
|
|
20
|
+
PLAYING: 'PLAYING',
|
|
21
|
+
PAUSED: 'PAUSED',
|
|
22
|
+
FINISH: 'FINISH',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
const dispatchOnReadyHandlers = Symbol();
|
|
26
|
+
const dispatchOnSongStartHandlers = Symbol();
|
|
27
|
+
const dispatchOnCountingInHandlers = Symbol();
|
|
28
|
+
const dispatchOnPlayHandlers = Symbol();
|
|
29
|
+
const dispatchOnPlayingHandlers = Symbol();
|
|
30
|
+
const dispatchOnPausedHandlers = Symbol();
|
|
31
|
+
const dispatchOnFinishHandlers = Symbol();
|
|
32
|
+
|
|
33
|
+
export class Reproduction {
|
|
34
|
+
private player: Player;
|
|
35
|
+
private requiresCountingIn: boolean;
|
|
36
|
+
|
|
37
|
+
private songTempo: number;
|
|
38
|
+
private state: number;
|
|
39
|
+
private ready: boolean;
|
|
40
|
+
private interval: ReturnType<typeof setInterval> | null;
|
|
41
|
+
private countingInCounter: number;
|
|
42
|
+
private [dispatchOnReadyHandlers]: (() => void)[];
|
|
43
|
+
private [dispatchOnSongStartHandlers]: (() => void)[];
|
|
44
|
+
private [dispatchOnCountingInHandlers]: (() => void)[];
|
|
45
|
+
private [dispatchOnPlayHandlers]: (() => void)[];
|
|
46
|
+
private [dispatchOnPlayingHandlers]: (() => void)[];
|
|
47
|
+
private [dispatchOnPausedHandlers]: (() => void)[];
|
|
48
|
+
private [dispatchOnFinishHandlers]: (() => void)[];
|
|
49
|
+
|
|
50
|
+
static get EVENTS() {
|
|
51
|
+
return EVENTS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static get STATES() {
|
|
55
|
+
return STATES;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor(player: Player, requiresCountingIn: boolean, songTempo: number) {
|
|
59
|
+
this[dispatchOnReadyHandlers] = [];
|
|
60
|
+
this[dispatchOnSongStartHandlers] = [];
|
|
61
|
+
this[dispatchOnCountingInHandlers] = [];
|
|
62
|
+
this[dispatchOnPlayHandlers] = [];
|
|
63
|
+
this[dispatchOnPlayingHandlers] = [];
|
|
64
|
+
this[dispatchOnPausedHandlers] = [];
|
|
65
|
+
this[dispatchOnFinishHandlers] = [];
|
|
66
|
+
|
|
67
|
+
this.songTempo = songTempo;
|
|
68
|
+
this.player = player;
|
|
69
|
+
this.ready = false;
|
|
70
|
+
|
|
71
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
72
|
+
this.interval = null;
|
|
73
|
+
|
|
74
|
+
this.requiresCountingIn = requiresCountingIn;
|
|
75
|
+
this.countingInCounter = 0;
|
|
76
|
+
|
|
77
|
+
this.player.on(PLAYER_EVENTS.READY, () => {
|
|
78
|
+
this.ready = true;
|
|
79
|
+
this.dispatch(Reproduction.EVENTS.READY);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.player.on(PLAYER_EVENTS.FINISH, () => {
|
|
83
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
84
|
+
clearInterval(this.interval as NodeJS.Timeout);
|
|
85
|
+
this.dispatch(Reproduction.EVENTS.FINISH);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
on(eventName: keyof typeof Reproduction.EVENTS, handler: () => void) {
|
|
90
|
+
switch (eventName) {
|
|
91
|
+
case Reproduction.EVENTS.READY:
|
|
92
|
+
return this[dispatchOnReadyHandlers].push(handler);
|
|
93
|
+
case Reproduction.EVENTS.START:
|
|
94
|
+
return this[dispatchOnSongStartHandlers].push(handler);
|
|
95
|
+
case Reproduction.EVENTS.COUNTING_IN:
|
|
96
|
+
return this[dispatchOnCountingInHandlers].push(handler);
|
|
97
|
+
case Reproduction.EVENTS.PLAY:
|
|
98
|
+
return this[dispatchOnPlayHandlers].push(handler);
|
|
99
|
+
case Reproduction.EVENTS.PLAYING:
|
|
100
|
+
return this[dispatchOnPlayingHandlers].push(handler);
|
|
101
|
+
case Reproduction.EVENTS.PAUSED:
|
|
102
|
+
return this[dispatchOnPausedHandlers].push(handler);
|
|
103
|
+
case Reproduction.EVENTS.FINISH:
|
|
104
|
+
return this[dispatchOnFinishHandlers].push(handler);
|
|
105
|
+
default:
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
dispatch(eventName: keyof typeof Reproduction.EVENTS) {
|
|
111
|
+
let handler, i, len;
|
|
112
|
+
|
|
113
|
+
let ref: (() => void)[] = [];
|
|
114
|
+
|
|
115
|
+
switch (eventName) {
|
|
116
|
+
case Reproduction.EVENTS.READY:
|
|
117
|
+
ref = this[dispatchOnReadyHandlers];
|
|
118
|
+
break;
|
|
119
|
+
case Reproduction.EVENTS.START:
|
|
120
|
+
ref = this[dispatchOnSongStartHandlers];
|
|
121
|
+
break;
|
|
122
|
+
case Reproduction.EVENTS.COUNTING_IN:
|
|
123
|
+
ref = this[dispatchOnCountingInHandlers];
|
|
124
|
+
break;
|
|
125
|
+
case Reproduction.EVENTS.PLAY:
|
|
126
|
+
ref = this[dispatchOnPlayHandlers];
|
|
127
|
+
break;
|
|
128
|
+
case Reproduction.EVENTS.PLAYING:
|
|
129
|
+
ref = this[dispatchOnPlayingHandlers];
|
|
130
|
+
break;
|
|
131
|
+
case Reproduction.EVENTS.PAUSED:
|
|
132
|
+
ref = this[dispatchOnPausedHandlers];
|
|
133
|
+
break;
|
|
134
|
+
case Reproduction.EVENTS.FINISH:
|
|
135
|
+
ref = this[dispatchOnFinishHandlers];
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
|
142
|
+
handler = ref[i];
|
|
143
|
+
handler();
|
|
144
|
+
// setTimeout(handler, 0);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private countIn(timeout: number, limit: number) {
|
|
149
|
+
// the initial count starts instantly, no waiting
|
|
150
|
+
this.countingInCounter++;
|
|
151
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN);
|
|
152
|
+
|
|
153
|
+
const interval = setInterval(() => {
|
|
154
|
+
this.countingInCounter++;
|
|
155
|
+
if (this.countingInCounter === limit) {
|
|
156
|
+
clearInterval(interval);
|
|
157
|
+
this.countingInCounter = 0;
|
|
158
|
+
if (limit !== 5) {
|
|
159
|
+
this.countIn(this.getBPMInterval(), 5);
|
|
160
|
+
} else {
|
|
161
|
+
this.play();
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
this.dispatch(Reproduction.EVENTS.COUNTING_IN);
|
|
165
|
+
}
|
|
166
|
+
}, timeout);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
start() {
|
|
170
|
+
if (this.state === Reproduction.STATES.STOPPED) {
|
|
171
|
+
this.dispatch(Reproduction.EVENTS.START);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.requiresCountingIn && this.getCurrentTime() === 0) {
|
|
175
|
+
this.state = Reproduction.STATES.COUNTING_IN;
|
|
176
|
+
this.countIn(this.getBPMInterval() * 2, 3);
|
|
177
|
+
} else {
|
|
178
|
+
this.play();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
play() {
|
|
183
|
+
this.state = Reproduction.STATES.PLAYING;
|
|
184
|
+
this.dispatch(Reproduction.EVENTS.PLAY);
|
|
185
|
+
this.player.play();
|
|
186
|
+
|
|
187
|
+
const intervalTimeout = 200;
|
|
188
|
+
|
|
189
|
+
this.interval = setInterval(() => {
|
|
190
|
+
if (this.isPlaying()) {
|
|
191
|
+
this.dispatch(Reproduction.EVENTS.PLAYING);
|
|
192
|
+
}
|
|
193
|
+
}, intervalTimeout);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
pause() {
|
|
197
|
+
this.state = Reproduction.STATES.PAUSED;
|
|
198
|
+
this.player.pause();
|
|
199
|
+
clearInterval(this.interval as NodeJS.Timeout);
|
|
200
|
+
|
|
201
|
+
this.dispatch(Reproduction.EVENTS.PAUSED);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
stop() {
|
|
205
|
+
this.state = Reproduction.STATES.STOPPED;
|
|
206
|
+
this.player.stop();
|
|
207
|
+
clearInterval(this.interval as NodeJS.Timeout);
|
|
208
|
+
this.dispatch(Reproduction.EVENTS.FINISH);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
isReady() {
|
|
212
|
+
// It's necessary to avoid play the reproduction-widget when the player is not ready
|
|
213
|
+
return this.ready;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
isPlaying() {
|
|
217
|
+
return this.state === Reproduction.STATES.PLAYING;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
isStopped() {
|
|
221
|
+
return this.state === Reproduction.STATES.STOPPED;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isPaused() {
|
|
225
|
+
return this.state === Reproduction.STATES.PAUSED;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
isCountingIn() {
|
|
229
|
+
return this.state === Reproduction.STATES.COUNTING_IN;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getPlayer() {
|
|
233
|
+
return this.player;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getTempo() {
|
|
237
|
+
return this.songTempo;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getCurrentTime() {
|
|
241
|
+
// in seconds with milliseconds.
|
|
242
|
+
return this.player.getCurrentTime();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getDuration() {
|
|
246
|
+
return this.player.getDuration();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
seekTo(seconds: number) {
|
|
250
|
+
this.player.seekTo(seconds);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getAvailablePlaybackRates() {
|
|
254
|
+
return this.player.getAvailablePlaybackRates();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setPlaybackRate(playbackRate: number) {
|
|
258
|
+
this.player.setPlaybackRate(playbackRate);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
isAvailable() {
|
|
262
|
+
return this.player.isAvailable();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getBPMInterval() {
|
|
266
|
+
return 60000 / this.getTempo();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static newBuilder() {
|
|
270
|
+
return new ReproductionBuilder();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { YouTubePlayer } from './Player/YouTubePlayer';
|
|
2
|
+
import { PlayAlongPlayer } from './Player/PlayAlongPlayer';
|
|
3
|
+
import { Reproduction } from './Reproduction';
|
|
4
|
+
export class ReproductionBuilder {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.trainingMode = false;
|
|
7
|
+
this.requiresCountingIn = false;
|
|
8
|
+
this.songDuration = null;
|
|
9
|
+
this.songTempo = null;
|
|
10
|
+
this.innerPlayer = null;
|
|
11
|
+
}
|
|
12
|
+
withSongDuration(songDuration) {
|
|
13
|
+
this.songDuration = songDuration;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
withSongTempo(songTempo) {
|
|
17
|
+
this.songTempo = songTempo;
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
withTrainingMode(trainingMode) {
|
|
21
|
+
this.trainingMode = trainingMode;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
withCountingIn(requiresCountingIn) {
|
|
25
|
+
this.requiresCountingIn = requiresCountingIn;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
withInnerPlayer(innerPlayer) {
|
|
29
|
+
this.innerPlayer = innerPlayer;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
createReproduction() {
|
|
33
|
+
if (this.requiresCountingIn && this.songTempo === null) {
|
|
34
|
+
throw new Error('The song tempo is mandatory');
|
|
35
|
+
}
|
|
36
|
+
if (this.innerPlayer === null) {
|
|
37
|
+
throw new Error('The inner player was not provided.');
|
|
38
|
+
}
|
|
39
|
+
let player;
|
|
40
|
+
if (this.trainingMode) {
|
|
41
|
+
player = new YouTubePlayer(this.innerPlayer);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (this.songDuration === null) {
|
|
45
|
+
throw new Error('The song duration is mandatory');
|
|
46
|
+
}
|
|
47
|
+
player = new PlayAlongPlayer(this.songDuration, this.innerPlayer);
|
|
48
|
+
}
|
|
49
|
+
return new Reproduction(player, this.requiresCountingIn, this.songTempo || 0);
|
|
50
|
+
}
|
|
51
|
+
}
|