diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e1768a..cb46d02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,8 @@ We want this community to be friendly and respectful to each other. Please follo To get started with the project, run `bun` in the root directory to install the required dependencies for each package: ```sh -bun +bun install +cd example && bun install ``` The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. @@ -87,9 +88,9 @@ Our pre-commit hooks verify that your commit message matches this format when co ### Linting and tests -[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) +[biomejs](https://biomejs.dev/), [TypeScript](https://www.typescriptlang.org/) -We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. +We use [TypeScript](https://www.typescriptlang.org/) for type checking, [biomejs](https://biomejs.dev/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. Our pre-commit hooks verify that the linter and tests pass when committing. @@ -97,12 +98,6 @@ Our pre-commit hooks verify that the linter and tests pass when committing. We use [changesets](https://github.com/changesets/changesets) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. -To publish new versions, run the following: - -```sh -bun run release -``` - ### Scripts The `package.json` file contains various scripts for common tasks: diff --git a/README-ko_kr.md b/README-ko_kr.md new file mode 100644 index 0000000..aeb20b0 --- /dev/null +++ b/README-ko_kr.md @@ -0,0 +1,240 @@ +# React Native Youtube Bridge + +> [English](./README.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)) + +`react-native-youtube-bridge`는 [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference)를 React Native에서 쉽게 사용할 수 있도록 도와주는 라이브러리입니다. + +- ✅ TypeScript 지원 +- ✅ iOS, Android, Web 플랫폼 지원 +- ✅ New Architecture 지원 +- ✅ YouTube 네이티브 플레이어 모듈 없이도 사용 가능 +- ✅ [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) 전체 기능 지원 +- ✅ 개발자 친화적인 API 제공 +- ✅ Expo 지원 + +## 예제 +> 빠른 시작을 원하신다면 [예제](/example/)를 확인해보세요. + +- [웹 데모](https://react-native-youtube-bridge.pages.dev/) + +## 설치 + +```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 +``` + +## 사용법 + +```tsx +import { YoutubePlayer } from 'react-native-youtube-bridge'; + +function App() { + return ( + + ) +} +``` + +### 이벤트 +YouTube iframe API의 상태 변화를 애플리케이션에 전달하기 위해 [이벤트](https://developers.google.com/youtube/iframe_api_reference#Events)를 발생시킵니다. 콜백 함수를 통해 원하는 이벤트를 구독할 수 있습니다. + +> 주의 - 성능 최적화 및 비정상 동작 방지를 위해 콜백 함수는 `useCallback`으로 감싸주세요. + +```tsx +function App() { + const playerRef = useRef(null); + + const handleReady = useCallback(() => { + console.log('플레이어 준비 완료!'); + }, []); + + const handleStateChange = useCallback((state: PlayerState) => { + console.log('플레이어 상태 변경:', state); + }, []); + + const handlePlaybackRateChange = useCallback((rate: number) => { + console.log('재생 속도 변경:', rate); + }, []); + + const handlePlaybackQualityChange = useCallback((quality: string) => { + console.log('재생 품질 변경:', quality); + }, []); + + const handleAutoplayBlocked = useCallback(() => { + console.log('자동 재생이 차단되었습니다'); + }, []); + + const handleError = useCallback((error: YouTubeError) => { + console.error('플레이어 오류:', error); + }, []); + + return ( + + ) +} +``` + +### 기능 +YouTube iframe API의 [함수들](https://developers.google.com/youtube/iframe_api_reference#Functions)을 `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)} + > + ⏪ -10초 + + + + {isPlaying ? '⏸️ 일시정지' : '▶️ 재생'} + + + + ⏹️ 정지 + + + seekTo(currentTime + 10, true)} + > + ⏭️ +10초 + + + + ) +} +``` + +### 플레이어 매개변수 +YouTube 내장 플레이어의 [매개변수](https://developers.google.com/youtube/player_parameters#Parameters)를 설정하여 재생 환경을 맞춤화할 수 있습니다. + +```tsx +function App() { + return ( + + ) +} +``` + +### 스타일 +YouTube 플레이어의 스타일을 원하는 대로 커스터마이징할 수 있습니다. + +```tsx +function App() { + return ( + + ) +} +``` + +### 유용한 기능 + +#### 재생 진행률 추적 + +```tsx +function App() { + // 1초마다 호출되는 진행률 이벤트 콜백 + const handleProgress = useCallback((progress: ProgressData) => { + setCurrentTime(progress.currentTime); + setDuration(progress.duration); + setLoadedFraction(progress.loadedFraction); + }, []); + + return ( + + ) +} +``` + +## 기여하기 + +리포지토리 기여 방법과 개발 워크플로우를 알아보려면 [기여 가이드](CONTRIBUTING.md)를 참고하세요. + +## 라이선스 + +[MIT](./LICENSE) diff --git a/README.md b/README.md index 23c3468..e55edfc 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,235 @@ -# [WIP] react-native-youtube-bridge +# React Native Youtube Bridge -react-native-youtube +> English | [한국어](./README-ko_kr.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)) + +`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 +- ✅ Full [YouTube iframe Player API](https://developers.google.com/youtube/iframe_api_reference) feature support +- ✅ Developer-friendly API +- ✅ Expo support + +## Example +> For a quick start, check out the [example](/example/). + +- [Web Demo](https://react-native-youtube-bridge.pages.dev/) ## Installation -```sh +```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 ( + + ) +} +``` + +### Useful Features + +#### Playback Progress Tracking + +```tsx +function App() { + // Progress event callback called every second + const handleProgress = useCallback((progress: ProgressData) => { + setCurrentTime(progress.currentTime); + setDuration(progress.duration); + setLoadedFraction(progress.loadedFraction); + }, []); + + return ( + + ) +} +``` ## Contributing @@ -17,4 +237,4 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the ## License -MIT +[MIT](./LICENSE) diff --git a/example/src/App.tsx b/example/src/App.tsx index 4bc9676..6298305 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -27,31 +27,29 @@ function App() { const [isMuted, setIsMuted] = useState(false); const [videoId, setVideoId] = useState('AbZH7XWDW_k'); - const handleReady = useCallback(async (playerInfo: PlayerInfo) => { + const handleReady = useCallback((playerInfo: PlayerInfo) => { console.log('Player is ready!'); Alert.alert('알림', 'YouTube 플레이어가 준비되었습니다!'); // 플레이어 준비 완료 후 정보 가져오기 - try { - 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); + 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); } }, []); - const handleStateChange = (state: PlayerState) => { + const handleStateChange = useCallback((state: PlayerState) => { console.log('Player state changed:', state); setIsPlaying(state === PlayerState.PLAYING); @@ -75,7 +73,7 @@ function App() { console.log('비디오가 큐에 준비되었습니다'); break; } - }; + }, []); const handleProgress = useCallback((progress: ProgressData) => { setCurrentTime(progress.currentTime);