diff --git a/.changeset/funny-moons-hear.md b/.changeset/funny-moons-hear.md index 9aa8d94..57b9b47 100644 --- a/.changeset/funny-moons-hear.md +++ b/.changeset/funny-moons-hear.md @@ -7,19 +7,127 @@ feat!: introduce hooks-based API for v2.0 -BREAKING CHANGE: Complete API redesign with React hooks +> [!important] +> BREAKING CHANGE: **Complete API Redesign with React Hooks** -- Replace `YoutubePlayer` component with `YoutubeView` + `useYouTubePlayer` hook -- Add `useYouTubeEvent` hook for reactive event handling -- Remove ref-based imperative API in favor of declarative approach -- Simplify component props and reduce coupling between components -- Follow expo patterns for better DX +#### New Hooks-Based Architecture -Migration required from v1: +- **`useYouTubePlayer(videoId, config)`** - Creates a player instance with declarative configuration +- **`useYouTubeEvent(player, eventName, intervalOrDefault?, deps?)`** - Reactive event handling with complete type inference +- **`YoutubeView`** - Simplified view component that accepts a player instance -- `YoutubePlayer` โ†’ `YoutubeView` + `useYouTubePlayer()` -- Event props โ†’ `useYouTubeEvent()` hooks -- `playerRef.current.method()` โ†’ `player.method()` +#### Migration from v1 to v2 -Fixes: Memory leaks, complex state management, tight coupling -Improves: Developer experience, maintainability, performance +**Before (v1):** +```jsx +// Imperative, ref-based API +const playerRef = useRef(null); + + + +// Manual event handlers and state management +const [isPlaying, setIsPlaying] = useState(false); +const handleStateChange = (state) => setIsPlaying(state === PlayerState.PLAYING); +``` + +**After (v2):** +```jsx +// Declarative, hooks-based API +const player = useYouTubePlayer(videoId, { + autoplay: true, + controls: true, + playsinline: true +}); + + + +// Reactive state with automatic updates - two usage patterns: + +// 1. State-based (returns reactive values) +const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); +const progress = useYouTubeEvent(player, 'progress', 1000); // 1000ms interval +const state = useYouTubeEvent(player, 'stateChange'); + +const isPlaying = state === PlayerState.PLAYING; + +// 2. Callback-based (for side effects) +useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player ready:', playerInfo); +}); + +useYouTubeEvent(player, 'error', (error) => { + console.error('Player error:', error); +}); +``` + +### โœจ New Features + +- **Declarative Configuration**: Configure player options directly in `useYouTubePlayer` hook +- **Automatic State Management**: No need to manually manage state for common use cases +- **Reactive Events**: `useYouTubeEvent` with two usage patterns - state-based for reactive values and callback-based for side effects +- **Better TypeScript Support**: Improved type inference and autocomplete +- **Reduced Boilerplate**: Significantly less code required for common operations +- **Automatic Cleanup**: Hooks handle cleanup automatically, preventing memory leaks + +### ๐ŸŽฏ Improvements + +- **Reduced Coupling**: Eliminated ref dependencies between parent and child components +- **Simplified API**: Fewer props to manage, more intuitive usage patterns +- **Better Developer Experience**: Following established React Native patterns (expo-audio, expo-video) +- **Performance**: More efficient event handling with automatic cleanup +- **Maintainability**: Cleaner separation of concerns + +### ๐Ÿ“ฆ Component Changes + +#### Removed +- โŒ `YoutubePlayer` component (replaced by `YoutubeView`) +- โŒ `PlayerControls` ref interface +- โŒ Direct event props (`onReady`, `onStateChange`, `onProgress`, etc.) + +#### Added +- โœ… `YoutubeView` component +- โœ… `useYouTubePlayer` hook +- โœ… `useYouTubeEvent` hook +- โœ… Simplified prop interface + +### ๐Ÿ“š Migration Guide + +For detailed migration examples and step-by-step instructions, see our [Migration Guide](/packages/react-native-youtube-bridge/docs/migration-v2.md). + +Key migration steps: +1. **Replace `YoutubePlayer` with `YoutubeView` + `useYouTubePlayer`** +2. **Convert event props to `useYouTubeEvent` hooks** (state-based or callback-based) +3. **Move `playerVars` to `useYouTubePlayer` config** +4. **Replace ref-based method calls with direct player method calls** +5. **Remove manual state management for events** + +### โš ๏ธ Breaking Changes + +- **API Surface**: Complete API redesign, no backward compatibility +- **Event Handling**: Manual event listeners replaced with reactive hooks +- **Component Structure**: `YoutubePlayer` split into `YoutubeView` + hooks + +### ๐Ÿ› Bug Fixes +- Fixed memory leaks from improper event cleanup +- Better handling of rapid video ID changes +- Manage multiple players independently + +### ๐Ÿ“– Documentation +- Complete API documentation rewrite +- Added migration guide from v1 +- Updated all examples to use v2 API +- Added TypeScript usage examples + +--- + +## Previous Versions + +### [1.x.x] - Legacy Version +See [v1 documentation](/packages/react-native-youtube-bridge/docs/v1.md) for the previous imperative API. diff --git a/.changeset/tired-papayas-sleep.md b/.changeset/tired-papayas-sleep.md deleted file mode 100644 index 14cd9ba..0000000 --- a/.changeset/tired-papayas-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@react-native-youtube-bridge/core": major ---- - -feat: create Store Class for YouTube iframe state using useSyncExternalStore diff --git a/README-ko_kr.md b/README-ko_kr.md index 3784e9e..e16e4ce 100644 --- a/README-ko_kr.md +++ b/README-ko_kr.md @@ -2,6 +2,9 @@ > [English](./README.md) | ํ•œ๊ตญ์–ด +> [!note] +> **V1 ์‚ฌ์šฉ์ž:** [V1 ๋ฌธ์„œ](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ](/packages/react-native-youtube-bridge/docs/migration-v2.md) + ## ๊ฐœ์š” React Native์—์„œ YouTube ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณต์žกํ•œ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ง€์†์ ์œผ๋กœ ์œ ์ง€๋ณด์ˆ˜๋˜๊ณ  ์žˆ๋Š” React Native์šฉ YouTube ํ”Œ๋ ˆ์ด์–ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๋Š” ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. (๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” react-native-youtube-iframe์˜ [์ตœ๊ทผ ๋ฆด๋ฆฌ์ฆˆ๋Š” 2023๋…„ 07์›” 02์ผ](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) @@ -13,7 +16,8 @@ React Native์—์„œ YouTube ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณต์žกํ•œ ์„ค์ •์ด - โœ… New Architecture ์ง€์› - โœ… YouTube ๋„ค์ดํ‹ฐ๋ธŒ ํ”Œ๋ ˆ์ด์–ด ๋ชจ๋“ˆ ์—†์ด๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ - โœ… ๋‹ค์–‘ํ•œ [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) ๊ธฐ๋Šฅ ์ง€์› -- โœ… ๊ฐœ๋ฐœ์ž ์นœํ™”์ ์ธ API ์ œ๊ณต +- โœ… ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ์ง€์› - ์—ฌ๋Ÿฌ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ +- โœ… Expo์˜ ์ ‘๊ทผ ๋ฐฉ์‹๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•œ ์ง๊ด€์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด Hook ๊ธฐ๋ฐ˜ API ์ œ๊ณต - โœ… Expo ์ง€์› - โœ… ์œ ์—ฐํ•œ ๋ Œ๋”๋ง ๋ชจ๋“œ (์ธ๋ผ์ธ HTML & ์›น๋ทฐ) @@ -42,96 +46,91 @@ bun add react-native-youtube-bridge ## ์‚ฌ์šฉ๋ฒ• ```tsx -import { YoutubePlayer } from 'react-native-youtube-bridge'; +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { + const videoIdOrUrl = 'AbZH7XWDW_k' + + // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) + // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) + const player = useYouTubePlayer(videoIdOrUrl); + return ( - - ) + + ); } ``` ### ์ด๋ฒคํŠธ -YouTube iframe API์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด [์ด๋ฒคํŠธ](https://developers.google.com/youtube/iframe_api_reference#Events)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์›ํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +YouTube iframe API์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด [์ด๋ฒคํŠธ](https://developers.google.com/youtube/iframe_api_reference#Events)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. -> ๐Ÿ”” Note - ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ๋น„์ •์ƒ ๋™์ž‘ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” `useCallback`์œผ๋กœ ๊ฐ์‹ธ์ฃผ์„ธ์š”. +`useYouTubeEvent` hook์„ ์‚ฌ์šฉํ•˜์—ฌ ์™„๋ฒฝํ•œ ํƒ€์ž… ์ถ”๋ก ์„ ์ง€์›ํ•˜๋ฉฐ, ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๊ฐ์ง€ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```tsx -function App() { - const playerRef = useRef(null); - - const handleReady = useCallback(() => { - console.log('ํ”Œ๋ ˆ์ด์–ด ์ค€๋น„ ์™„๋ฃŒ!'); - }, []); +import { YoutubeView, useYouTubeEvent, useYouTubePlayer } from 'react-native-youtube-bridge'; - const handleStateChange = useCallback((state: PlayerState) => { - console.log('ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ ๋ณ€๊ฒฝ:', state); - }, []); +function App() { + const player = useYouTubePlayer(videoIdOrUrl); - const handlePlaybackRateChange = useCallback((rate: number) => { - console.log('์žฌ์ƒ ์†๋„ ๋ณ€๊ฒฝ:', rate); - }, []); + const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); + const progress = useYouTubeEvent(player, 'progress', progressInterval); - const handlePlaybackQualityChange = useCallback((quality: string) => { - console.log('์žฌ์ƒ ํ’ˆ์งˆ ๋ณ€๊ฒฝ:', quality); - }, []); + useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player is ready!'); + Alert.alert('Alert', 'YouTube player is ready!'); + }); - const handleAutoplayBlocked = useCallback(() => { - console.log('์ž๋™ ์žฌ์ƒ์ด ์ฐจ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); - }, []); + useYouTubeEvent(player, 'autoplayBlocked', () => { + console.log('Autoplay was blocked'); + }); - const handleError = useCallback((error: YouTubeError) => { - console.error('ํ”Œ๋ ˆ์ด์–ด ์˜ค๋ฅ˜:', error); - }, []); + useYouTubeEvent(player, 'error', (error) => { + console.error('Player error:', error); + Alert.alert('Error', `Player error (${error.code}): ${error.message}`); + }); return ( - - ) + + ); } ``` +`useYouTubeEvent` hook์€ callback์œผ๋กœ ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›๋Š” ๋ฐฉ์‹๊ณผ state๋กœ ๊ฐ’์„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +1. Callback ๋ฐฉ์‹: ์˜์กด์„ฑ์— ๋”ฐ๋ผ ๋ฆฌ๋ Œ๋”๋ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ 4๋ฒˆ์งธ ์ธ์ž์— dependency array๋ฅผ ์ฃผ์ž…ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. +2. State ๋ฐฉ์‹: + 1. `progress` event์˜ ๊ฒฝ์šฐ 3๋ฒˆ์งธ ์ธ์ž์— interval ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’: 1000ms) + 2. ๋‚˜๋จธ์ง€ event์˜ ๊ฒฝ์šฐ 3๋ฒˆ์งธ ์ธ์ž์— ๊ธฐ๋ณธ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ### ๊ธฐ๋Šฅ -YouTube iframe API์˜ [ํ•จ์ˆ˜๋“ค](https://developers.google.com/youtube/iframe_api_reference#Functions)์„ `ref`๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœํ•˜์—ฌ ์Œ์†Œ๊ฑฐ, ์žฌ์ƒ, ๋ณผ๋ฅจ ์กฐ์ ˆ ๋“ฑ ๋‹ค์–‘ํ•œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +YouTube iframe API์˜ [ํ•จ์ˆ˜๋“ค](https://developers.google.com/youtube/iframe_api_reference#Functions)์„ `useYouTubePlayer`๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ player ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์Œ์†Œ๊ฑฐ, ์žฌ์ƒ, ๋ณผ๋ฅจ ์กฐ์ ˆ ๋“ฑ ๋‹ค์–‘ํ•œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```tsx +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; + function App() { - const playerRef = useRef(null); + const player = useYouTubePlayer(videoIdOrUrl); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const onPlay = useCallback(() => { if (isPlaying) { - playerRef.current?.pause(); + player.pause(); return; } - playerRef.current?.play(); + player.play(); }, [isPlaying]); - const seekTo = useCallback((time: number, allowSeekAhead: boolean) => { - playerRef.current?.seekTo(time, allowSeekAhead); - }, []); + const seekTo = (time: number, allowSeekAhead: boolean) => { + player.seekTo(time, allowSeekAhead); + }; - const stop = () => playerRef.current?.stop(); + const stop = () => player.stop(); return ( - + - ) + + ); } ``` @@ -187,8 +187,8 @@ YouTube ํ”Œ๋ ˆ์ด์–ด์˜ ์Šคํƒ€์ผ์„ ์›ํ•˜๋Š” ๋Œ€๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ```tsx function App() { return ( - { - setCurrentTime(progress.currentTime); - setDuration(progress.duration); - setLoadedFraction(progress.loadedFraction); - }, []); + const progressInterval = 1000; + + const player = useYouTubePlayer(videoIdOrUrl); + const progress = useYouTubeEvent(player, 'progress', progressInterval); return ( - + ) } ``` @@ -239,7 +236,9 @@ function App() { YouTube ํ”Œ๋ ˆ์ด์–ด ๋ Œ๋”๋ง ๋ฐฉ์‹์„ ์ œ์–ดํ•˜๊ณ  ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ์†Œ์Šค URL์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. 1. **์ธ๋ผ์ธ HTML ๋ชจ๋“œ** (`useInlineHtml: true`)๋Š” ์•ฑ ๋‚ด์—์„œ ์ง์ ‘ HTML์„ ๋กœ๋“œํ•˜์—ฌ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. (default) -2. **์›น๋ทฐ ๋ชจ๋“œ** (`useInlineHtml: false`)๋Š” ์™ธ๋ถ€ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ URI๋Š” https://react-native-youtube-bridge.pages.dev ์ž…๋‹ˆ๋‹ค. +2. **์›น๋ทฐ ๋ชจ๋“œ** (`useInlineHtml: false`)๋Š” ์™ธ๋ถ€ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. + - ๊ธฐ๋ณธ URI๋Š” https://react-native-youtube-bridge.pages.dev ์ž…๋‹ˆ๋‹ค. + - ์ง์ ‘ ์ œ์ž‘ํ•œ ์ปค์Šคํ…€ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€๋ฅผ ์™ธ๋ถ€ ์›น๋ทฐ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, `@react-native-youtube-bridge/web`์œผ๋กœ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ตฌ์ถ•ํ•œ ํ›„ `webViewUrl`์— ํ•ด๋‹น URL์„ ์„ค์ •ํ•˜์„ธ์š”. ์ž์„ธํ•œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ [์›น ํ”Œ๋ ˆ์ด์–ด ๊ฐ€์ด๋“œ](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web)๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”. > [!NOTE] > **webViewUrl ํ™œ์šฉ๋ฒ•** @@ -250,14 +249,14 @@ YouTube ํ”Œ๋ ˆ์ด์–ด ๋ Œ๋”๋ง ๋ฐฉ์‹์„ ์ œ์–ดํ•˜๊ณ  ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ์†Œ์Šค ```tsx // ์ธ๋ผ์ธ HTML (default) - // ์ปค์Šคํ…€ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•œ ์™ธ๋ถ€ ์›น๋ทฐ - English | [ํ•œ๊ตญ์–ด](./README-ko_kr.md) +> [!note] +> **V1 users:** [V1 Documentation](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 Migration Guide](/packages/react-native-youtube-bridge/docs/migration-v2.md) + ## Overview Using a YouTube player in React Native requires complex setup and configuration. However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) @@ -12,13 +15,15 @@ However, there are currently no actively maintained YouTube player libraries for - โœ… iOS, Android, and Web platform support - โœ… New Architecture support - โœ… Works without YouTube native player modules -- โœ… [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) feature support -- โœ… Developer-friendly API +- โœ… Support for various [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) features +- โœ… Multiple instance support - manage multiple players independently +- โœ… Intuitive and easy-to-use Hook-based API very similar to Expo's approach - โœ… Expo support -- โœ… Flexible rendering modes (Inline HTML & WebView) +- โœ… Flexible rendering modes (inline HTML & webview) + +## Examples -## Example -> For a quick start, check out the [example](/example/). +> If you want to get started quickly, check out the [example](/example/). - [Web Demo](https://react-native-youtube-bridge-example.pages.dev/) - [Expo Go](https://snack.expo.dev/@harang/react-native-youtube-bridge) @@ -42,96 +47,94 @@ bun add react-native-youtube-bridge ## Usage ```tsx -import { YoutubePlayer } from 'react-native-youtube-bridge'; +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { + const videoIdOrUrl = 'AbZH7XWDW_k' + + // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) + // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) + const player = useYouTubePlayer(videoIdOrUrl); + return ( - - ) + + ); } ``` ### Events -The library fires [events](https://developers.google.com/youtube/iframe_api_reference#Events) to notify your application of YouTube iframe API state changes. You can subscribe to these events using callback functions. -> ๐Ÿ”” Note - Wrap callback functions with `useCallback` for performance optimization and to prevent abnormal behavior. +[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. -```tsx -function App() { - const playerRef = useRef(null); +The `useYouTubeEvent` hook provides complete type inference and allows you to easily detect and use events in two ways. - const handleReady = useCallback(() => { - console.log('Player is ready!'); - }, []); +```tsx +import { YoutubeView, useYouTubeEvent, useYouTubePlayer } from 'react-native-youtube-bridge'; - const handleStateChange = useCallback((state: PlayerState) => { - console.log('Player state changed:', state); - }, []); +function App() { + const player = useYouTubePlayer(videoIdOrUrl); - const handlePlaybackRateChange = useCallback((rate: number) => { - console.log('Playback rate changed:', rate); - }, []); + const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); + const progress = useYouTubeEvent(player, 'progress', progressInterval); - const handlePlaybackQualityChange = useCallback((quality: string) => { - console.log('Playback quality changed:', quality); - }, []); + useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player is ready!'); + Alert.alert('Alert', 'YouTube player is ready!'); + }); - const handleAutoplayBlocked = useCallback(() => { + useYouTubeEvent(player, 'autoplayBlocked', () => { console.log('Autoplay was blocked'); - }, []); + }); - const handleError = useCallback((error: YouTubeError) => { + useYouTubeEvent(player, 'error', (error) => { console.error('Player error:', error); - }, []); + Alert.alert('Error', `Player error (${error.code}): ${error.message}`); + }); return ( - - ) + + ); } ``` -### Functions -You can control various player features like mute, play, volume, and more by calling YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions) through the `ref`. +The `useYouTubeEvent` hook provides two ways to receive values: callback-based and state-based. + +1. **Callback method**: If re-rendering is needed based on dependencies, inject a dependency array as the 4th argument. +2. **State method**: + 1. For `progress` events, you can set an interval value as the 3rd argument. (default: 1000ms) + 2. For other events, you can set a default value as the 3rd argument. + +### Features + +You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). ```tsx +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; + function App() { - const playerRef = useRef(null); + const player = useYouTubePlayer(videoIdOrUrl); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const onPlay = useCallback(() => { if (isPlaying) { - playerRef.current?.pause(); + player.pause(); return; } - playerRef.current?.play(); + player.play(); }, [isPlaying]); - const seekTo = useCallback((time: number, allowSeekAhead: boolean) => { - playerRef.current?.seekTo(time, allowSeekAhead); - }, []); + const seekTo = (time: number, allowSeekAhead: boolean) => { + player.seekTo(time, allowSeekAhead); + }; - const stop = () => playerRef.current?.stop(); + const stop = () => player.stop(); return ( - + - ) + + ); } ``` -### Styles -You can customize the YouTube player's styling to match your application's design. +### Styling + +You can customize the YouTube player's style as desired. ```tsx function App() { return ( - { - setCurrentTime(progress.currentTime); - setDuration(progress.duration); - setLoadedFraction(progress.loadedFraction); - }, []); + const progressInterval = 1000; + + const player = useYouTubePlayer(videoIdOrUrl); + const progress = useYouTubeEvent(player, 'progress', progressInterval); return ( - + ) } ``` -### Player Rendering & Source Configuration (ios, android) +### Player Rendering and Source Configuration (iOS, Android) **Inline HTML vs WebView Mode** -Control YouTube player rendering method and configure source URLs for compatibility. +Control the YouTube player rendering method and set source URLs for compatibility. -1. **Inline HTML Mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) -2. **WebView Mode** (`useInlineHtml: false`) loads an external player page. The default URI is https://react-native-youtube-bridge.pages.dev. +1. **Inline HTML mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) +2. **WebView mode** (`useInlineHtml: false`) loads an external player page. + - The default URI is https://react-native-youtube-bridge.pages.dev. + - To use your own custom player page as an external WebView, build your player with `@react-native-youtube-bridge/web` and set the URL in the `webViewUrl` property. For detailed implementation instructions, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). > [!NOTE] > **webViewUrl Usage** -> - When `useInlineHtml: true`: Set as the `baseUrl` for WebView source HTML. -> - When `useInlineHtml: false`: Overrides the WebView source `uri`. +> - When `useInlineHtml: true`: Set as the HTML `baseUrl` of the WebView source. +> - When `useInlineHtml: false`: Overrides the WebView source's `uri`. > -> **Embed Restriction Solution**: If you encounter `embed not allowed` errors with YouTube iframe when using inline HTML mode and videos don't load properly, switch to WebView mode to load YouTube iframe through an external player. +> **Resolving Embed Restrictions**: If you encounter `embed not allowed` errors from the YouTube iframe when using inline HTML and the video doesn't load properly, switch to WebView mode to load the YouTube iframe through an external player. ```tsx // Inline HTML (default) - -// External WebView with custom player page - ``` **Custom Player Page** -To use your own custom player page, you can build a React-based player using `@react-native-youtube-bridge/web`. +To use a custom player page you've created, you can build a React-based player page using `@react-native-youtube-bridge/web`. ```tsx import { YoutubePlayer } from '@react-native-youtube-bridge/web'; @@ -281,28 +287,29 @@ export default CustomPlayerPage; > For more details, please refer to the [Web Player Guide](./packages/web/). ### YouTube oEmbed API -Use the `useYoutubeOEmbed` hook to fetch YouTube video metadata. + +You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. This hook only supports YouTube URLs. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); - - if (isLoading) return Loading...; - if (error) return Error: {error.message}; - if (!oEmbed) return null; - - return ( - <> - {oEmbed.title} - - - ) + const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + + if (isLoading) return Loading...; + if (error) return Error: {error.message}; + if (!oEmbed) return null; + + return ( + <> + {oEmbed.title} + + + ) } ``` diff --git a/packages/react-native-youtube-bridge/README-ko_kr.md b/packages/react-native-youtube-bridge/README-ko_kr.md index 062a9ef..fb98b4a 100644 --- a/packages/react-native-youtube-bridge/README-ko_kr.md +++ b/packages/react-native-youtube-bridge/README-ko_kr.md @@ -2,6 +2,9 @@ > [English](./README.md) | ํ•œ๊ตญ์–ด +> [!note] +> **V1 ์‚ฌ์šฉ์ž:** [V1 ๋ฌธ์„œ](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ](/packages/react-native-youtube-bridge/docs/migration-v2.md) + ## ๊ฐœ์š” React Native์—์„œ YouTube ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณต์žกํ•œ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ง€์†์ ์œผ๋กœ ์œ ์ง€๋ณด์ˆ˜๋˜๊ณ  ์žˆ๋Š” React Native์šฉ YouTube ํ”Œ๋ ˆ์ด์–ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๋Š” ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. (๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” react-native-youtube-iframe์˜ [์ตœ๊ทผ ๋ฆด๋ฆฌ์ฆˆ๋Š” 2023๋…„ 07์›” 02์ผ](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) @@ -13,7 +16,8 @@ React Native์—์„œ YouTube ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณต์žกํ•œ ์„ค์ •์ด - โœ… New Architecture ์ง€์› - โœ… YouTube ๋„ค์ดํ‹ฐ๋ธŒ ํ”Œ๋ ˆ์ด์–ด ๋ชจ๋“ˆ ์—†์ด๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ - โœ… ๋‹ค์–‘ํ•œ [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) ๊ธฐ๋Šฅ ์ง€์› -- โœ… ๊ฐœ๋ฐœ์ž ์นœํ™”์ ์ธ API ์ œ๊ณต +- โœ… ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค ์ง€์› - ์—ฌ๋Ÿฌ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ +- โœ… Expo์˜ ์ ‘๊ทผ ๋ฐฉ์‹๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•œ ์ง๊ด€์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด Hook ๊ธฐ๋ฐ˜ API ์ œ๊ณต - โœ… Expo ์ง€์› - โœ… ์œ ์—ฐํ•œ ๋ Œ๋”๋ง ๋ชจ๋“œ (์ธ๋ผ์ธ HTML & ์›น๋ทฐ) @@ -42,96 +46,91 @@ bun add react-native-youtube-bridge ## ์‚ฌ์šฉ๋ฒ• ```tsx -import { YoutubePlayer } from 'react-native-youtube-bridge'; +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { + const videoIdOrUrl = 'AbZH7XWDW_k' + + // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) + // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) + const player = useYouTubePlayer(videoIdOrUrl); + return ( - - ) + + ); } ``` ### ์ด๋ฒคํŠธ -YouTube iframe API์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด [์ด๋ฒคํŠธ](https://developers.google.com/youtube/iframe_api_reference#Events)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์›ํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +YouTube iframe API์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด [์ด๋ฒคํŠธ](https://developers.google.com/youtube/iframe_api_reference#Events)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. -> ๐Ÿ”” Note - ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ๋น„์ •์ƒ ๋™์ž‘ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” `useCallback`์œผ๋กœ ๊ฐ์‹ธ์ฃผ์„ธ์š”. +`useYouTubeEvent` hook์„ ์‚ฌ์šฉํ•˜์—ฌ ์™„๋ฒฝํ•œ ํƒ€์ž… ์ถ”๋ก ์„ ์ง€์›ํ•˜๋ฉฐ, ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๊ฐ์ง€ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```tsx -function App() { - const playerRef = useRef(null); - - const handleReady = useCallback(() => { - console.log('ํ”Œ๋ ˆ์ด์–ด ์ค€๋น„ ์™„๋ฃŒ!'); - }, []); +import { YoutubeView, useYouTubeEvent, useYouTubePlayer } from 'react-native-youtube-bridge'; - const handleStateChange = useCallback((state: PlayerState) => { - console.log('ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ ๋ณ€๊ฒฝ:', state); - }, []); +function App() { + const player = useYouTubePlayer(videoIdOrUrl); - const handlePlaybackRateChange = useCallback((rate: number) => { - console.log('์žฌ์ƒ ์†๋„ ๋ณ€๊ฒฝ:', rate); - }, []); + const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); + const progress = useYouTubeEvent(player, 'progress', progressInterval); - const handlePlaybackQualityChange = useCallback((quality: string) => { - console.log('์žฌ์ƒ ํ’ˆ์งˆ ๋ณ€๊ฒฝ:', quality); - }, []); + useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player is ready!'); + Alert.alert('Alert', 'YouTube player is ready!'); + }); - const handleAutoplayBlocked = useCallback(() => { - console.log('์ž๋™ ์žฌ์ƒ์ด ์ฐจ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); - }, []); + useYouTubeEvent(player, 'autoplayBlocked', () => { + console.log('Autoplay was blocked'); + }); - const handleError = useCallback((error: YouTubeError) => { - console.error('ํ”Œ๋ ˆ์ด์–ด ์˜ค๋ฅ˜:', error); - }, []); + useYouTubeEvent(player, 'error', (error) => { + console.error('Player error:', error); + Alert.alert('Error', `Player error (${error.code}): ${error.message}`); + }); return ( - - ) + + ); } ``` +`useYouTubeEvent` hook์€ callback์œผ๋กœ ๊ฐ’์„ ์ „๋‹ฌ๋ฐ›๋Š” ๋ฐฉ์‹๊ณผ state๋กœ ๊ฐ’์„ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. +1. Callback ๋ฐฉ์‹: ์˜์กด์„ฑ์— ๋”ฐ๋ผ ๋ฆฌ๋ Œ๋”๋ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ 4๋ฒˆ์งธ ์ธ์ž์— dependency array๋ฅผ ์ฃผ์ž…ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. +2. State ๋ฐฉ์‹: + 1. `progress` event์˜ ๊ฒฝ์šฐ 3๋ฒˆ์งธ ์ธ์ž์— interval ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’: 1000ms) + 2. ๋‚˜๋จธ์ง€ event์˜ ๊ฒฝ์šฐ 3๋ฒˆ์งธ ์ธ์ž์— ๊ธฐ๋ณธ ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ### ๊ธฐ๋Šฅ -YouTube iframe API์˜ [ํ•จ์ˆ˜๋“ค](https://developers.google.com/youtube/iframe_api_reference#Functions)์„ `ref`๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœํ•˜์—ฌ ์Œ์†Œ๊ฑฐ, ์žฌ์ƒ, ๋ณผ๋ฅจ ์กฐ์ ˆ ๋“ฑ ๋‹ค์–‘ํ•œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +YouTube iframe API์˜ [ํ•จ์ˆ˜๋“ค](https://developers.google.com/youtube/iframe_api_reference#Functions)์„ `useYouTubePlayer`๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ player ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์Œ์†Œ๊ฑฐ, ์žฌ์ƒ, ๋ณผ๋ฅจ ์กฐ์ ˆ ๋“ฑ ๋‹ค์–‘ํ•œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ```tsx +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; + function App() { - const playerRef = useRef(null); + const player = useYouTubePlayer(videoIdOrUrl); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const onPlay = useCallback(() => { if (isPlaying) { - playerRef.current?.pause(); + player.pause(); return; } - playerRef.current?.play(); + player.play(); }, [isPlaying]); - const seekTo = useCallback((time: number, allowSeekAhead: boolean) => { - playerRef.current?.seekTo(time, allowSeekAhead); - }, []); + const seekTo = (time: number, allowSeekAhead: boolean) => { + player.seekTo(time, allowSeekAhead); + }; - const stop = () => playerRef.current?.stop(); + const stop = () => player.stop(); return ( - + - ) + + ); } ``` @@ -187,8 +187,8 @@ YouTube ํ”Œ๋ ˆ์ด์–ด์˜ ์Šคํƒ€์ผ์„ ์›ํ•˜๋Š” ๋Œ€๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ```tsx function App() { return ( - { - setCurrentTime(progress.currentTime); - setDuration(progress.duration); - setLoadedFraction(progress.loadedFraction); - }, []); + const progressInterval = 1000; + + const player = useYouTubePlayer(videoIdOrUrl); + const progress = useYouTubeEvent(player, 'progress', progressInterval); return ( - + ) } ``` @@ -252,14 +249,14 @@ YouTube ํ”Œ๋ ˆ์ด์–ด ๋ Œ๋”๋ง ๋ฐฉ์‹์„ ์ œ์–ดํ•˜๊ณ  ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ์†Œ์Šค ```tsx // ์ธ๋ผ์ธ HTML (default) - // ์ปค์Šคํ…€ ํ”Œ๋ ˆ์ด์–ด ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•œ ์™ธ๋ถ€ ์›น๋ทฐ - English | [ํ•œ๊ตญ์–ด](./README-ko_kr.md) +> [!note] +> **V1 users:** [V1 Documentation](/packages/react-native-youtube-bridge/docs/v1.md) | [V2 Migration Guide](/packages/react-native-youtube-bridge/docs/migration-v2.md) + ## Overview Using a YouTube player in React Native requires complex setup and configuration. However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) @@ -12,13 +15,15 @@ However, there are currently no actively maintained YouTube player libraries for - โœ… iOS, Android, and Web platform support - โœ… New Architecture support - โœ… Works without YouTube native player modules -- โœ… [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) feature support -- โœ… Developer-friendly API +- โœ… Support for various [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) features +- โœ… Multiple instance support - manage multiple players independently +- โœ… Intuitive and easy-to-use Hook-based API very similar to Expo's approach - โœ… Expo support -- โœ… Flexible rendering modes (Inline HTML & WebView) +- โœ… Flexible rendering modes (inline HTML & webview) + +## Examples -## Example -> For a quick start, check out the [example](/example/). +> If you want to get started quickly, check out the [example](/example/). - [Web Demo](https://react-native-youtube-bridge-example.pages.dev/) - [Expo Go](https://snack.expo.dev/@harang/react-native-youtube-bridge) @@ -42,96 +47,94 @@ bun add react-native-youtube-bridge ## Usage ```tsx -import { YoutubePlayer } from 'react-native-youtube-bridge'; +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; function App() { + const videoIdOrUrl = 'AbZH7XWDW_k' + + // OR useYouTubePlayer({ videoId: 'AbZH7XWDW_k' }) + // OR useYouTubePlayer({ url: 'https://youtube.com/watch?v=AbZH7XWDW_k' }) + const player = useYouTubePlayer(videoIdOrUrl); + return ( - - ) + + ); } ``` ### Events -The library fires [events](https://developers.google.com/youtube/iframe_api_reference#Events) to notify your application of YouTube iframe API state changes. You can subscribe to these events using callback functions. -> ๐Ÿ”” Note - Wrap callback functions with `useCallback` for performance optimization and to prevent abnormal behavior. +[Events](https://developers.google.com/youtube/iframe_api_reference#Events) are fired to communicate YouTube iframe API state changes to your application. -```tsx -function App() { - const playerRef = useRef(null); +The `useYouTubeEvent` hook provides complete type inference and allows you to easily detect and use events in two ways. - const handleReady = useCallback(() => { - console.log('Player is ready!'); - }, []); +```tsx +import { YoutubeView, useYouTubeEvent, useYouTubePlayer } from 'react-native-youtube-bridge'; - const handleStateChange = useCallback((state: PlayerState) => { - console.log('Player state changed:', state); - }, []); +function App() { + const player = useYouTubePlayer(videoIdOrUrl); - const handlePlaybackRateChange = useCallback((rate: number) => { - console.log('Playback rate changed:', rate); - }, []); + const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); + const progress = useYouTubeEvent(player, 'progress', progressInterval); - const handlePlaybackQualityChange = useCallback((quality: string) => { - console.log('Playback quality changed:', quality); - }, []); + useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player is ready!'); + Alert.alert('Alert', 'YouTube player is ready!'); + }); - const handleAutoplayBlocked = useCallback(() => { + useYouTubeEvent(player, 'autoplayBlocked', () => { console.log('Autoplay was blocked'); - }, []); + }); - const handleError = useCallback((error: YouTubeError) => { + useYouTubeEvent(player, 'error', (error) => { console.error('Player error:', error); - }, []); + Alert.alert('Error', `Player error (${error.code}): ${error.message}`); + }); return ( - - ) + + ); } ``` -### Functions -You can control various player features like mute, play, volume, and more by calling YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions) through the `ref`. +The `useYouTubeEvent` hook provides two ways to receive values: callback-based and state-based. + +1. **Callback method**: If re-rendering is needed based on dependencies, inject a dependency array as the 4th argument. +2. **State method**: + 1. For `progress` events, you can set an interval value as the 3rd argument. (default: 1000ms) + 2. For other events, you can set a default value as the 3rd argument. + +### Features + +You can control various player features like muting, playing, and volume adjustment by calling methods on the player instance returned from `useYouTubePlayer`, which uses the YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions). ```tsx +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; + function App() { - const playerRef = useRef(null); + const player = useYouTubePlayer(videoIdOrUrl); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const onPlay = useCallback(() => { if (isPlaying) { - playerRef.current?.pause(); + player.pause(); return; } - playerRef.current?.play(); + player.play(); }, [isPlaying]); - const seekTo = useCallback((time: number, allowSeekAhead: boolean) => { - playerRef.current?.seekTo(time, allowSeekAhead); - }, []); + const seekTo = (time: number, allowSeekAhead: boolean) => { + player.seekTo(time, allowSeekAhead); + }; - const stop = () => playerRef.current?.stop(); + const stop = () => player.stop(); return ( - + - ) + + ); } ``` -### Styles -You can customize the YouTube player's styling to match your application's design. +### Styling + +You can customize the YouTube player's style as desired. ```tsx function App() { return ( - { - setCurrentTime(progress.currentTime); - setDuration(progress.duration); - setLoadedFraction(progress.loadedFraction); - }, []); + const progressInterval = 1000; + + const player = useYouTubePlayer(videoIdOrUrl); + const progress = useYouTubeEvent(player, 'progress', progressInterval); return ( - + ) } ``` -### Player Rendering & Source Configuration (ios, android) +### Player Rendering and Source Configuration (iOS, Android) **Inline HTML vs WebView Mode** -Control YouTube player rendering method and configure source URLs for compatibility. +Control the YouTube player rendering method and set source URLs for compatibility. -1. **Inline HTML Mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) -2. **WebView Mode** (`useInlineHtml: false`) loads an external player page. +1. **Inline HTML mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) +2. **WebView mode** (`useInlineHtml: false`) loads an external player page. - The default URI is https://react-native-youtube-bridge.pages.dev. - To use your own custom player page as an external WebView, build your player with `@react-native-youtube-bridge/web` and set the URL in the `webViewUrl` property. For detailed implementation instructions, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). > [!NOTE] > **webViewUrl Usage** -> - When `useInlineHtml: true`: Set as the `baseUrl` for WebView source HTML. -> - When `useInlineHtml: false`: Overrides the WebView source `uri`. +> - When `useInlineHtml: true`: Set as the HTML `baseUrl` of the WebView source. +> - When `useInlineHtml: false`: Overrides the WebView source's `uri`. > -> **Embed Restriction Solution**: If you encounter `embed not allowed` errors with YouTube iframe when using inline HTML mode and videos don't load properly, switch to WebView mode to load YouTube iframe through an external player. +> **Resolving Embed Restrictions**: If you encounter `embed not allowed` errors from the YouTube iframe when using inline HTML and the video doesn't load properly, switch to WebView mode to load the YouTube iframe through an external player. ```tsx // Inline HTML (default) - -// External WebView with custom player page - ``` **Custom Player Page** -To use your own custom player page, you can build a React-based player using `@react-native-youtube-bridge/web`. +To use a custom player page you've created, you can build a React-based player page using `@react-native-youtube-bridge/web`. ```tsx import { YoutubePlayer } from '@react-native-youtube-bridge/web'; @@ -283,28 +287,29 @@ export default CustomPlayerPage; > For more details, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). ### YouTube oEmbed API -Use the `useYoutubeOEmbed` hook to fetch YouTube video metadata. + +You can fetch YouTube video metadata through the `useYoutubeOEmbed` hook. This hook only supports YouTube URLs. ```tsx import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; function App() { - const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); - - if (isLoading) return Loading...; - if (error) return Error: {error.message}; - if (!oEmbed) return null; - - return ( - <> - {oEmbed.title} - - - ) + const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + + if (isLoading) return Loading...; + if (error) return Error: {error.message}; + if (!oEmbed) return null; + + return ( + <> + {oEmbed.title} + + + ) } ``` diff --git a/packages/react-native-youtube-bridge/docs/migration-v2.md b/packages/react-native-youtube-bridge/docs/migration-v2.md new file mode 100644 index 0000000..e69453a --- /dev/null +++ b/packages/react-native-youtube-bridge/docs/migration-v2.md @@ -0,0 +1,231 @@ +# Migration Guide: v1 to v2 + +This guide will help you migrate from react-native-youtube-bridge v1 to v2. The v2 API is completely redesigned to be more React-friendly using hooks, similar to expo-audio and expo-video. + +## Overview of Changes + +| Aspect | v1 (Old) | v2 (New) | +|--------|----------|----------| +| **API Style** | Imperative (ref-based) | Declarative (hooks-based) | +| **Component** | `YoutubePlayer` | `YoutubeView` + `useYouTubePlayer` | +| **Event Handling** | Manual listeners + props | `useYouTubeEvent` hook | +| **State Management** | Manual `useState` | Automatic reactive updates | +| **Player Control** | `playerRef.current.method()` | `player.method()` | +| **Configuration** | Component props | Hook parameters | + +## Step-by-Step Migration + +### 1. Component Replacement + +**Before (v1):** +```jsx +import { YoutubePlayer } from 'react-native-youtube-bridge'; + + +``` + +**After (v2):** +```jsx +import { YoutubeView, useYouTubePlayer } from 'react-native-youtube-bridge'; + +const player = useYouTubePlayer(videoId, { + autoplay: true, + controls: true, + playsinline: true, + rel: false, + muted: true, +}); + + +``` + +### 2. Event Handling Migration + +The `useYouTubeEvent` hook provides complete type inference and allows you to handle events in two ways: **callback-based** and **state-based**. + +**Before (v1):** +```jsx +// Manual event handlers and state management +const [isPlaying, setIsPlaying] = useState(false); +const [currentTime, setCurrentTime] = useState(0); +const [duration, setDuration] = useState(0); +const [playbackRate, setPlaybackRate] = useState(1); +const [availableRates, setAvailableRates] = useState([1]); + +const handleReady = useCallback((playerInfo) => { + console.log('Player is ready!'); + if (playerInfo?.availablePlaybackRates) { + setAvailableRates(playerInfo.availablePlaybackRates); + } +}, []); + +const handleStateChange = useCallback((state) => { + setIsPlaying(state === PlayerState.PLAYING); +}, []); + +const handleProgress = useCallback((progress) => { + setCurrentTime(progress.currentTime); + setDuration(progress.duration); +}, []); + +const handlePlaybackRateChange = useCallback((rate) => { + setPlaybackRate(rate); +}, []); +``` + +**After (v2):** +```jsx +// State-based event handling (reactive values) +const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); +const progress = useYouTubeEvent(player, 'progress', 1000); // 1000ms interval +const state = useYouTubeEvent(player, 'stateChange'); + +const currentTime = progress?.currentTime ?? 0; +const duration = progress?.duration ?? 0; +const isPlaying = state === PlayerState.PLAYING; + +// Callback-based event handling (for side effects) +const [availableRates, setAvailableRates] = useState([1]); + +useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player is ready!'); + if (playerInfo?.availablePlaybackRates) { + setAvailableRates(playerInfo.availablePlaybackRates); + } +}); + +useYouTubeEvent(player, 'autoplayBlocked', () => { + console.log('Autoplay was blocked'); +}); + +useYouTubeEvent(player, 'error', (error) => { + console.error('Player error:', error); + Alert.alert('Error', `Player error (${error.code}): ${error.message}`); +}); +``` + +#### `useYouTubeEvent` Usage Patterns: + +1. **State method** - Returns reactive values: + ```jsx + // For progress events with custom interval + const progress = useYouTubeEvent(player, 'progress', 1000); // 1000ms interval + + // For other events with default value + const playbackRate = useYouTubeEvent(player, 'playbackRateChange', 1); + const state = useYouTubeEvent(player, 'stateChange'); + ``` + +2. **Callback method** - For side effects and actions: + ```jsx + // Simple callback + useYouTubeEvent(player, 'ready', (playerInfo) => { + console.log('Player ready:', playerInfo); + }); + + // With dependency array for re-rendering control + useYouTubeEvent(player, 'stateChange', (state) => { + // Handle state change + }, [/* dependencies */]); + ``` + +### 3. Player Control Migration + +**Before (v1):** +```jsx +const playerRef = useRef(null); + +// Player methods +const play = () => playerRef.current?.play(); +const pause = () => playerRef.current?.pause(); +const stop = () => playerRef.current?.stop(); +const seekTo = (time) => playerRef.current?.seekTo(time); +const setVolume = (volume) => playerRef.current?.setVolume(volume); +const mute = () => playerRef.current?.mute(); +const unMute = () => playerRef.current?.unMute(); + +// Getting player info +const getPlayerInfo = async () => { + const currentTime = await playerRef.current?.getCurrentTime(); + const duration = await playerRef.current?.getDuration(); + const state = await playerRef.current?.getPlayerState(); +}; +``` + +**After (v2):** +```jsx +const player = useYouTubePlayer(videoId, config); + +// Player methods (direct calls) +const play = () => player.play(); +const pause = () => player.pause(); +const stop = () => player.stop(); +const seekTo = (time) => player.seekTo(time); +const setVolume = (volume) => player.setVolume(volume); +const mute = () => player.mute(); +const unMute = () => player.unMute(); + +// Getting player info +const getPlayerInfo = async () => { + const currentTime = await player.getCurrentTime(); + const duration = await player.getDuration(); + const state = await player.getPlayerState(); +}; +``` + +### 4. Complete Migration Example + +For a complete migration example with all features, please see our [example applications](/example/). + +## Migration Checklist + +### โœ… Required Changes + +- [ ] Replace `YoutubePlayer` imports with `YoutubeView` and `useYouTubePlayer` +- [ ] Move `playerVars` from component props to hook parameters +- [ ] Replace event handler props with `useYouTubeEvent` hooks +- [ ] Remove `useRef` and replace `playerRef.current.method()` with `player.method()` +- [ ] Update component props (remove event handlers, keep styling props) +- [ ] Replace manual state management with reactive event hooks + +### โœ… Optional Improvements + +- [ ] Simplify state management by using reactive values directly +- [ ] Remove unnecessary `useState` calls for player-managed state +- [ ] Add error handling with `useYouTubeEvent(player, 'error')` +- [ ] Update TypeScript types if using TypeScript + +## Breaking Changes Summary + +- **Component**: `YoutubePlayer` โ†’ `YoutubeView` + `useYouTubePlayer` +- **Event Props**: Removed, use `useYouTubeEvent` instead +- **Player Control**: `playerRef.current.method()` โ†’ `player.method()` +- **Configuration**: `playerVars` prop โ†’ hook parameters +- **State Management**: Manual `useState` โ†’ reactive hook values + +The v2 API is designed to be more intuitive and reduce boilerplate code while providing the same functionality. Most applications will see a significant reduction in code complexity after migration. + +--- + +## Previous Versions + +### [1.x.x] - Legacy Version +See [v1 documentation](./v1.md) for the previous imperative API. diff --git a/packages/react-native-youtube-bridge/docs/v1.md b/packages/react-native-youtube-bridge/docs/v1.md new file mode 100644 index 0000000..89b224d --- /dev/null +++ b/packages/react-native-youtube-bridge/docs/v1.md @@ -0,0 +1,315 @@ +# React Native Youtube Bridge (Version 1) + +## Overview +Using a YouTube player in React Native requires complex setup and configuration. +However, there are currently no actively maintained YouTube player libraries for React Native. (The most popular react-native-youtube-iframe's [latest release was July 2, 2023](https://github.com/LonelyCpp/react-native-youtube-iframe/releases/tag/v2.3.0)) + +`react-native-youtube-bridge` is a library that makes it easy to use the [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) in React Native applications. + +- โœ… TypeScript support +- โœ… iOS, Android, and Web platform support +- โœ… New Architecture support +- โœ… Works without YouTube native player modules +- โœ… [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) feature support +- โœ… Developer-friendly API +- โœ… Expo support +- โœ… Flexible rendering modes (Inline HTML & WebView) + +## Example +> For a quick start, check out the [example](/example/). + +- [Web Demo](https://react-native-youtube-bridge-example.pages.dev/) +- [Expo Go](https://snack.expo.dev/@harang/react-native-youtube-bridge) + +

+ +

+ +## Installation + +```bash +npm install react-native-youtube-bridge + +pnpm add react-native-youtube-bridge + +yarn add react-native-youtube-bridge + +bun add react-native-youtube-bridge +``` + +## Usage + +```tsx +import { YoutubePlayer } from 'react-native-youtube-bridge'; + +function App() { + return ( + + ) +} +``` + +### Events +The library fires [events](https://developers.google.com/youtube/iframe_api_reference#Events) to notify your application of YouTube iframe API state changes. You can subscribe to these events using callback functions. + +> ๐Ÿ”” Note - Wrap callback functions with `useCallback` for performance optimization and to prevent abnormal behavior. + +```tsx +function App() { + const playerRef = useRef(null); + + const handleReady = useCallback(() => { + console.log('Player is ready!'); + }, []); + + const handleStateChange = useCallback((state: PlayerState) => { + console.log('Player state changed:', state); + }, []); + + const handlePlaybackRateChange = useCallback((rate: number) => { + console.log('Playback rate changed:', rate); + }, []); + + const handlePlaybackQualityChange = useCallback((quality: string) => { + console.log('Playback quality changed:', quality); + }, []); + + const handleAutoplayBlocked = useCallback(() => { + console.log('Autoplay was blocked'); + }, []); + + const handleError = useCallback((error: YouTubeError) => { + console.error('Player error:', error); + }, []); + + return ( + + ) +} +``` + +### Functions +You can control various player features like mute, play, volume, and more by calling YouTube iframe API [functions](https://developers.google.com/youtube/iframe_api_reference#Functions) through the `ref`. + +```tsx +function App() { + const playerRef = useRef(null); + + const [isPlaying, setIsPlaying] = useState(false); + const [currentTime, setCurrentTime] = useState(0); + + const onPlay = useCallback(() => { + if (isPlaying) { + playerRef.current?.pause(); + return; + } + + playerRef.current?.play(); + }, [isPlaying]); + + const seekTo = useCallback((time: number, allowSeekAhead: boolean) => { + playerRef.current?.seekTo(time, allowSeekAhead); + }, []); + + const stop = () => playerRef.current?.stop(); + + return ( + + + + + seekTo(currentTime > 10 ? currentTime - 10 : 0)} + > + โช -10s + + + + {isPlaying ? 'โธ๏ธ Pause' : 'โ–ถ๏ธ Play'} + + + + โน๏ธ Stop + + + seekTo(currentTime + 10, true)} + > + โญ๏ธ +10s + + + + ) +} +``` + +### Player Parameters +You can customize the playback environment by configuring YouTube embedded player [parameters](https://developers.google.com/youtube/player_parameters#Parameters). + +```tsx +function App() { + return ( + + ) +} +``` + +### Styles +You can customize the YouTube player's styling to match your application's design. + +```tsx +function App() { + return ( + + ) +} +``` + +### 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() { + const handleProgress = useCallback((progress: ProgressData) => { + setCurrentTime(progress.currentTime); + setDuration(progress.duration); + setLoadedFraction(progress.loadedFraction); + }, []); + + return ( + + ) +} +``` + +### Player Rendering & Source Configuration (ios, android) + +**Inline HTML vs WebView Mode** +Control YouTube player rendering method and configure source URLs for compatibility. + +1. **Inline HTML Mode** (`useInlineHtml: true`) renders the player by loading HTML directly within the app. (default) +2. **WebView Mode** (`useInlineHtml: false`) loads an external player page. + - The default URI is https://react-native-youtube-bridge.pages.dev. + - To use your own custom player page as an external WebView, build your player with `@react-native-youtube-bridge/web` and set the URL in the `webViewUrl` property. For detailed implementation instructions, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). + +> [!NOTE] +> **webViewUrl Usage** +> - When `useInlineHtml: true`: Set as the `baseUrl` for WebView source HTML. +> - When `useInlineHtml: false`: Overrides the WebView source `uri`. +> +> **Embed Restriction Solution**: If you encounter `embed not allowed` errors with YouTube iframe when using inline HTML mode and videos don't load properly, switch to WebView mode to load YouTube iframe through an external player. + +```tsx +// Inline HTML (default) + + +// External WebView with custom player page + +``` + +**Custom Player Page** + +To use your own custom player page, you can build a React-based player using `@react-native-youtube-bridge/web`. + +```tsx +import { YoutubePlayer } from '@react-native-youtube-bridge/web'; + +function CustomPlayerPage() { + return ; +} + +export default CustomPlayerPage; +``` + +> For more details, please refer to the [Web Player Guide](https://github.com/react-native-bridges/react-native-youtube-bridge/tree/main/packages/web). + +### YouTube oEmbed API +Use the `useYoutubeOEmbed` hook to fetch YouTube video metadata. +This hook only supports YouTube URLs. + +```tsx +import { useYoutubeOEmbed } from 'react-native-youtube-bridge'; + +function App() { + const { oEmbed, isLoading, error } = useYoutubeOEmbed('https://www.youtube.com/watch?v=AbZH7XWDW_k'); + + if (isLoading) return Loading...; + if (error) return Error: {error.message}; + if (!oEmbed) return null; + + return ( + <> + {oEmbed.title} + + + ) +} +``` + +## Contributing + +See the [contributing guide](/CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. + +## License + +[MIT](./LICENSE)