Skip to content

Commit 4925df0

Browse files
authored
feat: add flexible source prop to support videoId and URL (#24)
1 parent 07333dd commit 4925df0

File tree

11 files changed

+114
-34
lines changed

11 files changed

+114
-34
lines changed

.changeset/three-papayas-design.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"react-native-youtube-bridge": minor
3+
---
4+
5+
feat: add flexible source prop to support videoId and URL
6+
7+
> [!note]
8+
> ❗ BREAKING CHANGE: videoId prop replaced with source prop
9+
> - source accepts string (videoId/URL) or object {videoId} | {url}
10+
> - Add useYouTubeVideoId hook for internal parsing
11+
> - Support multiple YouTube URL formats

README-ko_kr.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ import { YoutubePlayer } from 'react-native-youtube-bridge';
4040

4141
function App() {
4242
return (
43-
<YoutubePlayer videoId={videoId} />
43+
<YoutubePlayer
44+
source={source} // youtube videoId or url
45+
// OR source={{ videoId: 'AbZH7XWDW_k' }}
46+
// OR source={{ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }}
47+
/>
4448
)
4549
}
4650
```
@@ -120,7 +124,7 @@ function App() {
120124
<View>
121125
<YoutubePlayer
122126
ref={playerRef}
123-
videoId={videoId}
127+
source={source}
124128
/>
125129

126130
<View style={styles.controls}>
@@ -158,7 +162,7 @@ YouTube 내장 플레이어의 [매개변수](https://developers.google.com/yout
158162
function App() {
159163
return (
160164
<YoutubePlayer
161-
videoId={videoId}
165+
source={source}
162166
playerVars={{
163167
autoplay: true,
164168
controls: true,
@@ -178,7 +182,7 @@ YouTube 플레이어의 스타일을 원하는 대로 커스터마이징할 수
178182
function App() {
179183
return (
180184
<YoutubePlayer
181-
videoId={videoId}
185+
source={source}
182186
height={400}
183187
width={200}
184188
style={{
@@ -217,7 +221,7 @@ function App() {
217221

218222
return (
219223
<YoutubePlayer
220-
videoId={videoId}
224+
source={source}
221225
progressInterval={1000}
222226
onProgress={handleProgress}
223227
/>

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ import { YoutubePlayer } from 'react-native-youtube-bridge';
4040

4141
function App() {
4242
return (
43-
<YoutubePlayer videoId={videoId} />
43+
<YoutubePlayer
44+
source={source} // youtube videoId or url
45+
// OR source={{ videoId: 'AbZH7XWDW_k' }}
46+
// OR source={{ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }}
47+
/>
4448
)
4549
}
4650
```
@@ -120,7 +124,7 @@ function App() {
120124
<View>
121125
<YoutubePlayer
122126
ref={playerRef}
123-
videoId={videoId}
127+
source={source}
124128
/>
125129

126130
<View style={styles.controls}>
@@ -158,7 +162,7 @@ You can customize the playback environment by configuring YouTube embedded playe
158162
function App() {
159163
return (
160164
<YoutubePlayer
161-
videoId={videoId}
165+
source={source}
162166
playerVars={{
163167
autoplay: true,
164168
controls: true,
@@ -178,7 +182,7 @@ You can customize the YouTube player's styling to match your application's desig
178182
function App() {
179183
return (
180184
<YoutubePlayer
181-
videoId={videoId}
185+
source={source}
182186
height={400}
183187
width={200}
184188
style={{
@@ -217,7 +221,7 @@ function App() {
217221

218222
return (
219223
<YoutubePlayer
220-
videoId={videoId}
224+
source={source}
221225
progressInterval={1000}
222226
onProgress={handleProgress}
223227
/>

example/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ function App() {
174174

175175
<YoutubePlayer
176176
ref={playerRef}
177-
videoId={videoId}
177+
source={videoId}
178178
height={Platform.OS === 'web' ? 'auto' : undefined}
179179
playerVars={{
180180
autoplay: true,

src/YoutubePlayer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type DataDetectorTypes, Dimensions, StyleSheet } from 'react-native';
33
import WebView, { type WebViewMessageEvent } from 'react-native-webview';
44
import YoutubePlayerWrapper from './YoutubePlayerWrapper';
55
import useCreateLocalPlayerHtml from './hooks/useCreateLocalPlayerHtml';
6+
import useYouTubeVideoId from './hooks/useYoutubeVideoId';
67
import type { CommandType, MessageData } from './types/message';
78
import type { PlayerControls, YoutubePlayerProps } from './types/youtube';
89
import { safeNumber, validateVideoId } from './utils/validate';
@@ -12,7 +13,7 @@ const { width: screenWidth } = Dimensions.get('window');
1213
const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
1314
(
1415
{
15-
videoId,
16+
source,
1617
width = screenWidth,
1718
height = 200,
1819
progressInterval,
@@ -40,6 +41,8 @@ const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>(
4041
) => {
4142
const { startTime = 0, endTime } = playerVars;
4243

44+
const videoId = useYouTubeVideoId(source);
45+
4346
const webViewRef = useRef<WebView>(null);
4447
const [isReady, setIsReady] = useState(false);
4548
const commandIdRef = useRef(0);

src/YoutubePlayer.web.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { forwardRef, useImperativeHandle } from 'react';
22
import { useWindowDimensions } from 'react-native';
33
import YoutubePlayerWrapper from './YoutubePlayerWrapper';
44
import useYouTubePlayer from './hooks/useYoutubePlayer';
5+
import useYouTubeVideoId from './hooks/useYoutubeVideoId';
56
import type { PlayerControls, YoutubePlayerProps } from './types/youtube';
67

78
const YoutubePlayer = forwardRef<PlayerControls, YoutubePlayerProps>((props, ref) => {
8-
const { containerRef, controls } = useYouTubePlayer(props);
9+
const videoId = useYouTubeVideoId(props.source);
10+
const { containerRef, controls } = useYouTubePlayer({ ...props, videoId });
911
const { width: screenWidth } = useWindowDimensions();
1012

1113
useImperativeHandle(ref, () => controls, [controls]);

src/hooks/useCreateLocalPlayerHtml.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const useCreateLocalPlayerHtml = ({
1717
}: YoutubePlayerVars & { videoId: string }) => {
1818
const createPlayerHTML = useCallback(() => {
1919
if (!validateVideoId(videoId)) {
20-
console.error('Invalid YouTube video ID:', videoId);
2120
return '<html><body><div>Invalid video ID</div></body></html>';
2221
}
2322

src/hooks/useYoutubeVideoId.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useMemo } from 'react';
2+
import { ERROR_CODES, type PlayerEvents, type YouTubeSource } from '../types/youtube';
3+
import { extractVideoIdFromUrl, validateVideoId } from '../utils/validate';
4+
5+
const useYouTubeVideoId = (source: YouTubeSource, onError?: PlayerEvents['onError']): string => {
6+
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
7+
const sourceValue = useMemo(() => {
8+
if (typeof source === 'string') {
9+
return source;
10+
}
11+
12+
if ('videoId' in source) {
13+
return source.videoId;
14+
}
15+
16+
if ('url' in source) {
17+
return source.url;
18+
}
19+
20+
return null;
21+
}, [
22+
typeof source === 'string' ? source : 'videoId' in source ? source.videoId : 'url' in source ? source.url : null,
23+
]);
24+
25+
const videoId = useMemo(() => {
26+
if (!sourceValue) {
27+
console.error('Invalid YouTube source: ', sourceValue);
28+
onError?.({
29+
code: 1002,
30+
message: ERROR_CODES[1002],
31+
});
32+
return '';
33+
}
34+
35+
if (validateVideoId(sourceValue)) {
36+
return sourceValue;
37+
}
38+
39+
const extractedId = extractVideoIdFromUrl(sourceValue);
40+
41+
if (!extractedId) {
42+
console.error('Invalid YouTube source: ', sourceValue);
43+
onError?.({
44+
code: 1002,
45+
message: ERROR_CODES[1002],
46+
});
47+
return '';
48+
}
49+
50+
return extractedId;
51+
}, [sourceValue, onError]);
52+
53+
return videoId;
54+
};
55+
56+
export default useYouTubeVideoId;

src/modules/YouTubePlayerCore.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import type { YouTubePlayer } from '../types/iframe';
2-
import { ERROR_CODES, type YoutubePlayerProps as PlayerConfig, type PlayerEvents, PlayerState } from '../types/youtube';
2+
import { ERROR_CODES, type PlayerEvents, PlayerState, type YoutubePlayerProps } from '../types/youtube';
33
import { validateVideoId } from '../utils/validate';
44

5+
type PlayerConfig = Omit<YoutubePlayerProps, 'source'> & {
6+
videoId: string;
7+
};
8+
59
class YouTubePlayerCore {
610
private player: YouTubePlayer | null = null;
711
private progressInterval: NodeJS.Timeout | null = null;

src/types/youtube.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ export type PlayerEvents = {
5151
onAutoplayBlocked?: () => void;
5252
};
5353

54-
// YouTube IFrame API official documentation based
54+
export type YouTubeSource = string | { videoId: string } | { url: string };
55+
5556
export type YoutubePlayerProps = {
56-
videoId: string;
57+
source: YouTubeSource;
5758
width?: DimensionValue;
5859
height?: DimensionValue;
5960
/**

0 commit comments

Comments
 (0)