diff --git a/cms/static/sass/elements/_xmodules.scss b/cms/static/sass/elements/_xmodules.scss
index 1c8b3ad5eedfcbc93c271e72536fb5ef394d988e..98418008ee5075e840513414d4147945c699f226 100644
--- a/cms/static/sass/elements/_xmodules.scss
+++ b/cms/static/sass/elements/_xmodules.scss
@@ -17,11 +17,6 @@
 .xmodule_VideoModule {
   // display mode
   &.xblock-student_view {
-    // full screen
-    .video-controls .add-fullscreen {
-      display: none !important; // nasty, but needed to override the bad specificity of the xmodule css selectors
-    }
-
     .video-tracks {
       .a11y-menu-container {
         .a11y-menu-list {
diff --git a/common/lib/xmodule/xmodule/css/video/accessible_menu.scss b/common/lib/xmodule/xmodule/css/video/accessible_menu.scss
index f7b868b1070b16b9530966885beb8dcbfa79892b..5cb2de0d41edafbeb11b8df3b8f567e3e19e1bfc 100644
--- a/common/lib/xmodule/xmodule/css/video/accessible_menu.scss
+++ b/common/lib/xmodule/xmodule/css/video/accessible_menu.scss
@@ -131,3 +131,96 @@ $a11y--blue-s1: saturate($blue,15%);
     }
   }
 }
+
+
+.contextmenu, .submenu {
+  border: 1px solid #333;
+  background: #fff;
+  color: #333;
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  position: absolute;
+  top: 0;
+  display: none;
+  z-index: 999999;
+  outline: none;
+  cursor: default;
+  white-space: nowrap;
+
+  &.is-opened {
+     display: block;
+  }
+
+  .menu-item, .submenu-item {
+    border-top: 1px solid #ccc;
+    padding: 5px 10px;
+    outline: none;
+
+    & > span {
+        color: #333;
+      }
+
+    &:first-child {
+      border-top: none;
+    }
+
+    &:focus {
+      background: #333;
+      color: #fff;
+
+      & > span {
+        color: #fff;
+      }
+    }
+  }
+
+  .submenu-item {
+    position: relative;
+    padding: 5px 20px 5px 10px;
+
+    &:after {
+      content: '\25B6';
+      position: absolute;
+      right: 5px;
+      line-height: 25px;
+      font-size: 10px;
+    }
+
+    .submenu {
+      display: none;
+    }
+
+    &.is-opened {
+      background: #333;
+      color: #fff;
+
+      & > span {
+        color: #fff;
+      }
+
+      & > .submenu {
+        display: block;
+      }
+    }
+
+    .is-selected {
+      font-weight: bold;
+    }
+  }
+
+  .is-disabled {
+    pointer-events: none;
+    color: #ccc;
+  }
+}
+
+.overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 900000;
+  background-color: transparent;
+}
diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss
index e6140f46c297616bf31e920eb856f5b64e152c95..a6c5cf74a56211da31adcbc10f25d7047054c13c 100644
--- a/common/lib/xmodule/xmodule/css/video/display.scss
+++ b/common/lib/xmodule/xmodule/css/video/display.scss
@@ -689,8 +689,9 @@ div.video {
     position: fixed;
     top: 0;
     width: 100%;
-    z-index: 999;
+    z-index: 9999;
     vertical-align: middle;
+    border-radius: 0;
 
     &.closed {
       div.tc-wrapper {
diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c779b9888d65ac24977460e5036be137aaa606f6
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/spec/video/video_context_menu_spec.js
@@ -0,0 +1,441 @@
+(function () {
+    'use strict';
+    describe('Video Context Menu', function () {
+        var state, openMenu, keyPressEvent, openSubmenuMouse, openSubmenuKeyboard, closeSubmenuMouse,
+            closeSubmenuKeyboard, menu, menuItems, menuSubmenuItem, submenu, submenuItems, overlay, playButton;
+
+        openMenu = function () {
+            var container = $('div.video');
+            jasmine.Clock.useMock();
+            container.find('video').trigger('contextmenu');
+            menu = container.children('ol.contextmenu');
+            menuItems = menu.children('li.menu-item').not('.submenu-item');
+            menuSubmenuItem = menu.children('li.menu-item.submenu-item');
+            submenu = menuSubmenuItem.children('ol.submenu');
+            submenuItems = submenu.children('li.menu-item');
+            overlay = container.children('div.overlay');
+            playButton = $('a.video_control.play');
+        };
+
+        keyPressEvent = function(key) {
+            return $.Event('keydown', {keyCode: key});
+        };
+
+        openSubmenuMouse = function (menuSubmenuItem) {
+            menuSubmenuItem.mouseover();
+            jasmine.Clock.tick(200);
+            expect(menuSubmenuItem).toHaveClass('is-opened');
+        };
+
+        openSubmenuKeyboard = function (menuSubmenuItem, keyCode) {
+            menuSubmenuItem.focus().trigger(keyPressEvent(keyCode || $.ui.keyCode.RIGHT));
+            expect(menuSubmenuItem).toHaveClass('is-opened');
+            expect(menuSubmenuItem.children().first()).toBeFocused();
+        };
+
+        closeSubmenuMouse = function (menuSubmenuItem) {
+            menuSubmenuItem.mouseleave();
+            jasmine.Clock.tick(200);
+            expect(menuSubmenuItem).not.toHaveClass('is-opened');
+        };
+
+        closeSubmenuKeyboard = function (menuSubmenuItem) {
+            menuSubmenuItem.children().first().focus().trigger(keyPressEvent($.ui.keyCode.LEFT));
+            expect(menuSubmenuItem).not.toHaveClass('is-opened');
+            expect(menuSubmenuItem).toBeFocused();
+        };
+
+        beforeEach(function () {
+            // $.cookie is mocked, make sure we have a state with an unmuted volume.
+            $.cookie.andReturn('100');
+            this.addMatchers({
+                toBeFocused: function () {
+                    return {
+                        compare: function (actual) {
+                            return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement };
+                        }
+                    };
+                },
+                toHaveCorrectLabels: function (labelsList) {
+                    return _.difference(labelsList, _.map(this.actual, function (item) {
+                        return $(item).text();
+                    })).length === 0;
+                }
+            });
+        });
+
+        afterEach(function () {
+            $('source').remove();
+            _.result(state.storage, 'clear');
+            _.result($('video').data('contextmenu'), 'destroy');
+        });
+
+        describe('constructor', function () {
+            it('the structure should be created on first `contextmenu` call', function () {
+                state = jasmine.initializePlayer();
+                expect(menu).not.toExist();
+                openMenu();
+                /*
+                  Make sure we have the expected HTML structure:
+                   - Play (Pause)
+                   - Mute (Unmute)
+                   - Fill browser (Exit full browser)
+                   - Speed >
+                             - 0.75x
+                             - 1.0x
+                             - 1.25x
+                             - 1.50x
+                */
+
+                // Only one context menu per video container
+                expect(menu).toExist();
+                expect(menu).toHaveClass('is-opened');
+                expect(menuItems).toHaveCorrectLabels(['Play', 'Mute', 'Fill browser']);
+                expect(menuSubmenuItem.children('span')).toHaveText('Speed');
+                expect(submenuItems).toHaveCorrectLabels(['0.75x', '1.0x', '1.25x', '1.50x']);
+                // Check that one of the speed submenu item is selected
+                expect(_.size(submenuItems.filter('.is-selected'))).toBe(1);
+            });
+
+            it('add ARIA attributes to menu, menu items, submenu and submenu items', function () {
+                state = jasmine.initializePlayer();
+                openMenu();
+                // Menu and its items.
+                expect(menu).toHaveAttr('role', 'menu');
+                menuItems.each(function () {
+                    expect($(this)).toHaveAttrs({
+                        'aria-selected': 'false',
+                        'role': 'menuitem'
+                    });
+                });
+
+                expect(menuSubmenuItem).toHaveAttrs({
+                    'aria-expanded': 'false',
+                    'aria-haspopup': 'true',
+                    'role': 'menuitem'
+                });
+
+                // Submenu and its items.
+                expect(submenu).toHaveAttr('role', 'menu');
+                submenuItems.each(function () {
+                    expect($(this)).toHaveAttr('role', 'menuitem');
+                    expect($(this)).toHaveAttr('aria-selected');
+                });
+            });
+
+            it('is not used by Youtube type of video player', function () {
+                state = jasmine.initializePlayer('video.html');
+                expect($('video, iframe')).not.toHaveData('contextmenu');
+            });
+        });
+
+        describe('methods:', function () {
+            beforeEach(function () {
+                state = jasmine.initializePlayer();
+                openMenu();
+            });
+
+            it('menu can be destroyed successfully', function () {
+                var menuitemEvents = ['click', 'keydown', 'contextmenu', 'mouseover'],
+                    menuEvents = ['keydown', 'contextmenu', 'mouseleave', 'mouseover'];
+
+                menu.data('menu').destroy();
+                expect(menu).not.toExist();
+                expect(overlay).not.toExist();
+                _.each(menuitemEvents, function (eventName) {
+                    expect(menuItems.first()).not.toHandle(eventName);
+                })
+                _.each(menuEvents, function (eventName) {
+                    expect(menuSubmenuItem).not.toHandle(eventName);
+                })
+                _.each(menuEvents, function (eventName) {
+                    expect(menu).not.toHandle(eventName);
+                })
+                expect($('video')).not.toHandle('contextmenu');
+                expect($('video')).not.toHaveData('contextmenu');
+            });
+
+            it('can change label for the submenu', function () {
+                expect(menuSubmenuItem.children('span')).toHaveText('Speed');
+                menuSubmenuItem.data('menu').setLabel('New Name');
+                expect(menuSubmenuItem.children('span')).toHaveText('New Name');
+            });
+
+            it('can change label for the menuitem', function () {
+                expect(menuItems.first()).toHaveText('Play');
+                menuItems.first().data('menu').setLabel('Pause');
+                expect(menuItems.first()).toHaveText('Pause');
+            });
+        });
+
+        describe('when video is right-clicked', function () {
+            beforeEach(function () {
+                state = jasmine.initializePlayer();
+                openMenu();
+            });
+
+            it('context menu opens', function () {
+                expect(menu).toHaveClass('is-opened');
+                expect(overlay).toExist();
+            });
+
+            it('mouseover and mouseleave behave as expected', function () {
+                openSubmenuMouse(menuSubmenuItem);
+                expect(menuSubmenuItem).toHaveClass('is-opened');
+                closeSubmenuMouse(menuSubmenuItem);
+                expect(menuSubmenuItem).not.toHaveClass('is-opened');
+                submenuItems.eq(1).mouseover();
+                expect(submenuItems.eq(1)).toBeFocused();
+            });
+
+            it('mouse left-clicking outside of the context menu will close it', function () {
+                // Left-click outside of open menu, for example on Play button
+                playButton.click();
+                expect(menu).not.toHaveClass('is-opened');
+                expect(overlay).not.toExist();
+            });
+
+            it('mouse right-clicking outside of video will close it', function () {
+                // Right-click outside of open menu for example on Play button
+                playButton.trigger('contextmenu');
+                expect(menu).not.toHaveClass('is-opened');
+                expect(overlay).not.toExist();
+            });
+
+            it('mouse right-clicking inside video but outside of context menu will not close it', function () {
+                spyOn(menu.data('menu'), 'pointInContainerBox').andReturn(true);
+                overlay.trigger('contextmenu');
+                expect(menu).toHaveClass('is-opened');
+                expect(overlay).toExist();
+            });
+
+            it('mouse right-clicking inside video but outside of context menu will close submenus', function () {
+                spyOn(menu.data('menu'), 'pointInContainerBox').andReturn(true);
+                openSubmenuMouse(menuSubmenuItem);
+                expect(menuSubmenuItem).toHaveClass('is-opened');
+                overlay.trigger('contextmenu');
+                expect(menuSubmenuItem).not.toHaveClass('is-opened');
+            });
+
+            it('mouse left/right-clicking behaves as expected on play/pause menu item', function () {
+                var menuItem = menuItems.first();
+                runs(function () {
+                    // Left-click on play
+                    menuItem.click();
+                });
+
+                waitsFor(function () {
+                    return state.videoPlayer.isPlaying();
+                }, 'video to start playing', 200);
+
+                runs(function () {
+                    expect(menuItem).toHaveText('Pause');
+                    openMenu();
+                    // Left-click on pause
+                    menuItem.click();
+                });
+
+                waitsFor(function () {
+                    return !state.videoPlayer.isPlaying();
+                }, 'video to start playing', 200);
+
+                runs(function () {
+                    expect(menuItem).toHaveText('Play');
+                    // Right-click on play
+                    menuItem.trigger('contextmenu');
+                });
+
+                waitsFor(function () {
+                    return state.videoPlayer.isPlaying();
+                }, 'video to start playing', 200);
+
+                runs(function () {
+                    expect(menuItem).toHaveText('Pause');
+                });
+            });
+
+            it('mouse left/right-clicking behaves as expected on mute/unmute menu item', function () {
+                var menuItem = menuItems.eq(1);
+                // Left-click on mute
+                menuItem.click();
+                expect(state.videoVolumeControl.getMuteStatus()).toBe(true);
+                expect(menuItem).toHaveText('Unmute');
+                openMenu();
+                // Left-click on unmute
+                menuItem.click();
+                expect(state.videoVolumeControl.getMuteStatus()).toBe(false);
+                expect(menuItem).toHaveText('Mute');
+                // Right-click on mute
+                menuItem.trigger('contextmenu');
+                expect(state.videoVolumeControl.getMuteStatus()).toBe(true);
+                expect(menuItem).toHaveText('Unmute');
+                openMenu();
+                // Right-click on unmute
+                menuItem.trigger('contextmenu');
+                expect(state.videoVolumeControl.getMuteStatus()).toBe(false);
+                expect(menuItem).toHaveText('Mute');
+            });
+
+            it('mouse left/right-clicking behaves as expected on go to Exit full browser menu item', function () {
+                var menuItem = menuItems.eq(2);
+                // Left-click on Fill browser
+                menuItem.click();
+                expect(state.isFullScreen).toBe(true);
+                expect(menuItem).toHaveText('Exit full browser');
+                openMenu();
+                // Left-click on Exit full browser
+                menuItem.click();
+                expect(state.isFullScreen).toBe(false);
+                expect(menuItem).toHaveText('Fill browser');
+                // Right-click on Fill browser
+                menuItem.trigger('contextmenu');
+                expect(state.isFullScreen).toBe(true);
+                expect(menuItem).toHaveText('Exit full browser');
+                openMenu();
+                // Right-click on Exit full browser
+                menuItem.trigger('contextmenu');
+                expect(state.isFullScreen).toBe(false);
+                expect(menuItem).toHaveText('Fill browser');
+            });
+
+            it('mouse left/right-clicking behaves as expected on speed submenu item', function () {
+                // Set speed to 0.75x
+                state.videoSpeedControl.setSpeed('0.75');
+                // Left-click on second submenu speed (1.0x)
+                openSubmenuMouse(menuSubmenuItem);
+                submenuItems.eq(1).click();
+
+                // Expect speed to be 1.0x
+                expect(state.videoSpeedControl.currentSpeed).toBe('1.0');
+                // Expect speed submenu item 0.75x not to be active
+                expect(submenuItems.first()).not.toHaveClass('is-selected');
+                // Expect speed submenu item 1.0x to be active
+                expect(submenuItems.eq(1)).toHaveClass('is-selected');
+
+                // Set speed to 0.75x
+                state.videoSpeedControl.setSpeed('0.75');
+                // Right-click on second submenu speed (1.0x)
+                openSubmenuMouse(menuSubmenuItem);
+                submenuItems.eq(1).trigger('contextmenu');
+
+                // Expect speed to be 1.0x
+                expect(state.videoSpeedControl.currentSpeed).toBe('1.0');
+                // Expect speed submenu item 0.75x not to be active
+                expect(submenuItems.first()).not.toHaveClass('is-selected');
+                // Expect speed submenu item 1.0x to be active
+                expect(submenuItems.eq(1)).toHaveClass('is-selected');
+            });
+        });
+
+        describe('Keyboard interactions', function () {
+            beforeEach(function () {
+                state = jasmine.initializePlayer();
+                openMenu();
+            });
+
+            it('focus the first item of the just opened menu on UP keydown', function () {
+                menu.trigger(keyPressEvent($.ui.keyCode.UP));
+                expect(menuSubmenuItem).toBeFocused();
+            });
+
+            it('focus the last item of the just opened menu on DOWN keydown', function () {
+                menu.trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuItems.first()).toBeFocused();
+            });
+
+            it('open the submenu on ENTER keydown', function () {
+                openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.ENTER);
+                expect(menuSubmenuItem).toHaveClass('is-opened');
+                expect(submenuItems.first()).toBeFocused();
+            });
+
+            it('open the submenu on SPACE keydown', function () {
+                openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.SPACE);
+                expect(menuSubmenuItem).toHaveClass('is-opened');
+                expect(submenuItems.first()).toBeFocused();
+            });
+
+            it('open the submenu on RIGHT keydown', function () {
+                openSubmenuKeyboard(menuSubmenuItem, $.ui.keyCode.RIGHT);
+                expect(menuSubmenuItem).toHaveClass('is-opened');
+                expect(submenuItems.first()).toBeFocused();
+            });
+
+            it('close the menu on ESCAPE keydown', function () {
+                menu.trigger(keyPressEvent($.ui.keyCode.ESCAPE));
+                expect(menu).not.toHaveClass('is-opened');
+                expect(overlay).not.toExist();
+            });
+
+            it('close the submenu on ESCAPE keydown', function () {
+                openSubmenuKeyboard(menuSubmenuItem);
+                menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.ESCAPE));
+                expect(menuSubmenuItem).not.toHaveClass('is-opened');
+                expect(overlay).not.toExist();
+            });
+
+            it('close the submenu on LEFT keydown on submenu items', function () {
+                closeSubmenuKeyboard(menuSubmenuItem);
+            });
+
+            it('do nothing on RIGHT keydown on submenu item', function () {
+                submenuItems.eq(1).focus().trigger(keyPressEvent($.ui.keyCode.RIGHT)); // Mute
+                // Is still focused.
+                expect(submenuItems.eq(1)).toBeFocused();
+            });
+
+            it('do nothing on TAB keydown on menu item', function () {
+                submenuItems.eq(1).focus().trigger(keyPressEvent($.ui.keyCode.TAB)); // Mute
+                // Is still focused.
+                expect(submenuItems.eq(1)).toBeFocused();
+            });
+
+            it('UP and DOWN keydown function as expected on menu/submenu items', function () {
+                menuItems.eq(0).focus(); // Play
+                expect(menuItems.eq(0)).toBeFocused();
+                menuItems.eq(0).trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuItems.eq(1)).toBeFocused(); // Mute
+                menuItems.eq(1).trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuItems.eq(2)).toBeFocused(); // Fullscreen
+                menuItems.eq(2).trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuSubmenuItem).toBeFocused(); // Speed
+                menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuItems.eq(0)).toBeFocused(); // Play
+
+                menuItems.eq(0).trigger(keyPressEvent($.ui.keyCode.UP));
+                expect(menuSubmenuItem).toBeFocused(); // Speed
+                menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.UP));
+                // Check if hidden item can be skipped correctly.
+                menuItems.eq(2).hide(); // hide Fullscreen item
+                expect(menuItems.eq(1)).toBeFocused(); // Mute
+                menuItems.eq(1).trigger(keyPressEvent($.ui.keyCode.UP));
+                expect(menuItems.eq(0)).toBeFocused(); // Play
+            });
+
+            it('current item is still focused if all siblings are hidden', function () {
+                menuItems.eq(0).focus(); // Play
+                expect(menuItems.eq(0)).toBeFocused(); // hide all siblings
+                menuItems.eq(0).siblings().hide();
+                menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.DOWN));
+                expect(menuItems.eq(0)).toBeFocused();
+                menuSubmenuItem.trigger(keyPressEvent($.ui.keyCode.UP));
+                expect(menuItems.eq(0)).toBeFocused();
+            });
+
+            it('ENTER keydown on menu/submenu item selects its data and closes menu', function () {
+                menuItems.eq(2).focus().trigger(keyPressEvent($.ui.keyCode.ENTER)); // Fullscreen
+                expect(menuItems.eq(2)).toHaveClass('is-selected');
+                expect(menuItems.eq(2).siblings()).not.toHaveClass('is-selected');
+                expect(state.isFullScreen).toBeTruthy();
+                expect(menuItems.eq(2)).toHaveText('Exit full browser');
+            });
+
+            it('SPACE keydown on menu/submenu item selects its data and closes menu', function () {
+                submenuItems.eq(2).focus().trigger(keyPressEvent($.ui.keyCode.SPACE)); // 1.25x
+                expect(submenuItems.eq(2)).toHaveClass('is-selected');
+                expect(submenuItems.eq(2).siblings()).not.toHaveClass('is-selected');
+                expect(state.videoSpeedControl.currentSpeed).toBe('1.25');
+            });
+        });
+    });
+})();
diff --git a/common/lib/xmodule/xmodule/js/src/video/00_component.js b/common/lib/xmodule/xmodule/js/src/video/00_component.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c3e1d10042159e7d5bea645537aadfb194e1851
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/video/00_component.js
@@ -0,0 +1,80 @@
+(function (define) {
+'use strict';
+define('video/00_component.js', [],
+function () {
+     /**
+     * Creates a new object with the specified prototype object and properties.
+     * @param {Object} o The object which should be the prototype of the
+     * newly-created object.
+     * @private
+     * @throws {TypeError, Error}
+     * @return {Object}
+     */
+    var inherit = Object.create || (function () {
+        var F = function () {};
+
+        return function (o) {
+            if (arguments.length > 1) {
+                throw Error('Second argument not supported');
+            }
+            if (_.isNull(o) || _.isUndefined(o)) {
+                throw Error('Cannot set a null [[Prototype]]');
+            }
+            if (!_.isObject(o)) {
+                throw TypeError('Argument must be an object');
+            }
+
+            F.prototype = o;
+
+            return new F();
+        };
+    })();
+
+    /**
+     * Component module.
+     * @exports video/00_component.js
+     * @constructor
+     * @return {jquery Promise}
+     */
+    var Component = function () {
+        if ($.isFunction(this.initialize)) {
+            return this.initialize.apply(this, arguments);
+        }
+    };
+
+    /**
+     * Returns new constructor that inherits form the current constructor.
+     * @static
+     * @param {Object} protoProps The object containing which will be added to
+     * the prototype.
+     * @return {Object}
+     */
+    Component.extend = function (protoProps, staticProps) {
+        var Parent = this,
+            Child = function () {
+                if ($.isFunction(this.initialize)) {
+                    return this.initialize.apply(this, arguments);
+                }
+            };
+
+        // Inherit methods and properties from the Parent prototype.
+        Child.prototype = inherit(Parent.prototype);
+        Child.constructor = Parent;
+        // Provide access to parent's methods and properties
+        Child.__super__ = Parent.prototype;
+
+        // Extends inherited methods and properties by methods/properties
+        // passed as argument.
+        if (protoProps) {
+            $.extend(Child.prototype, protoProps);
+        }
+
+        // Inherit static methods and properties
+        $.extend(Child, Parent, staticProps);
+
+        return Child;
+    };
+
+    return Component;
+});
+}(RequireJS.define));
diff --git a/common/lib/xmodule/xmodule/js/src/video/00_i18n.js b/common/lib/xmodule/xmodule/js/src/video/00_i18n.js
index fda1ada13c8c52589516e59e84984595ae4c5fd4..ba9f1f27db07478eab9024887e606c87cbbc6e7e 100644
--- a/common/lib/xmodule/xmodule/js/src/video/00_i18n.js
+++ b/common/lib/xmodule/xmodule/js/src/video/00_i18n.js
@@ -11,6 +11,13 @@ function() {
      */
 
     return {
+        'Play': gettext('Play'),
+        'Pause': gettext('Pause'),
+        'Mute': gettext('Mute'),
+        'Unmute': gettext('Unmute'),
+        'Exit full browser': gettext('Exit full browser'),
+        'Fill browser': gettext('Fill browser'),
+        'Speed': gettext('Speed'),
         'Volume': gettext('Volume'),
         // Translators: Volume level equals 0%.
         'Muted': gettext('Muted'),
diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
index 4d2b3adcd9498c26bca66f9cd2582987a80dc66c..084afc69323618bd6d19758b0fadc62157680be9 100644
--- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
+++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
@@ -30,7 +30,7 @@ function () {
     //     get the 'state' object as a context.
     function _makeFunctionsPublic(state) {
         var methodsDict = {
-            exitFullScreen: exitFullScreen,
+            exitFullScreenHandler: exitFullScreenHandler,
             hideControls: hideControls,
             hidePlayPlaceholder: hidePlayPlaceholder,
             pause: pause,
@@ -39,6 +39,7 @@ function () {
             showControls: showControls,
             showPlayPlaceholder: showPlayPlaceholder,
             toggleFullScreen: toggleFullScreen,
+            toggleFullScreenHandler: toggleFullScreenHandler,
             togglePlayback: togglePlayback,
             updateControlsHeight: updateControlsHeight,
             updateVcrVidTime: updateVcrVidTime
@@ -93,7 +94,7 @@ function () {
     //     Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
     function _bindHandlers(state) {
         state.videoControl.playPauseEl.on('click', state.videoControl.togglePlayback);
-        state.videoControl.fullScreenEl.on('click', state.videoControl.toggleFullScreen);
+        state.videoControl.fullScreenEl.on('click', state.videoControl.toggleFullScreenHandler);
         state.el.on('fullscreen', function (event, isFullScreen) {
             var height = state.videoControl.updateControlsHeight();
 
@@ -111,7 +112,7 @@ function () {
             }
         });
 
-        $(document).on('keyup', state.videoControl.exitFullScreen);
+        $(document).on('keyup', state.videoControl.exitFullScreenHandler);
 
         if ((state.videoType === 'html5') && (state.config.autohideHtml5)) {
             state.el.on('mousemove', state.videoControl.showControls);
@@ -246,19 +247,22 @@ function () {
 
     function togglePlayback(event) {
         event.preventDefault();
-
-        if (this.videoControl.isPlaying) {
-            this.trigger('videoPlayer.pause', null);
-        } else {
-            this.trigger('videoPlayer.play', null);
-        }
+        this.videoCommands.execute('togglePlayback');
     }
 
-    function toggleFullScreen(event) {
+    /**
+     * Event handler to toggle fullscreen mode.
+     * @param {jquery Event} event
+     */
+    function toggleFullScreenHandler(event) {
         event.preventDefault();
+        this.videoCommands.execute('toggleFullScreen');
+    }
+
+    /** Toggle fullscreen mode. */
+    function toggleFullScreen() {
         var fullScreenClassNameEl = this.el.add(document.documentElement),
-            win = $(window),
-            text;
+            win = $(window), text;
 
         if (this.videoControl.fullScreenState) {
             this.videoControl.fullScreenState = this.isFullScreen = false;
@@ -280,9 +284,14 @@ function () {
         this.el.trigger('fullscreen', [this.isFullScreen]);
     }
 
-    function exitFullScreen(event) {
+    /**
+     * Event handler to exit from fullscreen mode.
+     * @param {jquery Event} event
+     */
+    function exitFullScreenHandler(event) {
         if ((this.isFullScreen) && (event.keyCode === 27)) {
-            this.videoControl.toggleFullScreen(event);
+            event.preventDefault();
+            this.videoCommands.execute('toggleFullScreen');
         }
     }
 
diff --git a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
index 7467574024f673d2d6b9ff3831af897a278d209a..8f4b95d36df60af62395d5cc7912fd1766c76bfc 100644
--- a/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+++ b/common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
@@ -198,7 +198,7 @@ function (Iterator) {
             var speed = $(event.currentTarget).parent().data('speed');
 
             this.closeMenu();
-            this.setSpeed(this.state.speedToString(speed));
+            this.state.videoCommands.execute('speed', speed);
 
             return false;
         },
diff --git a/common/lib/xmodule/xmodule/js/src/video/095_video_context_menu.js b/common/lib/xmodule/xmodule/js/src/video/095_video_context_menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..33fbfa752bd77f625c76fcad8858e7edd93ba023
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/video/095_video_context_menu.js
@@ -0,0 +1,665 @@
+(function (define) {
+'use strict';
+// VideoContextMenu module.
+define(
+'video/095_video_context_menu.js',
+['video/00_component.js'],
+function (Component) {
+    var AbstractItem, AbstractMenu, Menu, Overlay, Submenu, MenuItem;
+
+    AbstractItem = Component.extend({
+        initialize: function (options) {
+            this.options = $.extend(true, {
+                label: '',
+                prefix: 'edx-',
+                dataAttrs: {menu: this},
+                attrs: {},
+                items: [],
+                callback: $.noop,
+                initialize: $.noop
+            }, options);
+
+            this.id = _.uniqueId();
+            this.element = this.createElement();
+            this.element.attr(this.options.attrs).data(this.options.dataAttrs);
+            this.children = [];
+            this.delegateEvents();
+            this.options.initialize.call(this, this);
+        },
+        destroy: function () {
+            _.invoke(this.getChildren(), 'destroy');
+            this.undelegateEvents();
+            this.getElement().remove();
+        },
+        open: function () {
+            this.getElement().addClass('is-opened');
+            return this;
+        },
+        close: function () { },
+        closeSiblings: function () {
+            _.invoke(this.getSiblings(), 'close');
+            return this;
+        },
+        getElement: function () {
+            return this.element;
+        },
+        addChild: function (child) {
+            var firstChild = null, lastChild = null;
+            if (this.hasChildren()) {
+                lastChild = this.getLastChild();
+                lastChild.next = child;
+                firstChild = this.getFirstChild();
+                firstChild.prev = child;
+            }
+            child.parent = this;
+            child.next = firstChild;
+            child.prev = lastChild;
+            this.children.push(child);
+            return this;
+        },
+        getChildren: function () {
+            // Returns the copy.
+            return this.children.concat();
+        },
+        hasChildren: function () {
+            return this.getChildren().length > 0;
+        },
+        getFirstChild: function () {
+            return _.first(this.children);
+        },
+        getLastChild: function () {
+            return _.last(this.children);
+        },
+        bindEvent: function (element, events, handler) {
+            $(element).on(this.addNamespace(events), handler);
+            return this;
+        },
+        getNext: function () {
+            var item = this.next;
+            while (item.isHidden() && this.id !== item.id) { item = item.next; }
+            return item;
+        },
+        getPrev: function () {
+            var item = this.prev;
+            while (item.isHidden() && this.id !== item.id) { item = item.prev; }
+            return item;
+        },
+        createElement: function () {
+            return null;
+        },
+        getRoot: function () {
+            var item = this;
+            while (item.parent) { item = item.parent; }
+            return item;
+        },
+        populateElement: function () { },
+        focus: function () {
+            this.getElement().focus();
+            this.closeSiblings();
+            return this;
+        },
+        isHidden: function () {
+            return this.getElement().is(':hidden');
+        },
+        getSiblings: function () {
+            var items = [],
+                item = this;
+            while (item.next && item.next.id !== this.id) {
+                item = item.next;
+                items.push(item);
+            }
+            return items;
+        },
+        select: function () { },
+        unselect: function () { },
+        setLabel: function () { },
+        itemHandler: function () { },
+        keyDownHandler: function () { },
+        delegateEvents: function () { },
+        undelegateEvents: function () {
+            this.getElement().off('.' + this.id);
+        },
+        addNamespace: function (events) {
+            return _.map(events.split(/\s+/), function (event) {
+                return event + '.' + this.id;
+            }, this).join(' ');
+        }
+    });
+
+    AbstractMenu = AbstractItem.extend({
+        delegateEvents: function () {
+            this.bindEvent(this.getElement(), 'keydown mouseleave mouseover', this.itemHandler.bind(this))
+                .bindEvent(this.getElement(), 'contextmenu', function (event) { event.preventDefault(); });
+            return this;
+        },
+
+        populateElement: function () {
+            var fragment = document.createDocumentFragment();
+
+            _.each(this.getChildren(), function (child) {
+                fragment.appendChild(child.populateElement()[0]);
+            }, this);
+
+            this.appendContent([fragment]);
+            this.isRendered = true;
+            return this.getElement();
+        },
+
+        close: function () {
+            this.closeChildren();
+            this.getElement().removeClass('is-opened');
+            return this;
+        },
+
+        closeChildren: function () {
+            _.invoke(this.getChildren(), 'close');
+            return this;
+        },
+
+        itemHandler: function (event) {
+            event.preventDefault();
+            var item = $(event.target).data('menu');
+            switch(event.type) {
+                case 'keydown':
+                    this.keyDownHandler.call(this, event, item);
+                    break;
+                case 'mouseover':
+                    this.mouseOverHandler.call(this, event, item);
+                    break;
+                case 'mouseleave':
+                    this.mouseLeaveHandler.call(this, event, item);
+                    break;
+            }
+        },
+
+        keyDownHandler: function () { },
+        mouseOverHandler: function () { },
+        mouseLeaveHandler: function () { }
+    });
+
+    Menu = AbstractMenu.extend({
+        initialize: function (options, contextmenuElement, container) {
+            this.contextmenuElement = $(contextmenuElement);
+            this.container = $(container);
+            this.overlay = this.getOverlay();
+            AbstractMenu.prototype.initialize.apply(this, arguments);
+            this.build(this, this.options.items);
+        },
+
+        createElement: function () {
+            return $('<ol />', {
+                'class': ['contextmenu', this.options.prefix + 'contextmenu'].join(' '),
+                'role': 'menu',
+                'tabindex': -1
+            });
+        },
+
+        delegateEvents: function () {
+            AbstractMenu.prototype.delegateEvents.call(this);
+            this.bindEvent(this.contextmenuElement, 'contextmenu', this.contextmenuHandler.bind(this))
+                .bindEvent(window, 'resize', _.debounce(this.close.bind(this), 100));
+            return this;
+        },
+
+        destroy: function () {
+            AbstractMenu.prototype.destroy.call(this);
+            this.overlay.destroy();
+            this.contextmenuElement.removeData('contextmenu');
+            return this;
+        },
+
+        undelegateEvents: function () {
+            AbstractMenu.prototype.undelegateEvents.call(this);
+            this.contextmenuElement.off(this.addNamespace('contextmenu'));
+            this.overlay.undelegateEvents();
+            return this;
+        },
+
+        appendContent: function (content) {
+            this.getElement().append(content);
+            return this;
+        },
+
+        addChild: function () {
+            AbstractMenu.prototype.addChild.apply(this, arguments);
+            this.next = this.getFirstChild();
+            this.prev = this.getLastChild();
+            return this;
+        },
+
+        build: function (container, items) {
+            _.each(items, function(item) {
+                var child;
+                if (_.has(item, 'items')) {
+                    child = this.build((new Submenu(item, this.contextmenuElement)), item.items);
+                } else {
+                    child = new MenuItem(item);
+                }
+                container.addChild(child);
+            }, this);
+            return container;
+        },
+
+        focus: function () {
+            this.getElement().focus();
+            return this;
+        },
+
+        open: function () {
+            var menu = (this.isRendered) ? this.getElement() : this.populateElement();
+            this.container.append(menu);
+            AbstractItem.prototype.open.call(this);
+            this.overlay.show(this.container);
+            return this;
+        },
+
+        close: function () {
+            AbstractMenu.prototype.close.call(this);
+            this.getElement().detach();
+            this.overlay.hide();
+            return this;
+        },
+
+        position: function(event) {
+            this.getElement().position({
+                my: 'left top',
+                of: event,
+                collision: 'flipfit flipfit',
+                within: this.contextmenuElement
+            });
+
+            return this;
+        },
+
+        pointInContainerBox: function (x, y) {
+            var containerOffset = this.contextmenuElement.offset(),
+                containerBox = {
+                    x0: containerOffset.left,
+                    y0: containerOffset.top,
+                    x1: containerOffset.left + this.contextmenuElement.outerWidth(),
+                    y1: containerOffset.top + this.contextmenuElement.outerHeight()
+                };
+            return containerBox.x0 <= x && x <= containerBox.x1 && containerBox.y0 <= y && y <= containerBox.y1;
+        },
+
+        getOverlay: function () {
+            return new Overlay(
+                this.close.bind(this),
+                function (event) {
+                    event.preventDefault();
+                    if (this.pointInContainerBox(event.pageX, event.pageY)) {
+                        this.position(event).focus();
+                        this.closeChildren();
+                    } else {
+                        this.close();
+                    }
+
+                }.bind(this)
+            );
+        },
+
+        contextmenuHandler: function (event) {
+            event.preventDefault();
+            event.stopPropagation();
+            this.open().position(event).focus();
+        },
+
+        keyDownHandler: function (event, item) {
+            var KEY = $.ui.keyCode,
+                keyCode = event.keyCode;
+
+            switch (keyCode) {
+                case KEY.UP:
+                    item.getPrev().focus();
+                    event.stopPropagation();
+                    break;
+                case KEY.DOWN:
+                    item.getNext().focus();
+                    event.stopPropagation();
+                    break;
+                case KEY.TAB:
+                    event.stopPropagation();
+                    break;
+                case KEY.ESCAPE:
+                    this.close();
+                    break;
+            }
+
+            return false;
+        }
+    });
+
+    Overlay = Component.extend({
+        ns: '.overlay',
+        initialize: function (clickHandler, contextmenuHandler) {
+            this.element = $('<div />', {
+                'class': 'overlay'
+            });
+            this.clickHandler = clickHandler;
+            this.contextmenuHandler = contextmenuHandler;
+        },
+
+        destroy: function () {
+            this.getElement().remove();
+            this.undelegateEvents();
+        },
+
+        getElement: function () {
+            return this.element;
+        },
+
+        hide: function () {
+            this.getElement().detach();
+            this.undelegateEvents();
+            return this;
+        },
+
+        show: function (container) {
+            $(container).append(this.getElement());
+            this.delegateEvents();
+            return this;
+        },
+
+        delegateEvents: function () {
+            var self = this;
+            $(document)
+                .on('click' + this.ns, function () {
+                    if (_.isFunction(self.clickHandler)) {
+                        self.clickHandler.apply(this, arguments);
+                    }
+                    self.hide();
+                })
+                .on('contextmenu' + this.ns, function () {
+                    if (_.isFunction(self.contextmenuHandler)) {
+                        self.contextmenuHandler.apply(this, arguments);
+                    }
+                });
+            return this;
+        },
+
+        undelegateEvents: function () {
+            $(document).off(this.ns);
+            return this;
+        }
+    });
+
+    Submenu = AbstractMenu.extend({
+        initialize: function (options, contextmenuElement) {
+            this.contextmenuElement = contextmenuElement;
+            AbstractMenu.prototype.initialize.apply(this, arguments);
+        },
+
+        createElement: function () {
+            var element = $('<li />', {
+                'class': ['submenu-item','menu-item', this.options.prefix + 'submenu-item'].join(' '),
+                'aria-expanded': 'false',
+                'aria-haspopup': 'true',
+                'aria-labelledby': 'submenu-item-label-' + this.id,
+                'role': 'menuitem',
+                'tabindex': -1
+            });
+
+            this.label = $('<span />', {
+                'id': 'submenu-item-label-' + this.id,
+                'text': this.options.label
+            }).appendTo(element);
+
+            this.list = $('<ol />', {
+                'class': ['submenu', this.options.prefix + 'submenu'].join(' '),
+                'role': 'menu'
+            }).appendTo(element);
+
+            return element;
+        },
+
+        appendContent: function (content) {
+            this.list.append(content);
+            return this;
+        },
+
+        setLabel: function (label) {
+            this.label.text(label);
+            return this;
+        },
+
+        openKeyboard: function () {
+            if (this.hasChildren()) {
+                this.open();
+                this.getFirstChild().focus();
+            }
+            return this;
+        },
+
+        keyDownHandler: function (event) {
+            var KEY = $.ui.keyCode,
+                keyCode = event.keyCode;
+
+            switch (keyCode) {
+                case KEY.LEFT:
+                    this.close().focus();
+                    event.stopPropagation();
+                    break;
+                case KEY.RIGHT:
+                case KEY.ENTER:
+                case KEY.SPACE:
+                    this.openKeyboard();
+                    event.stopPropagation();
+                    break;
+            }
+
+            return false;
+        },
+
+        open: function () {
+            AbstractMenu.prototype.open.call(this);
+            this.getElement().attr({'aria-expanded': 'true'});
+            this.position();
+            return this;
+        },
+
+        close: function () {
+            AbstractMenu.prototype.close.call(this);
+            this.getElement().attr({'aria-expanded': 'false'});
+            return this;
+        },
+
+        position: function () {
+            this.list.position({
+                my: 'left top',
+                at: 'right top',
+                of: this.getElement(),
+                collision: 'flipfit flipfit',
+                within: this.contextmenuElement
+            });
+            return this;
+        },
+
+        mouseOverHandler: function () {
+            clearTimeout(this.timer);
+            this.timer = setTimeout(this.open.bind(this), 200);
+            this.focus();
+        },
+
+        mouseLeaveHandler: function () {
+            clearTimeout(this.timer);
+            this.timer = setTimeout(this.close.bind(this), 200);
+            this.focus();
+        }
+    });
+
+    MenuItem = AbstractItem.extend({
+        createElement: function () {
+            var classNames = [
+                'menu-item', this.options.prefix + 'menu-item',
+                this.options.isSelected ? 'is-selected' : ''
+            ].join(' ');
+
+            return $('<li />', {
+                'class': classNames,
+                'aria-selected': this.options.isSelected ? 'true' : 'false',
+                'role': 'menuitem',
+                'tabindex': -1,
+                'text': this.options.label
+            });
+        },
+
+        populateElement: function () {
+            return this.getElement();
+        },
+
+        delegateEvents: function () {
+            this.bindEvent(this.getElement(), 'click keydown contextmenu mouseover', this.itemHandler.bind(this));
+            return this;
+        },
+
+        setLabel: function (label) {
+            this.getElement().text(label);
+            return this;
+        },
+
+        select: function (event) {
+            this.options.callback.call(this, event, this, this.options);
+            this.getElement()
+                .addClass('is-selected')
+                .attr({'aria-selected': 'true'});
+            _.invoke(this.getSiblings(), 'unselect');
+            // Hide the menu.
+            this.getRoot().close();
+            return this;
+        },
+
+        unselect: function () {
+            this.getElement()
+                .removeClass('is-selected')
+                .attr({'aria-selected': 'false'});
+            return this;
+        },
+
+        itemHandler: function (event) {
+            event.preventDefault();
+            switch(event.type) {
+                case 'contextmenu':
+                case 'click':
+                    this.select();
+                    break;
+                case 'mouseover':
+                    this.focus();
+                    event.stopPropagation();
+                    break;
+                case 'keydown':
+                    this.keyDownHandler.call(this, event, this);
+                    break;
+            }
+        },
+
+        keyDownHandler: function (event) {
+            var KEY = $.ui.keyCode,
+                keyCode = event.keyCode;
+
+            switch (keyCode) {
+                case KEY.RIGHT:
+                    event.stopPropagation();
+                    break;
+                case KEY.ENTER:
+                case KEY.SPACE:
+                    this.select();
+                    event.stopPropagation();
+                    break;
+            }
+
+            return false;
+        }
+    });
+
+    // VideoContextMenu() function - what this module 'exports'.
+    return function (state, i18n) {
+
+        var speedCallback = function (event, menuitem, options) {
+                var speed = parseFloat(options.label);
+                state.videoCommands.execute('speed', speed);
+            },
+            options = {
+                items: [{
+                    label: i18n['Play'], // jshint ignore:line
+                    callback: function () {
+                        state.videoCommands.execute('togglePlayback');
+                    },
+                    initialize: function (menuitem) {
+                        state.el.on({
+                            'play': function () {
+                                menuitem.setLabel(i18n['Pause']); // jshint ignore:line
+                            },
+                            'pause': function () {
+                                menuitem.setLabel(i18n['Play']); // jshint ignore:line
+                            }
+                        });
+                    }
+                }, {
+                    label: state.videoVolumeControl.getMuteStatus() ? i18n['Unmute'] : i18n['Mute'], // jshint ignore:line
+                    callback: function () {
+                        state.videoCommands.execute('toggleMute');
+                    },
+                    initialize: function (menuitem) {
+                        state.el.on({
+                            'volumechange': function () {
+                                if (state.videoVolumeControl.getMuteStatus()) {
+                                    menuitem.setLabel(i18n['Unmute']); // jshint ignore:line
+                                } else {
+                                    menuitem.setLabel(i18n['Mute']); // jshint ignore:line
+                                }
+                            }
+                        });
+                    }
+                }, {
+                    label: i18n['Fill browser'],
+                    callback: function () {
+                        state.videoCommands.execute('toggleFullScreen');
+                    },
+                    initialize: function (menuitem) {
+                        state.el.on({
+                            'fullscreen': function (event, isFullscreen) {
+                                if (isFullscreen) {
+                                    menuitem.setLabel(i18n['Exit full browser']);
+                                } else {
+                                    menuitem.setLabel(i18n['Fill browser']);
+                                }
+                            }
+                        });
+                    }
+                }, {
+                    label: i18n['Speed'], // jshint ignore:line
+                    items: _.map(state.speeds, function (speed) {
+                        var isSelected = speed === state.speed;
+                        return {label: speed + 'x', callback: speedCallback, speed: speed, isSelected: isSelected};
+                    }),
+                    initialize: function (menuitem) {
+                        state.el.on({
+                            'speedchange': function (event, speed) {
+                                var item = menuitem.getChildren().filter(function (item) {
+                                    return item.options.speed === speed;
+                                })[0];
+                                if (item) {
+                                    item.select();
+                                }
+                            }
+                        });
+                    }
+                }
+            ]
+        };
+
+        $.fn.contextmenu = function (container, options) {
+            return this.each(function() {
+                $(this).data('contextmenu', new Menu(options, this, container));
+            });
+        };
+
+        if (!state.isYoutubeType()) {
+            state.el.find('video').contextmenu(state.el, options);
+        }
+
+        return $.Deferred().resolve().promise();
+    };
+});
+
+}(RequireJS.define));
diff --git a/common/lib/xmodule/xmodule/js/src/video/10_commands.js b/common/lib/xmodule/xmodule/js/src/video/10_commands.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad78066864d34e5924b11c27069cb5cf19d54ee4
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/video/10_commands.js
@@ -0,0 +1,97 @@
+(function(define) {
+'use strict';
+// VideoCommands module.
+define('video/10_commands.js', [], function() {
+    var VideoCommands, Command, playCommand, pauseCommand, togglePlaybackCommand,
+        muteCommand, unmuteCommand, toggleMuteCommand, toggleFullScreenCommand,
+        setSpeedCommand;
+
+    /**
+     * Video commands module.
+     * @exports video/10_commands.js
+     * @constructor
+     * @param {Object} state The object containing the state of the video
+     * @param {Object} i18n The object containing strings with translations.
+     * @return {jquery Promise}
+     */
+    VideoCommands = function(state, i18n) {
+        if (!(this instanceof VideoCommands)) {
+            return new VideoCommands(state, i18n);
+        }
+
+        this.state = state;
+        this.state.videoCommands = this;
+        this.i18n = i18n;
+        this.commands = [];
+        this.initialize();
+
+        return $.Deferred().resolve().promise();
+    };
+
+    VideoCommands.prototype = {
+        /** Initializes the module. */
+        initialize: function() {
+            this.commands = this.getCommands();
+        },
+
+        execute: function (command) {
+            var args = [].slice.call(arguments, 1) || [];
+
+            if (_.has(this.commands, command)) {
+                this.commands[command].execute.apply(this, [this.state].concat(args));
+            } else {
+                console.log('Command "' + command + '" is not available.');
+            }
+        },
+
+        getCommands: function () {
+            var commands = {},
+                commandsList = [
+                    playCommand, pauseCommand, togglePlaybackCommand,
+                    toggleMuteCommand, toggleFullScreenCommand, setSpeedCommand
+                ];
+
+            _.each(commandsList, function(command) {
+                commands[command.name] = command;
+            }, this);
+
+            return commands;
+        }
+    };
+
+    Command = function (name, execute) {
+        this.name = name;
+        this.execute = execute;
+    };
+
+    playCommand = new Command('play', function (state) {
+        state.videoPlayer.play();
+    });
+
+    pauseCommand = new Command('pause', function (state) {
+        state.videoPlayer.pause();
+    });
+
+    togglePlaybackCommand = new Command('togglePlayback', function (state) {
+        if (state.videoControl.isPlaying) {
+            pauseCommand.execute(state);
+        } else {
+            playCommand.execute(state);
+        }
+    });
+
+    toggleMuteCommand = new Command('toggleMute', function (state) {
+        state.videoVolumeControl.toggleMute();
+    });
+
+    toggleFullScreenCommand = new Command('toggleFullScreen', function (state) {
+        state.videoControl.toggleFullScreen();
+    });
+
+    setSpeedCommand = new Command('speed', function (state, speed) {
+        state.videoSpeedControl.setSpeed(state.speedToString(speed));
+    });
+
+    return VideoCommands;
+});
+}(RequireJS.define));
diff --git a/common/lib/xmodule/xmodule/js/src/video/10_main.js b/common/lib/xmodule/xmodule/js/src/video/10_main.js
index 5e8d9f281176cb3ff631fba7639c7a884f125968..1ac539111726776ab91fd82806e5d1a9db44ad03 100644
--- a/common/lib/xmodule/xmodule/js/src/video/10_main.js
+++ b/common/lib/xmodule/xmodule/js/src/video/10_main.js
@@ -43,7 +43,9 @@
             'video/06_video_progress_slider.js',
             'video/07_video_volume_control.js',
             'video/08_video_speed_control.js',
-            'video/09_video_caption.js'
+            'video/09_video_caption.js',
+            'video/10_commands.js',
+            'video/095_video_context_menu.js'
         ],
         function (
             initialize,
@@ -54,7 +56,9 @@
             VideoProgressSlider,
             VideoVolumeControl,
             VideoSpeedControl,
-            VideoCaption
+            VideoCaption,
+            VideoCommands,
+            VideoContextMenu
         ) {
             var youtubeXhr = null,
                 oldVideo = window.Video;
@@ -87,7 +91,9 @@
                     VideoProgressSlider,
                     VideoVolumeControl,
                     VideoSpeedControl,
-                    VideoCaption
+                    VideoCaption,
+                    VideoCommands,
+                    VideoContextMenu
                 ];
 
                 state.youtubeXhr = youtubeXhr;
diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py
index 26e23207c93a4e86befc04a0cd59fc6471c1c700..51d0042101b6cbd9a24dc293f1b789070f2f7618 100644
--- a/common/lib/xmodule/xmodule/video_module/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module/video_module.py
@@ -67,6 +67,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
     module = __name__.replace('.video_module', '', 2)
     js = {
         'js': [
+            resource_string(module, 'js/src/video/00_component.js'),
             resource_string(module, 'js/src/video/00_video_storage.js'),
             resource_string(module, 'js/src/video/00_resizer.js'),
             resource_string(module, 'js/src/video/00_async_process.js'),
@@ -84,6 +85,8 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
             resource_string(module, 'js/src/video/07_video_volume_control.js'),
             resource_string(module, 'js/src/video/08_video_speed_control.js'),
             resource_string(module, 'js/src/video/09_video_caption.js'),
+            resource_string(module, 'js/src/video/095_video_context_menu.js'),
+            resource_string(module, 'js/src/video/10_commands.js'),
             resource_string(module, 'js/src/video/10_main.js')
         ]
     }
@@ -93,7 +96,6 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
     ]}
     js_module_name = "Video"
 
-
     def get_html(self):
         track_url = None
         download_video_link = None