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
98 changes: 64 additions & 34 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { useCallback, useRef, useState } from 'react';
import { Alert, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Alert, Platform, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import {
type PlayerControls,
type PlayerInfo,
PlayerState,
type ProgressData,
type YouTubeError,
YoutubePlayer,
} from 'react-native-youtube-bridge';

const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};

function App() {
const playerRef = useRef<PlayerControls>(null);
const [isPlaying, setIsPlaying] = useState(false);
Expand All @@ -20,19 +27,25 @@ function App() {
const [isMuted, setIsMuted] = useState(false);
const [videoId, setVideoId] = useState('AbZH7XWDW_k');

const handleReady = useCallback(async () => {
const handleReady = useCallback(async (playerInfo: PlayerInfo) => {
console.log('Player is ready!');
Alert.alert('알림', 'YouTube 플레이어가 준비되었습니다!');

// 플레이어 준비 완료 후 정보 가져오기
try {
const rates = await playerRef.current?.getAvailablePlaybackRates();
const vol = await playerRef.current?.getVolume();
const muted = await playerRef.current?.isMuted();

if (rates) setAvailableRates(rates);
if (vol !== undefined) setVolume(vol);
if (muted !== undefined) setIsMuted(muted);
console.log('rates', playerInfo.availablePlaybackRates);
console.log('vol', playerInfo.volume);
console.log('muted', playerInfo.muted);

if (playerInfo.availablePlaybackRates) {
setAvailableRates(playerInfo.availablePlaybackRates);
}
if (playerInfo.volume !== undefined) {
setVolume(playerInfo.volume);
}
if (playerInfo.muted !== undefined) {
setIsMuted(playerInfo.muted);
}
} catch (error) {
console.error('Error getting player info:', error);
}
Expand Down Expand Up @@ -90,42 +103,55 @@ function App() {
Alert.alert('알림', '자동재생이 브라우저에 의해 차단되었습니다');
}, []);

const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};

const changePlaybackRate = (rate: number) => {
playerRef.current?.setPlaybackRate(rate);
};

const changeVolume = async (newVolume: number) => {
const changeVolume = (newVolume: number) => {
playerRef.current?.setVolume(newVolume);
setVolume(newVolume);
};

const toggleMute = async () => {
const toggleMute = useCallback(() => {
if (isMuted) {
playerRef.current?.unMute();
} else {
playerRef.current?.mute();
setIsMuted(false);
return;
}

playerRef.current?.mute();
setIsMuted(true);
}, [isMuted]);

const onPlay = useCallback(() => {
if (isPlaying) {
playerRef.current?.pause();
return;
}
const muted = await playerRef.current?.isMuted();
if (muted !== undefined) setIsMuted(muted);
};

playerRef.current?.play();
}, [isPlaying]);

const getPlayerInfo = async () => {
try {
const [currentTime, duration, url, _, state, loaded] = await Promise.all([
const [currentTime, duration, url, state, loaded] = await Promise.all([
playerRef.current?.getCurrentTime(),
playerRef.current?.getDuration(),
playerRef.current?.getVideoUrl(),
playerRef.current?.getVideoEmbedCode(),
playerRef.current?.getPlayerState(),
playerRef.current?.getVideoLoadedFraction(),
]);

console.log(
`
currentTime: ${currentTime}
duration: ${duration}
url: ${url}
state: ${state}
loaded: ${loaded}
`,
);

Alert.alert(
'플레이어 정보',
`현재 시간: ${formatTime(currentTime || 0)}\n` +
Expand All @@ -151,7 +177,7 @@ function App() {
<YoutubePlayer
ref={playerRef}
videoId={videoId}
height={220}
height={Platform.OS === 'web' ? 'auto' : undefined}
playerVars={{
autoplay: true,
controls: true,
Expand All @@ -165,6 +191,12 @@ function App() {
onPlaybackRateChange={handlePlaybackRateChange}
onPlaybackQualityChange={handlePlaybackQualityChange}
onAutoplayBlocked={handleAutoplayBlocked}
style={{
maxHeight: 400,
}}
iframeStyle={{
aspectRatio: 16 / 9,
}}
/>

<View style={styles.progressContainer}>
Expand All @@ -187,15 +219,13 @@ function App() {

<View style={styles.controls}>
<TouchableOpacity
style={[styles.button, styles.playButton]}
onPress={() => {
if (isPlaying) {
playerRef.current?.pause();
} else {
playerRef.current?.play();
}
}}
style={[styles.button, styles.seekButton]}
onPress={() => playerRef.current?.seekTo(currentTime > 10 ? currentTime - 10 : 0)}
>
<Text style={styles.buttonText}>⏪ -10초</Text>
</TouchableOpacity>

<TouchableOpacity style={[styles.button, styles.playButton]} onPress={onPlay}>
<Text style={styles.buttonText}>{isPlaying ? '⏸️ 일시정지' : '▶️ 재생'}</Text>
</TouchableOpacity>

Expand All @@ -205,7 +235,7 @@ function App() {

<TouchableOpacity
style={[styles.button, styles.seekButton]}
onPress={() => playerRef.current?.seekTo(currentTime + 10)}
onPress={() => playerRef.current?.seekTo(currentTime + 10, true)}
>
<Text style={styles.buttonText}>⏭️ +10초</Text>
</TouchableOpacity>
Expand Down
20 changes: 18 additions & 2 deletions src/YoutubePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
playsinline: true,
rel: false,
},
webViewStyle,
webviewProps,
},
ref,
) => {
Expand All @@ -51,8 +53,21 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
console.log('handleMessage', data);

if (data.type === 'ready') {
const { playerInfo } = data;

setIsReady(true);
onReady?.();
onReady?.({
availablePlaybackRates: playerInfo.availablePlaybackRates,
availableQualityLevels: playerInfo.availableQualityLevels,
currentTime: playerInfo.currentTime,
duration: playerInfo.duration,
muted: playerInfo.muted,
playbackQuality: playerInfo.playbackQuality,
playbackRate: playerInfo.playbackRate,
playerState: playerInfo.playerState,
size: playerInfo.size,
volume: playerInfo.volume,
});
return;
}

Expand Down Expand Up @@ -205,8 +220,9 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
<WebView
ref={webViewRef}
source={{ html: createPlayerHTML() }}
style={styles.webView}
style={[styles.webView, webViewStyle]}
onMessage={handleMessage}
{...webviewProps}
javaScriptEnabled={true}
originWhitelist={['*']}
domStorageEnabled={true}
Expand Down
20 changes: 17 additions & 3 deletions src/YoutubePlayer.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
playsinline: true,
rel: false,
},
iframeStyle,
},
ref,
) => {
const { startTime = 0, endTime, autoplay, controls, loop, playsinline, rel } = playerVars;

const { width: screenWidth } = useWindowDimensions();

const playerRef = useRef<YouTubePlayer>(null);
Expand Down Expand Up @@ -144,8 +144,21 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
enablejsapi: 1,
},
events: {
onReady: () => {
onReady?.();
onReady: (event) => {
const { playerInfo } = event.target;

onReady?.({
availablePlaybackRates: playerInfo.availablePlaybackRates,
availableQualityLevels: playerInfo.availableQualityLevels,
currentTime: playerInfo.currentTime,
duration: playerInfo.duration,
muted: playerInfo.muted,
playbackQuality: playerInfo.playbackQuality,
playbackRate: playerInfo.playbackRate,
playerState: playerInfo.playerState,
size: playerInfo.size,
volume: playerInfo.volume,
});
startProgressTracking();
},
onStateChange: (event) => {
Expand Down Expand Up @@ -377,6 +390,7 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
style={{
width: '100%',
height: '100%',
...iframeStyle,
}}
/>
</YoutubePlayerWrapper>
Expand Down
6 changes: 3 additions & 3 deletions src/YoutubePlayerWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ReactNode } from 'react';
import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
import { type DimensionValue, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';

type YoutubePlayerWrapperProps = {
children: ReactNode;
width?: number | `${number}%`;
height?: number | `${number}%`;
width?: DimensionValue;
height?: DimensionValue;
style?: StyleProp<ViewStyle>;
};

Expand Down
3 changes: 1 addition & 2 deletions src/hooks/youtubeIframeScripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ const onPlayerReady = /* js */ `
try {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'ready',
duration: player.getDuration(),
availablePlaybackRates: player.getAvailablePlaybackRates()
playerInfo: event.target.playerInfo
}));
startProgressTracking();
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export {
type PlaybackQuality,
type PlayerControls,
type YouTubeError,
type PlayerInfo,
} from './types/youtube';
22 changes: 15 additions & 7 deletions src/types/iframe.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ERROR_CODES, PlaybackQuality, PlayerState, YouTubeError } from './youtube';
import type { ERROR_CODES, PlaybackQuality, PlayerInfo, PlayerState, YouTubeError } from './youtube';

declare global {
interface Window {
Expand All @@ -17,6 +17,14 @@ export type EventType =
| 'onApiChange'
| 'onAutoplayBlocked';

export type PlayerEvent<T> = {
target: YouTubePlayer & {
options: Options;
playerInfo: PlayerInfo;
};
data: T;
};

export interface Options {
width?: number | string | undefined;
height?: number | string | undefined;
Expand Down Expand Up @@ -48,12 +56,12 @@ export interface Options {
| undefined;
events?:
| {
onReady?: (event: CustomEvent) => void;
onStateChange?: (event: { data: PlayerState }) => void;
onPlaybackQualityChange?: (event: { data: PlaybackQuality }) => void;
onPlaybackRateChange?: (event: { data: number }) => void;
onError?: (event: { data: keyof typeof ERROR_CODES }) => void;
onAutoplayBlocked?: (event: CustomEvent) => void;
onReady?: (event: PlayerEvent<null>) => void;
onStateChange?: (event: PlayerEvent<PlayerState>) => void;
onPlaybackQualityChange?: (event: PlayerEvent<PlaybackQuality>) => void;
onPlaybackRateChange?: (event: PlayerEvent<number>) => void;
onError?: (event: PlayerEvent<keyof typeof ERROR_CODES>) => void;
onAutoplayBlocked?: (event: PlayerEvent<null>) => void;
}
| undefined;
}
Expand Down
5 changes: 2 additions & 3 deletions src/types/message.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PlaybackQuality, PlayerState, ProgressData, YouTubeError } from './youtube';
import type { PlaybackQuality, PlayerInfo, PlayerState, ProgressData, YouTubeError } from './youtube';

export type MessageType =
| 'ready'
Expand All @@ -12,8 +12,7 @@ export type MessageType =

interface ReadyMessageData {
type: 'ready';
duration: number;
availablePlaybackRates: number[];
playerInfo: PlayerInfo;
}

interface StateChangeMessageData {
Expand Down
Loading