diff --git a/scripts/youtube.js b/scripts/youtube.js index 2492fe3..2c1e44b 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -22,6 +22,150 @@ H5P.VideoYouTube = (function ($) { text: l10n.loading, html: `
` }).appendTo($wrapper); + var $sphericalDragOverlay; + var sphericalDragStart; + + /** + * Remove custom 360 drag handling. + * + * @private + */ + var teardownSphericalDrag = function () { + $(document).off('.' + id); + if ($sphericalDragOverlay) { + $sphericalDragOverlay.remove(); + $sphericalDragOverlay = undefined; + } + sphericalDragStart = undefined; + }; + + /** + * Add mouse drag support for 360° videos when YouTube controls are hidden. + * The YouTube embed player used to handle this itself, but H5P Interactive + * Video hides native YouTube controls and depends on H5P controls instead. + * + * @private + */ + var setupSphericalDrag = function () { + if (options.controls || $sphericalDragOverlay || + !player || !player.getSphericalProperties || !player.setSphericalProperties) { + return; + } + + var sphericalProperties = player.getSphericalProperties(); + if (!sphericalProperties || !Object.keys(sphericalProperties).length) { + return; + } + + var $host = $placeholder.children('div').first(); + $sphericalDragOverlay = $('
', { + 'class': 'h5p-youtube-360-drag', + 'aria-hidden': 'true' + }).css({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + cursor: 'grab', + 'touch-action': 'none', + 'z-index': 2 + }).appendTo($host); + + var normalizeYaw = function (yaw) { + yaw = yaw % 360; + return yaw < 0 ? yaw + 360 : yaw; + }; + + var clampPitch = function (pitch) { + return Math.max(-90, Math.min(90, pitch)); + }; + + $sphericalDragOverlay.on('pointerdown', function (event) { + var originalEvent = event.originalEvent || event; + if (originalEvent.button !== undefined && originalEvent.button !== 0) { + return; + } + + var current = player.getSphericalProperties(); + if (!current || !Object.keys(current).length) { + return; + } + + sphericalDragStart = { + x: originalEvent.clientX, + y: originalEvent.clientY, + yaw: current.yaw || 0, + pitch: current.pitch || 0, + fov: current.fov || 100, + moved: false + }; + + if (this.setPointerCapture && originalEvent.pointerId !== undefined) { + this.setPointerCapture(originalEvent.pointerId); + } + + $sphericalDragOverlay.css('cursor', 'grabbing'); + event.preventDefault(); + }); + + $(document).on('pointermove.' + id, function (event) { + if (!sphericalDragStart) { + return; + } + + var originalEvent = event.originalEvent || event; + var dx = originalEvent.clientX - sphericalDragStart.x; + var dy = originalEvent.clientY - sphericalDragStart.y; + sphericalDragStart.moved = sphericalDragStart.moved || + Math.abs(dx) > 2 || Math.abs(dy) > 2; + + var sensitivity = sphericalDragStart.fov / 650; + player.setSphericalProperties({ + yaw: normalizeYaw(sphericalDragStart.yaw - (dx * sensitivity)), + pitch: clampPitch(sphericalDragStart.pitch + (dy * sensitivity)), + roll: 0, + fov: sphericalDragStart.fov + }); + + event.preventDefault(); + }); + + $(document).on('pointerup.' + id + ' pointercancel.' + id, function () { + if (!sphericalDragStart) { + return; + } + + if (!sphericalDragStart.moved && player.getPlayerState) { + if (player.getPlayerState() === H5P.Video.PLAYING) { + player.pauseVideo(); + } + else { + player.playVideo(); + } + } + + sphericalDragStart = undefined; + $sphericalDragOverlay.css('cursor', 'grab'); + }); + }; + + /** + * Try to install custom drag handling after YouTube has detected that the + * current video is spherical. YouTube may not expose spherical properties + * immediately on ready. + * + * @private + */ + var scheduleSphericalDragSetup = function () { + if (options.controls || $sphericalDragOverlay) { + return; + } + + setupSphericalDrag(); + setTimeout(setupSphericalDrag, 500); + setTimeout(setupSphericalDrag, 1500); + }; // Optional placeholder // var $placeholder = $('').appendTo($wrapper); @@ -78,6 +222,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); + scheduleSphericalDragSetup(); if (!options.autoplay) { self.toPause = true; @@ -134,6 +279,7 @@ H5P.VideoYouTube = (function ($) { // End IE11 fix self.trigger('stateChange', state.data); + scheduleSphericalDragSetup(); } }, onPlaybackQualityChange: function (quality) { @@ -312,6 +458,7 @@ H5P.VideoYouTube = (function ($) { player.pauseVideo(); self.trigger('stateChange', H5P.Video.PAUSED); } + teardownSphericalDrag(); player.destroy(); player = undefined; }