3

I'm writing a directive which creates an mp3/audio player. The issue is that you can have many audio players in one page. What I would like to do is when one is playing and you start an other, that the one currently playing pauses. How can I achieve this with angular directives?

Thanks in advance!

4 Answers 4

8

Make a service that each directive uses and hold the state in there.

Something like this:

angular.module('MyPlayer' [])
.factory('playerState', function() {
    var players = [];
    return {
        registerPlayer: function(player) {
            players.add(player);
        },
        unregisterPlayer: function(player) {
            var i = players.indexOf(player);
            (i>-1) && players.splice(i,1);
        },
        stopAllPlayers: function() {
            for(var i=0;i<players.length;i++) {
                players[i].stop();
            }
        }
    }
})
.directive('player', function(playerState) {
    return {
        ...
        link: function(scope, elem, attr) {
            var player = {
                stop: function() {
                    /* logic to stop playing */
                },
                play = function(song) {
                    playerState.stopAllPlayers();
                    /* logic to start playing */
                }
            }

            playerState.registerPlayer(player);
            scope.$on("$destroy", function() {
                playerState.unregister(player);
            });

            scope.play = player.play;
            scope.stop = player.stop;

            ...
        }
    }
})
Sign up to request clarification or add additional context in comments.

6 Comments

I'm not a huge fan of this answer as you're basically building an event system on top of the existing event system, why not just use the built in? Props for providing code though.
Players should be unregistered at some point, otherwise you will be leaking memory when changing views/routes, etc.
is there a way to trigger the unregistration when the route changes ?
@JeanlucaScaljeri there is an event triggered when the scope is destroyed: scope.$on('$destroy', ...);
What about moving the logic of the service to the directive block above the return statement. The variables and methods would still be visible to the link function, and as far as I understand, the actual directive function is only invoked once?
|
5

Just to make the answers complete, next to broadcasting events, and exposing a service, you can also use directive controllers. These controllers are set through the controller property of a directive definition object and are shared between directives that require the same controller. This means you can have one controller for all the media players, where you can implement the logic you mentioned. See the documentation on directives (search for controller:) for more information.

I would recommend the service approach if you think there will be more consumers of the logic, or the directive controller approach if only the directives consume the logic. I would advise against broadcasting events on the root scope because of the uncoupled and global nature of it. Just my two cents! HTH

4 Comments

Be careful, I would say that controllers are generally shared between directives that share the same DOM element (default behavior) or in nested constructs (^ notation) like tabs for instance. What you advice might be possible with the ? notation but can very tricky, bug-prone and difficult to maintain. Service / Broadcast seems to be more adapted.
I tried to get your solution working in jsfiddle. I'm not sure how to do this with controllers. Here is my jsfiddle. Also, I still don't understand what to put in the link function and what in the controller
here is my working fiddle. Any suggestions how to update the other directives using a controller ?
here it is working for you with this method. jsfiddle.net/joshkurz/B8x3Q I think overall this is the cleanest way to go about doing something like this. A little more difficult than broadcasting, but the end result is 100% control of the players in whatever mannar needed.
1

How are your directives setup? Please provide some code.

This depends on the scope of your directives, I'm going to assume a child scope. To communicate between the directives, when a user clicked to start a player, I would call a $scope.$parent.$broadcast() - or $rootScope.$broadcast() if the directives are in different controllers or using isolated scopes, but then you need to inject $rootScope into your directive - to send an event to all child scopes. My directives would be watching for this event using $on and any players that were playing would stop. After this broadcast the player clicked would start.

$broadcast() and $on() scope documentation

Comments

0

You can also do $rootScope.$broadcast events like playerStarted. This event can be subscribed by all directives and they can react to this event by stopping themselves. The one thing that you need to do would be pass in the data about the player which is starting so that the new player does not stop itself as it too would subscribe to such event.

6 Comments

Bah, you got here first. My only difference is I would start the player playing after the event broadcast rather than send more information and have every player check to see if it's the event originator.
Actually, now that I think about it, I'm not sure if that would create a race condition or not. Do you know if $broadcast() is blocking until after the digest cycle is done?
$on callbacks are executed immediately when $broadcast is called. See the source. This means they are called synchronously/blocking.
Yup, I would use $broadcast and try and limit the scope as much as possible, so here I added in a controller and broadcast on the controller scope rather than the rootScope for efficiency.
Don't use broadcast on the rootscope, just don't. The idea about a service to isolate your communication and possibly state in an explicit and, more importunately, encapsulated way. This will make debugging a whole lot easier and will allow you to intercept / mock the communication during testing. It will also make the live of the next developer ( that is you to ) a lot easier when he has to refactor stuff.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.