diff --git a/.changeset/three-papayas-design.md b/.changeset/three-papayas-design.md
new file mode 100644
index 0000000..73220b8
--- /dev/null
+++ b/.changeset/three-papayas-design.md
@@ -0,0 +1,11 @@
+---
+"react-native-youtube-bridge": minor
+---
+
+feat: add flexible source prop to support videoId and URL
+
+> [!note]
+> ❗ BREAKING CHANGE: videoId prop replaced with source prop
+> - source accepts string (videoId/URL) or object {videoId} | {url}
+> - Add useYouTubeVideoId hook for internal parsing
+> - Support multiple YouTube URL formats
diff --git a/README-ko_kr.md b/README-ko_kr.md
index ea67057..1a58ce6 100644
--- a/README-ko_kr.md
+++ b/README-ko_kr.md
@@ -40,7 +40,11 @@ import { YoutubePlayer } from 'react-native-youtube-bridge';
function App() {
return (
-
+
)
}
```
@@ -120,7 +124,7 @@ function App() {
@@ -158,7 +162,7 @@ YouTube 내장 플레이어의 [매개변수](https://developers.google.com/yout
function App() {
return (
diff --git a/README.md b/README.md
index e08e932..456cfaf 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,11 @@ import { YoutubePlayer } from 'react-native-youtube-bridge';
function App() {
return (
-
+
)
}
```
@@ -120,7 +124,7 @@ function App() {
@@ -158,7 +162,7 @@ You can customize the playback environment by configuring YouTube embedded playe
function App() {
return (
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 3433773..9d922b0 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -174,7 +174,7 @@ function App() {
(
(
{
- videoId,
+ source,
width = screenWidth,
height = 200,
progressInterval,
@@ -40,6 +41,8 @@ const YoutubePlayer = forwardRef(
) => {
const { startTime = 0, endTime } = playerVars;
+ const videoId = useYouTubeVideoId(source);
+
const webViewRef = useRef(null);
const [isReady, setIsReady] = useState(false);
const commandIdRef = useRef(0);
diff --git a/src/YoutubePlayer.web.tsx b/src/YoutubePlayer.web.tsx
index 5ffdf49..4952281 100644
--- a/src/YoutubePlayer.web.tsx
+++ b/src/YoutubePlayer.web.tsx
@@ -2,10 +2,12 @@ import { forwardRef, useImperativeHandle } from 'react';
import { useWindowDimensions } from 'react-native';
import YoutubePlayerWrapper from './YoutubePlayerWrapper';
import useYouTubePlayer from './hooks/useYoutubePlayer';
+import useYouTubeVideoId from './hooks/useYoutubeVideoId';
import type { PlayerControls, YoutubePlayerProps } from './types/youtube';
const YoutubePlayer = forwardRef((props, ref) => {
- const { containerRef, controls } = useYouTubePlayer(props);
+ const videoId = useYouTubeVideoId(props.source);
+ const { containerRef, controls } = useYouTubePlayer({ ...props, videoId });
const { width: screenWidth } = useWindowDimensions();
useImperativeHandle(ref, () => controls, [controls]);
diff --git a/src/hooks/useCreateLocalPlayerHtml.ts b/src/hooks/useCreateLocalPlayerHtml.ts
index 0510df0..9762a0f 100644
--- a/src/hooks/useCreateLocalPlayerHtml.ts
+++ b/src/hooks/useCreateLocalPlayerHtml.ts
@@ -17,7 +17,6 @@ const useCreateLocalPlayerHtml = ({
}: YoutubePlayerVars & { videoId: string }) => {
const createPlayerHTML = useCallback(() => {
if (!validateVideoId(videoId)) {
- console.error('Invalid YouTube video ID:', videoId);
return 'Invalid video ID
';
}
diff --git a/src/hooks/useYoutubeVideoId.ts b/src/hooks/useYoutubeVideoId.ts
new file mode 100644
index 0000000..b4fc59c
--- /dev/null
+++ b/src/hooks/useYoutubeVideoId.ts
@@ -0,0 +1,56 @@
+import { useMemo } from 'react';
+import { ERROR_CODES, type PlayerEvents, type YouTubeSource } from '../types/youtube';
+import { extractVideoIdFromUrl, validateVideoId } from '../utils/validate';
+
+const useYouTubeVideoId = (source: YouTubeSource, onError?: PlayerEvents['onError']): string => {
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ const sourceValue = useMemo(() => {
+ if (typeof source === 'string') {
+ return source;
+ }
+
+ if ('videoId' in source) {
+ return source.videoId;
+ }
+
+ if ('url' in source) {
+ return source.url;
+ }
+
+ return null;
+ }, [
+ typeof source === 'string' ? source : 'videoId' in source ? source.videoId : 'url' in source ? source.url : null,
+ ]);
+
+ const videoId = useMemo(() => {
+ if (!sourceValue) {
+ console.error('Invalid YouTube source: ', sourceValue);
+ onError?.({
+ code: 1002,
+ message: ERROR_CODES[1002],
+ });
+ return '';
+ }
+
+ if (validateVideoId(sourceValue)) {
+ return sourceValue;
+ }
+
+ const extractedId = extractVideoIdFromUrl(sourceValue);
+
+ if (!extractedId) {
+ console.error('Invalid YouTube source: ', sourceValue);
+ onError?.({
+ code: 1002,
+ message: ERROR_CODES[1002],
+ });
+ return '';
+ }
+
+ return extractedId;
+ }, [sourceValue, onError]);
+
+ return videoId;
+};
+
+export default useYouTubeVideoId;
diff --git a/src/modules/YouTubePlayerCore.tsx b/src/modules/YouTubePlayerCore.tsx
index dc60507..f82c45b 100644
--- a/src/modules/YouTubePlayerCore.tsx
+++ b/src/modules/YouTubePlayerCore.tsx
@@ -1,7 +1,11 @@
import type { YouTubePlayer } from '../types/iframe';
-import { ERROR_CODES, type YoutubePlayerProps as PlayerConfig, type PlayerEvents, PlayerState } from '../types/youtube';
+import { ERROR_CODES, type PlayerEvents, PlayerState, type YoutubePlayerProps } from '../types/youtube';
import { validateVideoId } from '../utils/validate';
+type PlayerConfig = Omit & {
+ videoId: string;
+};
+
class YouTubePlayerCore {
private player: YouTubePlayer | null = null;
private progressInterval: NodeJS.Timeout | null = null;
diff --git a/src/types/youtube.ts b/src/types/youtube.ts
index 4861a57..c7542e5 100644
--- a/src/types/youtube.ts
+++ b/src/types/youtube.ts
@@ -51,9 +51,10 @@ export type PlayerEvents = {
onAutoplayBlocked?: () => void;
};
-// YouTube IFrame API official documentation based
+export type YouTubeSource = string | { videoId: string } | { url: string };
+
export type YoutubePlayerProps = {
- videoId: string;
+ source: YouTubeSource;
width?: DimensionValue;
height?: DimensionValue;
/**
diff --git a/src/utils/validate.ts b/src/utils/validate.ts
index 9aa83f4..7f2da47 100644
--- a/src/utils/validate.ts
+++ b/src/utils/validate.ts
@@ -1,23 +1,19 @@
-export const validateVideoId = (videoId?: string): boolean => {
- // YouTube video ID is 11 characters of alphanumeric and hyphen, underscore
- const videoIdRegex = /^[a-zA-Z0-9_-]{11}$/;
- return videoIdRegex.test(videoId ?? '');
-};
+const MATCH_URL_YOUTUBE =
+ /(?:youtu\.be\/|youtube(?:-nocookie|education)?\.com\/(?:embed\/|v\/|watch\/|watch\?v=|watch\?.+&v=|shorts\/|live\/))((\w|-){11})/;
-export const extractVideoIdFromUrl = (url: string): string | null => {
- const patterns = [
- /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
- /youtube\.com\/v\/([^&\n?#]+)/,
- ];
-
- for (const pattern of patterns) {
- const match = url.match(pattern);
- if (match?.[1]) {
- return match[1];
- }
+export const extractVideoIdFromUrl = (url?: string): string | undefined => {
+ if (!url) {
+ return undefined;
}
- return null;
+ const match = url.match(MATCH_URL_YOUTUBE);
+
+ return match ? match[1] : undefined;
+};
+
+export const validateVideoId = (videoId?: string): boolean => {
+ const videoIdRegex = /^[\w-]{11}$/;
+ return videoIdRegex.test(videoId ?? '');
};
export const escapeHtml = (unsafe?: string): string => {