@ldelia/react-media 0.8.1 → 0.8.2
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/dist/components/reproduction-widget/models/Player/PlayAlongPlayer.d.ts +4 -1
- package/dist/components/reproduction-widget/models/Player/PlayAlongPlayer.js +4 -7
- package/dist/components/reproduction-widget/models/Player/YouTubePlayer.d.ts +4 -0
- package/dist/components/reproduction-widget/models/Player/YouTubePlayer.js +30 -6
- package/dist/components/reproduction-widget/models/Reproduction.d.ts +0 -4
- package/dist/components/reproduction-widget/models/Reproduction.js +4 -20
- package/dist/stories/reproduction-widget.stories.js +7 -1
- package/package.json +1 -1
|
@@ -19,7 +19,6 @@ export declare class PlayAlongPlayer {
|
|
|
19
19
|
pause(): void;
|
|
20
20
|
stop(): void;
|
|
21
21
|
seekTo(seconds: number): void;
|
|
22
|
-
setVolume(volume: number): void;
|
|
23
22
|
getCurrentTime(): number;
|
|
24
23
|
getDuration(): number;
|
|
25
24
|
isAvailable(): boolean;
|
|
@@ -27,6 +26,10 @@ export declare class PlayAlongPlayer {
|
|
|
27
26
|
setPlaybackRate(playbackRate: number): void;
|
|
28
27
|
on(eventName: keyof typeof PlayAlongPlayer.EVENTS, handler: () => void): number | undefined;
|
|
29
28
|
dispatch(eventName: keyof typeof PlayAlongPlayer.EVENTS): void;
|
|
29
|
+
countingStarted(): void;
|
|
30
|
+
countingFinished(): void;
|
|
31
|
+
setVolume(volume: number): void;
|
|
32
|
+
getVolume(): number;
|
|
30
33
|
private setInnerPlayer;
|
|
31
34
|
}
|
|
32
35
|
export {};
|
|
@@ -44,7 +44,6 @@ export class PlayAlongPlayer {
|
|
|
44
44
|
seekTo(seconds) {
|
|
45
45
|
this.currentTime = seconds;
|
|
46
46
|
}
|
|
47
|
-
setVolume(volume) { }
|
|
48
47
|
getCurrentTime() {
|
|
49
48
|
return this.currentTime;
|
|
50
49
|
}
|
|
@@ -65,8 +64,6 @@ export class PlayAlongPlayer {
|
|
|
65
64
|
}
|
|
66
65
|
on(eventName, handler) {
|
|
67
66
|
switch (eventName) {
|
|
68
|
-
case PlayAlongPlayer.EVENTS.READY:
|
|
69
|
-
return this[dispatchOnReadyHandlers].push(handler);
|
|
70
67
|
case PlayAlongPlayer.EVENTS.FINISH:
|
|
71
68
|
return this[dispatchOnFinishHandlers].push(handler);
|
|
72
69
|
default:
|
|
@@ -77,9 +74,6 @@ export class PlayAlongPlayer {
|
|
|
77
74
|
let handler, i, len;
|
|
78
75
|
let ref = [];
|
|
79
76
|
switch (eventName) {
|
|
80
|
-
case PlayAlongPlayer.EVENTS.READY:
|
|
81
|
-
ref = this[dispatchOnReadyHandlers];
|
|
82
|
-
break;
|
|
83
77
|
case PlayAlongPlayer.EVENTS.FINISH:
|
|
84
78
|
ref = this[dispatchOnFinishHandlers];
|
|
85
79
|
break;
|
|
@@ -91,8 +85,11 @@ export class PlayAlongPlayer {
|
|
|
91
85
|
handler();
|
|
92
86
|
}
|
|
93
87
|
}
|
|
88
|
+
countingStarted() { }
|
|
89
|
+
countingFinished() { }
|
|
90
|
+
setVolume(volume) { }
|
|
91
|
+
getVolume() { return 0; }
|
|
94
92
|
setInnerPlayer(innerPlayer) {
|
|
95
93
|
this.innerPlayer = innerPlayer;
|
|
96
|
-
this.dispatch(PlayAlongPlayer.EVENTS.READY);
|
|
97
94
|
}
|
|
98
95
|
}
|
|
@@ -7,6 +7,7 @@ declare const dispatchOnErrorHandlers: unique symbol;
|
|
|
7
7
|
export declare class YouTubePlayer {
|
|
8
8
|
private currentTime;
|
|
9
9
|
private isRunning;
|
|
10
|
+
private volume;
|
|
10
11
|
private innerPlayer;
|
|
11
12
|
private [dispatchOnReadyHandlers];
|
|
12
13
|
private [dispatchOnFinishHandlers];
|
|
@@ -23,6 +24,7 @@ export declare class YouTubePlayer {
|
|
|
23
24
|
stop(): void;
|
|
24
25
|
seekTo(seconds: number): void;
|
|
25
26
|
setVolume(volume: number): void;
|
|
27
|
+
getVolume(): number;
|
|
26
28
|
getCurrentTime(): number;
|
|
27
29
|
getDuration(): number | undefined;
|
|
28
30
|
getAvailablePlaybackRates(): number[];
|
|
@@ -30,6 +32,8 @@ export declare class YouTubePlayer {
|
|
|
30
32
|
isAvailable(): boolean;
|
|
31
33
|
on(eventName: keyof typeof PlayAlongPlayer.EVENTS, handler: (error?: any) => void): number | undefined;
|
|
32
34
|
dispatch(eventName: keyof typeof PlayAlongPlayer.EVENTS, error?: any): void;
|
|
35
|
+
countingStarted(): void;
|
|
36
|
+
countingFinished(): void;
|
|
33
37
|
private getErrorMessage;
|
|
34
38
|
}
|
|
35
39
|
export {};
|
|
@@ -5,13 +5,13 @@ const dispatchOnFinishHandlers = Symbol();
|
|
|
5
5
|
const dispatchOnErrorHandlers = Symbol();
|
|
6
6
|
export class YouTubePlayer {
|
|
7
7
|
constructor(innerPlayer) {
|
|
8
|
+
this.volume = 50; // between 0 and 100
|
|
8
9
|
this[dispatchOnFinishHandlers] = [];
|
|
9
10
|
this[dispatchOnReadyHandlers] = [];
|
|
10
11
|
this[dispatchOnErrorHandlers] = [];
|
|
11
12
|
this.currentTime = 0;
|
|
12
13
|
this.isRunning = false;
|
|
13
14
|
this.innerPlayer = innerPlayer;
|
|
14
|
-
this.dispatch(YouTubePlayer.EVENTS.READY);
|
|
15
15
|
// This is necessary for avoiding the state video cued.
|
|
16
16
|
// When a video is in this state, when the user seeks to X, the song is played
|
|
17
17
|
this.innerPlayer.playVideo();
|
|
@@ -70,8 +70,12 @@ export class YouTubePlayer {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
setVolume(volume) {
|
|
73
|
+
this.volume = volume;
|
|
73
74
|
this.getInnerPlayer().setVolume(volume);
|
|
74
75
|
}
|
|
76
|
+
getVolume() {
|
|
77
|
+
return this.volume;
|
|
78
|
+
}
|
|
75
79
|
getCurrentTime() {
|
|
76
80
|
return this.isRunning
|
|
77
81
|
? this.getInnerPlayer().getCurrentTime()
|
|
@@ -96,8 +100,6 @@ export class YouTubePlayer {
|
|
|
96
100
|
}
|
|
97
101
|
on(eventName, handler) {
|
|
98
102
|
switch (eventName) {
|
|
99
|
-
case PlayAlongPlayer.EVENTS.READY:
|
|
100
|
-
return this[dispatchOnReadyHandlers].push(handler);
|
|
101
103
|
case PlayAlongPlayer.EVENTS.FINISH:
|
|
102
104
|
return this[dispatchOnFinishHandlers].push(handler);
|
|
103
105
|
case PlayAlongPlayer.EVENTS.ERROR:
|
|
@@ -112,9 +114,6 @@ export class YouTubePlayer {
|
|
|
112
114
|
let len;
|
|
113
115
|
let ref = [];
|
|
114
116
|
switch (eventName) {
|
|
115
|
-
case YouTubePlayer.EVENTS.READY:
|
|
116
|
-
ref = this[dispatchOnReadyHandlers];
|
|
117
|
-
break;
|
|
118
117
|
case YouTubePlayer.EVENTS.FINISH:
|
|
119
118
|
ref = this[dispatchOnFinishHandlers];
|
|
120
119
|
break;
|
|
@@ -129,6 +128,31 @@ export class YouTubePlayer {
|
|
|
129
128
|
setTimeout(() => handler(error), 0);
|
|
130
129
|
}
|
|
131
130
|
}
|
|
131
|
+
countingStarted() {
|
|
132
|
+
/**
|
|
133
|
+
* iOS browsers enforce strict autoplay policies that require video playback
|
|
134
|
+
* to begin synchronously within a user interaction event (e.g., a tap or click).
|
|
135
|
+
* When we implemented the counting-in feature (1, 2, 1, 2, 3, go...),
|
|
136
|
+
* the asynchronous delays between counts caused iOS to lose the user interaction context,
|
|
137
|
+
* blocking video playback after the countdown completed.
|
|
138
|
+
* The video would work fine on web and Android, but fail to start on iOS Safari.
|
|
139
|
+
* To resolve this, we adopted a muted-first approach:
|
|
140
|
+
* the video begins playing immediately when the user initiates playback,
|
|
141
|
+
* but remains muted during the countdown sequence.
|
|
142
|
+
* Once the countdown completes, we unmute the video and restore the desired volume.
|
|
143
|
+
* This strategy satisfies iOS's autoplay requirements (muted videos can autoplay)
|
|
144
|
+
* while preserving the musical countdown experience.
|
|
145
|
+
* The user never notices the video is playing during the countdown
|
|
146
|
+
* since it's both muted and visually obscured by the countdown overlay.
|
|
147
|
+
* */
|
|
148
|
+
this.getInnerPlayer().setVolume(0);
|
|
149
|
+
this.getInnerPlayer().playVideo();
|
|
150
|
+
}
|
|
151
|
+
countingFinished() {
|
|
152
|
+
this.getInnerPlayer().pauseVideo();
|
|
153
|
+
this.getInnerPlayer().seekTo(0, true);
|
|
154
|
+
this.setVolume(this.volume);
|
|
155
|
+
}
|
|
132
156
|
getErrorMessage(errorCode) {
|
|
133
157
|
switch (errorCode) {
|
|
134
158
|
case YT.PlayerError.InvalidParam:
|
|
@@ -16,10 +16,8 @@ export declare class Reproduction {
|
|
|
16
16
|
private requiresCountingIn;
|
|
17
17
|
private songTempo;
|
|
18
18
|
private state;
|
|
19
|
-
private ready;
|
|
20
19
|
private interval;
|
|
21
20
|
private countingInCounter;
|
|
22
|
-
private volume;
|
|
23
21
|
private [dispatchOnReadyHandlers];
|
|
24
22
|
private [dispatchOnSongStartHandlers];
|
|
25
23
|
private [dispatchOnCountingInHandlers];
|
|
@@ -30,7 +28,6 @@ export declare class Reproduction {
|
|
|
30
28
|
private [dispatchOnErrorHandlers];
|
|
31
29
|
constructor(player: Player, requiresCountingIn: boolean, songTempo: number, volume: number);
|
|
32
30
|
static get EVENTS(): {
|
|
33
|
-
readonly READY: "READY";
|
|
34
31
|
readonly START: "START";
|
|
35
32
|
readonly COUNTING_IN: "COUNTING_IN";
|
|
36
33
|
readonly PLAY: "PLAY";
|
|
@@ -52,7 +49,6 @@ export declare class Reproduction {
|
|
|
52
49
|
play(): void;
|
|
53
50
|
pause(): void;
|
|
54
51
|
stop(): void;
|
|
55
|
-
isReady(): boolean;
|
|
56
52
|
isPlaying(): boolean;
|
|
57
53
|
isStopped(): boolean;
|
|
58
54
|
isPaused(): boolean;
|
|
@@ -7,7 +7,6 @@ const STATES = {
|
|
|
7
7
|
PAUSED: 3,
|
|
8
8
|
};
|
|
9
9
|
const EVENTS = {
|
|
10
|
-
READY: 'READY',
|
|
11
10
|
START: 'START',
|
|
12
11
|
COUNTING_IN: 'COUNTING_IN',
|
|
13
12
|
PLAY: 'PLAY',
|
|
@@ -26,7 +25,6 @@ const dispatchOnFinishHandlers = Symbol();
|
|
|
26
25
|
const dispatchOnErrorHandlers = Symbol();
|
|
27
26
|
export class Reproduction {
|
|
28
27
|
constructor(player, requiresCountingIn, songTempo, volume) {
|
|
29
|
-
this.volume = 50; // between 0 and 100
|
|
30
28
|
this[dispatchOnReadyHandlers] = [];
|
|
31
29
|
this[dispatchOnSongStartHandlers] = [];
|
|
32
30
|
this[dispatchOnCountingInHandlers] = [];
|
|
@@ -37,16 +35,11 @@ export class Reproduction {
|
|
|
37
35
|
this[dispatchOnErrorHandlers] = [];
|
|
38
36
|
this.songTempo = songTempo;
|
|
39
37
|
this.player = player;
|
|
40
|
-
this.ready = false;
|
|
41
38
|
this.state = Reproduction.STATES.STOPPED;
|
|
42
39
|
this.interval = null;
|
|
43
40
|
this.requiresCountingIn = requiresCountingIn;
|
|
44
41
|
this.countingInCounter = 0;
|
|
45
|
-
this.setVolume(volume);
|
|
46
|
-
this.player.on(PLAYER_EVENTS.READY, () => {
|
|
47
|
-
this.ready = true;
|
|
48
|
-
this.dispatch(Reproduction.EVENTS.READY);
|
|
49
|
-
});
|
|
42
|
+
this.player.setVolume(volume);
|
|
50
43
|
this.player.on(PLAYER_EVENTS.FINISH, () => {
|
|
51
44
|
this.state = Reproduction.STATES.STOPPED;
|
|
52
45
|
clearInterval(this.interval);
|
|
@@ -67,8 +60,6 @@ export class Reproduction {
|
|
|
67
60
|
}
|
|
68
61
|
on(eventName, handler) {
|
|
69
62
|
switch (eventName) {
|
|
70
|
-
case Reproduction.EVENTS.READY:
|
|
71
|
-
return this[dispatchOnReadyHandlers].push(handler);
|
|
72
63
|
case Reproduction.EVENTS.START:
|
|
73
64
|
return this[dispatchOnSongStartHandlers].push(handler);
|
|
74
65
|
case Reproduction.EVENTS.COUNTING_IN:
|
|
@@ -91,9 +82,6 @@ export class Reproduction {
|
|
|
91
82
|
let handler, i, len;
|
|
92
83
|
let ref = [];
|
|
93
84
|
switch (eventName) {
|
|
94
|
-
case Reproduction.EVENTS.READY:
|
|
95
|
-
ref = this[dispatchOnReadyHandlers];
|
|
96
|
-
break;
|
|
97
85
|
case Reproduction.EVENTS.START:
|
|
98
86
|
ref = this[dispatchOnSongStartHandlers];
|
|
99
87
|
break;
|
|
@@ -132,7 +120,6 @@ export class Reproduction {
|
|
|
132
120
|
this.countInAndPlay(this.getBPMInterval() * 2, 3);
|
|
133
121
|
}
|
|
134
122
|
else {
|
|
135
|
-
this.seekTo(0);
|
|
136
123
|
this.play();
|
|
137
124
|
}
|
|
138
125
|
}
|
|
@@ -159,10 +146,6 @@ export class Reproduction {
|
|
|
159
146
|
clearInterval(this.interval);
|
|
160
147
|
this.dispatch(Reproduction.EVENTS.FINISH);
|
|
161
148
|
}
|
|
162
|
-
isReady() {
|
|
163
|
-
// It's necessary to avoid play the reproduction-widget when the player is not ready
|
|
164
|
-
return this.ready;
|
|
165
|
-
}
|
|
166
149
|
isPlaying() {
|
|
167
150
|
return this.state === Reproduction.STATES.PLAYING;
|
|
168
151
|
}
|
|
@@ -192,7 +175,7 @@ export class Reproduction {
|
|
|
192
175
|
this.player.seekTo(seconds);
|
|
193
176
|
}
|
|
194
177
|
getVolume() {
|
|
195
|
-
return this.
|
|
178
|
+
return this.player.getVolume();
|
|
196
179
|
}
|
|
197
180
|
setVolume(volume) {
|
|
198
181
|
if (volume < 0) {
|
|
@@ -201,7 +184,6 @@ export class Reproduction {
|
|
|
201
184
|
else if (volume > 100) {
|
|
202
185
|
volume = 100;
|
|
203
186
|
}
|
|
204
|
-
this.volume = volume;
|
|
205
187
|
this.player.setVolume(volume);
|
|
206
188
|
}
|
|
207
189
|
getAvailablePlaybackRates() {
|
|
@@ -218,6 +200,7 @@ export class Reproduction {
|
|
|
218
200
|
}
|
|
219
201
|
countInAndPlay(timeout, limit) {
|
|
220
202
|
// the initial count starts instantly, no need to wait
|
|
203
|
+
this.player.countingStarted();
|
|
221
204
|
this.countingInCounter++;
|
|
222
205
|
this.dispatch(Reproduction.EVENTS.COUNTING_IN, { countingInCounter: this.countingInCounter });
|
|
223
206
|
const interval = setInterval(() => {
|
|
@@ -229,6 +212,7 @@ export class Reproduction {
|
|
|
229
212
|
this.countInAndPlay(this.getBPMInterval(), 5);
|
|
230
213
|
}
|
|
231
214
|
else {
|
|
215
|
+
this.player.countingFinished();
|
|
232
216
|
this.play();
|
|
233
217
|
}
|
|
234
218
|
}
|
|
@@ -35,12 +35,18 @@ const Template = (args) => {
|
|
|
35
35
|
reproduction.play();
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
+
const handleStart = () => {
|
|
39
|
+
if (reproduction) {
|
|
40
|
+
reproduction.start();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
38
43
|
return (React.createElement("div", null,
|
|
39
44
|
React.createElement(ReproductionWidget, Object.assign({}, args, { onInit: handleInit })),
|
|
40
45
|
React.createElement("div", null,
|
|
41
46
|
React.createElement("button", { onClick: handleStop, disabled: !reproduction || reproduction.isStopped() }, "Stop"),
|
|
42
47
|
React.createElement("button", { onClick: handlePause, disabled: !reproduction || !reproduction.isPlaying() }, "Pause"),
|
|
43
|
-
React.createElement("button", { onClick: handleResume, disabled: !reproduction || reproduction.isPlaying() }, "Resume"),
|
|
48
|
+
React.createElement("button", { onClick: handleResume, disabled: !reproduction || reproduction.isPlaying() || reproduction.getCurrentTime() > 0 }, "Resume"),
|
|
49
|
+
React.createElement("button", { onClick: handleStart, disabled: !reproduction || reproduction.isPlaying() }, "Start"),
|
|
44
50
|
reproduction && (React.createElement("div", null,
|
|
45
51
|
"Current time: ", reproduction === null || reproduction === void 0 ? void 0 :
|
|
46
52
|
reproduction.getCurrentTime())),
|