@ldelia/react-media 0.5.3 → 0.5.5

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.
@@ -7,12 +7,14 @@ interface BaseProps {
7
7
  }
8
8
  interface TrainingProps extends BaseProps {
9
9
  trainingMode: true;
10
+ duration?: never;
10
11
  videoId: string;
11
12
  }
12
13
  interface NonTrainingProps extends BaseProps {
13
14
  trainingMode: false;
15
+ duration: number;
14
16
  videoId?: never;
15
17
  }
16
18
  export type ReproductionWidgetProps = TrainingProps | NonTrainingProps;
17
- export declare const ReproductionWidget: ({ trainingMode, videoId, songTempo, onInit, }: ReproductionWidgetProps) => React.JSX.Element;
19
+ export declare const ReproductionWidget: ({ trainingMode, duration, videoId, songTempo, onInit, }: ReproductionWidgetProps) => React.JSX.Element;
18
20
  export {};
@@ -2,12 +2,11 @@ import React from 'react';
2
2
  import { YouTubeInnerPlayer } from './inner-players/YouTubeInnerPlayer';
3
3
  import { PlayAlongInnerPlayer } from './inner-players/PlayAlongInnerPlayer';
4
4
  import { Reproduction } from './models/Reproduction';
5
- export const ReproductionWidget = ({ trainingMode, videoId, songTempo = 0, onInit, }) => {
6
- const DURATION_TO_TEST = 30;
5
+ export const ReproductionWidget = ({ trainingMode, duration, videoId, songTempo = 0, onInit, }) => {
7
6
  function onPlayAlongInnerPlayerReadyHandler(event) {
8
7
  let newReproduction = Reproduction.newBuilder()
9
8
  .withTrainingMode(false)
10
- .withSongDuration(DURATION_TO_TEST)
9
+ .withSongDuration(duration)
11
10
  .withSongTempo(songTempo)
12
11
  .withCountingIn(songTempo > 0)
13
12
  .withInnerPlayer(event.target)
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { YouTubePlayer as InnerYouTubePlayer } from 'react-youtube';
2
+ import { InnerYouTubePlayerInterface } from '../models/Player/YouTubePlayer';
3
3
  interface Props {
4
4
  videoId: string;
5
5
  onReady: (event: {
6
- target: InnerYouTubePlayer;
6
+ target: InnerYouTubePlayerInterface;
7
7
  }) => void;
8
8
  }
9
- export declare const YouTubeInnerPlayer: ({ videoId, onReady }: Props) => React.JSX.Element | null;
9
+ export declare const YouTubeInnerPlayer: ({ videoId, onReady }: Props) => React.JSX.Element;
10
10
  export {};
@@ -1,24 +1,5 @@
1
- import React, { useEffect, useState } from 'react';
2
- import YouTube from 'react-youtube';
1
+ import React from 'react';
2
+ import ReactPlayer from 'react-player/lazy';
3
3
  export const YouTubeInnerPlayer = ({ videoId, onReady }) => {
4
- const [origin, setOrigin] = useState(null);
5
- useEffect(() => {
6
- if (typeof window !== 'undefined') {
7
- setOrigin(window.location.href);
8
- }
9
- }, []);
10
- const opts = {
11
- playerVars: {
12
- // https://developers.google.com/youtube/player_parameters
13
- autoplay: 0,
14
- controls: 0,
15
- showinfo: 0,
16
- enablejsapi: 1,
17
- origin: origin,
18
- },
19
- };
20
- if (origin === null) {
21
- return null;
22
- }
23
- return (React.createElement(YouTube, { id: 'YT-' + videoId, className: 'youtube-player', videoId: videoId, opts: opts, onReady: onReady }));
4
+ return (React.createElement(ReactPlayer, { url: `https://www.youtube.com/watch?v=${videoId}`, onReady: (event) => onReady({ target: event.getInternalPlayer() }) }));
24
5
  };
@@ -1,5 +1,6 @@
1
- import { YouTubePlayer as InnerYouTubePlayer } from 'react-youtube';
1
+ /// <reference types="youtube" />
2
2
  import { PlayAlongPlayer } from './PlayAlongPlayer';
3
+ export type InnerYouTubePlayerInterface = YT.Player;
3
4
  declare const dispatchOnReadyHandlers: unique symbol;
4
5
  declare const dispatchOnFinishHandlers: unique symbol;
5
6
  export declare class YouTubePlayer {
@@ -12,16 +13,15 @@ export declare class YouTubePlayer {
12
13
  readonly READY: "READY";
13
14
  readonly FINISH: "FINISH";
14
15
  };
15
- constructor(innerPlayer: InnerYouTubePlayer);
16
- private setInnerPlayer;
17
- getInnerPlayer(): any;
16
+ constructor(innerPlayer: InnerYouTubePlayerInterface);
17
+ getInnerPlayer(): YT.Player;
18
18
  play(): void;
19
19
  pause(): void;
20
20
  stop(): void;
21
21
  seekTo(seconds: number): void;
22
- getCurrentTime(): any;
23
- getDuration(): any;
24
- getAvailablePlaybackRates(): any;
22
+ getCurrentTime(): number;
23
+ getDuration(): number | undefined;
24
+ getAvailablePlaybackRates(): number[];
25
25
  setPlaybackRate(playbackRate: number): void;
26
26
  isAvailable(): boolean;
27
27
  on(eventName: keyof typeof PlayAlongPlayer.EVENTS, handler: () => void): number | undefined;
@@ -1,15 +1,5 @@
1
1
  import { PlayAlongPlayer } from './PlayAlongPlayer';
2
2
  import { PLAYER_EVENTS } from './PlayerEvents';
3
- // https://developers.google.com/youtube/iframe_api_reference
4
- var INNER_YOUTUBE_PLAYER_EVENTS;
5
- (function (INNER_YOUTUBE_PLAYER_EVENTS) {
6
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_UNSTARTED"] = -1] = "VIDEO_UNSTARTED";
7
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_ENDED"] = 0] = "VIDEO_ENDED";
8
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_PLAYING"] = 1] = "VIDEO_PLAYING";
9
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_PAUSED"] = 2] = "VIDEO_PAUSED";
10
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_BUFFERING"] = 3] = "VIDEO_BUFFERING";
11
- INNER_YOUTUBE_PLAYER_EVENTS[INNER_YOUTUBE_PLAYER_EVENTS["VIDEO_CUED"] = 5] = "VIDEO_CUED";
12
- })(INNER_YOUTUBE_PLAYER_EVENTS || (INNER_YOUTUBE_PLAYER_EVENTS = {}));
13
3
  const dispatchOnReadyHandlers = Symbol();
14
4
  const dispatchOnFinishHandlers = Symbol();
15
5
  export class YouTubePlayer {
@@ -21,9 +11,7 @@ export class YouTubePlayer {
21
11
  this[dispatchOnReadyHandlers] = [];
22
12
  this.currentTime = 0;
23
13
  this.isRunning = false;
24
- this.setInnerPlayer(innerPlayer);
25
- }
26
- setInnerPlayer(innerPlayer) {
14
+ this.innerPlayer = innerPlayer;
27
15
  this.innerPlayer = innerPlayer;
28
16
  this.dispatch(YouTubePlayer.EVENTS.READY);
29
17
  // this is necessary for avoiding the state video cued.
@@ -32,7 +20,7 @@ export class YouTubePlayer {
32
20
  this.innerPlayer.pauseVideo();
33
21
  this.innerPlayer.addEventListener('onStateChange', (videoState) => {
34
22
  switch (videoState.data) {
35
- case INNER_YOUTUBE_PLAYER_EVENTS.VIDEO_ENDED:
23
+ case YT.PlayerState.ENDED:
36
24
  this.dispatch(YouTubePlayer.EVENTS.FINISH);
37
25
  this.isRunning = false;
38
26
  this.currentTime = 0;
@@ -68,7 +56,7 @@ export class YouTubePlayer {
68
56
  seekTo(seconds) {
69
57
  const videoPlayer = this.getInnerPlayer();
70
58
  this.currentTime = seconds;
71
- videoPlayer.seekTo(this.currentTime);
59
+ videoPlayer.seekTo(this.currentTime, true);
72
60
  if (this.isRunning) {
73
61
  this.play();
74
62
  }
@@ -43,7 +43,7 @@ export declare class Reproduction {
43
43
  constructor(player: Player, requiresCountingIn: boolean, songTempo: number);
44
44
  on(eventName: keyof typeof Reproduction.EVENTS, handler: Handler): number | undefined;
45
45
  dispatch(eventName: keyof typeof Reproduction.EVENTS, args?: {}): void;
46
- private countIn;
46
+ private countInAndPlay;
47
47
  start(): void;
48
48
  play(): void;
49
49
  pause(): void;
@@ -55,10 +55,10 @@ export declare class Reproduction {
55
55
  isCountingIn(): boolean;
56
56
  getPlayer(): Player;
57
57
  getTempo(): number;
58
- getCurrentTime(): any;
59
- getDuration(): any;
58
+ getCurrentTime(): number;
59
+ getDuration(): number | undefined;
60
60
  seekTo(seconds: number): void;
61
- getAvailablePlaybackRates(): any;
61
+ getAvailablePlaybackRates(): number[];
62
62
  setPlaybackRate(playbackRate: number): void;
63
63
  isAvailable(): boolean;
64
64
  getBPMInterval(): number;
@@ -108,7 +108,7 @@ export class Reproduction {
108
108
  // setTimeout(handler, 0);
109
109
  }
110
110
  }
111
- countIn(timeout, limit) {
111
+ countInAndPlay(timeout, limit) {
112
112
  // the initial count starts instantly, no need to wait
113
113
  this.countingInCounter++;
114
114
  this.dispatch(Reproduction.EVENTS.COUNTING_IN, { countingInCounter: this.countingInCounter });
@@ -118,7 +118,7 @@ export class Reproduction {
118
118
  clearInterval(interval);
119
119
  this.countingInCounter = 0;
120
120
  if (limit !== 5) {
121
- this.countIn(this.getBPMInterval(), 5);
121
+ this.countInAndPlay(this.getBPMInterval(), 5);
122
122
  }
123
123
  else {
124
124
  this.play();
@@ -135,7 +135,7 @@ export class Reproduction {
135
135
  }
136
136
  if (this.requiresCountingIn && this.getCurrentTime() === 0) {
137
137
  this.state = Reproduction.STATES.COUNTING_IN;
138
- this.countIn(this.getBPMInterval() * 2, 3);
138
+ this.countInAndPlay(this.getBPMInterval() * 2, 3);
139
139
  }
140
140
  else {
141
141
  this.play();
@@ -1,5 +1,5 @@
1
1
  import { Reproduction } from './Reproduction';
2
- import { YouTubePlayer as InnerYouTubePlayer } from 'react-youtube';
2
+ import { InnerYouTubePlayerInterface } from './Player/YouTubePlayer';
3
3
  export declare class ReproductionBuilder {
4
4
  private trainingMode;
5
5
  private requiresCountingIn;
@@ -11,6 +11,6 @@ export declare class ReproductionBuilder {
11
11
  withSongTempo(songTempo: number): this;
12
12
  withTrainingMode(trainingMode: boolean): this;
13
13
  withCountingIn(requiresCountingIn: boolean): this;
14
- withInnerPlayer(innerPlayer: InnerYouTubePlayer | string): this;
14
+ withInnerPlayer(innerPlayer: InnerYouTubePlayerInterface | string): this;
15
15
  createReproduction(): Reproduction;
16
16
  }
@@ -6,6 +6,7 @@ export interface TimelineProps {
6
6
  className?: string;
7
7
  selectedRange?: number[];
8
8
  withTimeBlocks?: boolean;
9
+ markers?: number[];
9
10
  onChange?: (value: number) => void;
10
11
  onRangeChange?: (value: number[]) => void;
11
12
  }
@@ -6,6 +6,7 @@ import styled from 'styled-components';
6
6
  import { TimelineValue } from './TimelineValue/TimelineValue';
7
7
  import { ZoomContext } from './ZoomContext/ZoomContext';
8
8
  import { getBlockOffsetForZoomLevel, getTimelineWrapperWidth, numberToPxString, } from './utils/utils';
9
+ import { TimelineMarkers } from './TimelineMarkers/TimelineMarkers';
9
10
  const TimelineContainer = styled.div `
10
11
  background-color: #f0f0f0;
11
12
  border: 1px solid #c9c9c9;
@@ -24,7 +25,7 @@ const TimelineWrapper = styled.div `
24
25
  align-items: center;
25
26
  overflow: hidden;
26
27
  `;
27
- export const Timeline = ({ duration, value, zoomLevel = 0, selectedRange = [], withTimeBlocks = true, onChange = () => { }, onRangeChange = () => { }, className = '', }) => {
28
+ export const Timeline = ({ duration, value, zoomLevel = 0, selectedRange = [], markers = [], withTimeBlocks = true, onChange = () => { }, onRangeChange = () => { }, className = '', }) => {
28
29
  const timeLineContainerRef = useRef(null);
29
30
  const canvasRef = useRef(null);
30
31
  const [zoomContextValue, setZoomContextValue] = useState({
@@ -73,5 +74,6 @@ export const Timeline = ({ duration, value, zoomLevel = 0, selectedRange = [], w
73
74
  React.createElement(ZoomContext.Provider, { value: zoomContextValue },
74
75
  React.createElement(TimelineCanvas, { ref: canvasRef, duration: duration, withTimeBlocks: withTimeBlocks }),
75
76
  React.createElement(TimelineValue, { value: value, canvasRef: canvasRef }),
77
+ React.createElement(TimelineMarkers, { markers: markers }),
76
78
  React.createElement(RangeSelectorCanvas, { selectedRange: selectedRange, onChange: onChange, onRangeChange: onRangeChange })))));
77
79
  };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ markers: number[];
4
+ }
5
+ export declare const TimelineMarkers: ({ markers }: Props) => React.JSX.Element;
6
+ export {};
@@ -0,0 +1,26 @@
1
+ import React, { useContext } from 'react';
2
+ import styled from 'styled-components';
3
+ import { ZoomContext } from '../ZoomContext/ZoomContext';
4
+ import { secondsToPixel } from '../utils/utils';
5
+ const MarkerContainer = styled.div `
6
+ position: relative;
7
+ height: 100px;
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: flex-start;
11
+ `;
12
+ const MarkerInner = styled.div `
13
+ & {
14
+ width: 0;
15
+ height: 0;
16
+ border-left: 10px solid transparent;
17
+ border-right: 10px solid transparent;
18
+ border-top: 20px solid #4CAF50; /* Green color */
19
+ position: absolute;
20
+ }
21
+ `;
22
+ export const TimelineMarkers = ({ markers }) => {
23
+ const zoomContextValue = useContext(ZoomContext);
24
+ return (React.createElement(React.Fragment, null, markers.map((marker, index) => (React.createElement(MarkerContainer, { key: index, style: { left: secondsToPixel(zoomContextValue, marker) } },
25
+ React.createElement(MarkerInner, null))))));
26
+ };
@@ -12,6 +12,7 @@ const ValueLine = styled.span `
12
12
  width: 1px;
13
13
  height: 100%;
14
14
  background-color: #575757;
15
+ z-index: 1; // force the ValueLine to be on top of the Markers
15
16
  `;
16
17
  const PostValueLine = styled.span `
17
18
  position: absolute;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState, useCallback } from 'react';
2
2
  import { ReproductionWidget } from '../components/reproduction-widget';
3
3
  export default {
4
4
  title: 'ReproductionWidget',
@@ -7,15 +7,44 @@ export default {
7
7
  onInit: { action: 'initiated' },
8
8
  },
9
9
  };
10
- const Template = (args) => (React.createElement(ReproductionWidget, Object.assign({}, args)));
10
+ const Template = (args) => {
11
+ const [reproduction, setReproduction] = useState(null);
12
+ const [reproductionTimestamp, setReproductionTimestamp] = useState(0);
13
+ // Handle initialization of reproduction
14
+ const handleInit = useCallback((reproductionInstance) => {
15
+ const refreshEvent = (args) => { setReproductionTimestamp(new Date().getTime()); };
16
+ setReproduction(reproductionInstance);
17
+ reproductionInstance.on('COUNTING_IN', (args) => { console.log("counting in", args); });
18
+ reproductionInstance.on('PLAYING', refreshEvent);
19
+ reproductionInstance.on('PAUSED', refreshEvent);
20
+ reproductionInstance.on('FINISH', refreshEvent);
21
+ reproductionInstance.start();
22
+ }, []);
23
+ const handleStop = () => {
24
+ if (reproduction) {
25
+ reproduction.stop();
26
+ }
27
+ };
28
+ const handlePause = () => {
29
+ if (reproduction) {
30
+ reproduction.pause();
31
+ }
32
+ };
33
+ const handleResume = () => {
34
+ if (reproduction) {
35
+ reproduction.play();
36
+ }
37
+ };
38
+ return (React.createElement("div", null,
39
+ React.createElement(ReproductionWidget, Object.assign({}, args, { onInit: handleInit })),
40
+ React.createElement("div", null,
41
+ React.createElement("button", { onClick: handleStop, disabled: !reproduction || reproduction.isStopped() }, "Stop"),
42
+ React.createElement("button", { onClick: handlePause, disabled: !reproduction || !reproduction.isPlaying() }, "Pause"),
43
+ React.createElement("button", { onClick: handleResume, disabled: !reproduction || reproduction.isPlaying() }, "Resume"))));
44
+ };
11
45
  export const Default = Template.bind({});
12
46
  Default.args = {
13
47
  trainingMode: true,
14
48
  videoId: 'jFI-RBqXzhU',
15
49
  songTempo: 180,
16
- onInit: (reproduction) => {
17
- console.log("on init");
18
- reproduction.on('COUNTING_IN', (args) => { console.log("counting in", args); });
19
- reproduction.start();
20
- }
21
50
  };
@@ -7,3 +7,4 @@ export declare const WithSelectedRange: import("@storybook/csf").AnnotatedStoryF
7
7
  export declare const WithCustomClassName: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react/dist/types-a5624094").R, TimelineProps>;
8
8
  export declare const WithoutTimeBlocks: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react/dist/types-a5624094").R, TimelineProps>;
9
9
  export declare const Minimalist: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react/dist/types-a5624094").R, TimelineProps>;
10
+ export declare const WithSelectedRangeAndMarkers: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react/dist/types-a5624094").R, TimelineProps>;
@@ -52,3 +52,11 @@ Minimalist.args = {
52
52
  withTimeBlocks: false,
53
53
  className: 'minimalist',
54
54
  };
55
+ export const WithSelectedRangeAndMarkers = Template.bind({});
56
+ WithSelectedRangeAndMarkers.args = {
57
+ duration: 305,
58
+ value: 31,
59
+ zoomLevel: 0,
60
+ selectedRange: [20, 30],
61
+ markers: [90, 108],
62
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ldelia/react-media",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "A React components collection for media-related features.",
5
5
  "private": false,
6
6
  "keywords": [
@@ -91,8 +91,8 @@
91
91
  "react-app-polyfill": "^3.0.0",
92
92
  "react-dev-utils": "^12.0.1",
93
93
  "react-dom": "^18.3.1",
94
+ "react-player": "^2.16.0",
94
95
  "react-refresh": "^0.11.0",
95
- "react-youtube": "^10.1.0",
96
96
  "resolve": "^1.20.0",
97
97
  "resolve-url-loader": "^5.0.0",
98
98
  "sass-loader": "^12.3.0",
@@ -136,6 +136,7 @@
136
136
  "@types/react": "^18.3.3",
137
137
  "@types/react-dom": "^18.3.0",
138
138
  "@types/styled-components": "^5.1.34",
139
+ "@types/youtube": "^0.0.50",
139
140
  "babel-loader": "^9.1.3",
140
141
  "eslint-config-prettier": "^9.1.0",
141
142
  "eslint-plugin-prettier": "^5.1.3",