diff --git a/video_xblock/backends/base.py b/video_xblock/backends/base.py index de733948..baf83c48 100644 --- a/video_xblock/backends/base.py +++ b/video_xblock/backends/base.py @@ -44,12 +44,21 @@ def get_frag(self, **context): frag.add_css_url( 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css' ) + frag.add_css(self.resource_string( + '../static/css/videojs-contextmenu-ui.css' + )) frag.add_javascript(self.resource_string( '../static/bower_components/video.js/dist/video.min.js' )) frag.add_javascript(self.resource_string( '../static/bower_components/videojs-transcript/dist/videojs-transcript.js' )) + frag.add_javascript(self.resource_string( + '../static/bower_components/videojs-contextmenu/dist/videojs-contextmenu.min.js' + )) + frag.add_javascript(self.resource_string( + '../static/bower_components/videojs-contextmenu-ui/dist/videojs-contextmenu-ui.min.js' + )) frag.add_javascript(self.resource_string( '../static/js/video-speed.js' )) diff --git a/video_xblock/backends/wistia.py b/video_xblock/backends/wistia.py index 66c8f5ec..465d77cb 100644 --- a/video_xblock/backends/wistia.py +++ b/video_xblock/backends/wistia.py @@ -61,4 +61,8 @@ def get_frag(self, **context): '../static/bower_components/videojs-offset/dist/videojs-offset.min.js' )) - return frag + frag.add_javascript(self.render_resource( + '../static/js/player-context-menu.js', **context) + ) + + return frag \ No newline at end of file diff --git a/video_xblock/static/bower.json b/video_xblock/static/bower.json index df35194d..2c97e408 100644 --- a/video_xblock/static/bower.json +++ b/video_xblock/static/bower.json @@ -7,7 +7,9 @@ "videojs-wistia": "raccoongang/videojs-wistia#playback-toggle-fix", "videojs-transcript": "0.8.0", "videojs-resolution-switcher": "85f1e51c8babefca2d670dbd0b21a6307dbb4d21", - "videojs-offset": "raccoongang/videojs-offset" + "videojs-offset": "raccoongang/videojs-offset", + "videojs-contextmenu": "brightcove/videojs-contextmenu", + "videojs-contextmenu-ui": "brightcove/videojs-contextmenu-ui" }, "private": true } diff --git a/video_xblock/static/css/videojs-contextmenu-ui.css b/video_xblock/static/css/videojs-contextmenu-ui.css new file mode 100644 index 00000000..2f8a98a5 --- /dev/null +++ b/video_xblock/static/css/videojs-contextmenu-ui.css @@ -0,0 +1,51 @@ +.vjs-contextmenu-ui-menu, +.vjs-contextmenu-ui-submenu { + position: absolute; +} + +.vjs-contextmenu-ui-menu .vjs-menu-content, +.vjs-contextmenu-ui-submenu { + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.9); + border-radius: 0.3em; + padding: 0.25em; +} + +.vjs-contextmenu-ui-submenu { + left: 100%; + top: 7em; + width: 50%; + display : none; +} +.vjs-contextmenu-ui-submenu:hover{ + display : block; +} + +.vjs-menu-item:last-of-type:hover .vjs-contextmenu-ui-submenu{ + display : block; +} + +.vjs-contextmenu-ui-menu .vjs-menu-item, +.vjs-contextmenu-ui-menu .vjs-submenu-item { + border-radius: 0.3em; + cursor: pointer; + margin: 0 0 1px; + padding: 0.5em 1em; + font-size: 2em; + line-height: 1.2; + text-transform: none; + text-align: left; +} + +.vjs-contextmenu-ui-submenu .vjs-submenu-item { + text-shadow: initial; + font-size: 1em; +} + +.vjs-contextmenu-ui-menu .vjs-menu-item:active, +.vjs-contextmenu-ui-menu .vjs-menu-item:hover, +.vjs-contextmenu-ui-menu .vjs-submenu-item:active, +.vjs-contextmenu-ui-submenu .vjs-submenu-item:hover { + background-color: rgba(0, 0, 0, 0.5); + text-shadow: 0em 0em 1em white; +} diff --git a/video_xblock/static/js/player-context-menu.js b/video_xblock/static/js/player-context-menu.js new file mode 100644 index 00000000..3b9962f3 --- /dev/null +++ b/video_xblock/static/js/player-context-menu.js @@ -0,0 +1,112 @@ +/** + * This part is responsible for the player context menu. + * Menu Options include: + * - Play / Pause + * - Mute / Unmute + * - Fill browser / Unfill browser + * - Speed + * + */ + +/** + * Initialise player context menu with nested elements. + */ +domReady(function() { + + var videoPlayer = document.getElementById("{{ video_player_id }}"); + var dataSetup = JSON.parse(videoPlayer.getAttribute('data-setup')); + var playbackRates = dataSetup.playbackRates; + var docfrag = document.createDocumentFragment(); + // VideoJS Player() object necessary for context menu creation + var player = videojs('{{ video_player_id }}'); + + /** + * Create elements of nested context submenu. + */ + function createNestedContextSubMenu(e) { + var target = e.target; + + // Generate nested submenu elements as document fragment + var ulSubMenu = document.createElement('ul'); + ulSubMenu.className = 'vjs-contextmenu-ui-submenu'; + playbackRates.forEach(function(rate) { + var liSubMenu = document.createElement('li'); + liSubMenu.className = 'vjs-submenu-item'; + liSubMenu.innerHTML = rate + 'x'; + ulSubMenu.appendChild(liSubMenu); + liSubMenu.onclick = function() { + player.playbackRate(parseFloat(rate)); + }; + }); + docfrag.appendChild(ulSubMenu); + + // Create nested submenu + if (target.matches("li.vjs-menu-item") + && target.innerText == getItem('speed').label + && !target.querySelector('.vjs-contextmenu-ui-submenu') ) { + target.appendChild(docfrag); + } + } + + // Delegate creation of a nested submenu for a context menu + videoPlayer.addEventListener('mouseover', createNestedContextSubMenu); + + // Create context menu options + var content = [{ + id: "play", + label: 'Play', + listener: function () { + var item = getItem('play'); + if (player.paused()) { + player.play(); + item['label'] = 'Pause'; + } else { + player.pause(); + item['label'] = 'Play'; + } + }}, { + id: "mute", + label: 'Mute', + listener: function () { + var item = getItem('mute'); + if (player.muted()){ + player.muted(false); + item['label'] = 'Mute'; + } else { + player.muted(true); + item['label'] = 'Unmute'; + } + }}, { + id: "fullscreen", + label: 'Fill browser', + listener: function () { + var item = getItem('fullscreen'); + if (player.isFullscreen()){ + player.exitFullscreen(); + item['label'] = 'Fill browser'; + } else { + player.requestFullscreen(); + item['label'] = 'Unfill browser'; + } + }}, { + // Nested submenu creation is delegated to the player + id: "speed", + label: 'Speed' + } + ]; + + // Fire up vjs-contextmenu-ui plugin + player.contextmenuUI({content: content}); + + // Update context menu labels + var getItem = (function(contextmenuUI) { + var hash = {}; + contextmenuUI.content.forEach(function(item) { + hash[item.id] = item; + }); + return function(id) { + return hash[id]; + }; + }(player.contextmenuUI)); + +});