Removes the player functions from the rootScope.

However, saving track's position is broken.
Some are still there because of problems I don't know how to solve, e.g. circular dependency between notifications and player.
Uses the queue controller for the sidebar queue.
Moves loadTrackPosition to the main controller.
This commit is contained in:
Hyzual 2014-11-30 18:02:15 +01:00
parent 74ac275ece
commit e5846e30f9
14 changed files with 352 additions and 278 deletions

View file

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

View file

@ -3,10 +3,11 @@
*
* Access Archive.org
*/
angular.module('jamstash.archive.service', ['jamstash.settings', 'jamstash.model', 'jamstash.notifications'])
angular.module('jamstash.archive.service', ['jamstash.settings', 'jamstash.model', 'jamstash.notifications',
'jamstash.player.service'])
.factory('archive', ['$rootScope', '$http', '$q', '$sce', 'globals', 'model', 'utils', 'map', 'notifications',
function($rootScope, $http, $q, $sce, globals, model, utils, map, notifications) {
.factory('archive', ['$rootScope', '$http', '$q', '$sce', 'globals', 'model', 'utils', 'map', 'notifications', 'player',
function($rootScope, $http, $q, $sce, globals, model, utils, map, notifications, player) {
'use strict';
var index = { shortcuts: [], artists: [] };
@ -192,7 +193,7 @@ angular.module('jamstash.archive.service', ['jamstash.settings', 'jamstash.model
}
});
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
notifications.updateMessage(Object.keys(items).length + ' Song(s) Added to Queue', true);
} else {
content.album = [];

View file

@ -27,13 +27,6 @@
$rootScope.go = function (path) {
$location.path(path);
};
/*
$scope.playSong = function (loadonly, data) {
$scope.$apply(function () {
$rootScope.playSong(loadonly, data);
});
}
*/
// Reads cookies and sets globals.settings values
$scope.loadSettings = function () {
@ -226,9 +219,9 @@
$('#left-component').stop().scrollTo(el, 400);
}
} else if (unicode == 39 || unicode == 176) { // right arrow
$rootScope.nextTrack();
player.nextTrack();
} else if (unicode == 37 || unicode == 177) { // back arrow
$rootScope.previousTrack();
player.previousTrack();
} else if (unicode == 32 || unicode == 179 || unicode.toString() == '0179') { // spacebar
player.playPauseSong();
return false;
@ -286,7 +279,7 @@
$rootScope.selectAll(songs);
$rootScope.addSongsToQueue();
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
};
$rootScope.playFrom = function (index, songs) {
var from = songs.slice(index,songs.length);
@ -299,7 +292,7 @@
$rootScope.queue = [];
$rootScope.addSongsToQueue();
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
}
};
$rootScope.addSongsToQueue = function () {
@ -417,6 +410,12 @@
}
});
};
// Hyz: I don't know yet how to remove the circular dependency between player-service
// and notification-service... So I'll keep this one there until I know.
$rootScope.nextTrack = function (loadonly, song) {
player.nextTrack(loadonly, song);
};
$scope.updateFavorite = function (item) {
var id = item.id;
var starred = item.starred;
@ -442,6 +441,26 @@
return $sce.trustAsHtml(html);
};
function loadTrackPosition() {
if (utils.browserStorageCheck()) {
// Load Saved Song
var song = angular.fromJson(localStorage.getItem('CurrentSong'));
if (song) {
player.playSong(true, song);
// Load Saved Queue
var items = angular.fromJson(localStorage.getItem('CurrentQueue'));
if (items) {
$rootScope.queue = items;
if ($rootScope.queue.length > 0) {
notifications.updateMessage($rootScope.queue.length + ' Saved Song(s)', true);
}
if (globals.settings.Debug) { console.log('Play Queue Loaded From localStorage: ' + $rootScope.queue.length + ' song(s)'); }
}
}
} else {
if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser'); }
}
}
/* Launch on Startup */
$scope.loadSettings();
@ -454,7 +473,7 @@
if ($scope.loggedIn()) {
//$scope.ping();
if (globals.settings.SaveTrackPosition) {
player.loadTrackPosition();
loadTrackPosition();
player.startSaveTrackPosition();
}
}

View file

@ -0,0 +1,30 @@
describe("Main controller", function() {
'use strict';
describe("updateFavorite -", function() {
it("when starring a song, it notifies the user that the star was saved", function() {
});
it("when starring an album, it notifies the user that the star was saved", function() {
});
it("when starring an artist, it notifies the user that the star was saved", function() {
});
it("given that the Subsonic server returns an error, when starring something, it notifies the user with the error message", function() {
//TODO: move to higher level
});
it("given that the Subsonic server is unreachable, when starring something, it notifies the user with the HTTP error code", function() {
//TODO: move to higher level
});
});
describe("toggleSetting -", function() {
});
});

View file

@ -49,7 +49,7 @@
<div class="clear"></div>
</div><!-- end #content -->
<div id="SideBar">
<div id="SideBar" ng-controller="QueueCtrl">
<div class="headeractions">
<a class="buttonimg" title="Shuffle Queue" ng-click="queueShuffle()"><img src="images/fork_gd_11x12.png"></a>
<a class="buttonimg" id="action_Empty" title="Delete Queue" ng-click="queueEmpty()"><img src="images/trash_fill_gd_12x12.png"></a>

View file

@ -9,32 +9,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
var player2 = '#playdeck_2';
var scrobbled = false;
var timerid = 0;
this.defaultPlay = function (data, event) {
if (typeof $(player1).data("jPlayer") == 'undefined') {
$rootScope.nextTrack();
}
if (typeof $(player1).data("jPlayer") != 'undefined' && globals.settings.Jukebox) {
if ($(player1).data("jPlayer").status.paused) {
$rootScope.sendToJukebox('start');
} else {
$rootScope.sendToJukebox('stop');
}
}
};
this.nextTrack = function () {
var next = getNextSong();
if (next) {
$rootScope.playSong(false, next);
}
};
this.previousTrack = function () {
var next = getNextSong(true);
if (next) {
$rootScope.playSong(false, next);
} else {
$rootScope.restartSong();
}
};
function getNextSong (previous) {
var song;
if (globals.settings.Debug) { console.log('Getting Next Song > ' + 'Queue length: ' + $rootScope.queue.length); }
@ -63,6 +38,32 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
return false;
}
}
this.nextTrack = function () {
var next = getNextSong();
if (next) {
this.playSong(false, next);
}
};
this.previousTrack = function () {
var next = getNextSong(true);
if (next) {
this.playSong(false, next);
} else {
this.restartSong();
}
};
this.defaultPlay = function (data, event) {
if (typeof $(player1).data("jPlayer") == 'undefined') {
this.nextTrack();
}
if (typeof $(player1).data("jPlayer") != 'undefined' && globals.settings.Jukebox) {
if ($(player1).data("jPlayer").status.paused) {
$rootScope.sendToJukebox('start');
} else {
$rootScope.sendToJukebox('stop');
}
}
};
function internalScrobbleSong(submission) {
if ($rootScope.loggedIn && submission) {
var id = $rootScope.playingSong.id;
@ -85,7 +86,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
}
timerid = $window.setInterval(function () {
if (globals.settings.SaveTrackPosition) {
this.saveTrackPosition();
$rootScope.saveTrackPosition();
}
}, 30000);
}
@ -100,7 +101,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
audio.options.muted = true;
}
}
}
};
this.saveTrackPosition = function () {
//var audio = typeof $(player1).data("jPlayer") != 'undefined' ? true : false;
var audio = $(player1).data("jPlayer");
@ -146,27 +147,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
if (globals.settings.Debug) { console.log('Saving Queue: No Audio Loaded'); }
}
};
this.loadTrackPosition = function () {
if (utils.browserStorageCheck()) {
// Load Saved Song
var song = angular.fromJson(localStorage.getItem('CurrentSong'));
if (song) {
$rootScope.playSong(true, song);
// Load Saved Queue
var items = angular.fromJson(localStorage.getItem('CurrentQueue'));
if (items) {
//$rootScope.queue = [];
$rootScope.queue = items;
if ($rootScope.queue.length > 0) {
notifications.updateMessage($rootScope.queue.length + ' Saved Song(s)', true);
}
if (globals.settings.Debug) { console.log('Play Queue Loaded From localStorage: ' + $rootScope.queue.length + ' song(s)'); }
}
}
} else {
if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser'); }
}
};
this.deleteCurrentQueue = function (data) {
if (utils.browserStorageCheck()) {
localStorage.removeItem('CurrentQueue');
@ -181,6 +162,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
audio.play(0);
};
this.playSong = function (loadonly, data) {
console.log('playSong');
if (globals.settings.Debug) { console.log('Play: ' + JSON.stringify(data, null, 2)); }
angular.forEach($rootScope.queue, function(item, key) {
item.playing = false;
@ -210,26 +192,29 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
if (globals.settings.Jukebox) {
$rootScope.addToJukebox(id);
$rootScope.loadjPlayer(player1, url, suffix, true, position);
this.loadjPlayer(player1, url, suffix, true, position);
} else {
$rootScope.loadjPlayer(player1, url, suffix, loadonly, position);
this.loadjPlayer(player1, url, suffix, loadonly, position);
}
var spechtml = '';
/*FIXME: var data = $(player1).data().jPlayer;
for (var i = 0; i < data.solutions.length; i++) {
var solution = data.solutions[i];
if (data[solution].used) {
var playerData = $(player1).data();
if(playerData !== null) {
var jplayerData = playerData.jPlayer;
for (var i = 0; i < jplayerData.solutions.length; i++) {
var solution = jplayerData.solutions[i];
if (jplayerData[solution].used) {
spechtml += "<strong class=\"codesyntax\">" + solution + "</strong> is";
spechtml += " currently being used with<strong>";
angular.forEach(data[solution].support, function (format) {
if (data[solution].support[format]) {
angular.forEach(jplayerData[solution].support, function (format) {
if (jplayerData[solution].support[format]) {
spechtml += " <strong class=\"codesyntax\">" + format + "</strong>";
}
});
spechtml += "</strong> support";
}
}*/
}
}
$('#SMStats').html(spechtml);
scrobbled = false;
@ -250,7 +235,7 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
$rootScope.$apply();
}
};
$rootScope.loadjPlayer = function (el, url, suffix, loadonly, position) {
this.loadjPlayer = function (el, url, suffix, loadonly, position) {
// jPlayer Setup
var volume = 1;
if (utils.getValue('Volume')) {
@ -338,13 +323,13 @@ angular.module('jamstash.player.service', ['jamstash.utils', 'jamstash.settings'
if (!getNextSong()) { // Action if we are at the last song in queue
if (globals.settings.LoopQueue) { // Loop to first track in queue if enabled
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
this.playSong(false, next);
} else if (globals.settings.AutoPlay) { // Load more tracks if enabled
$rootScope.getRandomSongs('play', '', '');
notifications.updateMessage('Auto Play Activated...', true);
}
} else {
$rootScope.nextTrack();
this.nextTrack();
}
}
},
@ -413,12 +398,7 @@ 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;
// Hyz: have to maintain that in rootScope because I don't yet know how to call it any other way
// from setInterval
$rootScope.saveTrackPosition = this.saveTrackPosition;
}]);

View file

@ -13,7 +13,6 @@ describe("Player service", function() {
describe("Given that I have 3 songs in my playing queue", function() {
var stubPlaySong, stubRestartSong;
beforeEach(function() {
$rootScope.queue = [
{
@ -33,15 +32,15 @@ describe("Player service", function() {
album: 'redux'
}
];
stubPlaySong = spyOn($rootScope, "playSong").and.stub();
stubRestartSong = spyOn($rootScope, "restartSong").and.stub();
spyOn(player, "playSong").and.stub();
spyOn(player, "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();
expect(player.playSong).toHaveBeenCalled();
});
it("and the first song is playing, it plays the second song", function() {
@ -49,7 +48,7 @@ describe("Player service", function() {
player.nextTrack();
expect(stubPlaySong).toHaveBeenCalled();
expect(player.playSong).toHaveBeenCalled();
});
it("and the last song is playing, it does nothing", function() {
@ -57,7 +56,7 @@ describe("Player service", function() {
player.nextTrack();
expect(stubPlaySong).not.toHaveBeenCalled();
expect(player.playSong).not.toHaveBeenCalled();
});
});
@ -65,7 +64,7 @@ describe("Player service", function() {
it("and no song is playing, it plays the first song", function() {
player.previousTrack();
expect(stubRestartSong).toHaveBeenCalled();
expect(player.restartSong).toHaveBeenCalled();
});
it("and the first song is playing, it restarts the first song", function() {
@ -73,7 +72,7 @@ describe("Player service", function() {
player.previousTrack();
expect(stubRestartSong).toHaveBeenCalled();
expect(player.restartSong).toHaveBeenCalled();
});
it("and the last song is playing, it plays the seconde song", function() {
@ -81,7 +80,7 @@ describe("Player service", function() {
player.previousTrack();
expect(stubPlaySong).toHaveBeenCalled();
expect(player.playSong).toHaveBeenCalled();
});
});
});

View file

@ -1,17 +1,16 @@
describe("Player controller", function() {
'use strict';
var player, scope, deferred;
var player, scope;
beforeEach(function() {
module('jamstash.player.ctrl');
inject(function ($controller, $rootScope, _player_, $q) {
inject(function ($controller, $rootScope, _player_) {
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();

View file

@ -1,10 +1,14 @@
angular.module('JamStash')
angular.module('jamstash.queue.ctrl', [])
.controller('QueueCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'utils', 'globals',
function QueueCtrl($scope, $rootScope, $routeParams, $location, utils, globals) {
.controller('QueueCtrl', ['$scope', '$rootScope', '$routeParams', '$location', 'utils', 'globals', 'player',
function QueueCtrl($scope, $rootScope, $routeParams, $location, utils, globals, player) {
'use strict';
$scope.settings = globals.settings;
$scope.song = $rootScope.queue;
//angular.copy($rootScope.queue, $scope.song);
$scope.itemType = 'pl';
$scope.playSong = function (loadonly, song) {
player.playSong(loadonly, song);
};
}]);

30
app/queue/queue_test.js Normal file
View file

@ -0,0 +1,30 @@
describe("Queue controller", function() {
'use strict';
var player, scope;
beforeEach(function() {
module('jamstash.queue.ctrl');
inject(function ($controller, $rootScope, _player_) {
scope = $rootScope.$new();
player = _player_;
// Mock the functions of the services
spyOn(player, "playSong").and.stub();
$controller('PlayerCtrl', {
$scope: scope,
player: player
});
});
it("When I call playSong, it calls playSong in the player service", function() {
var fakeSong = {"id": 3174};
scope.playSong(true, fakeSong);
expect(player.playSong).toHaveBeenCalledWith(true, fakeSong);
});
});
});

View file

@ -5,10 +5,10 @@
* Also offers more fine-grained functionality that is not part of Subsonic's API.
*/
angular.module('jamstash.subsonic.service', ['jamstash.settings', 'jamstash.utils', 'jamstash.model',
'jamstash.notifications', 'angular-underscore/utils'])
'jamstash.notifications', 'jamstash.player.service', 'angular-underscore/utils'])
.factory('subsonic', ['$rootScope', '$http', '$q', 'globals', 'utils', 'map', 'notifications',
function ($rootScope, $http, $q, globals, utils, map, notifications) {
.factory('subsonic', ['$rootScope', '$http', '$q', 'globals', 'utils', 'map', 'notifications', 'player',
function ($rootScope, $http, $q, globals, utils, map, notifications, player) {
'use strict';
var index = { shortcuts: [], artists: [] };
@ -318,7 +318,7 @@ angular.module('jamstash.subsonic.service', ['jamstash.settings', 'jamstash.util
$rootScope.queue.push(map.mapSong(item));
});
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
} else {
if (typeof data["subsonic-response"].directory.id != 'undefined') {
@ -466,7 +466,7 @@ angular.module('jamstash.subsonic.service', ['jamstash.settings', 'jamstash.util
$rootScope.queue.push(map.mapSong(item));
});
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
} else {
content.album = [];
@ -545,7 +545,7 @@ angular.module('jamstash.subsonic.service', ['jamstash.settings', 'jamstash.util
$rootScope.queue.push(map.mapSong(item));
});
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
} else {
content.album = [];
@ -775,7 +775,7 @@ angular.module('jamstash.subsonic.service', ['jamstash.settings', 'jamstash.util
}
});
var next = $rootScope.queue[0];
$rootScope.playSong(false, next);
player.playSong(false, next);
notifications.updateMessage(items.length + ' Song(s) Added to Queue', true);
} else {
content.album = [];

View file

@ -1,13 +1,13 @@
/**
/**
* jamstash.subsonic.ctrl Module
*
* Access and use the Subsonic Server. The Controller is in charge of relaying the Service's messages to the user through the
* notifications.
*/
angular.module('jamstash.subsonic.ctrl', ['jamstash.subsonic.service'])
angular.module('jamstash.subsonic.ctrl', ['jamstash.subsonic.service', 'jamstash.player.service'])
.controller('SubsonicCtrl', ['$scope', '$rootScope', '$routeParams', 'utils', 'globals', 'map', 'subsonic', 'notifications',
function SubsonicCtrl($scope, $rootScope, $routeParams, utils, globals, map, subsonic, notifications) {
.controller('SubsonicCtrl', ['$scope', '$rootScope', '$routeParams', 'utils', 'globals', 'map', 'subsonic', 'notifications', 'player',
function SubsonicCtrl($scope, $rootScope, $routeParams, utils, globals, map, subsonic, notifications, player) {
'use strict';
$scope.settings = globals.settings;
@ -283,7 +283,7 @@ angular.module('jamstash.subsonic.ctrl', ['jamstash.subsonic.service'])
if(action === 'play') {
$rootScope.queue = [].concat(mappedSongs);
notifications.updateMessage(mappedSongs.length + ' Song(s) Added to Queue', true);
$rootScope.playSong(false, $rootScope.queue[0]);
player.playSong(false, $rootScope.queue[0]);
} else if (action === 'add') {
$rootScope.queue = $rootScope.queue.concat(mappedSongs);
notifications.updateMessage(mappedSongs.length + ' Song(s) Added to Queue', true);
@ -448,6 +448,9 @@ angular.module('jamstash.subsonic.ctrl', ['jamstash.subsonic.service'])
end = ui.item.index();
$scope.song.splice(end, 0, $scope.song.splice(start, 1)[0]);
};
$scope.playSong = function (loadonly, song) {
player.playSong(loadonly, song);
};
/* Launch on Startup */
$scope.getArtists();

View file

@ -2,18 +2,19 @@ describe("Subsonic controller", function() {
'use strict';
var scope, $rootScope, subsonic, notifications, deferred;
var scope, $rootScope, subsonic, notifications, deferred, player;
beforeEach(function() {
jasmine.addCustomEqualityTester(angular.equals);
module('jamstash.subsonic.ctrl');
inject(function ($controller, _$rootScope_, utils, globals, map, _subsonic_, _notifications_, $q) {
inject(function ($controller, _$rootScope_, utils, globals, map, _subsonic_, _notifications_, $q, _player_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
subsonic = _subsonic_;
notifications = _notifications_;
player = _player_;
// Mock the functions of the services and the rootscope
deferred = $q.defer();
@ -21,8 +22,8 @@ describe("Subsonic controller", function() {
spyOn(map, 'mapSong').and.callFake(function (song) {
return {id: song.id};
});
spyOn(notifications, 'updateMessage');
$rootScope.playSong = jasmine.createSpy('playSong');
spyOn(notifications, 'updateMessage').and.stub();
spyOn(player, 'playSong').and.stub();
$rootScope.queue = [];
$controller('SubsonicCtrl', {
@ -77,7 +78,7 @@ describe("Subsonic controller", function() {
$rootScope.$apply();
expect(subsonic.getRandomStarredSongs).toHaveBeenCalled();
expect($rootScope.playSong).toHaveBeenCalledWith(false, {id: "2548"});
expect(player.playSong).toHaveBeenCalledWith(false, {id: "2548"});
expect($rootScope.queue).toEqual([
{id: "2548"}, {id: "8986"}, {id: "2986"}
]);
@ -94,7 +95,7 @@ describe("Subsonic controller", function() {
$rootScope.$apply();
expect(subsonic.getRandomStarredSongs).toHaveBeenCalled();
expect($rootScope.playSong).not.toHaveBeenCalled();
expect(player.playSong).not.toHaveBeenCalled();
expect($rootScope.queue).toEqual([{id: "7666"}]);
expect(notifications.updateMessage).toHaveBeenCalledWith('No starred songs found on the Subsonic server.', true);
});
@ -156,5 +157,13 @@ describe("Subsonic controller", function() {
});
});
it("When I call playSong, it calls playSong in the player service", function() {
var fakeSong = {"id": 3572};
scope.playSong(false, fakeSong);
expect(player.playSong).toHaveBeenCalledWith(false, fakeSong);
});
//TODO: JMA: all starred
});