Adds angular-locker dependency.
- It makes it easier to use localStorage and sessionStorage "the angular way". It also does all the error handling so we don't need to. - Adds back the automatic saving of the current track's position and playing queue in localStorage. It's fully unit tested. - Adds back the notifications. Every time we change songs (if the setting is true), it displays a notification. Clicking on it goes to the next song, just like before. - Bumps up the versions to the actual value on the various json files.
This commit is contained in:
parent
2e97e25f25
commit
83869b7808
12 changed files with 346 additions and 205 deletions
13
app/app.js
13
app/app.js
|
@ -1,11 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/* Declare app level module */
|
/* Declare app level module */
|
||||||
angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize',
|
angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize',
|
||||||
'jamstash.subsonic.controller', 'jamstash.archive.controller', 'jamstash.player.controller', 'jamstash.queue.controller'])
|
'jamstash.subsonic.controller', 'jamstash.archive.controller', 'jamstash.player.controller', 'jamstash.queue.controller', 'angular-locker'])
|
||||||
|
|
||||||
.config(['$routeProvider',function($routeProvider) {
|
.config(['$routeProvider',function($routeProvider) {
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$routeProvider
|
$routeProvider
|
||||||
.when('/index', { redirectTo: '/library' })
|
.when('/index', { redirectTo: '/library' })
|
||||||
.when('/settings', { templateUrl: 'settings/settings.html', controller: 'SettingsController' })
|
.when('/settings', { templateUrl: 'settings/settings.html', controller: 'SettingsController' })
|
||||||
|
@ -21,8 +20,6 @@ angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize',
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.config(['$httpProvider',function($httpProvider) {
|
.config(['$httpProvider',function($httpProvider) {
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$httpProvider.interceptors.push(['$rootScope', '$location', '$q', 'globals', function ($rootScope, $location, $q, globals) {
|
$httpProvider.interceptors.push(['$rootScope', '$location', '$q', 'globals', function ($rootScope, $location, $q, globals) {
|
||||||
return {
|
return {
|
||||||
'request': function (request) {
|
'request': function (request) {
|
||||||
|
@ -51,4 +48,10 @@ angular.module('JamStash', ['ngCookies', 'ngRoute', 'ngSanitize',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.config(['lockerProvider', function (lockerProvider) {
|
||||||
|
lockerProvider.setDefaultDriver('local')
|
||||||
|
.setDefaultNamespace('jamstash')
|
||||||
|
.setEventsEnabled(false);
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
angular.module('JamStash')
|
angular.module('JamStash')
|
||||||
.controller('AppController', ['$scope', '$rootScope', '$document', '$window', '$location', '$cookieStore', '$http', 'utils', 'globals', 'model', 'notifications', 'player',
|
.controller('AppController', ['$scope', '$rootScope', '$document', '$window', '$location', '$cookieStore', '$http', 'utils', 'globals', 'model', 'notifications', 'player', 'locker',
|
||||||
function($scope, $rootScope, $document, $window, $location, $cookieStore, $http, utils, globals, model, notifications, player) {
|
function($scope, $rootScope, $document, $window, $location, $cookieStore, $http, utils, globals, model, notifications, player, locker) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
$rootScope.settings = globals.settings;
|
$rootScope.settings = globals.settings;
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.$watchCollection('queue', function(newItem, oldItem) {
|
$scope.$watchCollection('queue', function(newItem, oldItem) {
|
||||||
if (oldItem.length != newItem.length
|
if (oldItem.length != newItem.length
|
||||||
&& globals.settings.ShowQueue) {
|
&& globals.settings.ShowQueue) {
|
||||||
$rootScope.showQueue();
|
$rootScope.showQueue();
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
$rootScope.queue.splice(start, 1)[0]);
|
$rootScope.queue.splice(start, 1)[0]);
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
};
|
};
|
||||||
$(document).on( 'click', 'message', function() {
|
$(document).on( 'click', 'message', function() {
|
||||||
$(this).fadeOut(function () { $(this).remove(); });
|
$(this).fadeOut(function () { $(this).remove(); });
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
@ -444,30 +444,22 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.loadTrackPosition = function () {
|
$scope.loadTrackPosition = function () {
|
||||||
if (utils.browserStorageCheck()) {
|
// Load Saved Song
|
||||||
// Load Saved Song
|
var song = locker.get('CurrentSong');
|
||||||
var song = angular.fromJson(localStorage.getItem('CurrentSong'));
|
if (song) {
|
||||||
if (song) {
|
player.load(song);
|
||||||
player.load(song);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser'); }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.loadQueue = function () {
|
$scope.loadQueue = function () {
|
||||||
if(utils.browserStorageCheck()) {
|
// load Saved queue
|
||||||
// load Saved queue
|
var queue = locker.get('CurrentQueue');
|
||||||
var queue = angular.fromJson(localStorage.getItem('CurrentQueue'));
|
if (queue) {
|
||||||
if (queue) {
|
player.addSongs(queue);
|
||||||
player.queue = queue;
|
if (player.queue.length > 0) {
|
||||||
if (player.queue.length > 0) {
|
notifications.updateMessage(player.queue.length + ' Saved Song(s)', true);
|
||||||
notifications.updateMessage(player.queue.length + ' Saved Song(s)', true);
|
|
||||||
}
|
|
||||||
if (globals.settings.Debug) { console.log('Play Queue Loaded From localStorage: ' + player.queue.length + ' song(s)'); }
|
|
||||||
}
|
}
|
||||||
} else {
|
if (globals.settings.Debug) { console.log('Play Queue Loaded From localStorage: ' + player.queue.length + ' song(s)'); }
|
||||||
if (globals.settings.Debug) { console.log('HTML5::loadStorage not supported on your browser'); }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
describe("Main controller", function() {
|
describe("Main controller", function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var scope, $rootScope, utils, globals, model, notifications, player;
|
var scope, $rootScope, utils, globals, notifications, player, locker;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
module('JamStash');
|
module('JamStash');
|
||||||
|
|
||||||
inject(function ($controller, _$rootScope_, _$document_, _$window_, _$location_, _$cookieStore_, _utils_, _globals_, _model_, _notifications_, _player_) {
|
inject(function ($controller, _$rootScope_, _$document_, _$window_, _$location_, _$cookieStore_, _utils_, _globals_, _model_, _notifications_, _player_, _locker_) {
|
||||||
$rootScope = _$rootScope_;
|
$rootScope = _$rootScope_;
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
utils = _utils_;
|
utils = _utils_;
|
||||||
globals = _globals_;
|
globals = _globals_;
|
||||||
model = _model_;
|
|
||||||
notifications = _notifications_;
|
notifications = _notifications_;
|
||||||
player = _player_;
|
player = _player_;
|
||||||
|
locker = _locker_;
|
||||||
|
|
||||||
$controller('AppController', {
|
$controller('AppController', {
|
||||||
$scope: scope,
|
$scope: scope,
|
||||||
|
@ -23,9 +23,10 @@ describe("Main controller", function() {
|
||||||
$cookieStore: _$cookieStore_,
|
$cookieStore: _$cookieStore_,
|
||||||
utils: utils,
|
utils: utils,
|
||||||
globals: globals,
|
globals: globals,
|
||||||
model: model,
|
model: _model_,
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
player: player
|
player: player,
|
||||||
|
locker: locker
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -62,7 +63,7 @@ describe("Main controller", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
fakeStorage = {};
|
fakeStorage = {};
|
||||||
|
|
||||||
spyOn(localStorage, "getItem").and.callFake(function(key) {
|
spyOn(locker, "get").and.callFake(function(key) {
|
||||||
return fakeStorage[key];
|
return fakeStorage[key];
|
||||||
});
|
});
|
||||||
spyOn(utils, "browserStorageCheck").and.returnValue(true);
|
spyOn(utils, "browserStorageCheck").and.returnValue(true);
|
||||||
|
@ -86,13 +87,13 @@ describe("Main controller", function() {
|
||||||
|
|
||||||
scope.loadTrackPosition();
|
scope.loadTrackPosition();
|
||||||
|
|
||||||
expect(localStorage.getItem).toHaveBeenCalledWith('CurrentSong');
|
expect(locker.get).toHaveBeenCalledWith('CurrentSong');
|
||||||
expect(player.load).toHaveBeenCalledWith(song);
|
expect(player.load).toHaveBeenCalledWith(song);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Given that we didn't save anything in local Storage, it doesn't load anything", function() {
|
it("Given that we didn't save anything in local Storage, it doesn't load anything", function() {
|
||||||
scope.loadTrackPosition();
|
scope.loadTrackPosition();
|
||||||
expect(localStorage.getItem).toHaveBeenCalledWith('CurrentSong');
|
expect(locker.get).toHaveBeenCalledWith('CurrentSong');
|
||||||
expect(player.load).not.toHaveBeenCalled();
|
expect(player.load).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -100,7 +101,10 @@ describe("Main controller", function() {
|
||||||
describe("loadQueue -", function() {
|
describe("loadQueue -", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
spyOn(notifications, "updateMessage");
|
spyOn(notifications, "updateMessage");
|
||||||
player.queue = [];
|
spyOn(player, "addSongs").and.callFake(function (songs) {
|
||||||
|
// Update the queue length so that notifications work
|
||||||
|
player.queue.length += songs.length;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Given that we previously saved the playing queue in local Storage, it fills the player's queue with what we saved and notifies the user", function() {
|
it("Given that we previously saved the playing queue in local Storage, it fills the player's queue with what we saved and notifies the user", function() {
|
||||||
|
@ -115,16 +119,16 @@ describe("Main controller", function() {
|
||||||
|
|
||||||
scope.loadQueue();
|
scope.loadQueue();
|
||||||
|
|
||||||
expect(localStorage.getItem).toHaveBeenCalledWith('CurrentQueue');
|
expect(locker.get).toHaveBeenCalledWith('CurrentQueue');
|
||||||
expect(player.queue).toEqual(queue);
|
expect(player.addSongs).toHaveBeenCalledWith(queue);
|
||||||
expect(notifications.updateMessage).toHaveBeenCalledWith('3 Saved Song(s)', true);
|
expect(notifications.updateMessage).toHaveBeenCalledWith('3 Saved Song(s)', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Given that we didn't save anything in local Storage, it doesn't load anything", function() {
|
it("Given that we didn't save anything in local Storage, it doesn't load anything", function() {
|
||||||
scope.loadQueue();
|
scope.loadQueue();
|
||||||
|
|
||||||
expect(localStorage.getItem).toHaveBeenCalledWith('CurrentQueue');
|
expect(locker.get).toHaveBeenCalledWith('CurrentQueue');
|
||||||
expect(player.queue).toEqual([]);
|
expect(player.addSongs).not.toHaveBeenCalled();
|
||||||
expect(notifications.updateMessage).not.toHaveBeenCalled();
|
expect(notifications.updateMessage).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
*
|
*
|
||||||
* Provides access to the notification UI.
|
* Provides access to the notification UI.
|
||||||
*/
|
*/
|
||||||
angular.module('jamstash.notifications', [])
|
angular.module('jamstash.notifications', ['jamstash.player.service'])
|
||||||
|
|
||||||
.service('notifications', ['$rootScope', 'globals', function($rootScope, globals) {
|
.service('notifications', ['$rootScope', 'globals', 'player', function($rootScope, globals, player) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var msgIndex = 1;
|
var msgIndex = 1;
|
||||||
|
@ -37,10 +37,12 @@ angular.module('jamstash.notifications', [])
|
||||||
if (this.hasNotificationPermission()) {
|
if (this.hasNotificationPermission()) {
|
||||||
//closeAllNotifications()
|
//closeAllNotifications()
|
||||||
var settings = {};
|
var settings = {};
|
||||||
if (bind = '#NextTrack') {
|
if (bind === '#NextTrack') {
|
||||||
settings.notifyClick = function () {
|
settings.notifyClick = function () {
|
||||||
$rootScope.nextTrack();
|
player.nextTrack();
|
||||||
this.close();
|
this.close();
|
||||||
|
//TODO: Hyz: This should be in a directive, so we wouldn't have to use this.
|
||||||
|
$rootScope.$apply();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (type === 'text') {
|
if (type === 'text') {
|
||||||
|
|
262
app/index.html
262
app/index.html
|
@ -1,131 +1,131 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<html lang="en" ng-app="JamStash">
|
<html lang="en" ng-app="JamStash">
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
|
||||||
<meta name="description" content="HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming">
|
<meta name="description" content="HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming">
|
||||||
<meta name="keywords" content="Subsonic, Archive.org, Live Music Archive, HTML5 Audio, Music Streaming, Live Music">
|
<meta name="keywords" content="Subsonic, Archive.org, Live Music Archive, HTML5 Audio, Music Streaming, Live Music">
|
||||||
<meta property="og:image" content="http://jamstash.com/images/fbpreview.png"/>
|
<meta property="og:image" content="http://jamstash.com/images/fbpreview.png"/>
|
||||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||||
<title>Jamstash</title>
|
<title>Jamstash</title>
|
||||||
<link href="images/favicon_32x32.ico" rel="shortcut icon" />
|
<link href="images/favicon_32x32.ico" rel="shortcut icon" />
|
||||||
<link rel="icon" href="images/favicon_48x48.png" sizes="48x48"/>
|
<link rel="icon" href="images/favicon_48x48.png" sizes="48x48"/>
|
||||||
<link rel="icon" href="images/favicon_32x32.png" sizes="32x32"/>
|
<link rel="icon" href="images/favicon_32x32.png" sizes="32x32"/>
|
||||||
<!-- build:css(.) styles/vendor.min.css -->
|
<!-- build:css(.) styles/vendor.min.css -->
|
||||||
<!-- bower:css -->
|
<!-- bower:css -->
|
||||||
<link rel="stylesheet" href="bower_components/jplayer/dist/skin/pink.flag/jplayer.pink.flag.css" />
|
<link rel="stylesheet" href="bower_components/jplayer/dist/skin/pink.flag/jplayer.pink.flag.css" />
|
||||||
<link rel="stylesheet" href="bower_components/fancybox/source/jquery.fancybox.css" />
|
<link rel="stylesheet" href="bower_components/fancybox/source/jquery.fancybox.css" />
|
||||||
<!-- endbower -->
|
<!-- endbower -->
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
<!--<link href="vendor/jquery-split-pane.css" rel="stylesheet" />-->
|
<link href="styles/Style.css" rel="stylesheet" type="text/css" data-name="main" />
|
||||||
<link href="styles/Style.css" rel="stylesheet" type="text/css" data-name="main" />
|
<link href="styles/Mobile.css" rel="stylesheet" type="text/css" data-name="main" />
|
||||||
<link href="styles/Mobile.css" rel="stylesheet" type="text/css" data-name="main" />
|
<link href="" rel="stylesheet" type="text/css" data-name="theme" />
|
||||||
<link href="" rel="stylesheet" type="text/css" data-name="theme" />
|
</head>
|
||||||
</head>
|
<body ng-controller="AppController">
|
||||||
<body ng-controller="AppController">
|
<div id="container">
|
||||||
<div id="container">
|
<div id="header">
|
||||||
<div id="header">
|
<div id="messages">
|
||||||
<div id="messages">
|
<span ng-attr-id="{{ 'msg_' + $index }}" class="message" ng-repeat="item in Messages track by $index" ng-bind-html="item"></span>
|
||||||
<span ng-attr-id="{{ 'msg_' + $index }}" class="message" ng-repeat="item in Messages track by $index" ng-bind-html="item"></span>
|
</div>
|
||||||
</div>
|
<div id="loading"></div>
|
||||||
<div id="loading"></div>
|
<a id="jslogo" title="Jamstash" class="showQueue" href=""></a>
|
||||||
<a id="jslogo" title="Jamstash" class="showQueue" href=""></a>
|
<a id="sslogo" target="_blank" ng-show="settings.Server" ng-href="{{settings.Server}}" title="{{settings.Server}}"></a>
|
||||||
<a id="sslogo" target="_blank" ng-show="settings.Server" ng-href="{{settings.Server}}" title="{{settings.Server}}"></a>
|
<div id="globalactions">
|
||||||
<div id="globalactions">
|
<a href="" class="button" ng-click="toggleQueue()" title="Pin Queue"><img src="images/arrow_right_gl_8x8.png" /></a>
|
||||||
<a href="" class="button" ng-click="toggleQueue()" title="Pin Queue"><img src="images/arrow_right_gl_8x8.png" /></a>
|
</div>
|
||||||
</div>
|
<div id="nav">
|
||||||
<div id="nav">
|
<ul class="tabs">
|
||||||
<ul class="tabs">
|
<li><a href="#/library" class="first" id="action_Library" title="Library" ng-class="{'active': isActive('/library')}"><img src="images/headphones_gd_16x14.png" /></a></li>
|
||||||
<li><a href="#/library" class="first" id="action_Library" title="Library" ng-class="{'active': isActive('/library')}"><img src="images/headphones_gd_16x14.png" /></a></li>
|
<li><a href="#/archive" id="action_Archive" class="" title="Archive.org - Live Music Archive" ng-class="{'active': isActive('/archive')}"><img src="images/archive_gd_16x16.png" /></a></li>
|
||||||
<li><a href="#/archive" id="action_Archive" class="" title="Archive.org - Live Music Archive" ng-class="{'active': isActive('/archive')}"><img src="images/archive_gd_16x16.png" /></a></li>
|
<li><a href="#/settings" id="action_Settings" class="last" title="Settings" ng-class="{'active': isActive('/settings')}"><img src="images/cog_16x16.png" /></a></li>
|
||||||
<li><a href="#/settings" id="action_Settings" class="last" title="Settings" ng-class="{'active': isActive('/settings')}"><img src="images/cog_16x16.png" /></a></li>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div id="content">
|
||||||
<div id="content">
|
<!-- Main -->
|
||||||
<!-- Main -->
|
<div ng-view></div>
|
||||||
<div ng-view></div>
|
<!-- Audio Player -->
|
||||||
<!-- Audio Player -->
|
<div class="clear"></div>
|
||||||
<div class="clear"></div>
|
|
||||||
|
<div class="clear"></div>
|
||||||
<div class="clear"></div>
|
</div><!-- end #content -->
|
||||||
</div><!-- end #content -->
|
<div id="SideBar" ng-controller="QueueController">
|
||||||
<div id="SideBar" ng-controller="QueueController">
|
<div class="headeractions">
|
||||||
<div class="headeractions">
|
<a class="buttonimg" title="Shuffle Queue" ng-click="queueShuffle()"><img src="images/fork_gd_11x12.png"></a>
|
||||||
<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>
|
||||||
<a class="buttonimg" id="action_Empty" title="Delete Queue" ng-click="queueEmpty()"><img src="images/trash_fill_gd_12x12.png"></a>
|
<a class="buttonimg" id="action_DeleteSelected" title="Remove Selected From Queue" ng-click="queueRemoveSelected()"><img src="images/x_11x11.png"></a>
|
||||||
<a class="buttonimg" id="action_DeleteSelected" title="Remove Selected From Queue" ng-click="queueRemoveSelected()"><img src="images/x_11x11.png"></a>
|
</div>
|
||||||
</div>
|
<div class="header">Queue</div>
|
||||||
<div class="header">Queue</div>
|
<div id="SideQueue">
|
||||||
<div id="SideQueue">
|
<ul class="simplelist songlist noselect">
|
||||||
<ul class="simplelist songlist noselect">
|
<div ng-repeat="song in [player.queue] track by $index" class="songs" ng-include src="'common/songs_lite.html'" sortable></div>
|
||||||
<div ng-repeat="song in [player.queue] track by $index" class="songs" ng-include src="'common/songs_lite.html'" sortable></div>
|
</ul>
|
||||||
</ul>
|
<div class="colspacer"></div>
|
||||||
<div class="colspacer"></div>
|
</div>
|
||||||
</div>
|
<!--
|
||||||
<!--
|
<div id="NowPlaying">
|
||||||
<div id="NowPlaying">
|
<div class="header"><img src="images/rss_12x12.png" /> Now Playing</div>
|
||||||
<div class="header"><img src="images/rss_12x12.png" /> Now Playing</div>
|
<div id="NowPlayingList"><span class="user">Loading...</span></div>
|
||||||
<div id="NowPlayingList"><span class="user">Loading...</span></div>
|
</div>
|
||||||
</div>
|
<div id="Chat">
|
||||||
<div id="Chat">
|
<div class="header"><img src="images/chat_alt_stroke_12x12.png" /> Chat</div>
|
||||||
<div class="header"><img src="images/chat_alt_stroke_12x12.png" /> Chat</div>
|
<div id="ChatMsgs"></div>
|
||||||
<div id="ChatMsgs"></div>
|
</div>
|
||||||
</div>
|
<div class="submit"><img src="images/comment_stroke_gl_12x11.png" /><input type="text" id="ChatMsg" class="chat" title="Hit [Enter] to Post" /></div>
|
||||||
<div class="submit"><img src="images/comment_stroke_gl_12x11.png" /><input type="text" id="ChatMsg" class="chat" title="Hit [Enter] to Post" /></div>
|
-->
|
||||||
-->
|
</div>
|
||||||
</div>
|
<!-- Player -->
|
||||||
<!-- Player -->
|
<div ng-include src="'player/player.html'" ng-controller="PlayerController"></div>
|
||||||
<div ng-include src="'player/player.html'" ng-controller="PlayerController"></div>
|
</div> <!-- End container -->
|
||||||
</div> <!-- End container -->
|
<script>
|
||||||
<script>
|
(function (i, s, o, g, r, a, m) {
|
||||||
(function (i, s, o, g, r, a, m) {
|
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
(i[r].q = i[r].q || []).push(arguments)
|
||||||
(i[r].q = i[r].q || []).push(arguments)
|
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
||||||
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
|
||||||
|
ga('create', 'UA-40174100-1', 'jamstash.com');
|
||||||
ga('create', 'UA-40174100-1', 'jamstash.com');
|
ga('send', 'pageview');
|
||||||
ga('send', 'pageview');
|
</script>
|
||||||
</script>
|
<!-- build:js({.,app}) scripts/vendor.min.js -->
|
||||||
<!-- build:js({.,app}) scripts/vendor.min.js -->
|
<!-- bower:js -->
|
||||||
<!-- bower:js -->
|
<script src="bower_components/jquery/jquery.js"></script>
|
||||||
<script src="bower_components/jquery/jquery.js"></script>
|
<script src="bower_components/angular/angular.js"></script>
|
||||||
<script src="bower_components/angular/angular.js"></script>
|
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
|
||||||
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
|
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
|
||||||
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
|
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
||||||
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
<script src="bower_components/jquery-ui/ui/jquery-ui.js"></script>
|
||||||
<script src="bower_components/jquery-ui/ui/jquery-ui.js"></script>
|
<script src="bower_components/jplayer/dist/jplayer/jquery.jplayer.js"></script>
|
||||||
<script src="bower_components/jplayer/dist/jplayer/jquery.jplayer.js"></script>
|
<script src="bower_components/fancybox/source/jquery.fancybox.js"></script>
|
||||||
<script src="bower_components/fancybox/source/jquery.fancybox.js"></script>
|
<script src="bower_components/notify.js/notify.js"></script>
|
||||||
<script src="bower_components/notify.js/notify.js"></script>
|
<script src="bower_components/jquery.scrollTo/jquery.scrollTo.js"></script>
|
||||||
<script src="bower_components/jquery.scrollTo/jquery.scrollTo.js"></script>
|
<script src="bower_components/underscore/underscore.js"></script>
|
||||||
<script src="bower_components/underscore/underscore.js"></script>
|
<script src="bower_components/angular-underscore/angular-underscore.js"></script>
|
||||||
<script src="bower_components/angular-underscore/angular-underscore.js"></script>
|
<script src="bower_components/angular-locker/dist/angular-locker.min.js"></script>
|
||||||
<!-- endbower -->
|
<!-- endbower -->
|
||||||
<script src="vendor/jquery.base64.js"></script>
|
<script src="vendor/jquery.base64.js"></script>
|
||||||
<script src="vendor/jquery.dateFormat-1.0.js"></script>
|
<script src="vendor/jquery.dateFormat-1.0.js"></script>
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
<!-- our scripts -->
|
<!-- our scripts -->
|
||||||
<!-- build:js(app) scripts/scripts.min.js -->
|
<!-- build:js(app) scripts/scripts.min.js -->
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
<script src="settings/settings.js"></script>
|
<script src="settings/settings.js"></script>
|
||||||
<script src="settings/settings-service.js"></script>
|
<script src="settings/settings-service.js"></script>
|
||||||
<script src="common/model-service.js"></script>
|
<script src="common/model-service.js"></script>
|
||||||
<script src="common/utils-service.js"></script>
|
<script src="common/utils-service.js"></script>
|
||||||
<script src="common/notification-service.js"></script>
|
<script src="common/notification-service.js"></script>
|
||||||
<script src="common/main-controller.js"></script>
|
<script src="common/main-controller.js"></script>
|
||||||
<script src="subsonic/subsonic.js"></script>
|
<script src="subsonic/subsonic.js"></script>
|
||||||
<script src="subsonic/subsonic-service.js"></script>
|
<script src="subsonic/subsonic-service.js"></script>
|
||||||
<script src="archive/archive.js"></script>
|
<script src="archive/archive.js"></script>
|
||||||
<script src="archive/archive-service.js"></script>
|
<script src="archive/archive-service.js"></script>
|
||||||
<script src="player/player.js"></script>
|
<script src="player/player.js"></script>
|
||||||
<script src="player/player-directive.js"></script>
|
<script src="player/player-directive.js"></script>
|
||||||
<script src="player/player-service.js"></script>
|
<script src="player/player-service.js"></script>
|
||||||
<script src="queue/queue.js"></script>
|
<script src="queue/queue.js"></script>
|
||||||
<script src="common/filters.js"></script>
|
<script src="common/filters.js"></script>
|
||||||
<script src="common/directives.js"></script>
|
<script src="common/directives.js"></script>
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,15 +4,17 @@
|
||||||
* Encapsulates the jPlayer plugin. It watches the player service for the song to play, load or restart.
|
* Encapsulates the jPlayer plugin. It watches the player service for the song to play, load or restart.
|
||||||
* It also enables jPlayer to attach event handlers to our UI through css Selectors.
|
* It also enables jPlayer to attach event handlers to our UI through css Selectors.
|
||||||
*/
|
*/
|
||||||
angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstash.settings', 'jamstash.subsonic.service'])
|
angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstash.settings', 'jamstash.subsonic.service', 'jamstash.notifications', 'jamstash.utils', 'angular-locker'])
|
||||||
|
|
||||||
.directive('jplayer', ['player', 'globals', 'subsonic', function(playerService, globals, subsonic) {
|
.directive('jplayer', ['player', 'globals', 'subsonic', 'notifications', 'utils', 'locker', '$window',
|
||||||
|
function(playerService, globals, subsonic, notifications, utils, locker, $window) {
|
||||||
'use strict';
|
'use strict';
|
||||||
return {
|
return {
|
||||||
restrict: 'EA',
|
restrict: 'EA',
|
||||||
template: '<div></div>',
|
template: '<div></div>',
|
||||||
link: function(scope, element) {
|
link: function(scope, element) {
|
||||||
|
|
||||||
|
var timerid;
|
||||||
var $player = element.children('div');
|
var $player = element.children('div');
|
||||||
var audioSolution = 'html,flash';
|
var audioSolution = 'html,flash';
|
||||||
if (globals.settings.ForceFlash) {
|
if (globals.settings.ForceFlash) {
|
||||||
|
@ -41,12 +43,10 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
duration: '#duration'
|
duration: '#duration'
|
||||||
},
|
},
|
||||||
play: function() {
|
play: function() {
|
||||||
console.log('jplayer play');
|
|
||||||
scope.revealControls();
|
scope.revealControls();
|
||||||
scope.scrobbled = false;
|
scope.scrobbled = false;
|
||||||
},
|
},
|
||||||
ended: function() {
|
ended: function() {
|
||||||
console.log('jplayer ended');
|
|
||||||
// We do this here and not on the service because we cannot create
|
// We do this here and not on the service because we cannot create
|
||||||
// a circular dependency between the player and subsonic services
|
// a circular dependency between the player and subsonic services
|
||||||
if(playerService.isLastSongPlaying() && globals.settings.AutoPlay) {
|
if(playerService.isLastSongPlaying() && globals.settings.AutoPlay) {
|
||||||
|
@ -69,23 +69,21 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
updatePlayer();
|
|
||||||
|
|
||||||
scope.currentSong = {};
|
|
||||||
scope.scrobbled = false;
|
|
||||||
|
|
||||||
scope.$watch(function () {
|
scope.$watch(function () {
|
||||||
return playerService.getPlayingSong();
|
return playerService.getPlayingSong();
|
||||||
}, function (newVal) {
|
}, function (newSong) {
|
||||||
console.log('playingSong changed !');
|
scope.currentSong = newSong;
|
||||||
scope.currentSong = newVal;
|
$player.jPlayer('setMedia', {'mp3': newSong.url});
|
||||||
$player.jPlayer('setMedia', {'mp3': newVal.url});
|
|
||||||
if(playerService.loadSong === true) {
|
if(playerService.loadSong === true) {
|
||||||
// Do not play, only load
|
// Do not play, only load
|
||||||
playerService.loadSong = false;
|
playerService.loadSong = false;
|
||||||
scope.revealControls();
|
scope.revealControls();
|
||||||
|
$player.jPlayer('pause', newSong.position);
|
||||||
} else {
|
} else {
|
||||||
$player.jPlayer('play');
|
$player.jPlayer('play');
|
||||||
|
if(globals.settings.NotificationSong) {
|
||||||
|
notifications.showNotification(newSong.coverartthumb, utils.toHTML.un(newSong.name), utils.toHTML.un(newSong.artist + ' - ' + newSong.album), 'text', '#NextTrack');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,7 +91,6 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
return playerService.restartSong;
|
return playerService.restartSong;
|
||||||
}, function (newVal) {
|
}, function (newVal) {
|
||||||
if(newVal === true) {
|
if(newVal === true) {
|
||||||
console.log('restartSong changed !');
|
|
||||||
$player.jPlayer('play', 0);
|
$player.jPlayer('play', 0);
|
||||||
playerService.restartSong = false;
|
playerService.restartSong = false;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +101,51 @@ angular.module('jamstash.player.directive', ['jamstash.player.service', 'jamstas
|
||||||
$('#songdetails').css('visibility', 'visible');
|
$('#songdetails').css('visibility', 'visible');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope.saveTrackPosition = function () {
|
||||||
|
var audio = $player.data('jPlayer');
|
||||||
|
if (audio !== undefined && scope.currentSong !== undefined) {
|
||||||
|
var position = audio.status.currentTime;
|
||||||
|
if (position !== null) {
|
||||||
|
scope.currentSong.position = position;
|
||||||
|
locker.put('CurrentSong', scope.currentSong);
|
||||||
|
if (globals.settings.Debug) { console.log('Saving Current Position: ', scope.currentSong); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.saveQueue = function () {
|
||||||
|
locker.put('CurrentQueue', playerService.queue);
|
||||||
|
if (globals.settings.Debug) { console.log('Saving Queue: ' + playerService.queue.length + ' songs'); }
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.startSavePosition = function () {
|
||||||
|
if (globals.settings.SaveTrackPosition) {
|
||||||
|
if (timerid !== 0) {
|
||||||
|
$window.clearInterval(timerid);
|
||||||
|
}
|
||||||
|
timerid = $window.setInterval(function () {
|
||||||
|
var audio = $player.data('jPlayer');
|
||||||
|
if (globals.settings.SaveTrackPosition && audio.status.currentTime > 0 && audio.status.paused === false) {
|
||||||
|
$('#action_SaveProgress')
|
||||||
|
.fadeTo("slow", 0).delay(500)
|
||||||
|
.fadeTo("slow", 1).delay(500)
|
||||||
|
.fadeTo("slow", 0).delay(500)
|
||||||
|
.fadeTo("slow", 1);
|
||||||
|
scope.saveTrackPosition();
|
||||||
|
scope.saveQueue();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Startup
|
||||||
|
timerid = 0;
|
||||||
|
scope.currentSong = {};
|
||||||
|
scope.scrobbled = false;
|
||||||
|
|
||||||
|
updatePlayer();
|
||||||
|
scope.startSavePosition();
|
||||||
|
|
||||||
} //end link
|
} //end link
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
describe("jplayer directive", function() {
|
describe("jplayer directive", function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var element, scope, playerService, mockGlobals, subsonic, $player, playingSong;
|
var element, scope, $player, playingSong,
|
||||||
|
playerService, mockGlobals, subsonic, notifications, locker, $window;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
playingSong = {};
|
playingSong = {};
|
||||||
|
@ -20,15 +21,23 @@ describe("jplayer directive", function() {
|
||||||
$delegate.nextTrack = jasmine.createSpy('nextTrack');
|
$delegate.nextTrack = jasmine.createSpy('nextTrack');
|
||||||
$delegate.songEnded = jasmine.createSpy('songEnded');
|
$delegate.songEnded = jasmine.createSpy('songEnded');
|
||||||
$delegate.isLastSongPlaying = jasmine.createSpy('isLastSongPlaying');
|
$delegate.isLastSongPlaying = jasmine.createSpy('isLastSongPlaying');
|
||||||
|
return $delegate;
|
||||||
|
});
|
||||||
|
//TODO: Hyz: We shouldn't have to know the utils service just for that. Remove these calls and deal with this in the Notifications service.
|
||||||
|
// Mock the utils service
|
||||||
|
$provide.decorator('utils', function ($delegate) {
|
||||||
|
$delegate.toHTML.un = jasmine.createSpy('un');
|
||||||
return $delegate;
|
return $delegate;
|
||||||
});
|
});
|
||||||
$provide.value('globals', mockGlobals);
|
$provide.value('globals', mockGlobals);
|
||||||
});
|
});
|
||||||
|
|
||||||
inject(function($rootScope, $compile, _player_, _subsonic_) {
|
inject(function($rootScope, $compile, _player_, _subsonic_, _notifications_, _locker_, _$window_) {
|
||||||
playerService = _player_;
|
playerService = _player_;
|
||||||
subsonic = _subsonic_;
|
subsonic = _subsonic_;
|
||||||
|
notifications = _notifications_;
|
||||||
|
locker = _locker_;
|
||||||
|
$window = _$window_;
|
||||||
// Compile the directive
|
// Compile the directive
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
element = '<div id="playdeck_1" jplayer></div>';
|
element = '<div id="playdeck_1" jplayer></div>';
|
||||||
|
@ -52,23 +61,35 @@ describe("jplayer directive", function() {
|
||||||
expect(scope.currentSong).toEqual(playingSong);
|
expect(scope.currentSong).toEqual(playingSong);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("if the player service's loadSong flag is true, it does not play the song and it displays the player controls", function() {
|
it("if the player service's loadSong flag is true, it does not play the song, it displays the player controls and sets the player to the song's supplied position", function() {
|
||||||
spyOn(scope, "revealControls");
|
spyOn(scope, "revealControls");
|
||||||
|
playingSong.position = 42.2784;
|
||||||
|
|
||||||
playerService.loadSong = true;
|
playerService.loadSong = true;
|
||||||
scope.$apply();
|
scope.$apply();
|
||||||
|
|
||||||
expect($player.jPlayer).not.toHaveBeenCalledWith('play');
|
expect($player.jPlayer).not.toHaveBeenCalledWith('play');
|
||||||
|
expect($player.jPlayer).toHaveBeenCalledWith('pause', playingSong.position);
|
||||||
expect(playerService.loadSong).toBeFalsy();
|
expect(playerService.loadSong).toBeFalsy();
|
||||||
expect(scope.revealControls).toHaveBeenCalled();
|
expect(scope.revealControls).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("otherwise, it plays it", function() {
|
describe("if the player service's loadSong flag is false,", function() {
|
||||||
playerService.loadSong = false;
|
it("it plays the song", function() {
|
||||||
scope.$apply();
|
playerService.loadSong = false;
|
||||||
|
scope.$apply();
|
||||||
|
|
||||||
expect($player.jPlayer).toHaveBeenCalledWith('play');
|
expect($player.jPlayer).toHaveBeenCalledWith('play');
|
||||||
expect(playerService.loadSong).toBeFalsy();
|
expect(playerService.loadSong).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("if the global setting NotificationSong is true, it displays a notification", function() {
|
||||||
|
spyOn(notifications, "showNotification");
|
||||||
|
mockGlobals.settings.NotificationSong = true;
|
||||||
|
scope.$apply();
|
||||||
|
|
||||||
|
expect(notifications.showNotification).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -168,4 +189,79 @@ describe("jplayer directive", function() {
|
||||||
expect(scope.scrobbled).toBeTruthy();
|
expect(scope.scrobbled).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("save to localStorage -", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
spyOn(locker, "put");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it saves the current song and its position to localStorage", function() {
|
||||||
|
var position = 48.0773;
|
||||||
|
$player.data('jPlayer').status.currentTime = position;
|
||||||
|
scope.currentSong = {
|
||||||
|
id: 419
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.saveTrackPosition();
|
||||||
|
|
||||||
|
expect(scope.currentSong.position).toBe(position);
|
||||||
|
expect(locker.put).toHaveBeenCalledWith('CurrentSong', scope.currentSong);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it saves the player queue to localStorage", function() {
|
||||||
|
var queue = [
|
||||||
|
{id: 2313},
|
||||||
|
{id: 4268},
|
||||||
|
{id: 5470}
|
||||||
|
];
|
||||||
|
playerService.queue = queue;
|
||||||
|
|
||||||
|
scope.saveQueue();
|
||||||
|
|
||||||
|
expect(locker.put).toHaveBeenCalledWith('CurrentQueue', queue);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Given that the global setting SaveTrackPosition is true,", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
jasmine.clock().install();
|
||||||
|
mockGlobals.settings.SaveTrackPosition = true;
|
||||||
|
spyOn(scope, "saveTrackPosition");
|
||||||
|
spyOn(scope, "saveQueue");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
jasmine.clock().uninstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("every 30 seconds, it saves the current song and queue", function() {
|
||||||
|
$player.data('jPlayer').status.currentTime = 35.3877;
|
||||||
|
$player.data('jPlayer').status.paused = false;
|
||||||
|
|
||||||
|
scope.startSavePosition();
|
||||||
|
jasmine.clock().tick(30001);
|
||||||
|
|
||||||
|
expect(scope.saveTrackPosition).toHaveBeenCalled();
|
||||||
|
expect(scope.saveQueue).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("if the song is not playing, it does not save anything", function() {
|
||||||
|
$player.data('jPlayer').status.currentTime = 0.0;
|
||||||
|
$player.data('jPlayer').status.paused = true;
|
||||||
|
|
||||||
|
scope.startSavePosition();
|
||||||
|
jasmine.clock().tick(30001);
|
||||||
|
|
||||||
|
expect(scope.saveTrackPosition).not.toHaveBeenCalled();
|
||||||
|
expect(scope.saveQueue).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("if there was already a watcher, it clears it before watching", function() {
|
||||||
|
spyOn($window, "clearInterval");
|
||||||
|
|
||||||
|
scope.startSavePosition();
|
||||||
|
scope.startSavePosition();
|
||||||
|
expect($window.clearInterval).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($scope.settings.SaveTrackPosition) {
|
if ($scope.settings.SaveTrackPosition) {
|
||||||
player.saveTrackPosition();
|
//TODO: Hyz: player.saveTrackPosition();
|
||||||
} else {
|
} else {
|
||||||
player.deleteCurrentQueue();
|
player.deleteCurrentQueue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "jamstash",
|
"name": "jamstash",
|
||||||
"version": "4.3",
|
"version": "4.3.1",
|
||||||
"description": "HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming",
|
"description": "HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming",
|
||||||
"authors": [
|
"authors": [
|
||||||
"tsquillario (https://github.com/tsquillario)",
|
"tsquillario (https://github.com/tsquillario)",
|
||||||
|
@ -38,7 +38,8 @@
|
||||||
"notify.js": "<=1.2.2",
|
"notify.js": "<=1.2.2",
|
||||||
"jquery.scrollTo": "~1.4.5",
|
"jquery.scrollTo": "~1.4.5",
|
||||||
"underscore": "~1.7.0",
|
"underscore": "~1.7.0",
|
||||||
"angular-underscore": "~0.5.0"
|
"angular-underscore": "~0.5.0",
|
||||||
|
"angular-locker": "~1.0.2"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"fancybox": {
|
"fancybox": {
|
||||||
|
|
|
@ -32,6 +32,7 @@ module.exports = function(config) {
|
||||||
'bower_components/jquery.scrollTo/jquery.scrollTo.js',
|
'bower_components/jquery.scrollTo/jquery.scrollTo.js',
|
||||||
'bower_components/underscore/underscore.js',
|
'bower_components/underscore/underscore.js',
|
||||||
'bower_components/angular-underscore/angular-underscore.js',
|
'bower_components/angular-underscore/angular-underscore.js',
|
||||||
|
'bower_components/angular-locker/dist/angular-locker.min.js',
|
||||||
'bower_components/angular-mocks/angular-mocks.js',
|
'bower_components/angular-mocks/angular-mocks.js',
|
||||||
'bower_components/jasmine-promise-matchers/dist/jasmine-promise-matchers.js',
|
'bower_components/jasmine-promise-matchers/dist/jasmine-promise-matchers.js',
|
||||||
'bower_components/jasmine-fixture/dist/jasmine-fixture.js',
|
'bower_components/jasmine-fixture/dist/jasmine-fixture.js',
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Jamstash",
|
"name": "Jamstash",
|
||||||
"description": "HTML5 Player for Subsonic & Archive.org",
|
"description": "HTML5 Player for Subsonic & Archive.org",
|
||||||
"version": "4.2.3",
|
"version": "4.3.1",
|
||||||
"app": {
|
"app": {
|
||||||
"launch": {
|
"launch": {
|
||||||
"web_url": "http://jamstash.com"
|
"web_url": "http://jamstash.com"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "jamstash",
|
"name": "jamstash",
|
||||||
"version": "4.3",
|
"version": "4.3.1",
|
||||||
"description": "HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming",
|
"description": "HTML5 Audio Streamer for Subsonic, Archive.org browsing and streaming",
|
||||||
"author": "Trevor Squillario (https://github.com/tsquillario)",
|
"author": "Trevor Squillario (https://github.com/tsquillario)",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue