diff --git a/README.md b/README.md index 5887015..d8f93a9 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,9 @@ - [API methods](#api-methods) - [Videos](#videos) - [HTML5 video player](#html5-video-player) + - [Video controls](#video-controls) + - [Video preloading](#video-preloading) + - [Fullscreen video](#fullscreen-video) - [Multiple video sources](#multiple-video-sources) - [YouTube](#youtube) - [Vimeo](#vimeo) @@ -44,7 +47,6 @@ - [Browser support](#browser-support) - [License](#license) - [Credits](#credits) -- [Notable forks](#notable-forks) ## Description @@ -165,8 +167,8 @@ the images in the Gallery lightbox on click of one of those links: ### Controls -To initialize the Gallery with visible controls, add the CSS class -`blueimp-gallery-controls` to the Gallery widget: +To initialize the Gallery with visible controls (previous slide, next slide, +etc.), add the CSS class `blueimp-gallery-controls` to the Gallery widget: ```html
``` +Please also note that by default, a click on an image slide or any Gallery +widget element with the `toggle` class will toggle the display of the Gallery +controls. + ### Contain To stretch smaller images to the dimensions of the Gallery container while @@ -590,6 +596,12 @@ var videoFactoryOptions = { videoLoadingClass: 'video-loading', // The class for video when it is playing: videoPlayingClass: 'video-playing', + // The class for video content displayed in an iframe: + videoIframeClass: 'video-iframe', + // The class for the video cover element: + videoCoverClass: 'video-cover', + // The class for the video play control: + videoPlayClass: 'video-play', // Play videos inline by default: videoPlaysInline: true, // The list object property (or data attribute) for video preload: @@ -828,10 +840,87 @@ if the browser supports the video content type. For videos, the `poster` property defines the URL of the poster image to display, before the video is started. +#### Video controls + +To start video playback, you can either click on the video play icon or on the +video slide itself. +Starting the video playback enables the native HTML5 video +[controls](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-controls). + +To toggle the Gallery controls (previous slide, next slide, etc.) instead of +starting video playback on click of a video slide, add the `toggle` class to the +video cover element using the `videoCoverClass` Gallery option: + +```js +blueimp.Gallery( + [ + { + title: 'Fruits', + type: 'video/mp4', + href: 'https://example.org/videos/fruits.mp4', + poster: 'https://example.org/images/fruits.jpg' + } + ], + { + videoCoverClass: 'video-cover toggle' + } +) +``` + +#### Video preloading + +You can set the `preload` property of a Gallery video object to a valid value +defined by the HTML5 video +[preload](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-preload) +attribute: + +- `none`: Indicates that the video should not be preloaded. +- `metadata`: Indicates that only video metadata (e.g. length) is fetched. +- `auto`: Indicates that the whole video file can be preloaded. + +```js +blueimp.Gallery([ + { + title: 'Fruits', + type: 'video/mp4', + href: 'https://example.org/videos/fruits.mp4', + preload: 'auto' + } +]) +``` + +#### Fullscreen video + +By default, videos are displayed with the HTML5 video +[playsinline](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-playsinline) +attribute set, which indicates that the video is to be played inline. +To disable this behavior, you can set the Gallery option `videoPlaysInline` to +`false`: + +```js +blueimp.Gallery( + [ + { + title: 'Fruits', + type: 'video/mp4', + href: 'https://example.org/videos/fruits.mp4', + poster: 'https://example.org/images/fruits.jpg' + } + ], + { + videoPlaysInline: false + } +) +``` + +Please note that this attribute only has an effect on some browsers, e.g. Safari +on iOS 10 and later. +However, most browser provide video controls to switch to fullscreen mode. + #### Multiple video sources To provide multiple video formats, the `sources` property of a list object can -be set to an array of objects with `href` and `type` properties for each video +be set to an array of objects with `type` and `src` properties for each video source. The first video format in the list that the browser can play will be displayed: diff --git a/css/blueimp-gallery-video.css b/css/blueimp-gallery-video.css index 8dc10e9..e8f4191 100644 --- a/css/blueimp-gallery-video.css +++ b/css/blueimp-gallery-video.css @@ -10,26 +10,25 @@ * https://opensource.org/licenses/MIT */ -.blueimp-gallery > .slides > .slide > .video-content > video { +.blueimp-gallery > .slides > .slide > .video-content > video, +.blueimp-gallery > .slides > .slide > .video-content > iframe, +.blueimp-gallery > .slides > .slide > .video-content > .video-cover { position: absolute; top: 0; left: 0; width: 100%; height: 100%; - display: none; -} -.blueimp-gallery > .slides > .slide > .video-content > iframe { - position: absolute; - top: 100%; - left: 0; - width: 100%; - height: 100%; border: none; } -.blueimp-gallery > .slides > .slide > .video-playing > iframe { - top: 0; +.blueimp-gallery > .slides > .slide > .video-content > .video-cover { + background: center no-repeat; + background-size: contain; +} +.blueimp-gallery > .slides > .slide > .video-iframe > .video-cover { + background-color: #000; + background-color: rgba(0, 0, 0, 0.7); } -.blueimp-gallery > .slides > .slide > .video-content > a { +.blueimp-gallery > .slides > .slide > .video-content > .video-play { position: absolute; top: 50%; right: 0; @@ -41,21 +40,18 @@ opacity: 0.8; cursor: pointer; } -.blueimp-gallery-svgasimg > .slides > .slide > .video-content > a { +.blueimp-gallery-svgasimg > .slides > .slide > .video-content > .video-play { background-image: url(../img/video-play.svg); } -.blueimp-gallery > .slides > .slide > .video-playing > a, -.blueimp-gallery > .slides > .slide > .video-playing > img { +.blueimp-gallery > .slides > .slide > .video-playing > .video-play, +.blueimp-gallery > .slides > .slide > .video-playing > .video-cover { display: none; } -.blueimp-gallery > .slides > .slide > .video-playing > video { - display: block; -} -.blueimp-gallery > .slides > .slide > .video-loading > a { +.blueimp-gallery > .slides > .slide > .video-loading > .video-play { background: url(../img/loading.gif) center no-repeat; background-size: 64px 64px; } -.blueimp-gallery-smil > .slides > .slide > .video-loading > a { +.blueimp-gallery-smil > .slides > .slide > .video-loading > .video-play { background-image: url(../img/loading.svg); } @@ -63,19 +59,11 @@ * + html .blueimp-gallery > .slides > .slide > .video-content { height: 100%; } -* + html .blueimp-gallery > .slides > .slide > .video-content > a { +* + html .blueimp-gallery > .slides > .slide > .video-content > .video-play { left: 50%; margin-left: -64px; } -.blueimp-gallery > .slides > .slide > .video-content > a:hover { +.blueimp-gallery > .slides > .slide > .video-content > .video-play:hover { opacity: 1; } - -@supports (object-fit: contain) { - .blueimp-gallery > .slides > .slide > .video-content > img { - width: 100%; - height: 100%; - object-fit: contain; - } -} diff --git a/js/blueimp-gallery-video.js b/js/blueimp-gallery-video.js index f001d98..d8b287c 100644 --- a/js/blueimp-gallery-video.js +++ b/js/blueimp-gallery-video.js @@ -32,6 +32,12 @@ videoLoadingClass: 'video-loading', // The class for video when it is playing: videoPlayingClass: 'video-playing', + // The class for video content displayed in an iframe: + videoIframeClass: 'video-iframe', + // The class for the video cover element: + videoCoverClass: 'video-cover', + // The class for the video play control: + videoPlayClass: 'video-play', // Play videos inline by default: videoPlaysInline: true, // The list object property (or data attribute) for video preload: @@ -45,9 +51,11 @@ $.extend(galleryPrototype, { handleSlide: function (oldIndex, newIndex) { handleSlide.call(this, oldIndex, newIndex) - if (this.playingVideo) { - this.playingVideo.pause() - } + this.setTimeout(function () { + if (this.activeVideo) { + this.activeVideo.pause() + } + }) }, videoFactory: function (obj, callback, videoInterface) { @@ -62,34 +70,42 @@ } ] var video = videoInterface || document.createElement('video') - var playMediaControl = document.createElement('a') + var coverElement = this.elementPrototype.cloneNode(false) + var playElement = document.createElement('a') var url = this.getItemProperty(obj, options.urlProperty) var sources = this.getItemProperty(obj, options.sourcesProperty) var title = this.getItemProperty(obj, options.titleProperty) var posterUrl = this.getItemProperty(obj, options.videoPosterProperty) - var posterImage + var playControls = [playElement] + var hasGalleryControls var isLoading - var hasControls var i videoContainer.addClass(options.videoContentClass) + $(playElement).addClass(options.videoPlayClass) + if ( + !$(coverElement) + .addClass(options.videoCoverClass) + .hasClass(options.toggleClass) + ) { + playControls.push(coverElement) + } + coverElement.draggable = false if (title) { videoContainerNode.title = title - playMediaControl.setAttribute('aria-label', title) + playElement.setAttribute('aria-label', title) } if (posterUrl) { - video.poster = posterUrl - posterImage = this.imagePrototype.cloneNode(false) - $(posterImage).addClass(options.toggleClass) - posterImage.src = posterUrl - posterImage.draggable = false - posterImage.alt = - this.getItemProperty(obj, this.options.altTextProperty) || title - videoContainerNode.appendChild(posterImage) + // Set as background image instead of as poster video element property: + // - Is accessible for browsers that do not support the video element + // - Is accessible for both video element and iframe video players + // - Avoids visual artifacts in IE with the poster property set + coverElement.style.backgroundImage = 'url("' + posterUrl + '")' } - if (video.setAttribute && options.videoPlaysInline) { - video.setAttribute('playsinline', '') + if (video.setAttribute) { + if (options.videoPlaysInline) video.setAttribute('playsinline', '') + } else { + videoContainer.addClass(options.videoIframeClass) } - video.controls = true video.preload = this.getItemProperty(obj, options.videoPreloadProperty) || 'none' if (this.support.source && sources) { @@ -100,8 +116,7 @@ } } if (url) video.src = url - playMediaControl.href = - url || (sources && sources.length && sources[0].src) + playElement.href = url || (sources && sources.length && sources[0].src) if (video.play && video.pause) { ;(videoInterface || $(video)) .on('error', function () { @@ -113,34 +128,40 @@ videoContainer .removeClass(that.options.videoLoadingClass) .removeClass(that.options.videoPlayingClass) - if (hasControls) { + if (hasGalleryControls) { that.container.addClass(that.options.controlsClass) } - delete that.playingVideo + video.controls = false + if (video === that.activeVideo) delete that.activeVideo if (that.interval) { + // Continue slideshow interval that.play() } }) .on('playing', function () { isLoading = false + coverElement.removeAttribute('style') videoContainer .removeClass(that.options.videoLoadingClass) .addClass(that.options.videoPlayingClass) - if (that.container.hasClass(that.options.controlsClass)) { - hasControls = true - that.container.removeClass(that.options.controlsClass) - } else { - hasControls = false - } }) .on('play', function () { + // Clear slideshow timeout: window.clearTimeout(that.timeout) isLoading = true videoContainer.addClass(that.options.videoLoadingClass) - that.playingVideo = video + if (that.container.hasClass(that.options.controlsClass)) { + hasGalleryControls = true + that.container.removeClass(that.options.controlsClass) + } else { + hasGalleryControls = false + } + video.controls = true + that.activeVideo = video }) - $(playMediaControl).on('click', function (event) { + $(playControls).on('click', function (event) { that.preventDefault(event) + that.activeVideo = video if (isLoading) { video.pause() } else { @@ -151,7 +172,8 @@ (videoInterface && videoInterface.element) || video ) } - videoContainerNode.appendChild(playMediaControl) + videoContainerNode.appendChild(coverElement) + videoContainerNode.appendChild(playElement) this.setTimeout(callback, [ { type: 'load',