update packages and add valign
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util.js'
|
||||
import { FRAGMENT_STYLE_REGEX } from '../utils/constants.js'
|
||||
import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util'
|
||||
|
||||
// Counter used to generate unique IDs for auto-animated elements
|
||||
let autoAnimateCounter = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { queryAll } from '../utils/util.js'
|
||||
import { colorToRgb, colorBrightness } from '../utils/color.js'
|
||||
import { queryAll } from '../utils/util'
|
||||
import { colorToRgb, colorBrightness } from '../utils/color'
|
||||
|
||||
/**
|
||||
* Creates and updates slide backgrounds.
|
||||
|
||||
15
scripts/reveal.js/js/controllers/controls.js
vendored
15
scripts/reveal.js/js/controllers/controls.js
vendored
@@ -1,5 +1,5 @@
|
||||
import { queryAll, enterFullscreen } from '../utils/util.js'
|
||||
import { isAndroid } from '../utils/device.js'
|
||||
import { queryAll, enterFullscreen } from '../utils/util'
|
||||
import { isAndroid } from '../utils/device'
|
||||
|
||||
/**
|
||||
* Manages our presentation controls. This includes both
|
||||
@@ -66,9 +66,11 @@ export default class Controls {
|
||||
*/
|
||||
configure( config, oldConfig ) {
|
||||
|
||||
const speakerOnly = config.controls === 'speaker' || config.controls === 'speaker-only';
|
||||
|
||||
this.element.style.display = (
|
||||
config.controls &&
|
||||
(config.controls !== 'speaker-only' || this.Reveal.isSpeakerNotes())
|
||||
(!speakerOnly || this.Reveal.isSpeakerNotes())
|
||||
) ? 'block' : 'none';
|
||||
|
||||
this.element.setAttribute( 'data-controls-layout', config.controlsLayout );
|
||||
@@ -83,9 +85,10 @@ export default class Controls {
|
||||
let pointerEvents = [ 'touchstart', 'click' ];
|
||||
|
||||
// Only support touch for Android, fixes double navigations in
|
||||
// stock browser
|
||||
// stock browser. Use touchend for it to be considered a valid
|
||||
// user interaction (so we're allowed to autoplay media).
|
||||
if( isAndroid ) {
|
||||
pointerEvents = [ 'touchstart' ];
|
||||
pointerEvents = [ 'touchend' ];
|
||||
}
|
||||
|
||||
pointerEvents.forEach( eventName => {
|
||||
@@ -102,7 +105,7 @@ export default class Controls {
|
||||
|
||||
unbind() {
|
||||
|
||||
[ 'touchstart', 'click' ].forEach( eventName => {
|
||||
[ 'touchstart', 'touchend', 'click' ].forEach( eventName => {
|
||||
this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
|
||||
this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
|
||||
this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { closest } from '../utils/util.js'
|
||||
import { closest } from '../utils/util'
|
||||
|
||||
/**
|
||||
* Manages focus when a presentation is embedded. This
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { extend, queryAll } from '../utils/util.js'
|
||||
import { extend, queryAll } from '../utils/util'
|
||||
|
||||
/**
|
||||
* Handles sorting and navigation of slide fragments.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { enterFullscreen } from '../utils/util.js'
|
||||
import { enterFullscreen } from '../utils/util'
|
||||
|
||||
/**
|
||||
* Handles all reveal.js keyboard interactions.
|
||||
@@ -398,6 +398,12 @@ export default class Keyboard {
|
||||
|
||||
event.preventDefault && event.preventDefault();
|
||||
}
|
||||
|
||||
// Enter to exit overview mode
|
||||
else if (keyCode === 13 && this.Reveal.overview.isActive()) {
|
||||
this.Reveal.overview.deactivate();
|
||||
event.preventDefault && event.preventDefault();
|
||||
}
|
||||
|
||||
// If auto-sliding is enabled we need to cue up
|
||||
// another timeout
|
||||
|
||||
@@ -60,11 +60,13 @@ export default class Location {
|
||||
name = name.split( '/' ).shift();
|
||||
}
|
||||
|
||||
// Ensure the named link is a valid HTML ID attribute
|
||||
// Ensure the named link is a valid HTML id or data-id attribute
|
||||
try {
|
||||
slide = document
|
||||
.getElementById( decodeURIComponent( name ) )
|
||||
.closest('.slides section');
|
||||
const decodedName = decodeURIComponent( name );
|
||||
slide = (
|
||||
document.getElementById( decodedName ) ||
|
||||
document.querySelector( `[data-id="${decodedName}"]` )
|
||||
).closest('.slides section');
|
||||
}
|
||||
catch ( error ) { }
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ export default class Overlay {
|
||||
|
||||
this.viewport.innerHTML =
|
||||
`<header class="r-overlay-header">
|
||||
<a class="r-overlay-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
|
||||
<button class="r-overlay-button r-overlay-close"><span class="icon"></span></button>
|
||||
<a class="r-overlay-header-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
|
||||
<button class="r-overlay-header-button r-overlay-close"><span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-spinner"></div>
|
||||
<div class="r-overlay-content">
|
||||
@@ -125,7 +125,7 @@ export default class Overlay {
|
||||
|
||||
this.viewport.innerHTML =
|
||||
`<header class="r-overlay-header">
|
||||
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-spinner"></div>
|
||||
<div class="r-overlay-content"></div>`;
|
||||
@@ -262,7 +262,7 @@ export default class Overlay {
|
||||
|
||||
this.viewport.innerHTML = `
|
||||
<header class="r-overlay-header">
|
||||
<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-content">
|
||||
<div class="r-overlay-help-content">${html}</div>
|
||||
@@ -348,7 +348,9 @@ export default class Overlay {
|
||||
// Let the browser handle meta keys naturally so users can cmd+click
|
||||
return;
|
||||
}
|
||||
let url = linkTarget.getAttribute( 'href' ) || linkTarget.getAttribute( 'data-preview-link' );
|
||||
const dataPreviewLink = linkTarget.getAttribute( 'data-preview-link' );
|
||||
const dataPreviewLinkIsUrl = typeof dataPreviewLink === 'string' && dataPreviewLink.startsWith( 'http' );
|
||||
let url = dataPreviewLinkIsUrl ? dataPreviewLink : linkTarget.getAttribute( 'href' );
|
||||
if( url ) {
|
||||
this.previewIframe( url );
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SLIDES_SELECTOR } from '../utils/constants.js'
|
||||
import { extend, queryAll, transformElement } from '../utils/util.js'
|
||||
import { SLIDES_SELECTOR } from '../utils/constants'
|
||||
import { extend, queryAll, transformElement } from '../utils/util'
|
||||
|
||||
/**
|
||||
* Handles all logic related to the overview mode
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { loadScript } from '../utils/loader.js'
|
||||
import { loadScript } from '../utils/loader'
|
||||
|
||||
/**
|
||||
* Manages loading and registering of reveal.js plugins.
|
||||
@@ -206,7 +206,6 @@ export default class Plugins {
|
||||
else {
|
||||
console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SLIDES_SELECTOR } from '../utils/constants.js'
|
||||
import { queryAll, createStyleSheet } from '../utils/util.js'
|
||||
import { SLIDES_SELECTOR } from '../utils/constants'
|
||||
import { queryAll, createStyleSheet } from '../utils/util'
|
||||
|
||||
/**
|
||||
* Setups up our presentation for printing/exporting to PDF.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants.js'
|
||||
import { queryAll } from '../utils/util.js'
|
||||
import { HORIZONTAL_SLIDES_SELECTOR, HORIZONTAL_BACKGROUNDS_SELECTOR } from '../utils/constants'
|
||||
import { queryAll } from '../utils/util'
|
||||
|
||||
const HIDE_SCROLLBAR_TIMEOUT = 500;
|
||||
const MAX_PROGRESS_SPACING = 4;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util.js'
|
||||
import { isMobile } from '../utils/device.js'
|
||||
import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util'
|
||||
import { isMobile } from '../utils/device'
|
||||
|
||||
import fitty from 'fitty';
|
||||
|
||||
@@ -9,11 +9,44 @@ import fitty from 'fitty';
|
||||
*/
|
||||
export default class SlideContent {
|
||||
|
||||
allowedToPlayAudio = null;
|
||||
|
||||
constructor( Reveal ) {
|
||||
|
||||
this.Reveal = Reveal;
|
||||
|
||||
this.startEmbeddedMedia = this.startEmbeddedMedia.bind( this );
|
||||
this.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );
|
||||
this.preventIframeAutoFocus = this.preventIframeAutoFocus.bind( this );
|
||||
this.ensureMobileMediaPlaying = this.ensureMobileMediaPlaying.bind( this );
|
||||
|
||||
this.failedAudioPlaybackTargets = new Set();
|
||||
this.failedVideoPlaybackTargets = new Set();
|
||||
this.failedMutedVideoPlaybackTargets = new Set();
|
||||
|
||||
this.renderMediaPlayButton();
|
||||
|
||||
}
|
||||
|
||||
renderMediaPlayButton() {
|
||||
|
||||
this.mediaPlayButton = document.createElement( 'button' );
|
||||
this.mediaPlayButton.className = 'r-overlay-button r-media-play-button';
|
||||
this.mediaPlayButton.addEventListener( 'click', () => {
|
||||
this.resetTemporarilyMutedMedia();
|
||||
|
||||
const failedTargets = new Set( [
|
||||
...this.failedAudioPlaybackTargets,
|
||||
...this.failedVideoPlaybackTargets,
|
||||
...this.failedMutedVideoPlaybackTargets
|
||||
] );
|
||||
|
||||
failedTargets.forEach( target => {
|
||||
this.startEmbeddedMedia( { target: target } );
|
||||
} );
|
||||
|
||||
this.clearMediaPlaybackErrors();
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
@@ -51,14 +84,25 @@ export default class SlideContent {
|
||||
load( slide, options = {} ) {
|
||||
|
||||
// Show the slide element
|
||||
slide.style.display = this.Reveal.getConfig().display;
|
||||
const displayValue = this.Reveal.getConfig().display;
|
||||
if( displayValue.includes('!important') ) {
|
||||
const value = displayValue.replace(/\s*!important\s*$/, '').trim();
|
||||
slide.style.setProperty('display', value, 'important');
|
||||
} else {
|
||||
slide.style.display = displayValue;
|
||||
}
|
||||
|
||||
// Media elements with data-src attributes
|
||||
// Media and iframe elements with data-src attributes
|
||||
queryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {
|
||||
if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
|
||||
const isIframe = element.tagName === 'IFRAME';
|
||||
if( !isIframe || this.shouldPreload( element ) ) {
|
||||
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
|
||||
element.setAttribute( 'data-lazy-loaded', '' );
|
||||
element.removeAttribute( 'data-src' );
|
||||
|
||||
if( isIframe ) {
|
||||
element.addEventListener( 'load', this.preventIframeAutoFocus );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -131,12 +175,7 @@ export default class SlideContent {
|
||||
}
|
||||
|
||||
// Enable inline playback in mobile Safari
|
||||
//
|
||||
// Mute is required for video to play when using
|
||||
// swipe gestures to navigate since they don't
|
||||
// count as direct user actions :'(
|
||||
if( isMobile ) {
|
||||
video.muted = true;
|
||||
video.setAttribute( 'playsinline', '' );
|
||||
}
|
||||
|
||||
@@ -318,20 +357,9 @@ export default class SlideContent {
|
||||
// Mobile devices never fire a loaded event so instead
|
||||
// of waiting, we initiate playback
|
||||
else if( isMobile ) {
|
||||
let promise = el.play();
|
||||
el.addEventListener( 'canplay', this.ensureMobileMediaPlaying );
|
||||
|
||||
// If autoplay does not work, ensure that the controls are visible so
|
||||
// that the viewer can start the media on their own
|
||||
if( promise && typeof promise.catch === 'function' && el.controls === false ) {
|
||||
promise.catch( () => {
|
||||
el.controls = true;
|
||||
|
||||
// Once the video does start playing, hide the controls again
|
||||
el.addEventListener( 'play', () => {
|
||||
el.controls = false;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
this.playMediaElement( el );
|
||||
}
|
||||
// If the media isn't loaded, wait before playing
|
||||
else {
|
||||
@@ -374,6 +402,40 @@ export default class SlideContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that an HTMLMediaElement is playing on mobile devices.
|
||||
*
|
||||
* This is a workaround for a bug in mobile Safari where
|
||||
* the media fails to display if many videos are started
|
||||
* at the same moment. When this happens, Mobile Safari
|
||||
* reports the video is playing, and the current time
|
||||
* advances, but nothing is visible.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
ensureMobileMediaPlaying( event ) {
|
||||
|
||||
const el = event.target;
|
||||
|
||||
// Ignore this check incompatible browsers
|
||||
if( typeof el.getVideoPlaybackQuality !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout( () => {
|
||||
|
||||
const playing = el.paused === false;
|
||||
const totalFrames = el.getVideoPlaybackQuality().totalVideoFrames;
|
||||
|
||||
if( playing && totalFrames === 0 ) {
|
||||
el.load();
|
||||
el.play();
|
||||
}
|
||||
|
||||
}, 1000 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing an embedded video/audio element after
|
||||
* it has finished loading.
|
||||
@@ -389,7 +451,7 @@ export default class SlideContent {
|
||||
// Don't restart if media is already playing
|
||||
if( event.target.paused || event.target.ended ) {
|
||||
event.target.currentTime = 0;
|
||||
event.target.play();
|
||||
this.playMediaElement( event.target );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +459,55 @@ export default class SlideContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the given HTMLMediaElement and handles any playback
|
||||
* errors, such as the browser not allowing audio to play without
|
||||
* user action.
|
||||
*
|
||||
* @param {HTMLElement} mediaElement
|
||||
*/
|
||||
playMediaElement( mediaElement ) {
|
||||
|
||||
const promise = mediaElement.play();
|
||||
|
||||
if( promise && typeof promise.catch === 'function' ) {
|
||||
promise
|
||||
.then( () => {
|
||||
if( !mediaElement.muted ) {
|
||||
this.allowedToPlayAudio = true;
|
||||
}
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
if( error.name === 'NotAllowedError' ) {
|
||||
this.allowedToPlayAudio = false;
|
||||
|
||||
// If this is a video, we record the error and try to play it
|
||||
// muted as a fallback. The user will be presented with an unmute
|
||||
// button.
|
||||
if( mediaElement.tagName === 'VIDEO' ) {
|
||||
this.onVideoPlaybackNotAllowed( mediaElement );
|
||||
|
||||
let isAttachedToDOM = !!closest( mediaElement, 'html' ),
|
||||
isVisible = !!closest( mediaElement, '.present' ),
|
||||
isMuted = mediaElement.muted;
|
||||
|
||||
if( isAttachedToDOM && isVisible && !isMuted ) {
|
||||
mediaElement.setAttribute( 'data-muted-by-reveal', 'true' );
|
||||
mediaElement.muted = true;
|
||||
mediaElement.play().catch(() => {
|
||||
this.onMutedVideoPlaybackNotAllowed( mediaElement );
|
||||
});
|
||||
}
|
||||
}
|
||||
else if( mediaElement.tagName === 'AUDIO' ) {
|
||||
this.onAudioPlaybackNotAllowed( mediaElement );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* "Starts" the content of an embedded iframe using the
|
||||
* postMessage API.
|
||||
@@ -407,6 +518,8 @@ export default class SlideContent {
|
||||
|
||||
let iframe = event.target;
|
||||
|
||||
this.preventIframeAutoFocus( event );
|
||||
|
||||
if( iframe && iframe.contentWindow ) {
|
||||
|
||||
let isAttachedToDOM = !!closest( event.target, 'html' ),
|
||||
@@ -461,12 +574,17 @@ export default class SlideContent {
|
||||
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
|
||||
el.setAttribute('data-paused-by-reveal', '');
|
||||
el.pause();
|
||||
|
||||
if( isMobile ) {
|
||||
el.removeEventListener( 'canplay', this.ensureMobileMediaPlaying );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
// Generic postMessage API for non-lazy loaded iframes
|
||||
queryAll( element, 'iframe' ).forEach( el => {
|
||||
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
|
||||
el.removeEventListener( 'load', this.preventIframeAutoFocus );
|
||||
el.removeEventListener( 'load', this.startEmbeddedIframe );
|
||||
});
|
||||
|
||||
@@ -497,4 +615,132 @@ export default class SlideContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether media playback is blocked by the browser. This
|
||||
* typically happens when media playback is initiated without a
|
||||
* direct user interaction.
|
||||
*/
|
||||
isAllowedToPlayAudio() {
|
||||
|
||||
return this.allowedToPlayAudio;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a manual button in situations where autoamtic media playback
|
||||
* is not allowed by the browser.
|
||||
*/
|
||||
showPlayOrUnmuteButton() {
|
||||
|
||||
const audioTargets = this.failedAudioPlaybackTargets.size;
|
||||
const videoTargets = this.failedVideoPlaybackTargets.size;
|
||||
const mutedVideoTargets = this.failedMutedVideoPlaybackTargets.size;
|
||||
|
||||
let label = 'Play media';
|
||||
|
||||
if( mutedVideoTargets > 0 ) {
|
||||
label = 'Play video';
|
||||
}
|
||||
else if( videoTargets > 0 ) {
|
||||
label = 'Unmute video';
|
||||
}
|
||||
else if( audioTargets > 0 ) {
|
||||
label = 'Play audio';
|
||||
}
|
||||
|
||||
this.mediaPlayButton.textContent = label;
|
||||
|
||||
this.Reveal.getRevealElement().appendChild( this.mediaPlayButton );
|
||||
|
||||
}
|
||||
|
||||
onAudioPlaybackNotAllowed( target ) {
|
||||
|
||||
this.failedAudioPlaybackTargets.add( target );
|
||||
this.showPlayOrUnmuteButton( target );
|
||||
|
||||
}
|
||||
|
||||
onVideoPlaybackNotAllowed( target ) {
|
||||
|
||||
this.failedVideoPlaybackTargets.add( target );
|
||||
this.showPlayOrUnmuteButton();
|
||||
|
||||
}
|
||||
|
||||
onMutedVideoPlaybackNotAllowed( target ) {
|
||||
|
||||
this.failedMutedVideoPlaybackTargets.add( target );
|
||||
this.showPlayOrUnmuteButton();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Videos may be temporarily muted by us to get around browser
|
||||
* restrictions on automatic playback. This method rolls back
|
||||
* all such temporary audio changes.
|
||||
*/
|
||||
resetTemporarilyMutedMedia() {
|
||||
|
||||
const failedTargets = new Set( [
|
||||
...this.failedAudioPlaybackTargets,
|
||||
...this.failedVideoPlaybackTargets,
|
||||
...this.failedMutedVideoPlaybackTargets
|
||||
] );
|
||||
|
||||
failedTargets.forEach( target => {
|
||||
if( target.hasAttribute( 'data-muted-by-reveal' ) ) {
|
||||
target.muted = false;
|
||||
target.removeAttribute( 'data-muted-by-reveal' );
|
||||
}
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
clearMediaPlaybackErrors() {
|
||||
|
||||
this.resetTemporarilyMutedMedia();
|
||||
|
||||
this.failedAudioPlaybackTargets.clear();
|
||||
this.failedVideoPlaybackTargets.clear();
|
||||
this.failedMutedVideoPlaybackTargets.clear();
|
||||
this.mediaPlayButton.remove();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents iframes from automatically focusing themselves.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
preventIframeAutoFocus( event ) {
|
||||
|
||||
const iframe = event.target;
|
||||
|
||||
if( iframe && this.Reveal.getConfig().preventIframeAutoFocus ) {
|
||||
|
||||
let elapsed = 0;
|
||||
const interval = 100;
|
||||
const maxTime = 1000;
|
||||
const checkFocus = () => {
|
||||
if( document.activeElement === iframe ) {
|
||||
document.activeElement.blur();
|
||||
} else if( elapsed < maxTime ) {
|
||||
elapsed += interval;
|
||||
setTimeout( checkFocus, interval );
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout( checkFocus, interval );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
afterSlideChanged() {
|
||||
|
||||
this.clearMediaPlaybackErrors();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isAndroid } from '../utils/device.js'
|
||||
import { matches } from '../utils/util.js'
|
||||
import { isAndroid } from '../utils/device'
|
||||
import { matches } from '../utils/util'
|
||||
|
||||
const SWIPE_THRESHOLD = 40;
|
||||
|
||||
@@ -216,6 +216,14 @@ export default class Touch {
|
||||
*/
|
||||
onTouchEnd( event ) {
|
||||
|
||||
// Media playback is only allowed as a direct result of a
|
||||
// user interaction. Some mobile devices do not consider a
|
||||
// 'touchmove' to be a direct user action. If this is the
|
||||
// case, we fall back to starting playback here instead.
|
||||
if( this.touchCaptured && !this.Reveal.slideContent.isAllowedToPlayAudio() ) {
|
||||
this.Reveal.startEmbeddedContent( this.Reveal.getCurrentSlide() );
|
||||
}
|
||||
|
||||
this.touchCaptured = false;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user