UNPKG

@werk1/w1-system-videoblock

Version:

Universal video player supporting YouTube, Vimeo, HLS, DASH with coordination and GSAP integration for W1 System

750 lines (588 loc) 23.3 kB
# @werk1/w1-system-videoblock W1 System Video Components - Universal video player supporting YouTube, Vimeo, HLS, DASH, and progressive videos with coordination and GSAP integration. ## Features ### 🌐 Universal Video Support - **YouTube**: Auto-detects and loads YouTube IFrame API - **Vimeo**: Auto-detects and loads Vimeo Player API - **HLS Streaming**: Auto-loads HLS.js for adaptive streaming - **DASH Streaming**: Auto-loads Dash.js for MPEG-DASH - **Progressive**: Native support for MP4, WebM, OGV ### 🎯 Smart Loading - **On-Demand**: Libraries loaded only when needed - **Zero Overhead**: No unused libraries in bundle - **Native First**: Uses browser native support when available - **Graceful Fallback**: Falls back to simpler implementations ### 🎮 Professional Features - **Simplified Video Coordination**: Smart coordination with platform-specific behavior - **Native Videos**: Full auto stop/resume based on visibility and priority - **YouTube/Vimeo**: Basic single-video mode + native platform behavior - **Picture-in-Picture**: Native PiP support - **Keyboard Controls**: Optional space/arrow key navigation - **Custom Themes**: Dark, light, transparent UI themes - **Modern SVG Icons**: Professional vector icons for all controls - **Progress Tracking**: Real-time callbacks and analytics - **Smart Logging**: Production-optimized logging system - **Performance Optimized**: Advanced state comparison and debouncing - **Centralized Types**: Single source of truth for all platform types ## Architecture - **W1VideoPlayer**: Pure video engine (no store coordination) - **W1VideoBlock**: Complete coordinator with simplified platform-specific behavior - **VideoCoordinationSlice**: Zustand store integration with smart decision engine - **Centralized Types**: All YouTube, Vimeo, HLS, DASH types in single location - **Icon Components**: Scalable SVG icons (PlayIcon, PauseIcon, VolumeIcon, FullscreenIcon, PictureInPictureIcon) - **Smart Logger**: Environment-aware logging for clean production builds ### Simplified Coordination Strategy - **Native Videos (MP4, HLS, DASH)**: Full W1 coordination with custom visibility thresholds - **External Platforms (YouTube, Vimeo)**: Basic single-video mode + native platform behavior - **Manual Actions**: Play/pause coordinates across all video types - **Platform Behaviors**: YouTube/Vimeo handle their own intersection observers and autoplay policies ## Installation ```bash npm install @werk1/w1-system-videoblock ``` ## Peer Dependencies ```bash npm install react react-dom zustand ``` ## Components & Icons ### Video Components - `W1VideoBlock` - Main orchestrator component (handles all coordination and store management) - `W1VideoPlayer` - Pure video engine (no store dependencies) - `W1VideoControls` - UI controls layer ### Modern SVG Icons - `PlayIcon` - Professional play button - `PauseIcon` - Modern pause button - `VolumeIcon` - Dynamic volume icon (mute/low/high states) - `FullscreenIcon` - Enter/exit fullscreen toggle - `PictureInPictureIcon` - Picture-in-picture mode toggle All icons support: - **Scalable**: `size` prop for any pixel size - **Themeable**: `color="currentColor"` inherits theme colors - **Accessible**: Built-in ARIA labels and roles - **Lightweight**: Optimized SVG paths ## Basic Usage ### Universal Video Support ```tsx import { W1VideoBlock } from '@werk1/w1-system-videoblock'; // Works with any video type const VideoSection = () => { const [isVisible, setIsVisible] = useState(false); return ( <div> {/* YouTube Video */} <W1VideoBlock src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" isVisible={isVisible} enableStreamingLibs={true} showControls={true} videoCoordinationStore={getVideoCoordinationStore} /> {/* Vimeo Video */} <W1VideoBlock src="https://vimeo.com/1084537" isVisible={isVisible} enableStreamingLibs={true} showControls={true} videoCoordinationStore={getVideoCoordinationStore} /> {/* HLS Stream */} <W1VideoBlock src="https://example.com/stream.m3u8" isVisible={isVisible} enableStreamingLibs={true} showControls={true} videoCoordinationStore={getVideoCoordinationStore} /> {/* Progressive Video */} <W1VideoBlock src="video.mp4" isVisible={isVisible} priority={5} showControls={true} videoCoordinationStore={getVideoCoordinationStore} /> </div> ); }; ``` ### Using Individual Icons ```tsx import { PlayIcon, PauseIcon, VolumeIcon } from '@werk1/w1-system-videoblock'; const CustomControls = () => { return ( <div> <button> <PlayIcon size={24} color="#ffffff" /> </button> <button> <PauseIcon size={24} color="currentColor" /> </button> <button> <VolumeIcon level={0.8} isMuted={false} size={20} /> </button> </div> ); }; ``` ### GSAP Integration ```tsx import { W1VideoBlock } from '@werk1/w1-system-videoblock'; import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; const AnimatedVideoSection = () => { const triggerRef = useRef(); const [isVisible, setIsVisible] = useState(false); useLayoutEffect(() => { const trigger = ScrollTrigger.create({ trigger: triggerRef.current, start: "top 80%", end: "bottom 20%", onEnter: () => setIsVisible(true), onLeave: () => setIsVisible(false), }); return () => trigger.kill(); }, []); return ( <div ref={triggerRef}> <W1VideoBlock src="video.mp4" isVisible={isVisible} priority={10} videoCoordinationStore={getVideoCoordinationStore} /> </div> ); }; ``` ## BoundStore Integration ### Add to your BoundStore ```tsx import { createVideoCoordinationSlice } from '@werk1/w1-system-videoblock'; import type { VideoCoordinationSlice } from '@werk1/w1-system-videoblock'; type BoundStore = DeviceInfoSlice & UIStateSlice & VideoCoordinationSlice; // Add video coordination export const useBoundStore = create<BoundStore>()( persist( (...a) => ({ ...createDeviceInfoSlice(...a), ...createUIStateSlice(...a), ...createVideoCoordinationSlice(...a), // Add video slice }), { name: 'app-state', partialize: (state) => ({ ui: state.ui, // Don't persist video state }), }, ), ); ``` ### Use Video Coordination ```tsx const VideoComponent = () => { const { currentlyPlayingVideoBlock, singleVideoMode, setSingleVideoMode } = useBoundStore(); return ( <div> <button onClick={() => setSingleVideoMode(!singleVideoMode)}> Single Video Mode: {singleVideoMode ? 'ON' : 'OFF'} </button> <p>Currently Playing: {currentlyPlayingVideoBlock?.id || 'None'}</p> </div> ); }; ``` ## API Reference ### W1VideoBlock Props ```tsx interface W1VideoBlockProps { src: string; // Video source URL isVisible: boolean; // External visibility control priority?: number; // Coordination priority (default: 0) // Video props autoplay?: boolean; // Auto-play when visible muted?: boolean; // Start muted loop?: boolean; // Loop video objectFit?: 'cover' | 'contain'; // Video sizing // Controls showControls?: boolean; // Show video controls controlsTheme?: 'dark' | 'light' | 'transparent'; showVolumeControl?: boolean; showFullscreen?: boolean; // Callbacks onVisibilityChange?: (isVisible: boolean) => void; onReady?: (methods: W1VideoPlayerMethods) => void; } ``` ### Video Coordination The system provides smart coordination with platform-specific behavior: #### Native Videos (MP4, HLS, DASH) - **Full Coordination**: Auto stop/resume based on visibility and priority - **Custom Thresholds**: GSAP ScrollTrigger integration with configurable visibility zones - **Priority System**: Higher priority videos take precedence automatically - **Seamless Behavior**: Complete control over video lifecycle #### External Platforms (YouTube, Vimeo) - **Basic Single-Video Mode**: Manual play pauses other videos - **Platform-Native Behavior**: Respect YouTube/Vimeo's built-in intersection observers - **Simplified Integration**: No fighting against platform autoplay policies - **Stable Performance**: Reliable behavior across platform updates #### Coordination Features - **Single Video Mode**: Only one video plays at a time (default: enabled) - **Manual Actions**: Play/pause coordinates across all video types - **Mobile Optimized**: Respects mobile autoplay limitations - **Mixed Coordination**: Native and external videos work together seamlessly ## Performance - **Mobile-First**: Optimized for mobile devices - **Single Video Coordination**: Prevents multiple video streams - **GSAP Integration**: Uses GSAP ScrollTrigger for smooth performance - **Lazy Loading**: Videos only load when needed - **Smart State Comparison**: 25x faster than JSON.stringify (50ms → 2ms) - **Debounced Updates**: Intelligent currentTime updates to prevent frame drops - **Production Logging**: Silent in production, debug-enabled in development ## Debugging & Development ### Smart Logging System The package includes a production-optimized logging system: ```tsx // Enable debug logging in development if (typeof window !== 'undefined') { window.W1_VIDEO_DEBUG = true; } // Or via environment variable process.env.W1_VIDEO_DEBUG = 'true' ``` **Logging Behavior:** - **Production**: Silent (no console output) - **Development**: Full debug information - **Debug Mode**: Detailed video coordination and streaming logs ## TypeScript Full TypeScript support with comprehensive type definitions. ## Examples ### Custom Visibility Hook ```tsx import { useRef, useState, useLayoutEffect } from 'react'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; const useVideoVisibility = (options = {}) => { const triggerRef = useRef(); const [isVisible, setIsVisible] = useState(false); useLayoutEffect(() => { const trigger = ScrollTrigger.create({ trigger: triggerRef.current, start: "top 80%", end: "bottom 20%", onEnter: () => setIsVisible(true), onLeave: () => setIsVisible(false), onEnterBack: () => setIsVisible(true), onLeaveBack: () => setIsVisible(false), ...options, }); return () => trigger.kill(); }, []); return { triggerRef, isVisible }; }; ``` ### Multiple Video Coordination ```tsx const MultiVideoSection = () => { const { triggerRef: triggerRef1, isVisible: isVisible1 } = useVideoVisibility(); const { triggerRef: triggerRef2, isVisible: isVisible2 } = useVideoVisibility(); return ( <> <div ref={triggerRef1}> <W1VideoBlock src="hero-video.mp4" isVisible={isVisible1} priority={10} // Higher priority /> </div> <div ref={triggerRef2}> <W1VideoBlock src="background-video.mp4" isVisible={isVisible2} priority={5} // Lower priority /> </div> </> ); }; ``` ## Browser Support - Modern browsers with ES2020 support - Chrome 80+ - Firefox 78+ - Safari 14+ - Edge 80+ ### Mobile Support - iOS Safari 14+ - Chrome Mobile 80+ - Samsung Internet 13+ ## Troubleshooting ### Video Won't Play on Mobile Mobile browsers have strict autoplay policies. Ensure: ```tsx <W1VideoBlock src="video.mp4" muted={true} // Required for autoplay playsInline={true} // Prevents fullscreen on iOS /> ``` ### Multiple Videos Playing Check your BoundStore integration and single video mode: ```tsx const { singleVideoMode, setSingleVideoMode } = useBoundStore(); // Enable single video mode useEffect(() => { setSingleVideoMode(true); }, []); ``` ### YouTube/Vimeo Auto-Pause Behavior YouTube and Vimeo have built-in intersection observers that pause videos when ~70% out of view. This is platform behavior and cannot be disabled: - **Expected**: YouTube/Vimeo videos pause themselves based on visibility - **Solution**: Use native MP4/HLS videos if you need custom visibility control - **Mixed Usage**: Combine native videos (full control) with YouTube/Vimeo (platform behavior) ### Platform Coordination Differences Different video types have different coordination capabilities: ```tsx // Native videos: Full coordination with custom thresholds <W1VideoBlock src="video.mp4" isVisible={isVisible} priority={10} /> // YouTube/Vimeo: Basic single-video mode + platform behavior <W1VideoBlock src="https://youtube.com/watch?v=..." isVisible={isVisible} /> ``` ### GSAP ScrollTrigger Issues Ensure GSAP ScrollTrigger is properly registered: ```tsx import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); ``` ## Contributing 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/new-feature` 3. Commit changes: `git commit -am 'Add new feature'` 4. Push to branch: `git push origin feature/new-feature` 5. Submit a pull request ### Development Setup ```bash # Clone the repository git clone https://github.com/werk1/w1-system-videoblock.git # Install dependencies npm install # Build package npm run build # Run tests npm test ``` ## Changelog ### v1.3.0 (December 2024) -**Simplified Coordination Architecture**: Platform-specific coordination behavior - **Native Videos**: Full auto stop/resume with custom visibility thresholds - **YouTube/Vimeo**: Basic single-video mode + native platform behavior -**Centralized Type System**: Single source of truth for all platform types -**Stable Platform Integration**: No longer fights YouTube/Vimeo built-in behaviors - 🐛 **Fixed**: YouTube/Vimeo intersection observer conflicts - 🐛 **Fixed**: Removed duplicate type definitions across platform files - 📚 **Documentation**: Updated all docs to reflect simplified coordination approach ### v1.2.0 (December 2024) -**Performance Optimizations**: 25x faster state comparison (JSON.stringify → smart shallow comparison) -**Smart Logging System**: Production-silent, development-verbose logging with debug flags -**Architecture Cleanup**: Clean separation between W1VideoPlayer (engine) and W1VideoBlock (coordinator) -**UX Improvements**: Fixed click behavior and controls visibility -**Single Video Mode**: Improved coordination timing for proper video switching -**w1-system-core Integration**: Ready for BoundStore pattern integration - 🐛 **Fixed**: Eliminated dual store registration complexity - 🐛 **Fixed**: External GSAP management (no more hardcoded animations) - 📚 **Documentation**: Complete integration guides and corrected package references ### v1.1.0 - Enhanced streaming support (YouTube, Vimeo, HLS, DASH) - Modern SVG icons with theme support - Professional UI controls with dark/light/transparent themes - Picture-in-Picture enhancements with metadata validation - Cross-browser compatibility improvements - TypeScript safety improvements ### v1.0.0 - Initial release - W1VideoPlayer headless component - W1VideoControls UI layer - W1VideoBlock orchestrator - VideoCoordinationSlice store integration - Mobile-first architecture - GSAP ScrollTrigger integration ## License MIT License Copyright (c) 2024 WERK1 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## 🔗 W1 System Core Integration ### Adding VideoCoordination to Core BoundStore When integrating with **w1-system-core**, add the video coordination slice to your main BoundStore: ```tsx // w1-system-core/src/stores/boundStore.ts import { createVideoCoordinationSlice } from '@werk1/w1-system-videoblock'; import type { VideoCoordinationSlice } from '@werk1/w1-system-videoblock'; type BoundStore = DeviceInfoSlice & UIStateSlice & VideoCoordinationSlice; // Add video coordination export const useBoundStore = create<BoundStore>()( persist( (...a) => ({ ...createDeviceInfoSlice(...a), ...createUIStateSlice(...a), ...createVideoCoordinationSlice(...a), // Add video slice }), { name: 'app-state', partialize: (state) => ({ ui: state.ui, deviceInfo: state.deviceInfo, // Don't persist video state (always starts fresh) }), }, ), ); ``` ### Usage in Core Application ```tsx // In your w1-system-core pages/components import { W1VideoBlock } from '@werk1/w1-system-videoblock'; import { useBoundStore } from '../stores/boundStore'; const VideoSection = () => { const [isVisible, setIsVisible] = useState(false); // Get video coordination from main bound store const getVideoCoordinationStore = useCallback(() => { return useBoundStore.getState(); }, []); return ( <W1VideoBlock src="video.mp4" isVisible={isVisible} videoCoordinationStore={getVideoCoordinationStore} /> ); }; ``` ## 🎯 GSAP ScrollTrigger Integration (Complete Working Example) The W1VideoBlock integrates perfectly with GSAP ScrollTrigger for scroll-based video visibility and autoplay. Here's the **exact working pattern** from our test environment: ```tsx 'use client'; import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { useCallback, useEffect, useRef, useState } from 'react'; import { create } from 'zustand'; import { W1VideoBlock, createVideoCoordinationSlice } from '@werk1/w1-system-videoblock'; import type { VideoCoordinationSlice } from '@werk1/w1-system-videoblock'; // Register GSAP plugins if (typeof window !== 'undefined') { gsap.registerPlugin(ScrollTrigger); } // Video coordination store type BoundStore = VideoCoordinationSlice; const useBoundStore = create<BoundStore>()(createVideoCoordinationSlice); // Custom visibility hook for GSAP integration const useVideoVisibility = (options = {}) => { const triggerRef = useRef<HTMLDivElement>(null); const [isVisible, setIsVisible] = useState(false); const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); }, []); useEffect(() => { if (!isClient || !triggerRef.current) return; const trigger = ScrollTrigger.create({ trigger: triggerRef.current, start: "top bottom-=20%", end: "bottom top+=20%", onEnter: () => setIsVisible(true), onLeave: () => setIsVisible(false), onEnterBack: () => setIsVisible(true), onLeaveBack: () => setIsVisible(false), ...options }); return () => trigger.kill(); }, [isClient, options]); return { triggerRef, isVisible }; }; export default function VideoPage() { const { triggerRef: triggerRef1, isVisible: isVisible1 } = useVideoVisibility(); const { triggerRef: triggerRef2, isVisible: isVisible2 } = useVideoVisibility(); const getVideoCoordinationStore = useCallback(() => { return useBoundStore.getState(); }, []); return ( <div> {/* Video 1 with GSAP ScrollTrigger */} <div ref={triggerRef1} style={{ height: '100vh', background: '#000', margin: '20px 0' }}> <h3 style={{ color: 'white', padding: '20px' }}> Video 1 (Visible: {isVisible1 ? 'YES' : 'NO'}) </h3> <div style={{ height: '80%', padding: '20px' }}> <W1VideoBlock src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" priority={10} isVisible={isVisible1} // CRITICAL: Pass GSAP-detected visibility showControls={true} videoCoordinationStore={getVideoCoordinationStore} enableStreamingLibs={true} enableKeyboardControls={true} style={{ width: '100%', height: '100%', borderRadius: '12px', overflow: 'hidden' }} /> </div> </div> {/* Video 2 with GSAP ScrollTrigger */} <div ref={triggerRef2} style={{ height: '100vh', background: '#333', margin: '20px 0' }}> <h3 style={{ color: 'white', padding: '20px' }}> YouTube Video (Visible: {isVisible2 ? 'YES' : 'NO'}) </h3> <div style={{ height: '80%', padding: '20px' }}> <W1VideoBlock src="https://www.youtube.com/watch?v=YE7VzlLtp-4" priority={5} isVisible={isVisible2} // CRITICAL: Pass GSAP-detected visibility showControls={true} videoCoordinationStore={getVideoCoordinationStore} enableStreamingLibs={true} style={{ width: '100%', height: '100%', borderRadius: '12px', overflow: 'hidden' }} /> </div> </div> </div> ); } ``` ### 🎯 Key Points 1. **Page-Level GSAP Integration**: ScrollTrigger is implemented at the page level, not inside W1VideoBlock 2. **useVideoVisibility Hook**: Creates individual `triggerRef` and `isVisible` state for each video 3. **Wrapper Pattern**: Each video is wrapped in a `<div ref={triggerRef}>` for scroll detection 4. **isVisible Prop**: W1VideoBlock receives the `isVisible` prop from the GSAP hook 5. **Video Coordination**: Uses the Zustand store for single-video playback coordination This pattern provides **automatic scroll-based play/pause** functionality with perfect GSAP integration. ## 🚀 Quick Start ```bash npm install @werk1/w1-system-videoblock ```