diff --git a/bower.json b/bower.json
index acaaef8..f171576 100755
--- a/bower.json
+++ b/bower.json
@@ -36,7 +36,6 @@
"notifyjs": "~0.3.2",
"oauth-js": "~0.4.3",
"remarkable-bootstrap-notify": "~3.1.3",
- "seiyria-bootstrap-slider": "~5.0.13",
"smalot-bootstrap-datetimepicker": "~2.3.4",
"js-data-angular": "~3.1.0"
},
@@ -46,7 +45,6 @@
},
"resolutions": {
"angular": "~1.4.3",
- "seiyria-bootstrap-slider": "~5.0.13",
"angular-bootstrap": "~0.13.3",
"js-data-angular": "~3.1.0"
}
diff --git a/client/app/components/footer/footer.controller.js b/client/app/components/footer/footer.controller.js
new file mode 100755
index 0000000..d3651d1
--- /dev/null
+++ b/client/app/components/footer/footer.controller.js
@@ -0,0 +1,73 @@
+'use strict';
+
+angular.module('dssWebApp')
+ .controller('FooterCtrl', function ($scope, AudioService, AUDIO_EVENTS, PLAYSTATES) {
+ var sliding = false;
+ $scope.mix = {};
+ $scope.sliding = false;
+ $scope.vm = {
+ playState: PLAYSTATES.stopped,
+ playPos: 0,
+ max: 100,
+ currentVolume: AudioService.getVolume(),
+ timeCurrent: "00:00",
+ timeDuration: "00:00"
+ };
+
+ $('.currentVolume').slider({
+ range: [0, 1],
+ step: 0.01,
+ start : 0.5,
+ handles: 1,
+ slide: function() {
+ var volume = Math.round($(this).val()* 100);
+ AudioService.setVolume(volume);
+ $('.volumeText').html('Volume: ' + (volume).toFixed(0) + '');
+ $scope.vm.currentVolume = volume;
+ $scope.$apply();
+ }
+ });
+ $scope.playPause = function () {
+ switch ($scope.vm.playState) {
+ case PLAYSTATES.playing:
+ AudioService.pause();
+ break;
+ case PLAYSTATES.paused:
+ AudioService.resume();
+ break;
+ default:
+ break;
+ }
+ event.stopPropagation();
+ };
+
+ $scope.$on(AUDIO_EVENTS.audioStart, function (event, mix, id, duration) {
+ $scope.mix = mix;
+ $scope.vm.playState = PLAYSTATES.playing;
+ $scope.vm.timeDuration = duration/1000;
+ });
+
+ $scope.$on(AUDIO_EVENTS.audioPause, function (event, id) {
+ $scope.vm.playState = PLAYSTATES.paused;
+ });
+
+ $scope.$on(AUDIO_EVENTS.audioResume, function (event, id) {
+ $scope.vm.playState = PLAYSTATES.playing;
+ });
+
+ $scope.$on(AUDIO_EVENTS.audioProgress, function (event, duration, position) {
+ if (position)
+ $scope.vm.timeCurrent = position / 1000;
+ if (duration)
+ $scope.vm.timeDuration = duration/1000;
+ $scope.vm.playPos = (position / duration) * 100;
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ });
+ $scope.doSeek = function($event){
+ var $bar = $(event.currentTarget);
+ var newPosition = ((event.pageX - ($bar.offset().left)) / ($bar.width()) * 100);
+ AudioService.setPosition(newPosition, false);
+ }
+ });
diff --git a/client/app/components/footer/footer.html b/client/app/components/footer/footer.html
new file mode 100755
index 0000000..3a56199
--- /dev/null
+++ b/client/app/components/footer/footer.html
@@ -0,0 +1,33 @@
+
diff --git a/client/app/components/navbar/navbar.html b/client/app/components/navbar/navbar.html
new file mode 100755
index 0000000..596d02a
--- /dev/null
+++ b/client/app/components/navbar/navbar.html
@@ -0,0 +1,169 @@
+
+
diff --git a/client/app/constants/audio.js b/client/app/constants/audio.js
index c986a1d..1b2f261 100755
--- a/client/app/constants/audio.js
+++ b/client/app/constants/audio.js
@@ -1,11 +1,6 @@
'use strict';
angular.module('dssWebApp')
- .constant('STORAGE', {
- authBackend: 'dss_at_backend',
- authToken: 'dss_at_token',
- authServerToken: 'dss_at_server_token',
- authLocalToken: 'dss_at_local_token',
- authServerSession: 'dss_at_server_session',
- authRefreshToken: 'refresh_token'
+ .constant('AUDIO_CONSTANTS', {
+ volume: 'dss_au_volume'
});
diff --git a/client/app/services/audio/audio.service.js b/client/app/services/audio/audio.service.js
new file mode 100644
index 0000000..4d6f7e8
--- /dev/null
+++ b/client/app/services/audio/audio.service.js
@@ -0,0 +1,129 @@
+'use strict';
+
+angular.module('dssWebApp')
+ .service('AudioService', function AudioService($rootScope, $interval, $q, logger, AUDIO_EVENTS, AUDIO_CONSTANTS) {
+ // AngularJS will instantiate a singleton by calling "new" on this function
+ var _currentMix;
+ var _currentSound;
+ var _soundId;
+
+ soundManager.setup({
+ html5PollingInterval: 50,
+ flashVersion: 9,
+ debugMode: false,
+ defaultOptions: {
+ volume: _getStoredVolume()
+ }
+ });
+
+ function _getStoredVolume() {
+ return Math.round(localStorage.getItem(AUDIO_CONSTANTS.volume) || 50);
+ }
+
+ function _setStoredVolume(volume) {
+ localStorage.setItem(AUDIO_CONSTANTS.volume, Math.round(volume));
+ }
+
+ function makeSound(url) {
+ var sound = soundManager.createSound({
+ url: url,
+ whileplaying: function () {
+ if (this.duration) {
+ $rootScope.$broadcast(AUDIO_EVENTS.audioProgress, this.durationEstimate, this.position, this.elapsed);
+ }
+ },
+ onplay: function () {
+ $rootScope.$broadcast(AUDIO_EVENTS.audioStart, _currentMix, _soundId, this.duration);
+ $rootScope.isPlaying = true;
+ },
+ onstop: function () {
+ $rootScope.$broadcast(AUDIO_EVENTS.audioStop, _soundId);
+ },
+ onfinish: function () {
+ $rootScope.$broadcast(AUDIO_EVENTS.audioFinish, _soundId);
+ }
+
+ });
+ return sound;
+ }
+
+ this.playLive = function (url, title) {
+ var _this = this;
+ return $q(function (resolve, reject) {
+ if ($rootScope.radioPlaying) {
+ _this.stop();
+ $rootScope.radioPlaying = false;
+ $rootScope.radioLoading = false;
+ resolve(true);
+ } else {
+ $rootScope.safeApply();
+ _this.stop();
+ _currentSound = soundManager.createSound({
+ url: url,
+ onload: function () {
+ resolve();
+ }
+ });
+ _currentSound.play({
+ url: url
+ });
+ _soundId = title;
+ }
+ });
+ };
+ this.play = function (mix, url) {
+ var _this = this;
+ return $q(function (resolve, reject) {
+ if (soundManager.canPlayURL(url)) {
+ _this.stop();
+ _currentMix = mix;
+ _currentSound = makeSound(url);
+ _currentSound.play({
+ url: mix.stream_url,
+ position: 0
+ });
+ _soundId = mix.slug;
+ resolve();
+ } else {
+ $rootScope.$broadcast(AUDIO_EVENTS.audioFailed, _soundId);
+ }
+ });
+ };
+ this.pause = function () {
+ if (_currentSound) {
+ _currentSound.togglePause();
+ $rootScope.$broadcast(AUDIO_EVENTS.audioPause, _soundId);
+ }
+ };
+ this.resume = function () {
+ if (_currentSound) {
+ _currentSound.togglePause();
+ $rootScope.$broadcast(AUDIO_EVENTS.audioResume, _soundId);
+ }
+ };
+ this.stop = function () {
+ if (_currentSound) {
+ _currentSound.stop();
+ $rootScope.$broadcast(AUDIO_EVENTS.audioStop, _soundId);
+ }
+ };
+ this.setPosition = function (position, absolute) {
+ if (absolute)
+ _currentSound.setPosition(position);
+ else
+ _currentSound.setPosition((_currentSound.durationEstimate / 100) * position);
+ _currentSound._iO.whileplaying.apply(_currentSound);
+ };
+ this.isPlaying = function (mix) {
+ return _soundId === mix.slug;
+ };
+ this.setVolume = function (volume) {
+ if (_currentSound) {
+ _currentSound.setVolume(volume);
+ }
+ _setStoredVolume(volume);
+ };
+ this.getVolume = function (volume) {
+ return _getStoredVolume();
+ };
+ });
diff --git a/client/assets/slider.js b/client/assets/slider.js
index 7afd1aa..0c2fb8c 100644
--- a/client/assets/slider.js
+++ b/client/assets/slider.js
@@ -1,3 +1,237 @@
-/**
- * Created by fergalm on 30/11/15.
- */
+(function ($) {
+ $.fn.slider = function (options, flag) {
+ var EVENT = window.navigator.msPointerEnabled ? 2 : "ontouchend" in document ? 3 : 1;
+ if (window.debug && console) {
+ console.log(EVENT)
+ }
+ function call(f, scope, args) {
+ if (typeof f === "function") {
+ f.call(scope, args)
+ }
+ }
+
+ var percentage = {
+ to: function (range, value) {
+ value = range[0] < 0 ? value + Math.abs(range[0]) : value - range[0];
+ return (value * 100) / this._length(range)
+ }, from: function (range, value) {
+ return (value * 100) / this._length(range)
+ }, is: function (range, value) {
+ return ((value * this._length(range)) / 100) + range[0]
+ }, _length: function (range) {
+ return (range[0] > range[1] ? range[0] - range[1] : range[1] - range[0])
+ }
+ };
+
+ function correct(proposal, slider, handle) {
+ var setup = slider.data("setup"), handles = setup.handles, settings = setup.settings, pos = setup.pos;
+ proposal = proposal < 0 ? 0 : proposal > 100 ? 100 : proposal;
+ if (settings.handles == 2) {
+ if (handle.is(":first-child")) {
+ var other = parseFloat(handles[1][0].style[pos]) - settings.margin;
+ proposal = proposal > other ? other : proposal
+ } else {
+ var other = parseFloat(handles[0][0].style[pos]) + settings.margin;
+ proposal = proposal < other ? other : proposal
+ }
+ }
+ if (settings.step) {
+ var per = percentage.from(settings.range, settings.step);
+ proposal = Math.round(proposal / per) * per
+ }
+ return proposal
+ }
+
+ function client(f) {
+ try {
+ return [(f.clientX || f.originalEvent.clientX || f.originalEvent.touches[0].clientX), (f.clientY || f.originalEvent.clientY || f.originalEvent.touches[0].clientY)]
+ } catch (e) {
+ return ["x", "y"]
+ }
+ }
+
+ function place(handle, pos) {
+ return parseFloat(handle[0].style[pos])
+ }
+
+ var defaults = {handles: 1, serialization: {to: ["", ""], resolution: 0.01}};
+ methods = {
+ create: function () {
+ return this.each(function () {
+ function setHandle(handle, to, slider) {
+ handle.css(pos, to + "%").data("input").val(percentage.is(settings.range, to).toFixed(res))
+ }
+
+ var settings = $.extend(defaults, options), handlehtml = "", slider = $(this).data("_isnS_", true), handles = [], pos, orientation, classes = "", num = function (e) {
+ return !isNaN(parseFloat(e)) && isFinite(e)
+ }, split = (settings.serialization.resolution = settings.serialization.resolution || 0.01).toString().split("."), res = split[0] == 1 ? 0 : split[1].length;
+ settings.start = num(settings.start) ? [settings.start, 0] : settings.start;
+ $.each(settings, function (a, b) {
+ if (num(b)) {
+ settings[a] = parseFloat(b)
+ } else {
+ if (typeof b == "object" && num(b[0])) {
+ b[0] = parseFloat(b[0]);
+ if (num(b[1])) {
+ b[1] = parseFloat(b[1])
+ }
+ }
+ }
+ var e = false;
+ b = typeof b == "undefined" ? "x" : b;
+ switch (a) {
+ case"range":
+ case"start":
+ e = b.length != 2 || !num(b[0]) || !num(b[1]);
+ break;
+ case"handles":
+ e = (b < 1 || b > 2 || !num(b));
+ break;
+ case"connect":
+ e = b != "lower" && b != "upper" && typeof b != "boolean";
+ break;
+ case"orientation":
+ e = (b != "vertical" && b != "horizontal");
+ break;
+ case"margin":
+ case"step":
+ e = typeof b != "undefined" && !num(b);
+ break;
+ case"serialization":
+ e = typeof b != "object" || !num(b.resolution) || (typeof b.to == "object" && b.to.length < settings.handles);
+ break;
+ case"slide":
+ e = typeof b != "function";
+ break
+ }
+ if (e && console) {
+ console.error("Bad input for " + a + " on slider:", slider)
+ }
+ });
+ settings.margin = settings.margin ? percentage.from(settings.range, settings.margin) : 0;
+ if (settings.serialization.to instanceof jQuery || typeof settings.serialization.to == "string" || settings.serialization.to === false) {
+ settings.serialization.to = [settings.serialization.to]
+ }
+ if (settings.orientation == "vertical") {
+ classes += "vertical";
+ pos = "top";
+ orientation = 1
+ } else {
+ classes += "horizontal";
+ pos = "left";
+ orientation = 0
+ }
+ classes += settings.connect ? settings.connect == "lower" ? " connect lower" : " connect" : "";
+ slider.addClass(classes);
+ for (var i = 0; i < settings.handles; i++) {
+ handles[i] = slider.append(handlehtml).children(":last");
+ var setTo = percentage.to(settings.range, settings.start[i]);
+ handles[i].css(pos, setTo + "%");
+ if (setTo == 100 && handles[i].is(":first-child")) {
+ handles[i].css("z-index", 2)
+ }
+ var bind = ".slider", onEvent = (EVENT === 1 ? "mousedown" : EVENT === 2 ? "MSPointerDown" : "touchstart") + bind + "X", moveEvent = (EVENT === 1 ? "mousemove" : EVENT === 2 ? "MSPointerMove" : "touchmove") + bind, offEvent = (EVENT === 1 ? "mouseup" : EVENT === 2 ? "MSPointerUp" : "touchend") + bind;
+ handles[i].find("div").on(onEvent, function (e) {
+ $("body").bind("selectstart" + bind, function () {
+ return false
+ });
+ if (!slider.hasClass("disabled")) {
+ $("body").addClass("TOUCH");
+ var handle = $(this).addClass("active").parent(), unbind = handle.add($(document)).add("body"), originalPosition = parseFloat(handle[0].style[pos]), originalClick = client(e), previousClick = originalClick, previousProposal = false;
+ $(document).on(moveEvent, function (f) {
+ f.preventDefault();
+ var currentClick = client(f);
+ if (currentClick[0] == "x") {
+ return
+ }
+ currentClick[0] -= originalClick[0];
+ currentClick[1] -= originalClick[1];
+ var movement = [previousClick[0] != currentClick[0], previousClick[1] != currentClick[1]], proposal = originalPosition + ((currentClick[orientation] * 100) / (orientation ? slider.height() : slider.width()));
+ proposal = correct(proposal, slider, handle);
+ if (movement[orientation] && proposal != previousProposal) {
+ handle.css(pos, proposal + "%").data("input").val(percentage.is(settings.range, proposal).toFixed(res));
+ call(settings.slide, slider.data("_n", true));
+ previousProposal = proposal;
+ handle.css("z-index", handles.length == 2 && proposal == 100 && handle.is(":first-child") ? 2 : 1)
+ }
+ previousClick = currentClick
+ }).on(offEvent, function () {
+ unbind.off(bind);
+ $("body").removeClass("TOUCH");
+ if (slider.find(".active").removeClass("active").end().data("_n")) {
+ slider.data("_n", false).change()
+ }
+ })
+ }
+ }).on("click", function (e) {
+ e.stopPropagation()
+ })
+ }
+ if (EVENT == 1) {
+ slider.on("click", function (f) {
+ if (!slider.hasClass("disabled")) {
+ var currentClick = client(f), proposal = ((currentClick[orientation] - slider.offset()[pos]) * 100) / (orientation ? slider.height() : slider.width()), handle = handles.length > 1 ? (currentClick[orientation] < (handles[0].offset()[pos] + handles[1].offset()[pos]) / 2 ? handles[0] : handles[1]) : handles[0];
+ setHandle(handle, correct(proposal, slider, handle), slider);
+ call(settings.slide, slider);
+ slider.change()
+ }
+ })
+ }
+ for (var i = 0; i < handles.length; i++) {
+ var val = percentage.is(settings.range, place(handles[i], pos)).toFixed(res);
+ if (typeof settings.serialization.to[i] == "string") {
+ handles[i].data("input", slider.append('').find("input:last").val(val).change(function (a) {
+ a.stopPropagation()
+ }))
+ } else {
+ if (settings.serialization.to[i] == false) {
+ handles[i].data("input", {
+ val: function (a) {
+ if (typeof a != "undefined") {
+ this.handle.data("noUiVal", a)
+ } else {
+ return this.handle.data("noUiVal")
+ }
+ }, handle: handles[i]
+ })
+ } else {
+ handles[i].data("input", settings.serialization.to[i].data("handleNR", i).val(val).change(function () {
+ var arr = [null, null];
+ arr[$(this).data("handleNR")] = $(this).val();
+ slider.val(arr)
+ }))
+ }
+ }
+ }
+ $(this).data("setup", {settings: settings, handles: handles, pos: pos, res: res})
+ })
+ }, val: function () {
+ if (typeof arguments[0] !== "undefined") {
+ var val = typeof arguments[0] == "number" ? [arguments[0]] : arguments[0];
+ return this.each(function () {
+ var setup = $(this).data("setup");
+ for (var i = 0; i < setup.handles.length; i++) {
+ if (val[i] != null) {
+ var proposal = correct(percentage.to(setup.settings.range, val[i]), $(this), setup.handles[i]);
+ setup.handles[i].css(setup.pos, proposal + "%").data("input").val(percentage.is(setup.settings.range, proposal).toFixed(setup.res))
+ }
+ }
+ })
+ } else {
+ var handles = $(this).data("setup").handles, re = [];
+ for (var i = 0; i < handles.length; i++) {
+ re.push(parseFloat(handles[i].data("input").val()))
+ }
+ return re.length == 1 ? re[0] : re
+ }
+ }, disabled: function () {
+ return flag ? $(this).addClass("disabled") : $(this).removeClass("disabled")
+ }
+ };
+ var $_val = jQuery.fn.val;
+ jQuery.fn.val = function () {
+ return this.data("_isnS_") ? methods.val.apply(this, arguments) : $_val.apply(this, arguments)
+ };
+ return options == "disabled" ? methods.disabled.apply(this) : methods.create.apply(this)
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/package.json b/package.json
index 21bc8e0..9c87cbc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dssweb",
- "version": "2.4.0",
+ "version": "3.0.1",
"main": "server/app.js",
"dependencies": {
"body-parser": "~1.5.0",