Refactors the player service.

The idea is that the service should ideally be used directly, so we know who depends on what and don't end up with cycles.
To achieve this, we should not add functions to $rootScope but instead provide them from our service.
Services can use them directly, and controllers have to create other functions in their own scope.

This means that we have a player-controller that does not do much by itself but it enables us to avoid filling up the $rootScope.

I have left the $rootScope additions at the end of player-service because it's going to take some time to refactor it, so in the meantime I want things to keep working.
This commit is contained in:
Hyzual 2014-11-25 17:38:53 +01:00
parent 1a01c4c23c
commit 74ac275ece
6 changed files with 164 additions and 94 deletions

View file

@ -1,7 +1,7 @@
/* Declare app level module */ /* Declare app level module */
angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize', angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize',
'jamstash.subsonic.ctrl', 'jamstash.archive.ctrl', 'jamstash.player']) 'jamstash.subsonic.ctrl', 'jamstash.archive.ctrl', 'jamstash.player.ctrl'])
.config(['$routeProvider',function($routeProvider) { .config(['$routeProvider',function($routeProvider) {
'use strict'; 'use strict';

View file

@ -9,7 +9,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
var player2 = '#playdeck_2'; var player2 = '#playdeck_2';
var scrobbled = false; var scrobbled = false;
var timerid = 0; var timerid = 0;
$rootScope.defaultPlay = function (data, event) { this.defaultPlay = function (data, event) {
if (typeof $(player1).data("jPlayer") == 'undefined') { if (typeof $(player1).data("jPlayer") == 'undefined') {
$rootScope.nextTrack(); $rootScope.nextTrack();
} }
@ -27,7 +27,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
$rootScope.playSong(false, next); $rootScope.playSong(false, next);
} }
}; };
$rootScope.previousTrack = function () { this.previousTrack = function () {
var next = getNextSong(true); var next = getNextSong(true);
if (next) { if (next) {
$rootScope.playSong(false, next); $rootScope.playSong(false, next);
@ -176,11 +176,11 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser, ' + html.length + ' characters'); } if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser, ' + html.length + ' characters'); }
} }
}; };
$rootScope.restartSong = function (loadonly, data) { this.restartSong = function (loadonly, data) {
var audio = $(player1).data("jPlayer"); var audio = $(player1).data("jPlayer");
audio.play(0); audio.play(0);
}; };
$rootScope.playSong = function (loadonly, data) { this.playSong = function (loadonly, data) {
if (globals.settings.Debug) { console.log('Play: ' + JSON.stringify(data, null, 2)); } if (globals.settings.Debug) { console.log('Play: ' + JSON.stringify(data, null, 2)); }
angular.forEach($rootScope.queue, function(item, key) { angular.forEach($rootScope.queue, function(item, key) {
item.playing = false; item.playing = false;
@ -413,4 +413,12 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
} }
}); });
}; };
// TODO: Hyz: Those are temporary. Remove them when no one else is calling them.
// The idea is to have them so while refactoring so we don't break anything
$rootScope.defaultPlay = this.defaultPlay;
$rootScope.nextTrack = this.nextTrack;
$rootScope.previousTrack = this.previousTrack;
$rootScope.restartSong = this.restartSong;
$rootScope.playSong = this.playSong;
}]); }]);

View file

@ -11,9 +11,10 @@ describe("Player service", function() {
}); });
}); });
describe("nextTrack -", function() { describe("Given that I have 3 songs in my playing queue", function() {
it("Given that I have 3 songs in my playing queue and no song is playing, it plays the first song", function() { var stubPlaySong, stubRestartSong;
beforeEach(function() {
$rootScope.queue = [ $rootScope.queue = [
{ {
id: 6726, id: 6726,
@ -32,10 +33,56 @@ describe("Player service", function() {
album: 'redux' album: 'redux'
} }
]; ];
stubPlaySong = spyOn($rootScope, "playSong").and.stub();
stubRestartSong = spyOn($rootScope, "restartSong").and.stub();
});
describe("when I call nextTrack", function() {
it("and no song is playing, it plays the first song", function() {
player.nextTrack();
expect(stubPlaySong).toHaveBeenCalled();
});
it("and the first song is playing, it plays the second song", function() {
$rootScope.queue[0].playing = true;
player.nextTrack(); player.nextTrack();
expect($rootScope.queue[0].playing).toBeTruthy(); expect(stubPlaySong).toHaveBeenCalled();
});
it("and the last song is playing, it does nothing", function() {
$rootScope.queue[2].playing = true;
player.nextTrack();
expect(stubPlaySong).not.toHaveBeenCalled();
});
});
describe("when I call previousTrack", function() {
it("and no song is playing, it plays the first song", function() {
player.previousTrack();
expect(stubRestartSong).toHaveBeenCalled();
});
it("and the first song is playing, it restarts the first song", function() {
$rootScope.queue[0].playing = true;
player.previousTrack();
expect(stubRestartSong).toHaveBeenCalled();
});
it("and the last song is playing, it plays the seconde song", function() {
$rootScope.queue[2].playing = true;
player.previousTrack();
expect(stubPlaySong).toHaveBeenCalled();
});
}); });
}); });
}); });

View file

@ -18,7 +18,7 @@
<div id="songdetails"> <div id="songdetails">
<div id="coverart"> <div id="coverart">
<a ng-click="fancyboxOpenImage(playingSong.coverartfull)"> <a ng-click="fancyboxOpenImage(playingSong.coverartfull)">
<img ng-src="{{playingSong.coverartthumb}}" src="images/albumdefault_60.jpg" height="60" width="60" /> <img ng-src="{{playingSong.coverartthumb}}" src="images/albumdefault_60.jpg" height="30" width="30" />
</a> </a>
</div> </div>
<ul> <ul>

View file

@ -3,45 +3,20 @@
* *
* Control the HTML5 player through jplayer.js * Control the HTML5 player through jplayer.js
*/ */
angular.module('jamstash.player', ['jamstash.player.service', 'jamstash.player.directive']) angular.module('jamstash.player.ctrl', ['jamstash.player.service', 'jamstash.player.directive'])
.controller('PlayerCtrl', ['$scope', 'player', function($scope, player){ .controller('PlayerCtrl', ['$scope', 'player', function($scope, player){
'use strict'; 'use strict';
$scope.playingSong = {
id: '',
album: '',
name: '',
specs: '',
starred: '',
coverartfull: '',
coverartthumb: ''
};
$scope.previousTrack = function () { $scope.previousTrack = function () {
player.previousTrack();
};
$scope.defaultPlay = function () {
}; };
$scope.nextTrack = function () { $scope.nextTrack = function () {
player.nextTrack();
}; };
$scope.updateFavorite = function (song) { $scope.defaultPlay = function () {
player.defaultPlay();
}; };
$scope.toggleSetting = function (settingName) {
};
$scope.settings = {
SaveTrackPosition: '',
Jukebox: '',
Repeat: ''
};
}]); }]);

View file

@ -1,3 +1,43 @@
describe("Player controller", function() { describe("Player controller", function() {
'use strict'; 'use strict';
var player, scope, deferred;
beforeEach(function() {
module('jamstash.player.ctrl');
inject(function ($controller, $rootScope, _player_, $q) {
scope = $rootScope.$new();
player = _player_;
// Mock the functions of the services
deferred = $q.defer();
spyOn(player, "nextTrack").and.stub();
spyOn(player, "previousTrack").and.stub();
spyOn(player, "defaultPlay").and.stub();
$controller('PlayerCtrl', {
$scope: scope,
player: player
});
});
});
it("When I call previousTrack, it calls previousTrack in the player service", function() {
scope.previousTrack();
expect(player.previousTrack).toHaveBeenCalled();
});
it("When I call nextTrack, it calls nextTrack in the player service", function() {
scope.nextTrack();
expect(player.nextTrack).toHaveBeenCalled();
});
it("When I call defaultPlay, it calls defaultPlay in the player service", function() {
scope.defaultPlay();
expect(player.defaultPlay).toHaveBeenCalled();
});
}); });