<!DOCTYPE html>
<html ng-app="myApp">

  <head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
        
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    


    <link href="https://vjs.zencdn.net/6.2.5/video-js.css" rel="stylesheet">
    
  </head>

  <body ng-controller="ayCtrl"> <!-- Controller for speech recognition -->
        
    <div ng-controller="myCtrl">
      <div>
        <video id='vid' preload="auto" controls class="video-js vjs-default-skin" vjs-video>
            <source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4" type="video/mp4">
            
        </video>
        <p>Say: "Play", "Pause", or "Stop" to test functionality.</p>
      </div>
    </div>
    
    <script src="https://code.jquery.com/jquery-2.0.3.min.js"> </script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script> 
    
    <script src="https://vjs.zencdn.net/6.2.5/video.js"></script>
    <script src="./vjs-video.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/annyang/2.5.0/annyang.min.js"></script>
    <script src="./annyangService.js"></script>
    
    <script src="./script.js"></script>
    <script src="./ayCtrl.js"></script>
  </body>
</html>
var myApp = angular.module('myApp');
    
myApp.controller('myCtrl', ['$scope', '$timeout',
function($scope, $timeout , AnnyangService) 
{
  // These are the commands for speech recognition. They are replicated in the
  // speech recognition service.
  /*
  
  $scope.commands = {
    'play': function() {
      $scope.vid.play();
    },
    'pause': function() {
      $scope.vid.pause(); 
    },
    'stop': function() {
      $scope.vid.currentTime(0); $scope.vid.pause();}
    };*/
  
  
  
 $scope.$on('vjsVideoReady', function (e, data) {
            //data contains `id`, `vid`, `player` and `controlBar`
            //NOTE: vid is depricated, use player instead
            console.log('video id:' + data.id);
            console.log('video.js player instance:' + data.player);
            console.log('video.js controlBar instance:' + data.controlBar);
            
            $timeout(function(){

                $scope.vid = data.player;
            
            $scope.vm.play = function() {
        $scope.vid.play();
      };

      $scope.vm.pause = function() {
        $scope.vid.pause();
      }

      $scope.vm.stop = function() {
        $scope.vid.currentTime(0);
        $scope.vid.pause();
      }
            
                $scope.vm.init();
                /*
                $scope.ay = annyang; 
                $scope.ay.addCommands($scope.commands);
            	$scope.ay.debug();
                $scope.ay.start();*/
            }, 1250);
 })  
 }]);
I'm trying to implement annyang speech recognition with videojs on AngularJS.

I have obtained a controller and service for implementing annyang on AngularJS,
but I have not found how to successfully install it for my purposes. 

The service and controller can be found at: https://github.com/levithomason/angular-annyang/tree/master/js

My intent is to set a controller for the speech recognition, which will set up and launch the service.

I'm getting the error: Error: $controller:ctrlreg
A controller with this name is not registered.

I know I'm doing something wrong with the AyCtrl controller, but I'm not sure what it is, and other articles, while
wordy, don't exactly *show* me what I'm doing wrong.

Thank you for your help.
/**
 * @ngdoc directive
 * @name vjsVideoApp.directive:vjs.directive.js
 * @description
 * # vjs.directive.js
 */
(function (root, factory) {
    //module loader detection derrived from http://tinyurl.com/hs2coz2
    if ((typeof define).match(/^(object|function)$/) && define.amd) {
        //AMD type module loader detected
        define(['angular', 'video.js'], factory);
    } else if (typeof module === 'object' && module.exports) {
        //CommonJS type module loader detected
        module.exports = factory(require('angular'), require('video.js'));
    } else {
        //we aren't using a module loader so angular and video.js
        //should exist globally
        //also, we don't need to add this module to global space
        factory(root.angular, root.videojs);
    }
}(this, function (angular, videojs) {
    'use strict';

    var module = angular.module('vjs.video', []);

    function getVersion() {
        return (videojs && videojs.VERSION) ?
                videojs.VERSION : '0.0.0';
    }

    function isMediaElement(element) {
        return element[0].nodeName === 'VIDEO' || element[0].nodeName === 'AUDIO';
    }

    module.controller('VjsVideoController', ['$scope', function ($scope) {
        var self = this;

        function getVidElement(element, isContainer) {
            var vid = null,
                videos;

            if (!videojs) {
                throw new Error('video.js was not found!');
            }

            if (isContainer) {
                videos = element[0].querySelectorAll('video, audio');
                if (videos.length === 0) {
                    throw new Error('video tag must be defined within container directive!');
                } else if (videos.length > 1) {
                    throw new Error('only one video can be defined within the container directive!');
                }

                vid = videos[0];
            } else {
                if (isMediaElement(element)) {
                    vid = element[0];
                } else {
                    throw new Error('directive must be attached to a video or audio tag!');
                }
            }

            return vid;
        }

        function applyRatio(el, ratioVal) {
            var ratio = ratioVal,
                style = document.createElement('style'),
                parseRatio = function (r) {
                    var tokens = r.split(':'),
                        tokenErrorMsg = 'the ratio must either be "wide", "standard" or ' +
                                        'decimal values in the format of w:h';

                    //if invalid ratio throw an error
                    if (tokens.length !== 2) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that both tokens are numbers
                    if (isNaN(tokens[0]) || isNaN(tokens[1])) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that the width or height is not zero
                    if (Number(tokens[0]) === 0 || Number(tokens[1]) === 0) {
                        throw new Error('neither the width or height ratio can be zero!');
                    }

                    return (Number(tokens[1]) / Number(tokens[0])) * 100;
                },
                genContainerId = function (element) {
                    var container = element[0].querySelector('.vjs-tech'),
                        vjsId;

                    if (container) {
                        vjsId = 'vjs-container-' + container.getAttribute('id');
                    } else {
                        //vjsId = 'vjs-container-default';
                        throw new Error('Failed to find instance of video-js class!');
                    }

                    //add generated id to container
                    element[0].setAttribute('id', vjsId);

                    return vjsId;
                },
                containerId,
                ratioPercentage,
                css;

            //if ratio isn't defined lets default to wide screen
            if (!ratio) {
                ratio = '16:9';
            }

            switch (ratio) {
            case 'wide':
                ratio = '16:9';
                break;
            case 'standard':
                ratio = '4:3';
                break;
            }

            containerId = genContainerId(el);

            ratioPercentage = parseRatio(ratio);

            css = ['#', containerId, ' ',
                   '.video-js {padding-top:', ratioPercentage,
                   '%;}\n', '.vjs-fullscreen {padding-top: 0px;}'].join('');

            style.type = 'text/css';
            style.rel = 'stylesheet';
            if (style.styleSheet) {
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }

            el[0].appendChild(style);
        }

        function generateMedia(ctrl, mediaChangedHandler) {
            var errMsgNoValid = 'a sources and/or tracks element must be ' +
                                'defined for the vjs-media attribute',
                errMsgNoSrcs  = 'sources must be an array of objects with at ' +
                                'least one item',
                errMsgNoTrks  = 'tracks must be an array of objects with at ' +
                                'least one item',
                div,
                curDiv;
            //check to see if vjsMedia is defined
            if (!ctrl.vjsMedia) {
                return;
            }

            //if sources and tracks aren't defined, throw an error
            if (!ctrl.vjsMedia.sources && !ctrl.vjsMedia.tracks) {
                throw new Error(errMsgNoValid);
            }

            //verify sources and tracks are arrays if they are defined
            if (ctrl.vjsMedia.sources && !(ctrl.vjsMedia.sources instanceof Array)) {
                throw new Error(errMsgNoSrcs);
            }
            if (ctrl.vjsMedia.tracks && !(ctrl.vjsMedia.tracks instanceof Array)) {
                throw new Error(errMsgNoTrks);
            }

            //build DOM elements for sources and tracks as children to a div
            div = document.createElement("div");

            if (ctrl.vjsMedia.sources) {
                ctrl.vjsMedia.sources.forEach(function (curObj) {
                    curDiv = document.createElement('source');
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('type', curObj.type || "");
                    div.appendChild(curDiv);
                });
            }

            if (ctrl.vjsMedia.tracks) {
                ctrl.vjsMedia.tracks.forEach(function (curObj) {
                    curDiv = document.createElement('track');
                    curDiv.setAttribute('kind', curObj.kind || "");
                    curDiv.setAttribute('label', curObj.label || "");
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('srclang', curObj.srclang || "");

                    //check for default flag
                    if (curObj.default === true) {
                        curDiv.setAttribute('default', "");
                    }

                    div.appendChild(curDiv);
                });
            }

            //invoke callback
            mediaChangedHandler.call(undefined, {element: div});

        }

        function initVideoJs(vid, params, element, mediaChangedHandler) {
            var opts = params.vjsSetup || {},
                ratio = params.vjsRatio,
                isValidContainer =
                    (!isMediaElement(element) && !getVersion().match(/^5\./)) ? true : false,
                mediaWatcher;

            if (!videojs) {
                return null;
            }

            //override poster settings if defined in vjsMedia
            if (params.vjsMedia && params.vjsMedia.poster) {
                opts.poster = params.vjsMedia.poster;
            }

            //generate any defined sources or tracks
            generateMedia(params, mediaChangedHandler);

            //watch for changes to vjs-media
            mediaWatcher = $scope.$watch(
                function () {
                    return params.vjsMedia;
                },
                function (newVal, oldVal) {

                    if (newVal && !angular.equals(newVal, oldVal)) {
                        //deregister watcher
                        mediaWatcher();

                        if (isValidContainer) {
                            videojs(vid).dispose();
                            $scope.$emit('vjsVideoMediaChanged');
                        } else {
                            $scope.$emit('vjsVideoMediaChanged');
                        }
                    }
                }
            );

            //bootstrap videojs
            videojs(vid, opts, function () {
                if (isValidContainer) {
                    applyRatio(element, ratio);
                }

                //emit ready event with reference to video
                $scope.$emit('vjsVideoReady', {
                    id: vid.getAttribute('id'),
                    vid: this,
                    player: this,
                    controlBar: this.controlBar
                });
            });

            //dispose of videojs before destroying directive
            $scope.$on('$destroy', function () {
                videojs(vid).dispose();
            });
        }

        //export public methods
        self.initVideoJs = initVideoJs;
        self.getVidElement = getVidElement;
    }]);

    module.directive('vjsVideo', ['$compile', '$timeout', function ($compile, $timeout) {

        return {
            restrict: 'A',
            transclude: true,
            scope: {
                vjsSetup: '=?',
                vjsRatio: '@',
                vjsMedia: '=?'
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    parentContainer,
                    origContent,
                    compiledEl,
                    mediaChangedHandler = function (e) {
                        //remove any inside contents
                        element.children().remove();
                        //add generated sources and tracks
                        element.append(e.element.childNodes);
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element);

                        //check if video.js version 5.x is running
                        if (getVersion().match(/^5\./)) {
                            //if vjsRatio is defined,
                            //add it to the vjsSetup options
                            if (ctrl.vjsRatio) {
                                if (!ctrl.vjsSetup) {
                                    ctrl.vjsSetup = {};
                                }

                                ctrl.vjsSetup.aspectRatio = ctrl.vjsRatio;
                            }
                        }

                        //attach transcluded content
                        transclude(function (content) {
                            element.append(content);

                            //now that the transcluded content is injected
                            //initialize video.js
                            ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                        });
                    };

                origContent = element.clone();

                //we need to wrap the video inside of a div
                //for easier DOM management
                if (!element.parent().hasClass('vjs-video-wrap')) {
                    element.wrap('<div class="vjs-video-wrap"></div>');
                }

                parentContainer = element.parent();

                scope.$on('vjsVideoMediaChanged', function () {
                    //retreive base element that video.js creates
                    var staleChild = parentContainer.children()[0];

                    //remove current directive instance
                    //destroy will trigger a video.js dispose
                    $timeout(function () {
                        scope.$destroy();
                    });

                    //compile the new directive and add it to the DOM
                    compiledEl = origContent.clone();
                    parentContainer.append(compiledEl);
                    //it is key to pass in the parent scope to the directive
                    compiledEl = $compile(compiledEl)(scope.$parent);

                    //remove original element created by video.js
                    staleChild.remove();
                });

                init();
            }
        };
    }]);

    module.directive('vjsVideoContainer', [function () {

        return {
            restrict: 'AE',
            transclude: true,
            template: '<div class="vjs-directive-container"><div ng-transclude></div></div>',
            scope: {
                vjsSetup: '=?',
                vjsRatio: '@',
                vjsMedia: '=?'
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    origContent,
                    mediaChangedHandler = function (e) {
                        var vidEl = element[0].querySelector('video, audio');

                        if (vidEl) {
                            //remove any inside contents
                            while (vidEl.firstChild) {
                                vidEl.removeChild(vidEl.firstChild);
                            }

                            //add generated sources and tracks
                            while (e.element.childNodes.length > 0) {
                                vidEl.appendChild(e.element.childNodes[0]);
                            }
                        }
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element, true);

                        //we want to confirm that the vjs-video directive or
                        //any corresponding attributes are not defined on the
                        //internal video element
                        if (vid.getAttribute('vjs-video') !== null) {
                            throw new Error(
                                'vjs-video should not be used on the video ' +
                                'tag when using vjs-video-container!');
                        }

                        //we also want to make sure that no vjs-* attributes
                        //are included on the internal video tag
                        if ((vid.getAttribute('vjs-setup') !== null) ||
                            (vid.getAttribute('vjs-media') !== null) ||
                            (vid.getAttribute('vjs-ratio') !== null)) {
                            throw new Error(
                                'directive attributes should not be used on ' +
                                'the video tag when using vjs-video-container!'
                            );

                        }

                        //check if video.js version 5.x is running
                        if (getVersion().match(/^5\./)) {

                            if (ctrl.vjsRatio) {
                                if (!ctrl.vjsSetup) {
                                    ctrl.vjsSetup = {};
                                }

                                ctrl.vjsSetup.aspectRatio = ctrl.vjsRatio;
                            }
                        } else {
                            //set width and height of video to auto
                            vid.setAttribute('width', 'auto');
                            vid.setAttribute('height', 'auto');
                        }

                        //bootstrap video js
                        ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                    };

                //save original content
                transclude(function (content) {
                    origContent = content.clone();
                });

                scope.$on('vjsVideoMediaChanged', function () {
                    //replace element children with orignal content
                    element.children().remove();
                    element.append(origContent.clone());
                    init();
                });

                init();
            }
        };
    }]);

    return module;
}));
(function(annyang) {
    'use strict';

    function AnnyangService($rootScope) {
        var service = {};
        
        // COMMANDS
        service.commands = {};

        service.addCommand = function(phrase, callback) {
            var command = {};
            
            // Wrap annyang command in scope apply
            command[phrase] = function(args) {
                $rootScope.$apply(callback(args));
            };

            // Extend our commands list
            angular.extend(service.commands, command);
            
            // Add the commands to annyang
            annyang.addCommands(service.commands);
            console.debug('added command "' + phrase + '"', service.commands);
        };

        service.start = function() {
            annyang.addCommands(service.commands);
            annyang.debug(true);
            annyang.start();
        };
        
        return service;
    }

    angular.module('myApp', ['vjs.video']) 
        .factory('AnnyangService', AnnyangService);

}(window.annyang));
(function(angular) {
    'use strict';

    function MyController(AnnyangService, $scope) {

      $scope.vm = this;

      $scope.vm.init = function() {

        AnnyangService.addCommand('play', function() {
          $scope.vm.play();
        });

        AnnyangService.addCommand('pause', function() {
          $scope.vm.pause();
        });

        AnnyangService.addCommand('stop', function() {
          $scope.vm.stop();
        });

        AnnyangService.start();
      };

      

      



    }

    angular.module('myApp') // Why is controller ayCtrl "not registered"? Isn't that what...
      .controller('ayCtrl', MyController); // <-- ...this is doing? Do I need to set the module to a var?
  }
  (window.angular));