Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README-ko_kr.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,11 @@ function App() {
### 유용한 기능

#### 재생 진행률 추적
- `progressInterval`이 설정된 경우, 해당 간격(ms)마다 `onProgress` 콜백이 호출됩니다.
- `progressInterval`이 `undefined`이거나 `0` 또는 `null`인 경우, `onProgress` 콜백은 호출되지 않습니다.

```tsx
function App() {
// 1초마다 호출되는 진행률 이벤트 콜백
const handleProgress = useCallback((progress: ProgressData) => {
setCurrentTime(progress.currentTime);
setDuration(progress.duration);
Expand All @@ -225,6 +226,7 @@ function App() {
return (
<YoutubePlayer
videoId={videoId}
progressInterval={1000}
onProgress={handleProgress}
/>
)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,11 @@ function App() {
### Useful Features

#### Playback Progress Tracking
- If `progressInterval` is provided, the `onProgress` callback will be invoked at the specified interval (in milliseconds).
- If `progressInterval` is `undefined`, `0`, or `null`, progress tracking is disabled and `onProgress` will not be called.

```tsx
function App() {
// Progress event callback called every second
const handleProgress = useCallback((progress: ProgressData) => {
setCurrentTime(progress.currentTime);
setDuration(progress.duration);
Expand All @@ -225,6 +226,7 @@ function App() {
return (
<YoutubePlayer
videoId={videoId}
progressInterval={1000}
onProgress={handleProgress}
/>
)
Expand Down
11 changes: 11 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function App() {
const [volume, setVolume] = useState(100);
const [isMuted, setIsMuted] = useState(false);
const [videoId, setVideoId] = useState('AbZH7XWDW_k');
const [progressInterval, setProgressInterval] = useState(1000);

const handleReady = useCallback((playerInfo: PlayerInfo) => {
console.log('Player is ready!');
Expand Down Expand Up @@ -183,6 +184,7 @@ function App() {
rel: false,
muted: true,
}}
progressInterval={progressInterval}
onReady={handleReady}
onStateChange={handleStateChange}
onProgress={handleProgress}
Expand Down Expand Up @@ -216,6 +218,15 @@ function App() {
<Text style={styles.bufferText}>버퍼: {(loadedFraction * 100).toFixed(1)}%</Text>
</View>

<View style={styles.progressContainer}>
<TouchableOpacity
style={[styles.button, { backgroundColor: progressInterval === 0 ? '#9E9E9E' : '#4CAF50' }]}
onPress={() => setProgressInterval(progressInterval === 0 ? 1000 : 0)}
>
<Text style={styles.buttonText}>{progressInterval}ms interval</Text>
</TouchableOpacity>
</View>

<View style={styles.controls}>
<TouchableOpacity
style={[styles.button, styles.seekButton]}
Expand Down
34 changes: 24 additions & 10 deletions src/YoutubePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { type DataDetectorTypes, Dimensions, StyleSheet } from 'react-native';
import WebView, { type WebViewMessageEvent } from 'react-native-webview';
import YoutubePlayerWrapper from './YoutubePlayerWrapper';
import useCreateLocalPlayerHtml from './hooks/useCreateLocalPlayerHtml';
import type { MessageData } from './types/message';
import type { CommandType, MessageData } from './types/message';
import type { PlayerControls, YoutubePlayerProps } from './types/youtube';
import { safeNumber } from './utils/validate';

const { width: screenWidth } = Dimensions.get('window');

Expand All @@ -14,6 +15,7 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
videoId,
width = screenWidth,
height = 200,
progressInterval,
onReady,
onStateChange,
onError,
Expand Down Expand Up @@ -121,8 +123,12 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
);

const sendCommand = useCallback(
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(command: string, args: (string | number | boolean | undefined)[] = [], needsResult = false): Promise<any> => {
(
command: CommandType,
args: (string | number | boolean | undefined)[] = [],
needsResult = false,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
): Promise<any> => {
return new Promise((resolve) => {
if (!webViewRef.current || !isReady) {
resolve(null);
Expand Down Expand Up @@ -222,6 +228,14 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
};
}, [isReady, sendCommand]);

useEffect(() => {
if (isReady) {
const safeInterval = safeNumber(progressInterval);

sendCommand('updateProgressInterval', [safeInterval]);
}
}, [progressInterval, isReady, sendCommand]);

return (
<YoutubePlayerWrapper width={width} height={height} style={style}>
<WebView
Expand All @@ -230,14 +244,14 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
style={[styles.webView, webViewStyle]}
onMessage={handleMessage}
{...webviewProps}
javaScriptEnabled={true}
originWhitelist={['*']}
domStorageEnabled={true}
mediaPlaybackRequiresUserAction={false}
allowsInlineMediaPlayback={true}
allowsFullscreenVideo={true}
scrollEnabled={false}
javaScriptEnabled
domStorageEnabled
allowsFullscreenVideo
allowsInlineMediaPlayback
bounces={false}
scrollEnabled={false}
mediaPlaybackRequiresUserAction={false}
originWhitelist={['*']}
onError={(error) => {
console.error('WebView error:', error);
onError?.({ code: -1, message: 'WebView loading error' });
Expand Down
19 changes: 18 additions & 1 deletion src/YoutubePlayer.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
videoId,
width,
height = 200,
progressInterval: interval,
onReady,
onStateChange,
onError,
Expand Down Expand Up @@ -38,6 +39,7 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
const containerRef = useRef<HTMLDivElement>(null);
const createPlayerRef = useRef<() => void>(null);
const progressInterval = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<number>(interval);

const stopProgressTracking = useCallback(() => {
if (progressInterval.current) {
Expand All @@ -47,6 +49,10 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
}, []);

const startProgressTracking = useCallback(() => {
if (!intervalRef.current) {
return;
}

if (progressInterval.current) {
clearInterval(progressInterval.current);
}
Expand All @@ -73,7 +79,7 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
console.error('Progress tracking error:', error);
stopProgressTracking();
}
}, 1000);
}, intervalRef.current);
}, [onProgress, stopProgressTracking]);

const loadYouTubeAPI = useCallback(() => {
Expand Down Expand Up @@ -229,6 +235,17 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
}
}, [videoId, createPlayer]);

useEffect(() => {
intervalRef.current = interval;

if (interval) {
startProgressTracking();
return;
}

stopProgressTracking();
}, [interval, startProgressTracking, stopProgressTracking]);

const play = useCallback(() => {
playerRef.current?.playVideo();
}, []);
Expand Down
17 changes: 15 additions & 2 deletions src/hooks/useCreateLocalPlayerHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,21 @@ const useCreateLocalPlayerHtml = ({
},

setSize: (width, height) => player && player.setSize(width, height),

cleanup: cleanup
updateProgressInterval: (newInterval) => {
const interval = Number(newInterval) > 0 ? Number(newInterval) : null;

window.currentInterval = interval;

if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}

if (interval && player && player.getPlayerState() === YT.PlayerState.PLAYING) {
startProgressTracking();
}
},
cleanup: cleanup,
};

window.addEventListener('message', function(event) {
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/youtubeIframeScripts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const startProgressTracking = /* js */ `
function startProgressTracking() {
if (isDestroyed) {
if (isDestroyed || typeof window.currentInterval !== 'number' || window.currentInterval <= 0) {
return;
}

Expand Down Expand Up @@ -31,7 +31,7 @@ const startProgressTracking = /* js */ `
console.error('Progress tracking error:', error);
stopProgressTracking();
}
}, 1000);
}, window.currentInterval);
}
`;

Expand Down
25 changes: 25 additions & 0 deletions src/types/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ export type MessageType =
| 'autoplayBlocked'
| 'commandResult';

export type CommandType =
| 'play'
| 'pause'
| 'stop'
| 'seekTo'
| 'setVolume'
| 'getVolume'
| 'mute'
| 'unMute'
| 'isMuted'
| 'getCurrentTime'
| 'getDuration'
| 'getVideoUrl'
| 'getVideoEmbedCode'
| 'getPlaybackRate'
| 'setPlaybackRate'
| 'getAvailablePlaybackRates'
| 'getPlayerState'
| 'getVideoLoadedFraction'
| 'loadVideoById'
| 'cueVideoById'
| 'setSize'
| 'cleanup'
| 'updateProgressInterval';

interface ReadyMessageData {
type: 'ready';
playerInfo: PlayerInfo;
Expand Down
10 changes: 10 additions & 0 deletions src/types/youtube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export type YoutubePlayerProps = {
videoId: string;
width?: DimensionValue;
height?: DimensionValue;
/**
* @description The interval (in milliseconds) at which `onProgress` callback is called.
* Must be a positive number to enable progress tracking.
* If not provided or set to 0/falsy value, progress tracking is disabled.
*/
progressInterval?: number;
style?: StyleProp<ViewStyle>;
/**
* @platform ios, android
Expand All @@ -40,6 +46,10 @@ export type YoutubePlayerProps = {
onReady?: (playerInfo: PlayerInfo) => void;
onStateChange?: (state: PlayerState) => void;
onError?: (error: YouTubeError) => void;
/**
* @description Callback function called at the specified `progressInterval`.
* Only invoked when `progressInterval` is provided as a positive number.
*/
onProgress?: (progress: ProgressData) => void;
onPlaybackRateChange?: (playbackRate: number) => void;
onPlaybackQualityChange?: (quality: string) => void;
Expand Down