3

I want to add videos to my PhotoSwipe gallery.

I read the documentation here: http://photoswipe.com/documentation/custom-html-in-slides.html

Unfortunately, I don't get what this means: "If you really need to have video in PhotoSwipe, you may add it as modal that appears when user taps on current slide, you can dynamically create modal in DOM and append it after .pswp__scroll-wrap element."

Has anyone been successful in making videos working reliably with PhotoSwipe?

Edit: I've tried this (last slide is a video): http://pixelkrams.de/2015/artspin Works on desktop but breaks on mobile (video does not start and its size is wrong). Relevant code: http://pixelkrams.de/js/main.js

Snippet from the PhotoSwipe initialization:

    // Pass data to PhotoSwipe and initialize it
    gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, dataLarge, options);
    gallery.init();
    gallery.listen('afterChange', function() {
        detectVideo(gallery);
    });

    gallery.listen('beforeChange', function() {
       removeVideo();
    });
   gallery.listen('resize', function() { 
       if ($('.videoHolder').length > 0) updateVideoPosition(gallery);
   });
   gallery.listen('close', function() {
       removeVideo();
   });

   detectVideo(gallery);

and the relevant functions for video:

function removeVideo() {
    if ($('.videoHolder').length > 0) { 
        if ($('#video').length > 0) {
            $('video')[0].pause();
            $('video')[0].src = "";
            $('.videoHolder').remove();
            $('.pswp__img').css('visibility','visible');
        } else {
            $('.videoHolder').remove();
        }
    }
}

function detectVideo(gallery) {
    var src = gallery.currItem.src;
    if (src.indexOf('video')>= 0) {
        addVideo(gallery.currItem);
        updateVideoPosition(gallery);
    }
}
function addVideo(item, vp) {
    var videofile = item.src.split(".");
    var v = $('<div />', {
                class:'videoHolder',
                css : ({'position': 'absolute','width':item.w, 'height':item.h})

    });
    v.one('click touchstart', (function() {
        var playerCode = '<video id="video" width="'+item.w+'" height="'+item.h+'" autoplay controls>' +
        '<source src="'+videofile[0]+'.mp4" type="video/mp4"></source>' +
        '<source src="'+videofile[0]+'.webm" type="video/webm"></source>' +
        '</video>';
         $(this).html(playerCode);
         $('.pswp__img').css('visibility','hidden');

    }));
    v.appendTo('.pswp__scroll-wrap');
}

function updateVideoPosition(o) {
    var item = o.currItem;
    var vp = o.viewportSize;
    var top = (vp.y - item.h)/2;
    var left = (vp.x - item.w)/2;
    $('.videoHolder').css({position:'absolute',top:top, left:left});

}
John Kornick
  • 301
  • 1
  • 6
  • 14
  • In general, it is preferred and more likely to get you good answers if you provide the code you tried and specific examples of errors you observe. – Marshall Davis Sep 24 '15 at 21:51
  • I've tried this (last slide is a video): http://pixelkrams.de/2015/artspin/ Works on desktop but breaks on mobile (video does not start and its size is wrong). – John Kornick Sep 24 '15 at 21:55
  • Great, I recommend appending that to your actual question and if possible copy and paste the actual code as well. People have to dig around for the information and click links to unknown URLs makes them less likely to answer. – Marshall Davis Sep 24 '15 at 21:57
  • Updated original question. – John Kornick Sep 24 '15 at 22:05
  • Or you could just switch to Fancybox – Andrew Mar 03 '23 at 15:10

3 Answers3

4

For the mobile size issue, I used an additional function that calculates the video size based on gallery.viewportSize and the videos aspect ratio.

function setVideoSize(item, vp) {
    var w = item.w,
        h = item.h,
        vw = vp.x,
        r,
        vh;
    if (vw < w) {
        r = w/h;
        vh = vw/r;
        w = vw;
        h = vh;
    }
    return {
            w: w,
            h: h
    };
}

Then change the addVideo call in detectVideo() to

addVideo(gallery.currItem, gallery.viewportSize);

Use it in the existing addVideo(item, vp) function

var vsize = setVideoSize(item, vp);
var v = $('<div />', {
            class:'.videoHolder',
            css : ({'position': 'absolute','width':vsize.w, 'height':vsize.h})

And also in updateVideoPosition(o)

var item = o.currItem;
var vp = o.viewportSize;
var vsize = setVideoSize(item, vp);
var top = (vp.y - vsize.h)/2;
var left = (vp.x - vsize.w)/2;
$('.videoHolder').css({position:'absolute',top:top, left:left});
T. Kuther
  • 610
  • 3
  • 7
1

Here is my solution to use Photoswipe with Youtube&Vimeo videos.

It is a mix of java-script libraries and solutions which I saw here at Stackoverflow and on the web.

Stylings

<link href="photoswipe.css" rel="stylesheet">
<link href="default-skin/default-skin.css" rel="stylesheet">
<style>
  .gallery-viewer-play-video-btn-container {
    position: relative;
    cursor: pointer;
  }
  .gallery-viewer-play-video-btn-container button {
    pointer-events: none;
    position: absolute;
    outline: 0;
    border: none;
    background-color: transparent;
    padding: 0;
    color: inherit;
    text-align: inherit;
    font-size: 100%;
    font-family: inherit;
    cursor: pointer;
    line-height: inherit;    
    left: 50%;
    top: 50%;
    width: 68px;
    height: 48px;
    margin-left: -34px;
    margin-top: -24px;
    -moz-transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);
    -webkit-transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);
    transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);
  }

  .gallery-viewer-play-video-btn-container .ytp-large-play-button-bg {
    -moz-transition: fill .1s cubic-bezier(0.4,0.0,1,1),fill-opacity .1s cubic-bezier(0.4,0.0,1,1);
    -webkit-transition: fill .1s cubic-bezier(0.4,0.0,1,1),fill-opacity .1s cubic-bezier(0.4,0.0,1,1);
    transition: fill .1s cubic-bezier(0.4,0.0,1,1),fill-opacity .1s cubic-bezier(0.4,0.0,1,1);
    fill: #cc181e;
    fill-opacity: 1;
  }

  .YouTubePopUp-Wrap .loading {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -13px;
    margin-left: -36px;
    color: #fff;
  }
</style>

Markup (the standard one)

<div class="pswp pswp-chat" tabindex="-1" role="dialog" aria-hidden="true">
    <!-- Background of PhotoSwipe.
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>
    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">
        <!-- Container that holds slides.
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>
        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">
            <div class="pswp__top-bar">
                <!--  Controls are self-explanatory. Order can be changed. -->
                <div class="pswp__counter"></div>
                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>                
                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>
            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>
            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>
            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>
        </div>
    </div>
</div>

Items

<div class="list">
    <div class="element">      
        <a data-index="0" class="media" href="file58dc530580a94.jpg" target="_blank">            
            <img src="thumb/file58dc530580a94.jpg" itemprop="thumbnail"/>
        </a>
    </div>  

    <div class="element">      
        <a data-index="1" class="media" href="file58dc530580a94.jpg" target="_blank">            
            <img src="thumb/file58dc530580a94.jpg" itemprop="thumbnail"/>
        </a>
    </div>  

    <div class="element">      
        <a data-index="2" class="media" href="file58dc530580a94.jpg" target="_blank">            
            <img src="thumb/file58dc530580a94.jpg" itemprop="thumbnail"/>
        </a>
    </div>  

    <div class="element">      
        <a data-index="3" class="media" href="file58dc530580a94.jpg" target="_blank">            
            <img src="thumb/file58dc530580a94.jpg" itemprop="thumbnail"/>
        </a>
    </div>  
</div>

Scripts

<script src="photoswipe.js"></script>
<script src="photoswipe-ui-default.js"></script>
<script src="jquery.min.js"></script>
<script src="jquery-ui.min.js"></script>
<script src="YouTubePopUp.jquery.js"></script>
<script src="custom.js"></script>

YouTubePopUp.jquery.js (this is a jQuery Plugin by Qassim Hassan). You need to download it as well. I've made some custom changes in it, so after downloading simply replace the js-code with mine.

/*
    Name: YouTubePopUp
    Description: jQuery plugin to display YouTube or Vimeo video in PopUp, responsive and retina, easy to use.
    Version: 1.0.1
    Plugin URL: http://wp-time.com/youtube-popup-jquery-plugin/
    Written By: Qassim Hassan
    Twitter: @QQQHZ
    Websites: wp-time.com | qass.im | wp-plugins.in
    Dual licensed under the MIT and GPL licenses:
        http://www.opensource.org/licenses/mit-license.php
        http://www.gnu.org/licenses/gpl.html
    Copyright (c) 2016 - Qassim Hassan


    Mod by MaximusBaton
*/

(function ( $ ) {

    $.fn.YouTubePopUp = function(options) {

        var YouTubePopUpOptions = $.extend({
                autoplay       : 1,
                controls       : 1,
                cc_load_policy : 0,
                iv_load_policy : 3,
                rel            : 0,
                showinfo       : 0
        }, options );

        $(this).on('click', function (e) {

            var youtubeLink = $(this).attr("href");

            if( youtubeLink.match(/(youtube.com)/) ){
                var split_c = "v=";
                var split_n = 1;
            }

            if( youtubeLink.match(/(youtu.be)/) || youtubeLink.match(/(vimeo.com\/)+[0-9]/) ){
                var split_c = "/";
                var split_n = 3;
            }

            if( youtubeLink.match(/(vimeo.com\/)+[a-zA-Z]/) ){
                var split_c = "/";
                var split_n = 5;
            }

            var getYouTubeVideoID = youtubeLink.split(split_c)[split_n];

            var cleanVideoID = getYouTubeVideoID.replace(/(&)+(.*)/, "");

            if( youtubeLink.match(/(youtu.be)/) || youtubeLink.match(/(youtube.com)/) ){
                var videoEmbedLink = "https://www.youtube.com/embed/"+cleanVideoID+"?autoplay="+YouTubePopUpOptions.autoplay+"&controls="+ YouTubePopUpOptions.controls +"&cc_load_policy="+ YouTubePopUpOptions.cc_load_policy +"&iv_load_policy="+ YouTubePopUpOptions.iv_load_policy +"&rel="+ YouTubePopUpOptions.rel +"&showinfo="+ YouTubePopUpOptions.showinfo +"";
            }

            if( youtubeLink.match(/(vimeo.com\/)+[0-9]/) || youtubeLink.match(/(vimeo.com\/)+[a-zA-Z]/) ){
                var videoEmbedLink = "https://player.vimeo.com/video/"+cleanVideoID+"?autoplay="+YouTubePopUpOptions.autoplay+"";
            }

            $("body").append('<div class="YouTubePopUp-Wrap YouTubePopUp-animation"><div class="YouTubePopUp-Content"><span class="loading">Loading...</span><span class="YouTubePopUp-Close"></span><iframe src="'+videoEmbedLink+'" allowfullscreen></iframe></div></div>');
            $('.YouTubePopUp-Content iframe')[0].onload = function() {
                $('.YouTubePopUp-Wrap .loading').hide();
                $('.YouTubePopUp-Wrap iframe').show();
            };

            if( $('.YouTubePopUp-Wrap').hasClass('YouTubePopUp-animation') ){
                setTimeout(function() {
                    $('.YouTubePopUp-Wrap').removeClass("YouTubePopUp-animation");
                }, 600);
            }

            $(".YouTubePopUp-Wrap, .YouTubePopUp-Close").click(function(){
                $.event.trigger({type : 'youtubeVideoBeforeClose', link : youtubeLink});
                $(".YouTubePopUp-Wrap").addClass("YouTubePopUp-Hide").delay(515).queue(function() { $(this).remove(); });
            });

            $.event.trigger({type : 'youtubeVideoStarted', link : youtubeLink});

            e.preventDefault();

        });

        $(document).keyup(function(e) {

            if ( e.keyCode == 27 ){
                $('.YouTubePopUp-Wrap, .YouTubePopUp-Close').click();
            }

        });

    };

}( jQuery ));

custom.js (the main js :)

$(document).ready(function () {

    var mediaList              = [];

    var interval,
        intervalTries = 0,
        maxIntervalTries = 40;

    mediaList.push({
        'src'   : 'file58dc530580a94.jpg',
        'w'     : 1726,
        'h'     : 2506
    });

    mediaList.push({
        'src'   : 'file58dae7f57ea15.jpg',
        'w'     : 4032,
        'h'     : 3024
    });

    mediaList.push({
        'src'   : 'file58daa7b097cc2.jpg',
        'w'     : 3799,
        'h'     : 2849
    });

    mediaList.push({
        'src'      : 'file58dc530580a94.jpg',
        'w'        : 1000,
        'h'        : 667,
        'videoSrc' : '//www.youtube.com/watch?v=a_Ypr_uV-mw'
    });


    var pswpElement = $('.pswp.pswp-chat')[0];
    $(document).on("click", ".media", function(e){
        e.preventDefault();
        var index   = $(this).data("index"),
            options = {
                index           : index,
                bgOpacity       : 0.7,
                overlayIcon     : true,
                showHideOpacity : true
            },
            gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, mediaList, options);

        gallery.init();

        gallery.listen('close', function() {
            removeVideo(gallery);
        });

        gallery.listen('beforeChange', function() {
            removeVideo(gallery);
        });

        gallery.listen('afterChange', function() { 
            detectVideo(gallery.currItem);
        });

        gallery.listen('resize', function() {
            resizeItem(gallery.currItem);
        });        

        gallery.listen('imageLoadComplete', function(index, item) { 
            resizeItem(gallery.currItem);
        });


        detectVideo(gallery.currItem);


        $(document).off('pswpTap').on('pswpTap', function(e){
            var container = $(gallery.currItem.container),
                img       = container.find('img.youtubeClass');

            if (img.length) {
                if (typeof(img.data('youtubeVideoStarted')) == 'undefined') {
                    img.data('youtubeVideoStarted', false);
                }

                if (img.data('youtubeVideoStarted') === false) {
                    img.data('youtubeVideoStarted', true);

                    if (typeof(img.data('youtubeInitialed')) == 'undefined') {
                        img.data('youtubeInitialed', false);
                    }

                    if (img.data('youtubeInitialed') === false) {
                        img.data('youtubeInitialed', true);
                        img.YouTubePopUp();
                    }

                    if (e.detail.pointerType != 'mouse') {
                        img.click();
                    }
                }
            }
        });

        $(document).off('youtubeVideoStarted').on('youtubeVideoStarted', function(e){
            var container = $(gallery.container),
                img       = container.find('img.youtubeClass[href="'+ e.link +'"]');

            if (img.length) {
                container = img.closest('.pswp__item');

                if (container.length) {
                    var buttonContainer = container.find('.gallery-viewer-play-video-btn-container');

                    img.fadeOut(200);

                    if (buttonContainer.length) {
                        buttonContainer.fadeOut(200);
                    }
                }

            }
        });

        $(document).off('youtubeVideoBeforeClose').on('youtubeVideoBeforeClose', function(e){
            var container = $(gallery.container),
                img       = container.find('img.youtubeClass[href="'+ e.link +'"]');

            if (img.length) {
                container = img.closest('.pswp__item');

                if (container.length) {
                    var buttonContainer = container.find('.gallery-viewer-play-video-btn-container');

                    img.fadeIn(300);
                    img.data('youtubeVideoStarted', false);

                    if (buttonContainer.length) {
                        buttonContainer.fadeIn(300);
                    }
                }

            }
        });
    });

    function resizeItem(item) {
        var container = $(item.container),
            children  = container.children();

        if (children.length > 1) {
            var newWidth  = $(children[ children.length - 1 ]).css('width'),
                newHeight = $(children[ children.length - 1 ]).css('height');

            children.css({
                'width'  : newWidth,
                'height' : newHeight
            });
        }
    }

    function removeVideo(gallery) {
        clearVideoInterval();

        var container        = $(gallery.container),
            buttonContainers = container.find('.gallery-viewer-play-video-btn-container');

        $.each(buttonContainers, function(){
            var $thisButtonContainer = $(this),
                container            = $thisButtonContainer.closest('.pswp__item');

            if (container.length) {
                var img = container.find('img:not(.youtubeClass)');

                $thisButtonContainer.remove();

                if (img.length) {
                    img.show();
                }
            }
        });
    }

    function detectVideo(item) {
        clearVideoInterval();

        if (typeof(item.videoSrc) != 'undefined') {
            interval = setInterval(function(){ addVideoIframe(item); }, 100);
        }
    }

    function clearVideoInterval() {
        clearInterval(interval);
        intervalTries = 0;
    }

    function addVideoIframe(item) {
        if (intervalTries >= maxIntervalTries) {
            clearVideoInterval();
            return;
        }

        intervalTries++;

        var container = $(item.container),
            img       = container.find('img:not(.youtubeClass)');

        if (img.length) {
            resizeItem(item);

            var imgSrcCode      = '_'+ img.attr('src'),
                buttonContainer = container.find('.gallery-viewer-play-video-btn-container');

            if (buttonContainer.length == 0) {
                buttonContainer = $('<div class="gallery-viewer-play-video-btn-container"><button><svg height="100%" version="1.1" viewBox="0 0 68 48" width="100%"><path class="ytp-large-play-button-bg" d="m .66,37.62 c 0,0 .66,4.70 2.70,6.77 2.58,2.71 5.98,2.63 7.49,2.91 5.43,.52 23.10,.68 23.12,.68 .00,-1.3e-5 14.29,-0.02 23.81,-0.71 1.32,-0.15 4.22,-0.17 6.81,-2.89 2.03,-2.07 2.70,-6.77 2.70,-6.77 0,0 .67,-5.52 .67,-11.04 l 0,-5.17 c 0,-5.52 -0.67,-11.04 -0.67,-11.04 0,0 -0.66,-4.70 -2.70,-6.77 C 62.03,.86 59.13,.84 57.80,.69 48.28,0 34.00,0 34.00,0 33.97,0 19.69,0 10.18,.69 8.85,.84 5.95,.86 3.36,3.58 1.32,5.65 .66,10.35 .66,10.35 c 0,0 -0.55,4.50 -0.66,9.45 l 0,8.36 c .10,4.94 .66,9.45 .66,9.45 z" fill="#1f1f1e" fill-opacity="0.81"></path><path d="m 26.96,13.67 18.37,9.62 -18.37,9.55 -0.00,-19.17 z" fill="#fff"></path><path d="M 45.02,23.46 45.32,23.28 26.96,13.67 43.32,24.34 45.02,23.46 z" fill="#ccc"></path></svg></button></div>');

                var clonedImg = img.clone();
                clonedImg.attr('href', item.videoSrc)
                    .addClass('youtubeClass')
                    .removeClass('pswp__img')
                    .css({
                        'width'  : '100%',
                        'height' : '100%'
                    });

                buttonContainer.prepend( clonedImg );
            }

            buttonContainer.css({
                    'width'  : img.outerWidth() +'px',
                    'height' : img.outerHeight() +'px'
                })
                .appendTo(container);

            img.hide();

            clearVideoInterval();
        }
    }
});

Have fun!

Maxim Ilin
  • 125
  • 3
  • 9
0

Via the jAlbum PhotoSwipe skin, see http://jalbum.net/nl/skins/skin/PhotoSwipe, it is possible to add a video to the PhotoSwipe gallery. To see it open the Sample album: http://andrewolff.jalbum.net/Reestdal_PS/

I use the next code for the first 2 images and the next video in the skin code:

  // build items array
var items = [
  {
   src: 'slides/151228-112819_Kerststal.jpg',
   w: 1625,
   h: 1080,
   title: 'Vooraan: Maria\'s dorp.'
 },
  {
   src: 'slides/20150703-ch_IMG_2160.jpg',
   w: 810,
   h: 1080,
   title: 'My Cabin!<br>Hey boss, I must\'ve taken a wrong turn, cause I\'m stuck up here!'  
  },
  {
   html: '<video controls autoplay  style="padding-top: 40px;"><source src="slides/IMG_4979.mp4" type="video/mp4"></video>'
  },
  • This may theoretically answer the question, but it would be best to include the essential parts of the answer here for future users, and provide the link for reference. [Link-dominated answers](//meta.stackexchange.com/questions/8231) can become invalid through [link rot](//en.wikipedia.org/wiki/Link_rot). – Mogsdad Mar 01 '16 at 21:52
  • I am new and this forum an did delete an improvement by clicking the delete flag under my improvement comment. Is that the reasonable that I see now a -1 score left of my original solution? – André Wolff Mar 02 '16 at 07:35
  • This is what I was looking for. Thank you. – Daniel Logan Aug 23 '16 at 07:04