UNPKG

@react-native-youtube-bridge/core

Version:
353 lines (348 loc) 10.2 kB
//#region src/types/index.ts let PlayerState = /* @__PURE__ */ function(PlayerState$1) { PlayerState$1[PlayerState$1["UNSTARTED"] = -1] = "UNSTARTED"; PlayerState$1[PlayerState$1["ENDED"] = 0] = "ENDED"; PlayerState$1[PlayerState$1["PLAYING"] = 1] = "PLAYING"; PlayerState$1[PlayerState$1["PAUSED"] = 2] = "PAUSED"; PlayerState$1[PlayerState$1["BUFFERING"] = 3] = "BUFFERING"; PlayerState$1[PlayerState$1["CUED"] = 5] = "CUED"; return PlayerState$1; }({}); //#endregion //#region src/constants.ts const ERROR_CODES = { 2: "INVALID_PARAMETER_VALUE", 5: "HTML5_PLAYER_ERROR", 100: "VIDEO_NOT_FOUND_OR_PRIVATE", 101: "EMBEDDED_PLAYBACK_NOT_ALLOWED", 150: "EMBEDDED_RESTRICTED", 1e3: "FAILED_TO_PARSE_WEBVIEW_MESSAGE", 1001: "WEBVIEW_LOADING_ERROR", 1002: "INVALID_YOUTUBE_VIDEO_ID", 1003: "FAILED_TO_LOAD_YOUTUBE_API", 1004: "UNKNOWN_ERROR" }; const MATCH_URL_YOUTUBE = /(?:youtu\.be\/|youtube(?:-nocookie|education)?\.com\/(?:embed\/|v\/|watch\/|watch\?v=|watch\?.+&v=|shorts\/|live\/))((\w|-){11})/; //#endregion //#region src/utils.ts const extractVideoIdFromUrl = (url) => { if (!url) return void 0; const match = url.match(MATCH_URL_YOUTUBE); return match ? match[1] : void 0; }; const validateVideoId = (videoId) => { if (!videoId) return false; const videoIdRegex = /^[\w-]{11}$/; return videoIdRegex.test(videoId); }; const escapeHtml = (unsafe) => { if (!unsafe) return ""; return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); }; const safeNumber = (value, defaultValue = 0) => { if (typeof value !== "number" || Number.isNaN(value)) return defaultValue; return Math.max(0, Math.floor(value)); }; //#endregion //#region src/WebYoutubePlayerController.ts var WebYoutubePlayerController = class WebYoutubePlayerController { player = null; progressInterval = null; callbacks = {}; progressIntervalMs = 1e3; seekTimeout = null; static createInstance() { return new WebYoutubePlayerController(); } static async initialize() { 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, origin: config.playerVars?.origin, 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 }); } getPlayer() { return this.player; } 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; } async setPlaybackRate(suggestedRate) { await 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); } updateProgressInterval(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; } } }; var WebYoutubePlayerController_default = WebYoutubePlayerController; //#endregion exports.ERROR_CODES = ERROR_CODES; exports.MATCH_URL_YOUTUBE = MATCH_URL_YOUTUBE; exports.PlayerState = PlayerState; exports.WebYoutubePlayerController = WebYoutubePlayerController_default; exports.escapeHtml = escapeHtml; exports.extractVideoIdFromUrl = extractVideoIdFromUrl; exports.safeNumber = safeNumber; exports.validateVideoId = validateVideoId;