chunk-018-video.js

I’ll build this as a standalone advanced video chunk file for RX Theme: performance-friendly, accessible, lazy-loaded, WordPress-friendly, and safe to enqueue only where video features are needed.

Dear friend, create this file:

assets/static-js/static-chunks-js/chunk-018-video.js

This file is advanced video functionality for RX Theme: lazy video loading, YouTube/Vimeo support, native video support, play/pause buttons, mute, fullscreen, progress bar, keyboard support, accessibility, modal video, scroll pause, reduced-motion respect, and performance-safe initialization.

/*!
 * RX Theme - Chunk 018 Video
 * File: assets/static-js/static-chunks-js/chunk-018-video.js
 * Purpose: Advanced video functionality for RX Theme
 * Author: RxHarun
 */

(function () {
  'use strict';

  /**
   * Prevent duplicate loading
   */
  if (window.RXVideoChunkLoaded) {
    return;
  }

  window.RXVideoChunkLoaded = true;

  /**
   * Main namespace
   */
  window.RXTheme = window.RXTheme || {};

  const RXVideo = {
    version: '1.0.0',

    selectors: {
      root: '[data-rx-video]',
      nativeVideo: 'video[data-rx-video-native]',
      youtube: '[data-rx-youtube]',
      vimeo: '[data-rx-vimeo]',
      lazyVideo: '[data-rx-video-lazy]',
      playButton: '[data-rx-video-play]',
      pauseButton: '[data-rx-video-pause]',
      muteButton: '[data-rx-video-mute]',
      fullscreenButton: '[data-rx-video-fullscreen]',
      progress: '[data-rx-video-progress]',
      progressBar: '[data-rx-video-progress-bar]',
      timeCurrent: '[data-rx-video-current]',
      timeDuration: '[data-rx-video-duration]',
      modalTrigger: '[data-rx-video-modal]',
      modalClose: '[data-rx-video-modal-close]',
      poster: '[data-rx-video-poster]',
      iframeWrapper: '[data-rx-video-iframe-wrapper]'
    },

    classes: {
      initialized: 'rx-video-initialized',
      loading: 'rx-video-loading',
      loaded: 'rx-video-loaded',
      playing: 'rx-video-playing',
      paused: 'rx-video-paused',
      muted: 'rx-video-muted',
      error: 'rx-video-error',
      modalOpen: 'rx-video-modal-open',
      hidden: 'rx-hidden',
      active: 'rx-active'
    },

    settings: {
      lazyRootMargin: '300px 0px',
      lazyThreshold: 0.01,
      autoPauseOnScrollOut: true,
      pauseOtherVideos: true,
      respectReducedMotion: true,
      enableKeyboard: true,
      debug: false
    },

    videos: new Map(),
    observer: null,
    modal: null,
    lastFocusedElement: null,

    /**
     * Debug logger
     */
    log(...args) {
      if (this.settings.debug) {
        console.log('[RX Video]', ...args);
      }
    },

    /**
     * Safe query helpers
     */
    qs(selector, context = document) {
      return context.querySelector(selector);
    },

    qsa(selector, context = document) {
      return Array.from(context.querySelectorAll(selector));
    },

    /**
     * Check reduced motion
     */
    prefersReducedMotion() {
      return (
        this.settings.respectReducedMotion &&
        window.matchMedia &&
        window.matchMedia('(prefers-reduced-motion: reduce)').matches
      );
    },

    /**
     * Generate unique ID
     */
    uid(prefix = 'rx-video') {
      return `${prefix}-${Math.random().toString(36).slice(2, 11)}`;
    },

    /**
     * Format seconds to mm:ss
     */
    formatTime(seconds) {
      if (!Number.isFinite(seconds)) {
        return '0:00';
      }

      const totalSeconds = Math.max(0, Math.floor(seconds));
      const minutes = Math.floor(totalSeconds / 60);
      const remainingSeconds = totalSeconds % 60;

      return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
    },

    /**
     * Add ARIA attributes safely
     */
    setAria(element, attrs = {}) {
      if (!element) return;

      Object.entries(attrs).forEach(([key, value]) => {
        element.setAttribute(key, value);
      });
    },

    /**
     * Dispatch custom event
     */
    emit(name, detail = {}) {
      document.dispatchEvent(
        new CustomEvent(`rxVideo:${name}`, {
          detail,
          bubbles: true
        })
      );
    },

    /**
     * Initialize all video features
     */
    init(options = {}) {
      this.settings = {
        ...this.settings,
        ...options
      };

      this.setupLazyObserver();
      this.initNativeVideos();
      this.initLazyEmbeds();
      this.initVideoControls();
      this.initVideoModals();
      this.bindGlobalEvents();

      this.emit('ready', {
        version: this.version
      });

      this.log('Initialized');
    },

    /**
     * Setup lazy observer
     */
    setupLazyObserver() {
      if (!('IntersectionObserver' in window)) {
        this.loadAllLazyVideos();
        return;
      }

      this.observer = new IntersectionObserver(
        entries => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              this.loadLazyVideo(entry.target);

              if (this.observer) {
                this.observer.unobserve(entry.target);
              }
            }
          });
        },
        {
          root: null,
          rootMargin: this.settings.lazyRootMargin,
          threshold: this.settings.lazyThreshold
        }
      );
    },

    /**
     * Load all lazy videos fallback
     */
    loadAllLazyVideos() {
      this.qsa(this.selectors.lazyVideo).forEach(video => {
        this.loadLazyVideo(video);
      });

      this.qsa(this.selectors.youtube).forEach(embed => {
        this.loadLazyEmbed(embed);
      });

      this.qsa(this.selectors.vimeo).forEach(embed => {
        this.loadLazyEmbed(embed);
      });
    },

    /**
     * Native video initialization
     */
    initNativeVideos() {
      const videos = this.qsa(this.selectors.nativeVideo);

      videos.forEach(video => {
        if (video.classList.contains(this.classes.initialized)) {
          return;
        }

        this.prepareNativeVideo(video);
      });
    },

    /**
     * Prepare native HTML5 video
     */
    prepareNativeVideo(video) {
      const id = video.id || this.uid('rx-native-video');
      video.id = id;

      video.classList.add(this.classes.initialized);

      if (!video.hasAttribute('preload')) {
        video.setAttribute('preload', 'metadata');
      }

      if (!video.hasAttribute('playsinline')) {
        video.setAttribute('playsinline', '');
      }

      this.setAria(video, {
        tabindex: '0'
      });

      this.videos.set(id, {
        type: 'native',
        element: video,
        loaded: !video.dataset.src,
        playing: false
      });

      this.bindNativeVideoEvents(video);

      if (video.matches(this.selectors.lazyVideo) && this.observer) {
        this.observer.observe(video);
      } else if (video.matches(this.selectors.lazyVideo)) {
        this.loadLazyVideo(video);
      }
    },

    /**
     * Bind native video events
     */
    bindNativeVideoEvents(video) {
      video.addEventListener('loadedmetadata', () => {
        this.updateDuration(video);
        this.emit('loadedmetadata', { video });
      });

      video.addEventListener('timeupdate', () => {
        this.updateProgress(video);
      });

      video.addEventListener('play', () => {
        this.markPlaying(video);

        if (this.settings.pauseOtherVideos) {
          this.pauseOtherVideos(video);
        }

        this.emit('play', { video });
      });

      video.addEventListener('pause', () => {
        this.markPaused(video);
        this.emit('pause', { video });
      });

      video.addEventListener('ended', () => {
        this.markPaused(video);
        this.emit('ended', { video });
      });

      video.addEventListener('volumechange', () => {
        this.updateMuteState(video);
      });

      video.addEventListener('error', () => {
        this.markError(video);
        this.emit('error', { video });
      });

      if (this.settings.enableKeyboard) {
        video.addEventListener('keydown', event => {
          this.handleVideoKeyboard(event, video);
        });
      }
    },

    /**
     * Lazy video loading
     */
    loadLazyVideo(video) {
      if (!video || video.dataset.rxLoaded === 'true') {
        return;
      }

      video.classList.add(this.classes.loading);

      const src = video.dataset.src;
      const poster = video.dataset.poster;

      if (poster && !video.getAttribute('poster')) {
        video.setAttribute('poster', poster);
      }

      this.qsa('source[data-src]', video).forEach(source => {
        source.src = source.dataset.src;
        source.removeAttribute('data-src');
      });

      if (src) {
        video.src = src;
        video.removeAttribute('data-src');
      }

      video.load();

      video.dataset.rxLoaded = 'true';
      video.classList.remove(this.classes.loading);
      video.classList.add(this.classes.loaded);

      this.emit('lazyloaded', { video });
    },

    /**
     * Lazy YouTube/Vimeo embeds
     */
    initLazyEmbeds() {
      const embeds = [
        ...this.qsa(this.selectors.youtube),
        ...this.qsa(this.selectors.vimeo)
      ];

      embeds.forEach(embed => {
        if (embed.classList.contains(this.classes.initialized)) {
          return;
        }

        embed.classList.add(this.classes.initialized);

        if (this.observer) {
          this.observer.observe(embed);
        } else {
          this.loadLazyEmbed(embed);
        }
      });
    },

    /**
     * Load YouTube/Vimeo iframe
     */
    loadLazyEmbed(embed) {
      if (!embed || embed.dataset.rxLoaded === 'true') {
        return;
      }

      const type = embed.matches(this.selectors.youtube) ? 'youtube' : 'vimeo';
      const id =
        embed.dataset.rxYoutube ||
        embed.dataset.rxVimeo ||
        embed.dataset.videoId ||
        '';

      if (!id) {
        this.markError(embed);
        return;
      }

      const title = embed.dataset.title || 'Embedded video';
      const autoplay = embed.dataset.autoplay === 'true';
      const muted = embed.dataset.muted === 'true';
      const controls = embed.dataset.controls !== 'false';

      let src = '';

      if (type === 'youtube') {
        src = this.buildYouTubeURL(id, {
          autoplay,
          muted,
          controls
        });
      }

      if (type === 'vimeo') {
        src = this.buildVimeoURL(id, {
          autoplay,
          muted,
          controls
        });
      }

      const iframe = document.createElement('iframe');

      iframe.src = src;
      iframe.title = title;
      iframe.loading = 'lazy';
      iframe.allow =
        'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
      iframe.allowFullscreen = true;
      iframe.referrerPolicy = 'strict-origin-when-cross-origin';

      iframe.setAttribute('frameborder', '0');

      embed.classList.add(this.classes.loading);
      embed.innerHTML = '';
      embed.appendChild(iframe);

      iframe.addEventListener('load', () => {
        embed.dataset.rxLoaded = 'true';
        embed.classList.remove(this.classes.loading);
        embed.classList.add(this.classes.loaded);

        this.emit('embedloaded', {
          embed,
          iframe,
          type
        });
      });

      iframe.addEventListener('error', () => {
        this.markError(embed);
      });
    },

    /**
     * Build YouTube privacy-friendly URL
     */
    buildYouTubeURL(id, options = {}) {
      const params = new URLSearchParams();

      params.set('rel', '0');
      params.set('modestbranding', '1');
      params.set('playsinline', '1');

      if (options.autoplay) {
        params.set('autoplay', '1');
      }

      if (options.muted || options.autoplay) {
        params.set('mute', '1');
      }

      if (!options.controls) {
        params.set('controls', '0');
      }

      return `https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}?${params.toString()}`;
    },

    /**
     * Build Vimeo URL
     */
    buildVimeoURL(id, options = {}) {
      const params = new URLSearchParams();

      params.set('dnt', '1');
      params.set('playsinline', '1');

      if (options.autoplay) {
        params.set('autoplay', '1');
      }

      if (options.muted || options.autoplay) {
        params.set('muted', '1');
      }

      if (!options.controls) {
        params.set('controls', '0');
      }

      return `https://player.vimeo.com/video/${encodeURIComponent(id)}?${params.toString()}`;
    },

    /**
     * Initialize custom controls
     */
    initVideoControls() {
      const roots = this.qsa(this.selectors.root);

      roots.forEach(root => {
        if (root.dataset.rxControlsReady === 'true') {
          return;
        }

        root.dataset.rxControlsReady = 'true';

        const video = this.qs('video', root);

        if (!video) {
          return;
        }

        this.bindControlButtons(root, video);
        this.updateDuration(video);
        this.updateProgress(video);
        this.updateMuteState(video);
      });
    },

    /**
     * Bind control buttons
     */
    bindControlButtons(root, video) {
      const playButtons = this.qsa(this.selectors.playButton, root);
      const pauseButtons = this.qsa(this.selectors.pauseButton, root);
      const muteButtons = this.qsa(this.selectors.muteButton, root);
      const fullscreenButtons = this.qsa(this.selectors.fullscreenButton, root);
      const progress = this.qs(this.selectors.progress, root);

      playButtons.forEach(button => {
        this.setAria(button, {
          type: 'button',
          'aria-label': button.getAttribute('aria-label') || 'Play video'
        });

        button.addEventListener('click', event => {
          event.preventDefault();
          this.playVideo(video);
        });
      });

      pauseButtons.forEach(button => {
        this.setAria(button, {
          type: 'button',
          'aria-label': button.getAttribute('aria-label') || 'Pause video'
        });

        button.addEventListener('click', event => {
          event.preventDefault();
          this.pauseVideo(video);
        });
      });

      muteButtons.forEach(button => {
        this.setAria(button, {
          type: 'button',
          'aria-label': button.getAttribute('aria-label') || 'Mute video'
        });

        button.addEventListener('click', event => {
          event.preventDefault();
          this.toggleMute(video);
        });
      });

      fullscreenButtons.forEach(button => {
        this.setAria(button, {
          type: 'button',
          'aria-label': button.getAttribute('aria-label') || 'Fullscreen video'
        });

        button.addEventListener('click', event => {
          event.preventDefault();
          this.toggleFullscreen(root);
        });
      });

      if (progress) {
        this.setAria(progress, {
          role: 'slider',
          tabindex: '0',
          'aria-valuemin': '0',
          'aria-valuemax': '100',
          'aria-valuenow': '0',
          'aria-label': progress.getAttribute('aria-label') || 'Video progress'
        });

        progress.addEventListener('click', event => {
          this.seekFromPointer(event, progress, video);
        });

        progress.addEventListener('keydown', event => {
          this.handleProgressKeyboard(event, video);
        });
      }
    },

    /**
     * Play video safely
     */
    playVideo(video) {
      if (!video) return;

      if (video.matches(this.selectors.lazyVideo)) {
        this.loadLazyVideo(video);
      }

      const playPromise = video.play();

      if (playPromise && typeof playPromise.catch === 'function') {
        playPromise.catch(() => {
          this.log('Autoplay or play blocked');
        });
      }
    },

    /**
     * Pause video
     */
    pauseVideo(video) {
      if (!video) return;

      video.pause();
    },

    /**
     * Toggle play
     */
    togglePlay(video) {
      if (!video) return;

      if (video.paused) {
        this.playVideo(video);
      } else {
        this.pauseVideo(video);
      }
    },

    /**
     * Toggle mute
     */
    toggleMute(video) {
      if (!video) return;

      video.muted = !video.muted;
      this.updateMuteState(video);
    },

    /**
     * Seek video from pointer
     */
    seekFromPointer(event, progress, video) {
      if (!video || !progress || !Number.isFinite(video.duration)) {
        return;
      }

      const rect = progress.getBoundingClientRect();
      const position = Math.min(Math.max(0, event.clientX - rect.left), rect.width);
      const percent = position / rect.width;

      video.currentTime = percent * video.duration;
    },

    /**
     * Keyboard controls for video
     */
    handleVideoKeyboard(event, video) {
      const key = event.key;

      if (key === ' ' || key === 'Enter') {
        event.preventDefault();
        this.togglePlay(video);
      }

      if (key === 'ArrowRight') {
        event.preventDefault();
        this.seekBy(video, 5);
      }

      if (key === 'ArrowLeft') {
        event.preventDefault();
        this.seekBy(video, -5);
      }

      if (key.toLowerCase() === 'm') {
        event.preventDefault();
        this.toggleMute(video);
      }

      if (key.toLowerCase() === 'f') {
        event.preventDefault();
        const root = video.closest(this.selectors.root) || video.parentElement;
        this.toggleFullscreen(root);
      }
    },

    /**
     * Keyboard progress controls
     */
    handleProgressKeyboard(event, video) {
      if (!video) return;

      if (event.key === 'ArrowRight') {
        event.preventDefault();
        this.seekBy(video, 5);
      }

      if (event.key === 'ArrowLeft') {
        event.preventDefault();
        this.seekBy(video, -5);
      }

      if (event.key === 'Home') {
        event.preventDefault();
        video.currentTime = 0;
      }

      if (event.key === 'End' && Number.isFinite(video.duration)) {
        event.preventDefault();
        video.currentTime = video.duration;
      }
    },

    /**
     * Seek by seconds
     */
    seekBy(video, seconds) {
      if (!video || !Number.isFinite(video.duration)) {
        return;
      }

      video.currentTime = Math.min(
        Math.max(0, video.currentTime + seconds),
        video.duration
      );
    },

    /**
     * Update progress UI
     */
    updateProgress(video) {
      const root = video.closest(this.selectors.root);

      if (!root || !Number.isFinite(video.duration)) {
        return;
      }

      const percent = (video.currentTime / video.duration) * 100;
      const progress = this.qs(this.selectors.progress, root);
      const progressBar = this.qs(this.selectors.progressBar, root);
      const current = this.qs(this.selectors.timeCurrent, root);

      if (progressBar) {
        progressBar.style.width = `${percent}%`;
      }

      if (progress) {
        progress.setAttribute('aria-valuenow', String(Math.round(percent)));
      }

      if (current) {
        current.textContent = this.formatTime(video.currentTime);
      }
    },

    /**
     * Update duration UI
     */
    updateDuration(video) {
      const root = video.closest(this.selectors.root);

      if (!root || !Number.isFinite(video.duration)) {
        return;
      }

      const duration = this.qs(this.selectors.timeDuration, root);

      if (duration) {
        duration.textContent = this.formatTime(video.duration);
      }
    },

    /**
     * Mark playing
     */
    markPlaying(video) {
      const root = video.closest(this.selectors.root);

      video.classList.add(this.classes.playing);
      video.classList.remove(this.classes.paused);

      if (root) {
        root.classList.add(this.classes.playing);
        root.classList.remove(this.classes.paused);
      }
    },

    /**
     * Mark paused
     */
    markPaused(video) {
      const root = video.closest(this.selectors.root);

      video.classList.add(this.classes.paused);
      video.classList.remove(this.classes.playing);

      if (root) {
        root.classList.add(this.classes.paused);
        root.classList.remove(this.classes.playing);
      }
    },

    /**
     * Mark error
     */
    markError(element) {
      if (!element) return;

      element.classList.add(this.classes.error);
      element.classList.remove(this.classes.loading);

      const root = element.closest(this.selectors.root);

      if (root) {
        root.classList.add(this.classes.error);
      }

      this.emit('error', {
        element
      });
    },

    /**
     * Update mute state
     */
    updateMuteState(video) {
      const root = video.closest(this.selectors.root);

      if (!root) return;

      const muteButtons = this.qsa(this.selectors.muteButton, root);

      if (video.muted || video.volume === 0) {
        root.classList.add(this.classes.muted);

        muteButtons.forEach(button => {
          button.setAttribute('aria-label', 'Unmute video');
          button.setAttribute('aria-pressed', 'true');
        });
      } else {
        root.classList.remove(this.classes.muted);

        muteButtons.forEach(button => {
          button.setAttribute('aria-label', 'Mute video');
          button.setAttribute('aria-pressed', 'false');
        });
      }
    },

    /**
     * Pause other videos
     */
    pauseOtherVideos(currentVideo) {
      this.qsa('video').forEach(video => {
        if (video !== currentVideo && !video.paused) {
          video.pause();
        }
      });
    },

    /**
     * Fullscreen support
     */
    toggleFullscreen(element) {
      if (!element) return;

      if (!document.fullscreenElement) {
        if (element.requestFullscreen) {
          element.requestFullscreen().catch(() => {});
        }
      } else if (document.exitFullscreen) {
        document.exitFullscreen().catch(() => {});
      }
    },

    /**
     * Video modal system
     */
    initVideoModals() {
      const triggers = this.qsa(this.selectors.modalTrigger);

      if (!triggers.length) {
        return;
      }

      this.createModal();

      triggers.forEach(trigger => {
        if (trigger.dataset.rxModalReady === 'true') {
          return;
        }

        trigger.dataset.rxModalReady = 'true';

        trigger.addEventListener('click', event => {
          event.preventDefault();
          this.openModalFromTrigger(trigger);
        });
      });
    },

    /**
     * Create reusable modal
     */
    createModal() {
      if (this.modal) {
        return;
      }

      const modal = document.createElement('div');

      modal.className = 'rx-video-modal';
      modal.setAttribute('role', 'dialog');
      modal.setAttribute('aria-modal', 'true');
      modal.setAttribute('aria-label', 'Video player');
      modal.setAttribute('hidden', '');

      modal.innerHTML = `
        <div class="rx-video-modal__overlay" data-rx-video-modal-close></div>
        <div class="rx-video-modal__dialog" role="document">
          <button class="rx-video-modal__close" type="button" data-rx-video-modal-close aria-label="Close video">
            ×
          </button>
          <div class="rx-video-modal__content" data-rx-video-modal-content></div>
        </div>
      `;

      document.body.appendChild(modal);

      this.modal = modal;

      this.qsa(this.selectors.modalClose, modal).forEach(close => {
        close.addEventListener('click', () => {
          this.closeModal();
        });
      });

      document.addEventListener('keydown', event => {
        if (event.key === 'Escape' && document.body.classList.contains(this.classes.modalOpen)) {
          this.closeModal();
        }
      });
    },

    /**
     * Open modal from trigger
     */
    openModalFromTrigger(trigger) {
      if (!this.modal) {
        return;
      }

      const content = this.qs('[data-rx-video-modal-content]', this.modal);

      if (!content) {
        return;
      }

      this.lastFocusedElement = document.activeElement;

      const youtubeId = trigger.dataset.youtube || trigger.dataset.rxYoutube;
      const vimeoId = trigger.dataset.vimeo || trigger.dataset.rxVimeo;
      const videoSrc = trigger.dataset.videoSrc;
      const title = trigger.dataset.title || 'Video player';

      let html = '';

      if (youtubeId) {
        html = `
          <iframe
            src="${this.buildYouTubeURL(youtubeId, {
              autoplay: true,
              muted: false,
              controls: true
            })}"
            title="${this.escapeHTML(title)}"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
            allowfullscreen
            loading="lazy"
            referrerpolicy="strict-origin-when-cross-origin"></iframe>
        `;
      } else if (vimeoId) {
        html = `
          <iframe
            src="${this.buildVimeoURL(vimeoId, {
              autoplay: true,
              muted: false,
              controls: true
            })}"
            title="${this.escapeHTML(title)}"
            allow="autoplay; fullscreen; picture-in-picture"
            allowfullscreen
            loading="lazy"></iframe>
        `;
      } else if (videoSrc) {
        html = `
          <video controls autoplay playsinline preload="metadata">
            <source src="${this.escapeAttribute(videoSrc)}">
          </video>
        `;
      }

      if (!html) {
        return;
      }

      content.innerHTML = html;

      this.modal.removeAttribute('hidden');
      document.body.classList.add(this.classes.modalOpen);

      const closeButton = this.qs(this.selectors.modalClose, this.modal);

      if (closeButton) {
        closeButton.focus();
      }

      this.emit('modalopen', {
        trigger
      });
    },

    /**
     * Close modal
     */
    closeModal() {
      if (!this.modal) {
        return;
      }

      const content = this.qs('[data-rx-video-modal-content]', this.modal);

      if (content) {
        content.innerHTML = '';
      }

      this.modal.setAttribute('hidden', '');
      document.body.classList.remove(this.classes.modalOpen);

      if (this.lastFocusedElement && typeof this.lastFocusedElement.focus === 'function') {
        this.lastFocusedElement.focus();
      }

      this.emit('modalclose');
    },

    /**
     * Escape HTML
     */
    escapeHTML(value) {
      return String(value)
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');
    },

    /**
     * Escape attribute
     */
    escapeAttribute(value) {
      return this.escapeHTML(value);
    },

    /**
     * Global events
     */
    bindGlobalEvents() {
      document.addEventListener('click', event => {
        const poster = event.target.closest(this.selectors.poster);

        if (poster) {
          const root = poster.closest(this.selectors.root);
          const video = root ? this.qs('video', root) : null;

          if (video) {
            this.playVideo(video);
          }
        }
      });

      if (this.settings.autoPauseOnScrollOut && 'IntersectionObserver' in window) {
        this.setupAutoPauseObserver();
      }

      document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
          this.pauseAllVideos();
        }
      });
    },

    /**
     * Pause videos when out of viewport
     */
    setupAutoPauseObserver() {
      const pauseObserver = new IntersectionObserver(
        entries => {
          entries.forEach(entry => {
            const video = entry.target;

            if (!entry.isIntersecting && !video.paused) {
              video.pause();
            }
          });
        },
        {
          threshold: 0.15
        }
      );

      this.qsa('video').forEach(video => {
        pauseObserver.observe(video);
      });
    },

    /**
     * Pause all native videos
     */
    pauseAllVideos() {
      this.qsa('video').forEach(video => {
        if (!video.paused) {
          video.pause();
        }
      });
    },

    /**
     * Public refresh method for AJAX/Load More
     */
    refresh() {
      this.initNativeVideos();
      this.initLazyEmbeds();
      this.initVideoControls();
      this.initVideoModals();

      this.emit('refresh');
    },

    /**
     * Public destroy method
     */
    destroy() {
      if (this.observer) {
        this.observer.disconnect();
      }

      this.pauseAllVideos();
      this.videos.clear();

      if (this.modal) {
        this.modal.remove();
        this.modal = null;
      }

      window.RXVideoChunkLoaded = false;

      this.emit('destroy');
    }
  };

  /**
   * Expose API
   */
  window.RXTheme.Video = RXVideo;

  /**
   * Auto init
   */
  const boot = () => {
    RXVideo.init();
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot, {
      once: true
    });
  } else {
    boot();
  }
})();

Example HTML for native video

<div class="rx-video-box" data-rx-video>
  <video
    data-rx-video-native
    data-rx-video-lazy
    data-src="https://example.com/video.mp4"
    data-poster="https://example.com/poster.jpg"
    preload="metadata"
    playsinline>
  </video>

  <button data-rx-video-play type="button">Play</button>
  <button data-rx-video-pause type="button">Pause</button>
  <button data-rx-video-mute type="button">Mute</button>
  <button data-rx-video-fullscreen type="button">Fullscreen</button>

  <div data-rx-video-progress class="rx-video-progress">
    <span data-rx-video-progress-bar class="rx-video-progress-bar"></span>
  </div>

  <span data-rx-video-current>0:00</span>
  <span data-rx-video-duration>0:00</span>
</div>

Example HTML for lazy YouTube

<div
  class="rx-video-embed"
  data-rx-youtube="YOUTUBE_VIDEO_ID"
  data-title="RX Theme Video"
  data-autoplay="false"
  data-muted="false"
  data-controls="true">
</div>

Example HTML for lazy Vimeo

<div
  class="rx-video-embed"
  data-rx-vimeo="VIMEO_VIDEO_ID"
  data-title="RX Theme Vimeo Video"
  data-autoplay="false"
  data-muted="false"
  data-controls="true">
</div>

Example HTML for modal video

<button
  type="button"
  data-rx-video-modal
  data-youtube="YOUTUBE_VIDEO_ID"
  data-title="Watch RX Theme Video">
  Watch Video
</button>

Enqueue in functions.php

function rx_theme_enqueue_video_chunk() {
    wp_enqueue_script(
        'rx-theme-video-chunk',
        get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-018-video.js',
        array(),
        wp_get_theme()->get('Version'),
        true
    );
}
add_action('wp_enqueue_scripts', 'rx_theme_enqueue_video_chunk');

Better advanced conditional loading:

function rx_theme_enqueue_video_chunk_conditionally() {
    if (is_admin()) {
        return;
    }

    if (is_singular() || is_front_page() || is_home()) {
        wp_enqueue_script(
            'rx-theme-video-chunk',
            get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-018-video.js',
            array(),
            wp_get_theme()->get('Version'),
            true
        );
    }
}
add_action('wp_enqueue_scripts', 'rx_theme_enqueue_video_chunk_conditionally');

Best naming, dear friend

For RX Theme, this file name is good:

chunk-018-video.js

But for a more professional future system, this is also excellent:

chunk-018-rx-video.js

My recommendation:

assets/static-js/static-chunks-js/chunk-018-rx-video.js

Because later you may have many chunks, and every RX Theme chunk will look branded and organized.

Leave a Reply

Your email address will not be published. Required fields are marked *