@cinecrew/cinecrew-player 0.1.4 → 0.1.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.
@@ -1,225 +0,0 @@
1
- import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
2
-
3
- let youtubeApiPromise;
4
- const YOUTUBE_API_TIMEOUT_MS = 15000;
5
- const YOUTUBE_PLAYER_TIMEOUT_MS = 15000;
6
-
7
- function loadYouTubeApi() {
8
- if (typeof window === 'undefined') return Promise.reject(new Error('YouTube playback requires a browser.'));
9
- if (window.YT?.Player) return Promise.resolve(window.YT);
10
- if (!youtubeApiPromise) {
11
- youtubeApiPromise = new Promise((resolve, reject) => {
12
- let timeoutId;
13
- const cleanup = () => {
14
- clearTimeout(timeoutId);
15
- if (window.onYouTubeIframeAPIReady === onReady) {
16
- window.onYouTubeIframeAPIReady = previousReady;
17
- }
18
- };
19
- const existing = document.querySelector('script[data-cinecrew-youtube-api]');
20
- const previousReady = window.onYouTubeIframeAPIReady;
21
- const onReady = () => {
22
- try { previousReady?.(); } catch {}
23
- if (window.YT?.Player) {
24
- cleanup();
25
- resolve(window.YT);
26
- } else {
27
- cleanup();
28
- reject(new Error('The YouTube player API loaded without becoming ready.'));
29
- }
30
- };
31
- window.onYouTubeIframeAPIReady = onReady;
32
- timeoutId = setTimeout(() => {
33
- cleanup();
34
- reject(new Error('Timed out loading the YouTube player API. Check your network and allow youtube.com.'));
35
- }, YOUTUBE_API_TIMEOUT_MS);
36
- if (!existing) {
37
- const script = document.createElement('script');
38
- script.src = 'https://www.youtube.com/iframe_api';
39
- script.async = true;
40
- script.dataset.cinecrewYoutubeApi = 'true';
41
- script.onerror = () => {
42
- cleanup();
43
- reject(new Error('Could not load the YouTube player API.'));
44
- };
45
- document.head.appendChild(script);
46
- }
47
- });
48
- youtubeApiPromise = youtubeApiPromise.catch((error) => {
49
- youtubeApiPromise = null;
50
- throw error;
51
- });
52
- }
53
- return youtubeApiPromise;
54
- }
55
-
56
- const stateName = (value) => ({ 0: 'ended', 1: 'playing', 2: 'paused', 3: 'buffering', 5: 'cued' })[value] || 'unstarted';
57
-
58
- export const YouTubeVideoPlayer = forwardRef(function YouTubeVideoPlayer({
59
- videoId,
60
- paused = false,
61
- muted = false,
62
- volume = 1,
63
- playbackRate = 1,
64
- onReady,
65
- onProgress,
66
- onPlaying,
67
- onBuffering,
68
- onStateChange,
69
- onError,
70
- onEnded,
71
- style,
72
- }, ref) {
73
- const hostRef = useRef(null);
74
- const playerRef = useRef(null);
75
- const latestPropsRef = useRef({});
76
- const onReadyRef = useRef(onReady);
77
- const onProgressRef = useRef(onProgress);
78
- const onPlayingRef = useRef(onPlaying);
79
- const onBufferingRef = useRef(onBuffering);
80
- const onStateChangeRef = useRef(onStateChange);
81
- const onErrorRef = useRef(onError);
82
- const onEndedRef = useRef(onEnded);
83
- const readyRef = useRef(false);
84
- latestPropsRef.current = { paused, muted, volume, playbackRate };
85
- const stateRef = useRef('unstarted');
86
- onReadyRef.current = onReady;
87
- onProgressRef.current = onProgress;
88
- onPlayingRef.current = onPlaying;
89
- onBufferingRef.current = onBuffering;
90
- onStateChangeRef.current = onStateChange;
91
- onErrorRef.current = onError;
92
- onEndedRef.current = onEnded;
93
-
94
- useEffect(() => {
95
- let cancelled = false;
96
- let progressTimer;
97
- let readyTimer;
98
- let player;
99
- loadYouTubeApi().then((YT) => {
100
- if (cancelled || !hostRef.current) return;
101
- player = new YT.Player(hostRef.current, {
102
- width: '100%',
103
- height: '100%',
104
- videoId,
105
- playerVars: {
106
- autoplay: latestPropsRef.current.paused ? 0 : 1,
107
- controls: 0,
108
- disablekb: 1,
109
- enablejsapi: 1,
110
- fs: 0,
111
- iv_load_policy: 3,
112
- modestbranding: 1,
113
- mute: latestPropsRef.current.muted ? 1 : 0,
114
- origin: window.location.origin,
115
- playsinline: 1,
116
- rel: 0,
117
- showinfo: 0,
118
- },
119
- events: {
120
- onReady: (event) => {
121
- if (cancelled) return;
122
- clearTimeout(readyTimer);
123
- playerRef.current = event.target;
124
- readyRef.current = true;
125
- const latest = latestPropsRef.current;
126
- event.target.setVolume(Math.round(Math.max(0, Math.min(1, latest.volume)) * 100));
127
- if (latest.muted) event.target.mute();
128
- if (latest.paused) event.target.pauseVideo();
129
- else event.target.playVideo();
130
- event.target.setPlaybackRate?.(Number(latest.playbackRate) || 1);
131
- onBufferingRef.current?.(false);
132
- onReadyRef.current?.(event.target);
133
- progressTimer = setInterval(() => {
134
- const target = playerRef.current;
135
- if (!target || stateRef.current !== 'playing') return;
136
- const currentTime = Number(target.getCurrentTime?.()) || 0;
137
- const duration = Number(target.getDuration?.()) || 0;
138
- onProgressRef.current?.({ currentTime: currentTime * 1000, duration: duration * 1000, target: currentTime });
139
- }, 500);
140
- },
141
- onStateChange: (event) => {
142
- const state = stateName(event.data);
143
- stateRef.current = state;
144
- onStateChangeRef.current?.(state);
145
- onBufferingRef.current?.(state === 'buffering');
146
- if (state === 'playing') onPlayingRef.current?.(event);
147
- if (state === 'ended') onEndedRef.current?.();
148
- },
149
- onError: (event) => {
150
- clearTimeout(readyTimer);
151
- onBufferingRef.current?.(false);
152
- onErrorRef.current?.({ code: event.data, message: `YouTube playback failed (${event.data}).` });
153
- },
154
- },
155
- });
156
- playerRef.current = player;
157
- readyTimer = setTimeout(() => {
158
- if (cancelled || readyRef.current) return;
159
- onBufferingRef.current?.(false);
160
- onErrorRef.current?.(new Error('The YouTube player did not become ready. Check that embedding is allowed and the page can send a referrer.'));
161
- }, YOUTUBE_PLAYER_TIMEOUT_MS);
162
- }).catch((error) => {
163
- if (!cancelled) {
164
- onBufferingRef.current?.(false);
165
- onErrorRef.current?.(error);
166
- }
167
- });
168
- return () => {
169
- cancelled = true;
170
- clearInterval(progressTimer);
171
- clearTimeout(readyTimer);
172
- readyRef.current = false;
173
- try { player?.destroy?.(); } catch {}
174
- playerRef.current = null;
175
- };
176
- }, [videoId]);
177
-
178
- useEffect(() => {
179
- const player = playerRef.current;
180
- if (!player || !readyRef.current) return;
181
- if (paused) player.pauseVideo?.();
182
- else player.playVideo?.();
183
- }, [paused, videoId]);
184
-
185
- useEffect(() => {
186
- const player = playerRef.current;
187
- if (!player || !readyRef.current) return;
188
- if (muted) player.mute?.();
189
- else player.unMute?.();
190
- player.setVolume?.(Math.round(Math.max(0, Math.min(1, volume)) * 100));
191
- }, [muted, volume, videoId]);
192
-
193
- useEffect(() => {
194
- if (readyRef.current) playerRef.current?.setPlaybackRate?.(Number(playbackRate) || 1);
195
- }, [playbackRate, videoId]);
196
-
197
- useImperativeHandle(ref, () => ({
198
- play: () => playerRef.current?.playVideo?.(),
199
- resume: () => playerRef.current?.playVideo?.(),
200
- pause: () => playerRef.current?.pauseVideo?.(),
201
- seek: (ratio) => {
202
- const player = playerRef.current;
203
- const duration = Number(player?.getDuration?.()) || 0;
204
- if (duration > 0) player?.seekTo?.(duration * Math.max(0, Math.min(1, Number(ratio) || 0)), true);
205
- },
206
- seekTo: (seconds) => playerRef.current?.seekTo?.(Math.max(0, Number(seconds) || 0), true),
207
- getVideoElement: () => null,
208
- }), []);
209
-
210
- return React.createElement('div', {
211
- ref: hostRef,
212
- className: 'cinecrew-player__youtube',
213
- style: {
214
- position: 'absolute',
215
- inset: 0,
216
- width: '100%',
217
- height: '100%',
218
- background: '#000',
219
- ...style,
220
- pointerEvents: 'none',
221
- },
222
- });
223
- });
224
-
225
- export default YouTubeVideoPlayer;
@@ -1,34 +0,0 @@
1
- export function buildYouTubePlayerHtml(videoId, autoPlay = true) {
2
- const id = String(videoId || '');
3
- if (!/^[\w-]{11}$/.test(id)) throw new Error('A valid YouTube video ID is required.');
4
- const autoplayFlag = autoPlay ? 1 : 0;
5
- return `<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><style>html,body,#player{width:100%;height:100%;margin:0;background:#000;overflow:hidden}body{display:flex}</style></head><body><div id="player"></div><script>
6
- (function(){
7
- var player=null, state=-1;
8
- function send(type, data){try{window.ReactNativeWebView.postMessage(JSON.stringify({type:type,data:data}));}catch(e){}}
9
- window.cinecrewPlayerCommand=function(name,value){
10
- if(!player)return;
11
- try{
12
- if(name==='play')player.playVideo();
13
- else if(name==='pause')player.pauseVideo();
14
- else if(name==='seek')player.seekTo(Number(value)||0,true);
15
- else if(name==='seekRatio'){var duration=player.getDuration();if(duration>0)player.seekTo(duration*Math.max(0,Math.min(1,Number(value)||0)),true);}
16
- else if(name==='mute')player.mute();
17
- else if(name==='unmute')player.unMute();
18
- else if(name==='volume')player.setVolume(Math.max(0,Math.min(100,Number(value)||0)));
19
- else if(name==='rate')player.setPlaybackRate(Number(value)||1);
20
- }catch(e){}
21
- };
22
- window.onYouTubeIframeAPIReady=function(){
23
- player=new YT.Player('player',{width:'100%',height:'100%',videoId:'${id}',playerVars:{autoplay:${autoplayFlag},controls:0,playsinline:1,enablejsapi:1,rel:0,modestbranding:1,fs:0},events:{
24
- onReady:function(){send('ready',{});if(${autoplayFlag})player.playVideo();},
25
- onStateChange:function(event){state=event.data;send('state',{state:state});},
26
- onError:function(event){send('error',{code:event.data});}
27
- }});
28
- };
29
- var script=document.createElement('script');script.src='https://www.youtube.com/iframe_api';
30
- document.head.appendChild(script);
31
- setInterval(function(){if(player&&state===1){send('progress',{currentTime:player.getCurrentTime(),duration:player.getDuration()});}},500);
32
- })();
33
- </script></body></html>`;
34
- }