UNPKG

react-native-youtube-bridge

Version:

🎥 Easy-to-use YouTube player for React Native with cross-platform support

313 lines (312 loc) • 8.94 kB
"use strict"; import { ERROR_CODES, PlayerState } from "../types/youtube.js"; import { validateVideoId } from "../utils/validate.js"; class YouTubePlayerCore { player = null; progressInterval = null; callbacks = {}; progressIntervalMs = 1000; seekTimeout = null; constructor(callbacks = {}) { this.callbacks = callbacks; } static async loadAPI() { if (typeof window === 'undefined' || window.YT?.Player) { return Promise.resolve(); } if (window._ytApiPromise) { return window._ytApiPromise; } window._ytApiPromise = new Promise(resolve => { if (document.querySelector('script[src*="youtube.com/iframe_api"]')) { let attempts = 0; const maxAttempts = 100; const checkAPI = () => { if (window.YT?.Player) { resolve(); return; } if (attempts >= maxAttempts) { console.error('YouTube API failed to load after timeout'); resolve(); return; } attempts++; setTimeout(checkAPI, 100); }; checkAPI(); return; } window.onYouTubeIframeAPIReady = () => { resolve(); }; const script = document.createElement('script'); script.src = 'https://www.youtube.com/iframe_api'; script.async = true; document.head.appendChild(script); }); return window._ytApiPromise; } createPlayer(containerId, config) { if (typeof window === 'undefined' || !window.YT?.Player) { return; } const container = document.getElementById(containerId); if (!container) { return; } if (!validateVideoId(config.videoId)) { this.callbacks.onError?.({ code: 1002, message: ERROR_CODES[1002] }); return; } if (this.player) { try { this.player.destroy(); } catch (error) { console.warn('Error destroying YouTube player:', error); } } this.player = new window.YT.Player(containerId, { width: '100%', height: '100%', videoId: config.videoId, playerVars: { autoplay: config.playerVars?.autoplay ? 1 : 0, controls: config.playerVars?.controls ? 1 : 0, loop: config.playerVars?.loop ? 1 : 0, start: config.playerVars?.startTime, end: config.playerVars?.endTime, playsinline: config.playerVars?.playsinline ? 1 : 0, rel: config.playerVars?.rel ? 1 : 0, enablejsapi: 1 }, events: { onReady: event => { const { playerInfo } = event.target; this.callbacks.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 }); this.startProgressTracking(); }, onStateChange: event => { const state = event.data; this.callbacks.onStateChange?.(state); this.handleStateChange(state); }, onError: event => { console.error('YouTube player error:', event.data); const errorCode = event.data; if (ERROR_CODES[errorCode]) { this.callbacks.onError?.({ code: errorCode, message: ERROR_CODES[errorCode] }); return; } this.callbacks.onError?.({ code: 1004, message: 'UNKNOWN_ERROR' }); }, onPlaybackQualityChange: event => { this.callbacks.onPlaybackQualityChange?.(event.data); }, onPlaybackRateChange: event => { this.callbacks.onPlaybackRateChange?.(event.data); }, onAutoplayBlocked: this.callbacks.onAutoplayBlocked } }); } handleStateChange(state) { if (state === PlayerState.ENDED) { this.stopProgressTracking(); this.sendProgress(); return; } if (state === PlayerState.PLAYING) { this.startProgressTracking(); return; } if (state === PlayerState.PAUSED) { this.stopProgressTracking(); this.sendProgress(); return; } if (state === PlayerState.BUFFERING) { this.startProgressTracking(); return; } if (state === PlayerState.CUED) { this.stopProgressTracking(); this.sendProgress(); return; } this.stopProgressTracking(); } startProgressTracking() { if (!this.progressIntervalMs || this.progressInterval) { return; } this.progressInterval = setInterval(async () => { if (!this.player || !this.player.getCurrentTime) { this.stopProgressTracking(); return; } try { await this.sendProgress(); } catch (error) { console.error('Progress tracking error:', error); this.stopProgressTracking(); } }, this.progressIntervalMs); } stopProgressTracking() { if (this.progressInterval) { clearInterval(this.progressInterval); this.progressInterval = null; } } async sendProgress() { if (!this.player || !this.player.getCurrentTime) { return; } const currentTime = await this.player.getCurrentTime(); const duration = await this.player.getDuration(); const percentage = duration > 0 ? currentTime / duration * 100 : 0; const loadedFraction = await this.player.getVideoLoadedFraction(); this.callbacks.onProgress?.({ currentTime, duration, percentage, loadedFraction }); } play() { this.player?.playVideo(); } pause() { this.player?.pauseVideo(); } stop() { this.player?.stopVideo(); } async seekTo(seconds, allowSeekAhead = true) { this.player?.seekTo(seconds, allowSeekAhead); if (this.seekTimeout) { clearTimeout(this.seekTimeout); } this.seekTimeout = setTimeout(() => { this.sendProgress(); }, 200); } setVolume(volume) { this.player?.setVolume(volume); } async getVolume() { const volume = await this.player?.getVolume(); return volume ?? 0; } mute() { this.player?.mute(); } unMute() { this.player?.unMute(); } async isMuted() { const isMuted = await this.player?.isMuted(); return isMuted ?? false; } async getCurrentTime() { const currentTime = await this.player?.getCurrentTime(); return currentTime ?? 0; } async getDuration() { const duration = await this.player?.getDuration(); return duration ?? 0; } async getVideoUrl() { const videoUrl = await this.player?.getVideoUrl(); return videoUrl ?? ''; } async getVideoEmbedCode() { const videoEmbedCode = await this.player?.getVideoEmbedCode(); return videoEmbedCode ?? ''; } async getPlaybackRate() { const playbackRate = await this.player?.getPlaybackRate(); return playbackRate ?? 1; } async getAvailablePlaybackRates() { const availablePlaybackRates = await this.player?.getAvailablePlaybackRates(); return availablePlaybackRates ?? [1]; } async getPlayerState() { const playerState = await this.player?.getPlayerState(); return playerState ?? PlayerState.UNSTARTED; } setPlaybackRate(suggestedRate) { this.player?.setPlaybackRate(suggestedRate); } async getVideoLoadedFraction() { const videoLoadedFraction = await this.player?.getVideoLoadedFraction(); return videoLoadedFraction ?? 0; } loadVideoById(videoId, startSeconds, endSeconds) { this.player?.loadVideoById(videoId, startSeconds, endSeconds); } cueVideoById(videoId, startSeconds, endSeconds) { this.player?.cueVideoById(videoId, startSeconds, endSeconds); } setSize(width, height) { this.player?.setSize(width, height); } setProgressInterval(intervalMs) { this.progressIntervalMs = intervalMs; if (this.progressInterval) { this.stopProgressTracking(); } if (intervalMs) { this.startProgressTracking(); return; } this.stopProgressTracking(); } updateCallbacks(newCallbacks) { this.callbacks = { ...this.callbacks, ...newCallbacks }; } destroy() { this.stopProgressTracking(); if (this.seekTimeout) { clearTimeout(this.seekTimeout); this.seekTimeout = null; } if (this.player) { try { this.player.destroy(); } catch (error) { console.warn('Error destroying YouTube player:', error); } this.player = null; } } } export default YouTubePlayerCore; //# sourceMappingURL=YouTubePlayerCore.js.map