@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.
@@ -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.volume;
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())),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldelia/react-media",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "A React components collection for media-related features.",
5
5
  "private": false,
6
6
  "keywords": [