From: Andrew Karpow Date: Thu, 16 Jan 2014 17:32:20 +0000 (+0100) Subject: fix crash when seeking without song, new slider X-Git-Url: http://gitweb.hugovil.com/?a=commitdiff_plain;h=cd865b2cef904466008cdd315c0fbdb63b86fffa;p=ympd.git fix crash when seeking without song, new slider --- diff --git a/htdocs/css/mpd.css b/htdocs/css/mpd.css index b1a3a0a..ff4d073 100644 --- a/htdocs/css/mpd.css +++ b/htdocs/css/mpd.css @@ -7,18 +7,18 @@ body { padding: 40px 15px; } -.slider.slider-horizontal { - height: 15px; +#volumeslider { + width: 150px; } -.slider.slider-horizontal .slider-track { - height: 10px; - margin-top: -6px; +#volumeslider .progress { + margin-bottom: 0; } -.progress { - margin-top: 0px; - margin-bottom: 0px; +#volume-icon { + float: left; + margin-right: 10px; + margin-top: 2px; } #counter { @@ -29,5 +29,44 @@ body { } .btn-group-hover { - opacity: 0; + opacity: 20%; } + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + color: #428bca; + background-color: #fdfdfd; + border-color: #adadad; +} + + + +#salamisandwich td:nth-child(3), th:nth-child(3) { + text-align: right; +} + +tbody { + cursor: pointer; +} + +.notifications { + position: fixed; + z-index: 9999; +} + +/* Positioning */ +.notifications.top-right { + right: 10px; + top: 60px; +} + +/* Notification Element */ +.notifications > div { + position: relative; + z-index: 9999; + margin: 5px 0px; +} \ No newline at end of file diff --git a/htdocs/css/slider.css b/htdocs/css/slider.css index b527aa8..a7f8370 100644 --- a/htdocs/css/slider.css +++ b/htdocs/css/slider.css @@ -12,7 +12,7 @@ position: relative; } .slider.slider-horizontal { - width: 210px; + width: 100%; height: 20px; } .slider.slider-horizontal .slider-track { diff --git a/htdocs/index.html b/htdocs/index.html index 6ef24fa..b8e338e 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -13,7 +13,6 @@ - @@ -40,7 +39,7 @@ @@ -71,7 +72,7 @@
-
+
@@ -87,10 +88,9 @@

  

-
- -
+
+ +
-
+
- - +
+ + +
+
@@ -150,9 +152,13 @@
@@ -179,7 +185,8 @@ - + + diff --git a/htdocs/js/bootstrap-notify.js b/htdocs/js/bootstrap-notify.js new file mode 100644 index 0000000..b4ae197 --- /dev/null +++ b/htdocs/js/bootstrap-notify.js @@ -0,0 +1,585 @@ +/** Notify.js - v0.3.1 - 2013/07/05 + * http://notifyjs.com/ + * Copyright (c) 2013 Jaime Pillora - MIT + */ +(function(window,document,$,undefined) { +'use strict'; + +var Notification, addStyle, blankFieldName, coreStyle, createElem, defaults, encode, find, findFields, getAnchorElement, getStyle, globalAnchors, hAligns, incr, inherit, insertCSS, mainPositions, opposites, parsePosition, pluginClassName, pluginName, pluginOptions, positions, realign, stylePrefixes, styles, vAligns, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +pluginName = 'notify'; + +pluginClassName = pluginName + 'js'; + +blankFieldName = pluginName + "!blank"; + +positions = { + t: 'top', + m: 'middle', + b: 'bottom', + l: 'left', + c: 'center', + r: 'right' +}; + +hAligns = ['l', 'c', 'r']; + +vAligns = ['t', 'm', 'b']; + +mainPositions = ['t', 'b', 'l', 'r']; + +opposites = { + t: 'b', + m: null, + b: 't', + l: 'r', + c: null, + r: 'l' +}; + +parsePosition = function(str) { + var pos; + pos = []; + $.each(str.split(/\W+/), function(i, word) { + var w; + w = word.toLowerCase().charAt(0); + if (positions[w]) { + return pos.push(w); + } + }); + return pos; +}; + +styles = {}; + +coreStyle = { + name: 'core', + html: "
\n
\n
\n
", + css: "." + pluginClassName + "-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n." + pluginClassName + "-corner ." + pluginClassName + "-wrapper,\n." + pluginClassName + "-corner ." + pluginClassName + "-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n." + pluginClassName + "-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n." + pluginClassName + "-container {\n display: none;\n z-index: 1;\n position: absolute;\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n." + pluginClassName + "-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}" +}; + +stylePrefixes = { + "border-radius": ["-webkit-", "-moz-"] +}; + +getStyle = function(name) { + return styles[name]; +}; + +addStyle = function(name, def) { + var cssText, elem, fields, _ref; + if (!name) { + throw "Missing Style name"; + } + if (!def) { + throw "Missing Style definition"; + } + if (!def.html) { + throw "Missing Style HTML"; + } + if ((_ref = styles[name]) != null ? _ref.cssElem : void 0) { + if (window.console) { + console.warn("" + pluginName + ": overwriting style '" + name + "'"); + } + styles[name].cssElem.remove(); + } + def.name = name; + styles[name] = def; + cssText = ""; + if (def.classes) { + $.each(def.classes, function(className, props) { + cssText += "." + pluginClassName + "-" + def.name + "-" + className + " {\n"; + $.each(props, function(name, val) { + if (stylePrefixes[name]) { + $.each(stylePrefixes[name], function(i, prefix) { + return cssText += " " + prefix + name + ": " + val + ";\n"; + }); + } + return cssText += " " + name + ": " + val + ";\n"; + }); + return cssText += "}\n"; + }); + } + if (def.css) { + cssText += "/* styles for " + def.name + " */\n" + def.css; + } + if (cssText) { + def.cssElem = insertCSS(cssText); + def.cssElem.attr('id', "notify-" + def.name); + } + fields = {}; + elem = $(def.html); + findFields('html', elem, fields); + findFields('text', elem, fields); + return def.fields = fields; +}; + +insertCSS = function(cssText) { + var elem; + elem = createElem("style"); + elem.attr('type', 'text/css'); + $("head").append(elem); + try { + elem.html(cssText); + } catch (e) { + elem[0].styleSheet.cssText = cssText; + } + return elem; +}; + +findFields = function(type, elem, fields) { + var attr; + if (type !== 'html') { + type = 'text'; + } + attr = "data-notify-" + type; + return find(elem, "[" + attr + "]").each(function() { + var name; + name = $(this).attr(attr); + if (!name) { + name = blankFieldName; + } + return fields[name] = type; + }); +}; + +find = function(elem, selector) { + if (elem.is(selector)) { + return elem; + } else { + return elem.find(selector); + } +}; + +pluginOptions = { + clickToHide: true, + autoHide: true, + autoHideDelay: 5000, + arrowShow: true, + arrowSize: 5, + breakNewLines: true, + elementPosition: 'bottom', + globalPosition: 'top right', + style: 'bootstrap', + className: 'error', + showAnimation: 'slideDown', + showDuration: 400, + hideAnimation: 'slideUp', + hideDuration: 200, + gap: 5 +}; + +inherit = function(a, b) { + var F; + F = function() {}; + F.prototype = a; + return $.extend(true, new F(), b); +}; + +defaults = function(opts) { + return $.extend(pluginOptions, opts); +}; + +createElem = function(tag) { + return $("<" + tag + ">"); +}; + +globalAnchors = {}; + +getAnchorElement = function(element) { + var radios; + if (element.is('[type=radio]')) { + radios = element.parents('form:first').find('[type=radio]').filter(function(i, e) { + return $(e).attr('name') === element.attr('name'); + }); + element = radios.first(); + } + return element; +}; + +incr = function(obj, pos, val) { + var opp, temp; + if (typeof val === 'string') { + val = parseInt(val, 10); + } else if (typeof val !== 'number') { + return; + } + if (isNaN(val)) { + return; + } + opp = positions[opposites[pos.charAt(0)]]; + temp = pos; + if (obj[opp] !== undefined) { + pos = positions[opp.charAt(0)]; + val = -val; + } + if (obj[pos] === undefined) { + obj[pos] = val; + } else { + obj[pos] += val; + } + return null; +}; + +realign = function(alignment, inner, outer) { + if (alignment === 'l' || alignment === 't') { + return 0; + } else if (alignment === 'c' || alignment === 'm') { + return outer / 2 - inner / 2; + } else if (alignment === 'r' || alignment === 'b') { + return outer - inner; + } + throw "Invalid alignment"; +}; + +encode = function(text) { + encode.e = encode.e || createElem("div"); + return encode.e.text(text).html(); +}; + +Notification = (function() { + + function Notification(elem, data, options) { + if (typeof options === 'string') { + options = { + className: options + }; + } + this.options = inherit(pluginOptions, $.isPlainObject(options) ? options : {}); + this.loadHTML(); + this.wrapper = $(coreStyle.html); + this.wrapper.data(pluginClassName, this); + this.arrow = this.wrapper.find("." + pluginClassName + "-arrow"); + this.container = this.wrapper.find("." + pluginClassName + "-container"); + this.container.append(this.userContainer); + if (elem && elem.length) { + this.elementType = elem.attr('type'); + this.originalElement = elem; + this.elem = getAnchorElement(elem); + this.elem.data(pluginClassName, this); + this.elem.before(this.wrapper); + } + this.container.hide(); + this.run(data); + } + + Notification.prototype.loadHTML = function() { + var style; + style = this.getStyle(); + this.userContainer = $(style.html); + return this.userFields = style.fields; + }; + + Notification.prototype.show = function(show, userCallback) { + var args, callback, elems, fn, hidden, + _this = this; + callback = function() { + if (!show && !_this.elem) { + _this.destroy(); + } + if (userCallback) { + return userCallback(); + } + }; + hidden = this.container.parent().parents(':hidden').length > 0; + elems = this.container.add(this.arrow); + args = []; + if (hidden && show) { + fn = 'show'; + } else if (hidden && !show) { + fn = 'hide'; + } else if (!hidden && show) { + fn = this.options.showAnimation; + args.push(this.options.showDuration); + } else if (!hidden && !show) { + fn = this.options.hideAnimation; + args.push(this.options.hideDuration); + } else { + return callback(); + } + args.push(callback); + return elems[fn].apply(elems, args); + }; + + Notification.prototype.setGlobalPosition = function() { + var align, anchor, css, key, main, pAlign, pMain, position; + position = this.getPosition(); + pMain = position[0], pAlign = position[1]; + main = positions[pMain]; + align = positions[pAlign]; + key = pMain + "|" + pAlign; + anchor = globalAnchors[key]; + if (!anchor) { + anchor = globalAnchors[key] = createElem("div"); + css = {}; + css[main] = 0; + if (align === 'middle') { + css.top = '45%'; + } else if (align === 'center') { + css.left = '45%'; + } else { + css[align] = 0; + } + anchor.css(css).addClass("" + pluginClassName + "-corner"); + $("body").append(anchor); + } + return anchor.prepend(this.wrapper); + }; + + Notification.prototype.setElementPosition = function() { + var arrowColor, arrowCss, arrowSize, color, contH, contW, css, elemH, elemIH, elemIW, elemPos, elemW, gap, mainFull, margin, opp, oppFull, pAlign, pArrow, pMain, pos, posFull, position, wrapPos, _i, _j, _len, _len1, _ref; + position = this.getPosition(); + pMain = position[0], pAlign = position[1], pArrow = position[2]; + elemPos = this.elem.position(); + elemH = this.elem.outerHeight(); + elemW = this.elem.outerWidth(); + elemIH = this.elem.innerHeight(); + elemIW = this.elem.innerWidth(); + wrapPos = this.wrapper.position(); + contH = this.container.height(); + contW = this.container.width(); + mainFull = positions[pMain]; + opp = opposites[pMain]; + oppFull = positions[opp]; + css = {}; + css[oppFull] = pMain === 'b' ? elemH : pMain === 'r' ? elemW : 0; + incr(css, 'top', elemPos.top - wrapPos.top); + incr(css, 'left', elemPos.left - wrapPos.left); + _ref = ['top', 'left']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pos = _ref[_i]; + margin = parseInt(this.elem.css("margin-" + pos), 10); + if (margin) { + incr(css, pos, margin); + } + } + gap = Math.max(0, this.options.gap - (this.options.arrowShow ? arrowSize : 0)); + incr(css, oppFull, gap); + if (!this.options.arrowShow) { + this.arrow.hide(); + } else { + arrowSize = this.options.arrowSize; + arrowCss = $.extend({}, css); + arrowColor = this.userContainer.css("border-color") || this.userContainer.css("background-color") || 'white'; + for (_j = 0, _len1 = mainPositions.length; _j < _len1; _j++) { + pos = mainPositions[_j]; + posFull = positions[pos]; + if (pos === opp) { + continue; + } + color = posFull === mainFull ? arrowColor : 'transparent'; + arrowCss["border-" + posFull] = "" + arrowSize + "px solid " + color; + } + incr(css, positions[opp], arrowSize); + if (__indexOf.call(mainPositions, pAlign) >= 0) { + incr(arrowCss, positions[pAlign], arrowSize * 2); + } + } + if (__indexOf.call(vAligns, pMain) >= 0) { + incr(css, 'left', realign(pAlign, contW, elemW)); + if (arrowCss) { + incr(arrowCss, 'left', realign(pAlign, arrowSize, elemIW)); + } + } else if (__indexOf.call(hAligns, pMain) >= 0) { + incr(css, 'top', realign(pAlign, contH, elemH)); + if (arrowCss) { + incr(arrowCss, 'top', realign(pAlign, arrowSize, elemIH)); + } + } + if (this.container.is(":visible")) { + css.display = 'block'; + } + this.container.removeAttr('style').css(css); + if (arrowCss) { + return this.arrow.removeAttr('style').css(arrowCss); + } + }; + + Notification.prototype.getPosition = function() { + var pos, text, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + text = this.options.position || (this.elem ? this.options.elementPosition : this.options.globalPosition); + pos = parsePosition(text); + if (pos.length === 0) { + pos[0] = 'b'; + } + if (_ref = pos[0], __indexOf.call(mainPositions, _ref) < 0) { + throw "Must be one of [" + mainPositions + "]"; + } + if (pos.length === 1 || ((_ref1 = pos[0], __indexOf.call(vAligns, _ref1) >= 0) && (_ref2 = pos[1], __indexOf.call(hAligns, _ref2) < 0)) || ((_ref3 = pos[0], __indexOf.call(hAligns, _ref3) >= 0) && (_ref4 = pos[1], __indexOf.call(vAligns, _ref4) < 0))) { + pos[1] = (_ref5 = pos[0], __indexOf.call(hAligns, _ref5) >= 0) ? 'm' : 'l'; + } + if (pos.length === 2) { + pos[2] = pos[1]; + } + return pos; + }; + + Notification.prototype.getStyle = function(name) { + var style; + if (!name) { + name = this.options.style; + } + if (!name) { + name = 'default'; + } + style = styles[name]; + if (!style) { + throw "Missing style: " + name; + } + return style; + }; + + Notification.prototype.updateClasses = function() { + var classes, style; + classes = ['base']; + if ($.isArray(this.options.className)) { + classes = classes.concat(this.options.className); + } else if (this.options.className) { + classes.push(this.options.className); + } + style = this.getStyle(); + classes = $.map(classes, function(n) { + return "" + pluginClassName + "-" + style.name + "-" + n; + }).join(' '); + return this.userContainer.attr('class', classes); + }; + + Notification.prototype.run = function(data, options) { + var d, datas, name, type, value, + _this = this; + if ($.isPlainObject(options)) { + $.extend(this.options, options); + } else if ($.type(options) === 'string') { + this.options.color = options; + } + if (this.container && !data) { + this.show(false); + return; + } else if (!this.container && !data) { + return; + } + datas = {}; + if ($.isPlainObject(data)) { + datas = data; + } else { + datas[blankFieldName] = data; + } + for (name in datas) { + d = datas[name]; + type = this.userFields[name]; + if (!type) { + continue; + } + if (type === 'text') { + d = encode(d); + if (this.options.breakNewLines) { + d = d.replace(/\n/g, '
'); + } + } + value = name === blankFieldName ? '' : '=' + name; + find(this.userContainer, "[data-notify-" + type + value + "]").html(d); + } + this.updateClasses(); + if (this.elem) { + this.setElementPosition(); + } else { + this.setGlobalPosition(); + } + this.show(true); + if (this.options.autoHide) { + clearTimeout(this.autohideTimer); + return this.autohideTimer = setTimeout(function() { + return _this.show(false); + }, this.options.autoHideDelay); + } + }; + + Notification.prototype.destroy = function() { + return this.wrapper.remove(); + }; + + return Notification; + +})(); + +$[pluginName] = function(elem, data, options) { + if ((elem && elem.nodeName) || elem.jquery) { + $(elem)[pluginName](data, options); + } else { + options = data; + data = elem; + new Notification(null, data, options); + } + return elem; +}; + +$.fn[pluginName] = function(data, options) { + $(this).each(function() { + var inst; + inst = getAnchorElement($(this)).data(pluginClassName); + if (inst) { + return inst.run(data, options); + } else { + return new Notification($(this), data, options); + } + }); + return this; +}; + +$.extend($[pluginName], { + defaults: defaults, + addStyle: addStyle, + pluginOptions: pluginOptions, + getStyle: getStyle, + insertCSS: insertCSS +}); + +$(function() { + insertCSS(coreStyle.css).attr('id', 'core-notify'); + return $(document).on('click notify-hide', "." + pluginClassName + "-wrapper", function(e) { + var inst; + inst = $(this).data(pluginClassName); + if (inst && (inst.options.clickToHide || e.type === 'notify-hide')) { + return inst.show(false); + } + }); +}); + +}(window,document,jQuery)); + +$.notify.addStyle("bootstrap", { + html: "
\n\n
", + classes: { + base: { + "font-weight": "bold", + "padding": "8px 15px 8px 14px", + "text-shadow": "0 1px 0 rgba(255, 255, 255, 0.5)", + "background-color": "#fcf8e3", + "border": "1px solid #fbeed5", + "border-radius": "4px", + "white-space": "nowrap", + "padding-left": "25px", + }, + error: { + "color": "#B94A48", + "background-color": "#F2DEDE", + "border-color": "#EED3D7", + }, + success: { + "color": "#468847", + "background-color": "#DFF0D8", + "border-color": "#D6E9C6", + }, + info: { + "color": "#3A87AD", + "background-color": "#D9EDF7", + "border-color": "#BCE8F1", + }, + warn: { + "color": "#C09853", + "background-color": "#FCF8E3", + "border-color": "#FBEED5", + } + } +}); diff --git a/htdocs/js/bootstrap-slider.js b/htdocs/js/bootstrap-slider.js index 0dcdf1a..1fa4268 100644 --- a/htdocs/js/bootstrap-slider.js +++ b/htdocs/js/bootstrap-slider.js @@ -1,388 +1,139 @@ -/* ========================================================= - * bootstrap-slider.js v2.0.0 - * http://www.eyecon.ro/bootstrap-slider - * ========================================================= - * Copyright 2012 Stefan Petre - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================= */ - -!function( $ ) { - - var Slider = function(element, options) { - this.element = $(element); - this.picker = $('
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
') - .insertBefore(this.element) - .append(this.element); - this.id = this.element.data('slider-id')||options.id; - if (this.id) { - this.picker[0].id = this.id; - } - - if (typeof Modernizr !== 'undefined' && Modernizr.touch) { - this.touchCapable = true; - } - - var tooltip = this.element.data('slider-tooltip')||options.tooltip; - - this.tooltip = this.picker.find('.tooltip'); - this.tooltipInner = this.tooltip.find('div.tooltip-inner'); - - this.orientation = this.element.data('slider-orientation')||options.orientation; - switch(this.orientation) { - case 'vertical': - this.picker.addClass('slider-vertical'); - this.stylePos = 'top'; - this.mousePos = 'pageY'; - this.sizePos = 'offsetHeight'; - this.tooltip.addClass('right')[0].style.left = '100%'; - break; - default: - this.picker - .addClass('slider-horizontal') - .css('width', this.element.outerWidth()); - this.orientation = 'horizontal'; - this.stylePos = 'left'; - this.mousePos = 'pageX'; - this.sizePos = 'offsetWidth'; - this.tooltip.addClass('top')[0].style.top = -this.tooltip.outerHeight() - 14 + 'px'; - break; - } - - this.min = this.element.data('slider-min')||options.min; - this.max = this.element.data('slider-max')||options.max; - this.step = this.element.data('slider-step')||options.step; - this.value = this.element.data('slider-value')||options.value; - if (this.value[1]) { - this.range = true; - } - - this.selection = this.element.data('slider-selection')||options.selection; - this.selectionEl = this.picker.find('.slider-selection'); - if (this.selection === 'none') { - this.selectionEl.addClass('hide'); - } - this.selectionElStyle = this.selectionEl[0].style; - - - this.handle1 = this.picker.find('.slider-handle:first'); - this.handle1Stype = this.handle1[0].style; - this.handle2 = this.picker.find('.slider-handle:last'); - this.handle2Stype = this.handle2[0].style; - - var handle = this.element.data('slider-handle')||options.handle; - switch(handle) { - case 'round': - this.handle1.addClass('round'); - this.handle2.addClass('round'); - break - case 'triangle': - this.handle1.addClass('triangle'); - this.handle2.addClass('triangle'); - break - } - - if (this.range) { - this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0])); - this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1])); - } else { - this.value = [ Math.max(this.min, Math.min(this.max, this.value))]; - this.handle2.addClass('hide'); - if (this.selection == 'after') { - this.value[1] = this.max; - } else { - this.value[1] = this.min; - } - } - this.diff = this.max - this.min; - this.percentage = [ - (this.value[0]-this.min)*100/this.diff, - (this.value[1]-this.min)*100/this.diff, - this.step*100/this.diff - ]; - - this.offset = this.picker.offset(); - this.size = this.picker[0][this.sizePos]; - - this.formater = options.formater; - - this.layout(); - - if (this.touchCapable) { - // Touch: Bind touch events: - this.picker.on({ - touchstart: $.proxy(this.mousedown, this) - }); - } else { - this.picker.on({ - mousedown: $.proxy(this.mousedown, this) - }); - } - - if (tooltip === 'show') { - this.picker.on({ - mouseenter: $.proxy(this.showTooltip, this), - mouseleave: $.proxy(this.hideTooltip, this) - }); - } else { - this.tooltip.addClass('hide'); - } - }; - - Slider.prototype = { - constructor: Slider, - - over: false, - inDrag: false, - - showTooltip: function(){ - this.tooltip.addClass('in'); - //var left = Math.round(this.percent*this.width); - //this.tooltip.css('left', left - this.tooltip.outerWidth()/2); - this.over = true; - }, - - hideTooltip: function(){ - if (this.inDrag === false) { - this.tooltip.removeClass('in'); - } - this.over = false; - }, - - layout: function(){ - this.handle1Stype[this.stylePos] = this.percentage[0]+'%'; - this.handle2Stype[this.stylePos] = this.percentage[1]+'%'; - if (this.orientation == 'vertical') { - this.selectionElStyle.top = Math.min(this.percentage[0], this.percentage[1]) +'%'; - this.selectionElStyle.height = Math.abs(this.percentage[0] - this.percentage[1]) +'%'; - } else { - this.selectionElStyle.left = Math.min(this.percentage[0], this.percentage[1]) +'%'; - this.selectionElStyle.width = Math.abs(this.percentage[0] - this.percentage[1]) +'%'; - } - if (this.range) { - this.tooltipInner.text( - this.formater(this.value[0]) + - ' : ' + - this.formater(this.value[1]) - ); - this.tooltip[0].style[this.stylePos] = this.size * (this.percentage[0] + (this.percentage[1] - this.percentage[0])/2)/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px'; - } else { - this.tooltipInner.text( - this.formater(this.value[0]) - ); - this.tooltip[0].style[this.stylePos] = this.size * this.percentage[0]/100 - (this.orientation === 'vertical' ? this.tooltip.outerHeight()/2 : this.tooltip.outerWidth()/2) +'px'; - } - }, - - mousedown: function(ev) { - - // Touch: Get the original event: - if (this.touchCapable && ev.type === 'touchstart') { - ev = ev.originalEvent; - } - - this.offset = this.picker.offset(); - this.size = this.picker[0][this.sizePos]; - - var percentage = this.getPercentage(ev); - - if (this.range) { - var diff1 = Math.abs(this.percentage[0] - percentage); - var diff2 = Math.abs(this.percentage[1] - percentage); - this.dragged = (diff1 < diff2) ? 0 : 1; - } else { - this.dragged = 0; - } - - this.percentage[this.dragged] = percentage; - this.layout(); - - if (this.touchCapable) { - // Touch: Bind touch events: - $(document).on({ - touchmove: $.proxy(this.mousemove, this), - touchend: $.proxy(this.mouseup, this) - }); - } else { - $(document).on({ - mousemove: $.proxy(this.mousemove, this), - mouseup: $.proxy(this.mouseup, this) - }); - } - - this.inDrag = true; - var val = this.calculateValue(); - this.element.trigger({ - type: 'slideStart', - value: val - }).trigger({ - type: 'slide', - value: val - }); - return false; - }, - - mousemove: function(ev) { - - // Touch: Get the original event: - if (this.touchCapable && ev.type === 'touchmove') { - ev = ev.originalEvent; - } - - var percentage = this.getPercentage(ev); - if (this.range) { - if (this.dragged === 0 && this.percentage[1] < percentage) { - this.percentage[0] = this.percentage[1]; - this.dragged = 1; - } else if (this.dragged === 1 && this.percentage[0] > percentage) { - this.percentage[1] = this.percentage[0]; - this.dragged = 0; - } - } - this.percentage[this.dragged] = percentage; - this.layout(); - var val = this.calculateValue(); - this.element - .trigger({ - type: 'slide', - value: val - }) - .data('value', val) - .prop('value', val); - return false; - }, - - mouseup: function(ev) { - if (this.touchCapable) { - // Touch: Bind touch events: - $(document).off({ - touchmove: this.mousemove, - touchend: this.mouseup - }); - } else { - $(document).off({ - mousemove: this.mousemove, - mouseup: this.mouseup - }); - } - - this.inDrag = false; - if (this.over == false) { - this.hideTooltip(); - } - this.element; - var val = this.calculateValue(); - this.element - .trigger({ - type: 'slideStop', - value: val - }) - .data('value', val) - .prop('value', val); - return false; - }, - - calculateValue: function() { - var val; - if (this.range) { - val = [ - (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step), - (this.min + Math.round((this.diff * this.percentage[1]/100)/this.step)*this.step) - ]; - this.value = val; - } else { - val = (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step); - this.value = [val, this.value[1]]; - } - return val; - }, - - getPercentage: function(ev) { - if (this.touchCapable) { - ev = ev.touches[0]; - } - var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size; - percentage = Math.round(percentage/this.percentage[2])*this.percentage[2]; - return Math.max(0, Math.min(100, percentage)); - }, - - getValue: function() { - if (this.range) { - return this.value; - } - return this.value[0]; - }, - - setValue: function(val) { - this.value = val; - - if (this.range) { - this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0])); - this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1])); - } else { - this.value = [ Math.max(this.min, Math.min(this.max, this.value))]; - this.handle2.addClass('hide'); - if (this.selection == 'after') { - this.value[1] = this.max; - } else { - this.value[1] = this.min; - } - } - this.diff = this.max - this.min; - this.percentage = [ - (this.value[0]-this.min)*100/this.diff, - (this.value[1]-this.min)*100/this.diff, - this.step*100/this.diff - ]; - this.layout(); - } - }; - - $.fn.slider = function ( option, val ) { - return this.each(function () { - var $this = $(this), - data = $this.data('slider'), - options = typeof option === 'object' && option; - if (!data) { - $this.data('slider', (data = new Slider(this, $.extend({}, $.fn.slider.defaults,options)))); - } - if (typeof option == 'string') { - data[option](val); - } - }) - }; - - $.fn.slider.defaults = { - min: 0, - max: 10, - step: 1, - orientation: 'horizontal', - value: 5, - selection: 'before', - tooltip: 'show', - handle: 'round', - formater: function(value) { - return value; - } - }; - - $.fn.slider.Constructor = Slider; - -}( window.jQuery ); \ No newline at end of file +(function($){ + + function slider(options){ + if(typeof options === 'number'){ + options = $.extend( + { + origVal:options + }, + defaults, + { + val:(( options < 0 ) ? 0 : ( (options > 100 ) ? 100 : options)) + } + ); + } + else if (options === "get"){ + var vals = []; + + $(this).each(function() { + vals.push($(this).data("sliderValue")); + }); + return vals; + } + else if(typeof options === 'object'){ + options = $.extend({origVal:options.val,origBarColor:options.barColor},defaults,options); + } + + return $(this).each (function() { + var self=$(this); + + if(self.hasClass("slider-wrapper-jq")){ + if(self.data("dragSlider") === "true") + return; + if(typeof options.origVal !== "undefined") + self.slider._setValue.call(self,options.val,null,true); + if(typeof options.origBarColor !== "undefined") + self.find('.progress-bar').css("background-color",options.barColor); + return; + } + + self.addClass("slider-wrapper-jq") + .append($("
") + .append("
") + .append("
")); + + self.find('.progress').on('mousedown', function(evt){ + self.data("dragSlider","true") + .data("startPoint",evt.pageX) + .data("endPoint",evt.pageX); + + if(!$(evt.target).hasClass("btn")){ + self.slider._setWidthFromEvent.call(self,evt.pageX,null,true); + } + else{ + self.data("btnTarget","true"); + } + + evt.preventDefault(); + evt.stopPropagation(); + }); + + $(window).on('mouseup', function(evt){ + if(self.data("dragSlider")==="true"){ + if(!(self.data("btnTarget") === "true" && self.data("startPoint") === self.data("endPoint") )){ + self.slider._setWidthFromEvent.call(self,evt.pageX); + } + + self.removeData("dragSlider") + .removeData("btnTarget") + .removeData("startPoint") + .removeData("endPoint"); + } + }).on('mousemove',function(evt){ + if(self.data("dragSlider")==="true"){ + self.slider._setWidthFromEvent.call(self,evt.pageX,null,true); + self.data("endPoint",evt.pageX); + evt.preventDefault(); + } + }); + + self.slider._setValue.call(self,options.val); + }); + + } + + var defaults={ + val:50, + barColor:"#428bca" + }, + _setWidthFromEvent = function(pageX,reqVals,supress) { + if(!reqVals){ + reqVals = this.slider._getRequiredValues.call(this); + } + else{ + reqVals = null; + } + + var width = pageX - reqVals.pbar.offset().left, + perc = ((100.0*width) / (reqVals.progw)); + + return this.slider._setValue.call(this,perc,reqVals,supress); + }, + _setValue = function (val,reqVals,supress) { + if(!reqVals){ + reqVals = this.slider._getRequiredValues.call(this); + } + + val = ((val<0)?0:((val>100)?100:val)); + var adjVal= ((val*(100-reqVals.pbutp)/100) + (reqVals.pbutp/2)); + + this.data("sliderValue",val); + reqVals.pbar.css({width:adjVal+"%"}); + this.find('div.btn').css('left',adjVal+"%"); + + if(supress !== true){ + this.trigger("slider.newValue",{val:Math.round(val)}); + } + + return val; + }, + _getRequiredValues = function(){ + var pbar=this.find('.progress-bar'), + progw=this.children('.progress').get(0).clientWidth, + pbutp=((this.find('div.btn').get(0).clientWidth*100.0)/progw); + + return { + pbar:pbar, + progw:progw, + pbutp:pbutp + }; + }; + + $.fn.slider = slider; + $.fn.slider.defaults = defaults; + $.fn.slider._getRequiredValues = _getRequiredValues ; + $.fn.slider._setWidthFromEvent = _setWidthFromEvent; + $.fn.slider._setValue = _setValue; + +})(jQuery); diff --git a/htdocs/js/jquery.simplePagination.js b/htdocs/js/jquery.simplePagination.js deleted file mode 100644 index e5a4664..0000000 --- a/htdocs/js/jquery.simplePagination.js +++ /dev/null @@ -1,268 +0,0 @@ -/** -* simplePagination.js v1.6 -* A simple jQuery pagination plugin. -* http://flaviusmatis.github.com/simplePagination.js/ -* -* Copyright 2012, Flavius Matis -* Released under the MIT license. -* http://flaviusmatis.github.com/license.html -*/ - -(function($){ - - var methods = { - init: function(options) { - var o = $.extend({ - items: 1, - itemsOnPage: 1, - pages: 0, - displayedPages: 5, - edges: 2, - currentPage: 1, - hrefTextPrefix: '#page-', - hrefTextSuffix: '', - prevText: 'Prev', - nextText: 'Next', - ellipseText: '…', - cssStyle: 'light-theme', - labelMap: [], - selectOnClick: true, - onPageClick: function(pageNumber, event) { - // Callback triggered when a page is clicked - // Page number is given as an optional parameter - }, - onInit: function() { - // Callback triggered immediately after initialization - } - }, options || {}); - - var self = this; - - o.pages = o.pages ? o.pages : Math.ceil(o.items / o.itemsOnPage) ? Math.ceil(o.items / o.itemsOnPage) : 1; - o.currentPage = o.currentPage - 1; - o.halfDisplayed = o.displayedPages / 2; - - this.each(function() { - self.addClass(o.cssStyle + ' simple-pagination').data('pagination', o); - methods._draw.call(self); - }); - - o.onInit(); - - return this; - }, - - selectPage: function(page) { - methods._selectPage.call(this, page - 1); - return this; - }, - - prevPage: function() { - var o = this.data('pagination'); - if (o.currentPage > 0) { - methods._selectPage.call(this, o.currentPage - 1); - } - return this; - }, - - nextPage: function() { - var o = this.data('pagination'); - if (o.currentPage < o.pages - 1) { - methods._selectPage.call(this, o.currentPage + 1); - } - return this; - }, - - getPagesCount: function() { - return this.data('pagination').pages; - }, - - getCurrentPage: function () { - return this.data('pagination').currentPage + 1; - }, - - destroy: function(){ - this.empty(); - return this; - }, - - drawPage: function (page) { - var o = this.data('pagination'); - o.currentPage = page - 1; - this.data('pagination', o); - methods._draw.call(this); - return this; - }, - - redraw: function(){ - methods._draw.call(this); - return this; - }, - - disable: function(){ - var o = this.data('pagination'); - o.disabled = true; - this.data('pagination', o); - methods._draw.call(this); - return this; - }, - - enable: function(){ - var o = this.data('pagination'); - o.disabled = false; - this.data('pagination', o); - methods._draw.call(this); - return this; - }, - - updateItems: function (newItems) { - var o = this.data('pagination'); - o.items = newItems; - o.pages = methods._getPages(o); - this.data('pagination', o); - methods._draw.call(this); - }, - - updateItemsOnPage: function (itemsOnPage) { - var o = this.data('pagination'); - o.itemsOnPage = itemsOnPage; - o.pages = methods._getPages(o); - this.data('pagination', o); - methods._selectPage.call(this, 0); - return this; - }, - - _draw: function() { - var o = this.data('pagination'), - interval = methods._getInterval(o), - i, - tagName; - - methods.destroy.call(this); - - tagName = (typeof this.prop === 'function') ? this.prop('tagName') : this.attr('tagName'); - - var $panel = tagName === 'UL' ? this : $('
    ').appendTo(this); - - // Generate Prev link - if (o.prevText) { - methods._appendItem.call(this, o.currentPage - 1, {text: o.prevText, classes: 'prev'}); - } - - // Generate start edges - if (interval.start > 0 && o.edges > 0) { - var end = Math.min(o.edges, interval.start); - for (i = 0; i < end; i++) { - methods._appendItem.call(this, i); - } - if (o.edges < interval.start && (interval.start - o.edges != 1)) { - $panel.append('
  • ' + o.ellipseText + '
  • '); - } else if (interval.start - o.edges == 1) { - methods._appendItem.call(this, o.edges); - } - } - - // Generate interval links - for (i = interval.start; i < interval.end; i++) { - methods._appendItem.call(this, i); - } - - // Generate end edges - if (interval.end < o.pages && o.edges > 0) { - if (o.pages - o.edges > interval.end && (o.pages - o.edges - interval.end != 1)) { - $panel.append('
  • ' + o.ellipseText + '
  • '); - } else if (o.pages - o.edges - interval.end == 1) { - methods._appendItem.call(this, interval.end++); - } - var begin = Math.max(o.pages - o.edges, interval.end); - for (i = begin; i < o.pages; i++) { - methods._appendItem.call(this, i); - } - } - - // Generate Next link - if (o.nextText) { - methods._appendItem.call(this, o.currentPage + 1, {text: o.nextText, classes: 'next'}); - } - }, - - _getPages: function(o) { - var pages = Math.ceil(o.items / o.itemsOnPage); - return pages || 1; - }, - - _getInterval: function(o) { - return { - start: Math.ceil(o.currentPage > o.halfDisplayed ? Math.max(Math.min(o.currentPage - o.halfDisplayed, (o.pages - o.displayedPages)), 0) : 0), - end: Math.ceil(o.currentPage > o.halfDisplayed ? Math.min(o.currentPage + o.halfDisplayed, o.pages) : Math.min(o.displayedPages, o.pages)) - }; - }, - - _appendItem: function(pageIndex, opts) { - var self = this, options, $link, o = self.data('pagination'), $linkWrapper = $('
  • '), $ul = self.find('ul'); - - pageIndex = pageIndex < 0 ? 0 : (pageIndex < o.pages ? pageIndex : o.pages - 1); - - options = { - text: pageIndex + 1, - classes: '' - }; - - if (o.labelMap.length && o.labelMap[pageIndex]) { - options.text = o.labelMap[pageIndex]; - } - - options = $.extend(options, opts || {}); - - if (pageIndex == o.currentPage || o.disabled) { - if (o.disabled) { - $linkWrapper.addClass('disabled'); - } else { - $linkWrapper.addClass('active'); - } - $link = $('' + (options.text) + ''); - } else { - $link = $('' + (options.text) + ''); - $link.click(function(event){ - return methods._selectPage.call(self, pageIndex, event); - }); - } - - if (options.classes) { - $link.addClass(options.classes); - } - - $linkWrapper.append($link); - - if ($ul.length) { - $ul.append($linkWrapper); - } else { - self.append($linkWrapper); - } - }, - - _selectPage: function(pageIndex, event) { - var o = this.data('pagination'); - o.currentPage = pageIndex; - if (o.selectOnClick) { - methods._draw.call(this); - } - return o.onPageClick(pageIndex + 1, event); - } - - }; - - $.fn.pagination = function(method) { - - // Method calling logic - if (methods[method] && method.charAt(0) != '_') { - return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method) { - return methods.init.apply(this, arguments); - } else { - $.error('Method ' + method + ' does not exist on jQuery.pagination'); - } - - }; - -})(jQuery); diff --git a/htdocs/js/mpd.js b/htdocs/js/mpd.js index f343033..7dd87b3 100644 --- a/htdocs/js/mpd.js +++ b/htdocs/js/mpd.js @@ -1,371 +1,386 @@ var socket; var last_state; var current_app; -var is_firefox; - -$('#volumeslider').slider().on('slide', function(event) { - socket.send("MPD_API_SET_VOLUME,"+event.value); -}); +var current_song = new Object(); var app = $.sammy(function() { - this.before('/', function(e, data) { - socket.send("MPD_API_GET_TRACK_INFO"); - $('#nav_links > li').removeClass('active'); - }); - - this.get('#/', function() { - current_app = 'playlist'; - $('#breadcrump').addClass('hide'); - $('#salamisandwich').find("tr:gt(0)").remove(); - //if(is_firefox) - $.get( "/api/get_playlist", socket.onmessage); - //else - // socket.send("MPD_API_GET_PLAYLIST"); - - $('#panel-heading').text("Playlist"); - $('#playlist').addClass('active'); - }); - - this.get(/\#\/browse\/(.*)/, function() { - current_app = 'browse'; - $('#breadcrump').removeClass('hide').empty(); - $('#salamisandwich').find("tr:gt(0)").remove(); - var path = this.params['splat']; - if(path == '') - path = "/"; - - //if(is_firefox) - $.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage); - //else - // socket.send("MPD_API_GET_BROWSE,"+path); - - $('#panel-heading').text("Browse database: "+path+""); - var path_array = path[0].split('/'); - var full_path = ""; - $.each(path_array, function(index, chunk) { - if(path_array.length - 1 == index) { - $('#breadcrump').append("
  • "+ chunk + "
  • "); - return; - } - - full_path = full_path + chunk; - $('#breadcrump').append("
  • "+chunk+"
  • "); - full_path += "/"; - }); - $('#browse').addClass('active'); - }); - - this.get("/", function(context) { - context.redirect("#/"); - }); + this.before('/', function(e, data) { + socket.send("MPD_API_GET_TRACK_INFO"); + $('#nav_links > li').removeClass('active'); + }); + + this.get('#/', function() { + current_app = 'playlist'; + $('#breadcrump').addClass('hide'); + $('#salamisandwich').find("tr:gt(0)").remove(); + $.get( "/api/get_playlist", socket.onmessage); + + $('#panel-heading').text("Playlist"); + $('#playlist').addClass('active'); + }); + + this.get(/\#\/browse\/(.*)/, function() { + current_app = 'browse'; + $('#breadcrump').removeClass('hide').empty().append("
  • root
  • "); + $('#salamisandwich').find("tr:gt(0)").remove(); + var path = this.params['splat'][0]; + + $.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage); + + $('#panel-heading').text("Browse database: "+path+""); + var path_array = path.split('/'); + var full_path = ""; + $.each(path_array, function(index, chunk) { + if(path_array.length - 1 == index) { + $('#breadcrump').append("
  • "+ chunk + "
  • "); + return; + } + + full_path = full_path + chunk; + $('#breadcrump').append("
  • "+chunk+"
  • "); + full_path += "/"; + }); + $('#browse').addClass('active'); + }); + + this.get("/", function(context) { + context.redirect("#/"); + }); }); $(document).ready(function(){ - is_firefox = true; - webSocketConnect(); + webSocketConnect(); + $("#volumeslider").slider(0); + $("#volumeslider").on('slider.newValue', function(evt,data){ + socket.send("MPD_API_SET_VOLUME,"+data.val); + }); + $('#progressbar').slider(0); + $("#progressbar").on('slider.newValue', function(evt,data){ + if(current_song) { + var seekVal = Math.ceil(current_song.totalTime*(data.val/100)); + socket.send("MPD_API_SET_SEEK,"+current_song.currentSongId+","+seekVal); + } + }); }); function webSocketConnect() { - if (typeof MozWebSocket != "undefined") { - socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client"); - } else { - socket = new WebSocket(get_appropriate_ws_url(), "ympd-client"); - } - - try { - socket.onopen = function() { - console.log("Connected"); - app.run(); - - /* Push Initial state on first visit */ - //$(window).trigger("statechange") - } - - socket.onmessage =function got_packet(msg) { - console.log(typeof msg); - if(msg instanceof MessageEvent) { - if(msg.data === last_state) - return; - - var obj = JSON.parse(msg.data); - } else { - var obj = msg; - } - - switch (obj.type) { - case "playlist": - if(current_app !== 'playlist') - break; - - $('#salamisandwich > tbody').empty(); - for (var song in obj.data) { - var minutes = Math.floor(obj.data[song].duration / 60); - var seconds = obj.data[song].duration - minutes * 60; - - $('#salamisandwich > tbody').append( - "" + obj.data[song].pos + "" + - ""+ obj.data[song].title +"" + - ""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds + - ""); - } - - $('#salamisandwich > tbody > tr').on({ - mouseover: function(){ - if($(this).children().last().has("a").length == 0) - $(this).children().last().append( - "" + - "") - .find('a').fadeTo('fast',1); - }, - click: function() { - $('#salamisandwich > tbody > tr').removeClass('active'); - socket.send('MPD_API_PLAY_TRACK,'+$(this).attr('trackid')); - $(this).addClass('active'); - }, - mouseleave: function(){ - $(this).children().last().find("a").stop().remove(); - } - }); - - break; - case "browse": - //if(state.data.state !== 'nav_browse') - // break; - if(current_app !== 'browse') - break; - - for (var item in obj.data) { - switch(obj.data[item].type) { - case "directory": - $('#salamisandwich > tbody').append( - "" + - "" + - "" + basename(obj.data[item].dir) +"" + - ""); - break; - case "song": - var minutes = Math.floor(obj.data[item].duration / 60); - var seconds = obj.data[item].duration - minutes * 60; - - $('#salamisandwich > tbody').append( - "" + - "" + - "" + obj.data[item].title +"" + - ""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +""); - break; - - case "playlist": - break; - } - } - - $('#salamisandwich > tbody > tr').on({ - mouseover: function(){ - if($(this).children().last().has("a").length == 0) - if($(this).attr("uri").length > 0) - $(this).children().last().append( - "" + - "") - .find('a').fadeTo('fast',1); - }, - mouseleave: function(){ - $(this).children().last().find("a").stop().remove(); - } - }); - - break; - case "state": - if(JSON.stringify(obj) === JSON.stringify(last_state)) - break; - - var total_minutes = Math.floor(obj.data.totalTime / 60); - var total_seconds = obj.data.totalTime - total_minutes * 60; - - var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60); - var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60; - - $('#volumeslider').slider('setValue', obj.data.volume); - var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime) + "%"; - $('#progressbar').width(progress); - - $('#counter') - .text(elapsed_minutes + ":" + - (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " + - total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds); - - $('#salamisandwich > tbody > tr').removeClass('active').css("font-weight", ""); - $('#salamisandwich > tbody > tr[trackid='+obj.data.currentsongid+']').addClass('active').css("font-weight", "bold"); - - if(obj.data.random) - $('#btnrandom').addClass("active") - else - $('#btnrandom').removeClass("active"); - - if(obj.data.consume) - $('#btnconsume').addClass("active") - else - $('#btnconsume').removeClass("active"); - - if(obj.data.single) - $('#btnsingle').addClass("active") - else - $('#btnsingle').removeClass("active"); - - if(obj.data.repeat) - $('#btnrepeat').addClass("active") - else - $('#btnrepeat').removeClass("active"); - - if(last_state && (obj.data.state !== last_state.data.state)) - updatePlayIcon(obj.data.state); - if(last_state && (obj.data.volume !== last_state.data.volume)) - updateVolumeIcon(obj.data.volume); - - if(obj.data.elapsedTime <= 1) - socket.send("MPD_API_GET_TRACK_INFO"); - - $('#alert').addClass("hide"); - last_state = obj; - break; - case "disconnected": - $('#alert') - .text("Server lost connection to MPD Host.") - .removeClass("hide alert-info") - .addClass("alert-danger"); + if (typeof MozWebSocket != "undefined") { + socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client"); + } else { + socket = new WebSocket(get_appropriate_ws_url(), "ympd-client"); + } + + try { + socket.onopen = function() { + $('.top-right').notify({ + message:{text:"Connected"}, + fadeOut: { enabled: true, delay: 1000 } + }).show(); + + app.run(); + } + + socket.onmessage =function got_packet(msg) { + if(msg instanceof MessageEvent) { + if(msg.data === last_state) + return; + + var obj = JSON.parse(msg.data); + } else { + var obj = msg; + } + + switch (obj.type) { + case "playlist": + if(current_app !== 'playlist') + break; + + $('#salamisandwich > tbody').empty(); + for (var song in obj.data) { + var minutes = Math.floor(obj.data[song].duration / 60); + var seconds = obj.data[song].duration - minutes * 60; + + $('#salamisandwich > tbody').append( + "" + obj.data[song].pos + "" + + ""+ obj.data[song].title +"" + + ""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds + + ""); + } + + $('#salamisandwich > tbody > tr').on({ + mouseover: function(){ + if($(this).children().last().has("a").length == 0) + $(this).children().last().append( + "" + + "") + .find('a').fadeTo('fast',1); + }, + click: function() { + $('#salamisandwich > tbody > tr').removeClass('active'); + socket.send('MPD_API_PLAY_TRACK,'+$(this).attr('trackid')); + $(this).addClass('active'); + }, + mouseleave: function(){ + $(this).children().last().find("a").stop().remove(); + } + }); + + break; + case "browse": + if(current_app !== 'browse') + break; + + for (var item in obj.data) { + switch(obj.data[item].type) { + case "directory": + $('#salamisandwich > tbody').append( + "" + + "" + + "" + basename(obj.data[item].dir) + "" + + ""); + break; + case "song": + var minutes = Math.floor(obj.data[item].duration / 60); + var seconds = obj.data[item].duration - minutes * 60; + + $('#salamisandwich > tbody').append( + "" + + "" + + "" + obj.data[item].title +"" + + ""+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +""); + break; + + case "playlist": + break; + } + } + + $('#salamisandwich > tbody > tr').on({ + mouseenter: function(){ + if($(this).is(".dir")) { + $(this).children().last().append( + "" + + "Add Directory ") + .find('a').click(function(e) { + e.stopPropagation(); + socket.send("MPD_API_ADD_TRACK," + $(this).parents("tr").attr("uri")); + $('.top-right').notify({ + message:{ + text:"Added " + $('td:nth-child(2)', $(this).parents("tr")).text() + " to playlist " + } + }).show(); + }).fadeTo('fast',1); + } + }, + click: function() { + if($(this).is(".song")) { + socket.send("MPD_API_ADD_TRACK," + $(this).attr("uri")); + $('.top-right').notify({ + message:{ + text:"Added " + $('td:nth-child(2)', this).text() + " to playlist " + } + }).show(); + + } else + app.setLocation("#/browse/"+$(this).attr("uri")); + }, + mouseleave: function(){ + $(this).children().last().find("a").stop().remove(); + } + }); + + break; + case "state": + updatePlayIcon(obj.data.state); + updateVolumeIcon(obj.data.volume); + + if(JSON.stringify(obj) === JSON.stringify(last_state)) + break; + + current_song.totalTime = obj.data.totalTime; + current_song.currentSongId = obj.data.currentsongid; + var total_minutes = Math.floor(obj.data.totalTime / 60); + var total_seconds = obj.data.totalTime - total_minutes * 60; + + var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60); + var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60; + + $('#volumeslider').slider(obj.data.volume); + var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime); + $('#progressbar').slider(progress); + + $('#counter') + .text(elapsed_minutes + ":" + + (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " + + total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds); + + $('#salamisandwich > tbody > tr').removeClass('active').css("font-weight", ""); + $('#salamisandwich > tbody > tr[trackid='+obj.data.currentsongid+']').addClass('active').css("font-weight", "bold"); + + if(obj.data.random) + $('#btnrandom').addClass("active") + else + $('#btnrandom').removeClass("active"); + + if(obj.data.consume) + $('#btnconsume').addClass("active") + else + $('#btnconsume').removeClass("active"); + + if(obj.data.single) + $('#btnsingle').addClass("active") + else + $('#btnsingle').removeClass("active"); + + if(obj.data.repeat) + $('#btnrepeat').addClass("active") + else + $('#btnrepeat').removeClass("active"); + + if(obj.data.elapsedTime <= 1) + socket.send("MPD_API_GET_TRACK_INFO"); + + last_state = obj; + break; + case "disconnected": + if($('.top-right').has('div').length == 0) + $('.top-right').notify({ + message:{text:"ympd lost connection to MPD "}, + type: "danger", + fadeOut: { enabled: true, delay: 1000 }, + }).show(); + break; + case "update_playlist": + if(current_app === 'playlist') + $.get( "/api/get_playlist", socket.onmessage); + break; + case "current_song": + $('#currenttrack').text(" " + obj.data.title); + if(obj.data.album) + $('#album').text(obj.data.album); + if(obj.data.artist) + $('#artist').text(obj.data.artist); break; + case "error": + $('.top-right').notify({ + message:{text: obj.data}, + type: "danger", + }).show(); + default: + break; + } + - case "current_song": - $('#currenttrack').text(" " + obj.data.title); - if(obj.data.album) - $('#album').text(obj.data.album); - if(obj.data.artist) - $('#artist').text(obj.data.artist); - default: - break; - } - - - } - socket.onclose = function(){ - console.log("Disconnected"); - - var seconds = 5; - var tm = setInterval(disconnectAlert,1000); - - function disconnectAlert() { - $('#alert') - .text("Connection to MPD lost, retrying in " + seconds + " seconds") - .removeClass("hide alert-info") - .addClass("alert-danger"); - - if(seconds-- <= 0) { - webSocketConnect(); - seconds = 5; - $('#alert').addClass("hide"); - clearInterval(tm); - } - } - - } - - } catch(exception) { - alert('

    Error' + exception); - } + } + socket.onclose = function(){ + console.log("Disconnected"); + $('.top-right').notify({ + message:{text:"Connection to ympd lost, retrying in 3 seconds "}, + type: "danger", + onClose: function () { + webSocketConnect(); + } + }).show(); + } + + } catch(exception) { + alert('

    Error' + exception); + } } function get_appropriate_ws_url() { - var pcol; - var u = document.URL; - - /* - /* We open the websocket encrypted if this page came on an - /* https:// url itself, otherwise unencrypted - /*/ - - if (u.substring(0, 5) == "https") { - pcol = "wss://"; - u = u.substr(8); - } else { - pcol = "ws://"; - if (u.substring(0, 4) == "http") - u = u.substr(7); - } - - u = u.split('/'); - - return pcol + u[0]; + var pcol; + var u = document.URL; + + /* + /* We open the websocket encrypted if this page came on an + /* https:// url itself, otherwise unencrypted + /*/ + + if (u.substring(0, 5) == "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) == "http") + u = u.substr(7); + } + + u = u.split('/'); + + return pcol + u[0]; } var updateVolumeIcon = function(volume) { - $("#volume-icon").removeClass("glyphicon-volume-off"); - $("#volume-icon").removeClass("glyphicon-volume-up"); - $("#volume-icon").removeClass("glyphicon-volume-down"); - - if(volume == 0) { - $("#volume-icon").addClass("glyphicon-volume-off"); - } else if (volume < 50) { - $("#volume-icon").addClass("glyphicon-volume-down"); - } else { - $("#volume-icon").addClass("glyphicon-volume-up"); - } + $("#volume-icon").removeClass("glyphicon-volume-off"); + $("#volume-icon").removeClass("glyphicon-volume-up"); + $("#volume-icon").removeClass("glyphicon-volume-down"); + + if(volume == 0) { + $("#volume-icon").addClass("glyphicon-volume-off"); + } else if (volume < 50) { + $("#volume-icon").addClass("glyphicon-volume-down"); + } else { + $("#volume-icon").addClass("glyphicon-volume-up"); + } } var updatePlayIcon = function(state) { - $("#play-icon").removeClass("glyphicon-play") - .removeClass("glyphicon-pause") - .removeClass("glyphicon-stop"); - $('#track-icon').removeClass("glyphicon-play") - .removeClass("glyphicon-pause") - .removeClass("glyphicon-stop"); - - if(state == 1) { - $("#play-icon").addClass("glyphicon-stop"); - $('#track-icon').addClass("glyphicon-stop"); - } else if(state == 2) { - $("#play-icon").addClass("glyphicon-pause"); - $('#track-icon').addClass("glyphicon-play"); - } else { - $("#play-icon").addClass("glyphicon-play"); - $('#track-icon').addClass("glyphicon-pause"); - } + $("#play-icon").removeClass("glyphicon-play") + .removeClass("glyphicon-pause"); + $('#track-icon').removeClass("glyphicon-play") + .removeClass("glyphicon-pause") + .removeClass("glyphicon-stop"); + + if(state == 1) { // stop + $("#play-icon").addClass("glyphicon-play"); + $('#track-icon').addClass("glyphicon-stop"); + } else if(state == 2) { // pause + $("#play-icon").addClass("glyphicon-pause"); + $('#track-icon').addClass("glyphicon-play"); + } else { // play + $("#play-icon").addClass("glyphicon-play"); + $('#track-icon').addClass("glyphicon-pause"); + } } function updateDB() { - socket.send('MPD_API_UPDATE_DB'); - - $('#alert') - .text("Updating MPD Database...") - .removeClass("hide alert-danger") - .addClass("alert-info"); - - setTimeout(function() { - $('#alert').addClass("hide"); - }, 5000); + socket.send('MPD_API_UPDATE_DB'); + $('.top-right').notify({ + message:{text:"Updating MPD Database... "} + }).show(); } function basename(path) { - return path.split('/').reverse()[0]; + return path.split('/').reverse()[0]; } $('#btnrandom').on('click', function (e) { - socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1)); + socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1)); }); $('#btnconsume').on('click', function (e) { - socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1)); + socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1)); }); $('#btnsingle').on('click', function (e) { - socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1)); + socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1)); }); $('#btnrepeat').on('click', function (e) { - socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1)); + socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1)); }); + +function getVersion() +{ + $.get( "/api/get_version", function(response) { + $('#ympd_version').text(response.data.ympd_version); + $('#mpd_version').text(response.data.mpd_version); + }); +} \ No newline at end of file diff --git a/src/http_server.c b/src/http_server.c index 6b2a71b..511f7c9 100644 --- a/src/http_server.c +++ b/src/http_server.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,7 @@ #include "config.h" char *resource_path = LOCAL_RESOURCE_PATH; +extern enum mpd_conn_states mpd_conn_state; struct serveable { const char *urlpath; @@ -25,6 +27,7 @@ static const struct serveable whitelist[] = { { "/js/mpd.js", "text/javascript" }, { "/js/jquery-1.10.2.min.js", "text/javascript" }, { "/js/bootstrap-slider.js", "text/javascript" }, + { "/js/bootstrap-notify.js", "text/javascript" }, { "/js/sammy.js", "text/javascript" }, { "/fonts/glyphicons-halflings-regular.woff", "application/x-font-woff"}, @@ -38,6 +41,11 @@ static const struct serveable whitelist[] = { { "/index.html", "text/html" }, }; +static const char http_header[] = "HTTP/1.0 200 OK\x0d\x0a" + "Server: libwebsockets\x0d\x0a" + "Content-Type: application/json\x0d\x0a" + "Content-Length: 000000\x0d\x0a\x0d\x0a"; + /* Converts a hex character to its integer value */ char from_hex(char ch) { return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; @@ -70,34 +78,37 @@ int callback_http(struct libwebsocket_context *context, void *in, size_t len) { char *response_buffer, *p; - size_t n, response_size; + char buf[64]; + size_t n, response_size = 0; switch (reason) { case LWS_CALLBACK_HTTP: if(in && strncmp((const char *)in, "/api/", 5) == 0) { - response_buffer = (char *)malloc(MAX_SIZE + 100); - p = response_buffer; + + p = (char *)malloc(MAX_SIZE + 100); + memcpy(p, http_header, sizeof(http_header) - 1); + response_buffer = p + sizeof(http_header) - 1; /* put content length and payload to buffer */ - if(strncmp((const char *)in, "/api/get_browse", 15) == 0) + if(mpd_conn_state != MPD_CONNECTED) {} + else if(strncmp((const char *)in, "/api/get_browse", 15) == 0) { char *url; - if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url)) + if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url) == 1) { char *url_decoded = url_decode(url); - printf("searching for %s", url_decoded); - response_size = mpd_put_browse(response_buffer + 98, url_decoded); + response_size = mpd_put_browse(response_buffer, url_decoded); free(url_decoded); free(url); } else - response_size = mpd_put_browse(response_buffer + 98, "/"); + response_size = mpd_put_browse(response_buffer, "/"); } else if(strncmp((const char *)in, "/api/get_playlist", 17) == 0) - response_size = mpd_put_playlist(response_buffer + 98); - else if(strncmp((const char *)in, "/api/version", 17) == 0) + response_size = mpd_put_playlist(response_buffer); + else if(strncmp((const char *)in, "/api/get_version", 16) == 0) response_size = snprintf(response_buffer, MAX_SIZE, "{\"type\":\"version\",\"data\":{" "\"ympd_version\":\"%d.%d.%d\"," @@ -106,24 +117,15 @@ int callback_http(struct libwebsocket_context *context, YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH, LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION); - else - { - /* invalid request, close connection */ - free(response_buffer); - return -1; - } - p += response_size + sprintf(p, "HTTP/1.0 200 OK\x0d\x0a" - "Server: libwebsockets\x0d\x0a" - "Content-Type: application/json\x0d\x0a" - "Content-Length: %6lu\x0d\x0a\x0d\x0a", - response_size - ); - response_buffer[98] = '{'; - - n = libwebsocket_write(wsi, (unsigned char *)response_buffer, - p - response_buffer, LWS_WRITE_HTTP); - - free(response_buffer); + + /* Copy size to content-length field */ + sprintf(buf, "%6lu", response_size); + memcpy(p + sizeof(http_header) - 11, buf, 6); + + n = libwebsocket_write(wsi, (unsigned char *)p, + sizeof(http_header) - 1 + response_size, LWS_WRITE_HTTP); + + free(p); /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ diff --git a/src/mpd_client.c b/src/mpd_client.c index b38a9a2..6ceb56a 100644 --- a/src/mpd_client.c +++ b/src/mpd_client.c @@ -41,12 +41,21 @@ int callback_ympd(struct libwebsocket_context *context, } p = &buf[LWS_SEND_BUFFER_PRE_PADDING]; - if(mpd_conn_state != MPD_CONNECTED) { + if(pss->do_send & DO_SEND_ERROR) { + n = snprintf(p, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}", + mpd_connection_get_error_message(conn)); + pss->do_send &= ~DO_SEND_ERROR; + + /* Try to recover error */ + if (!mpd_connection_clear_error(conn)) + mpd_conn_state = MPD_FAILURE; + } + else if(mpd_conn_state != MPD_CONNECTED) { n = snprintf(p, MAX_SIZE, "{\"type\":\"disconnected\"}"); } - //else if((pss->queue_version != queue_version) || (pss->do_send & DO_SEND_PLAYLIST)) { - else if(pss->do_send & DO_SEND_PLAYLIST) { - n = mpd_put_playlist(p); + else if((pss->queue_version != queue_version) || (pss->do_send & DO_SEND_PLAYLIST)) { + /*n = mpd_put_playlist(p);*/ + n = snprintf(p, MAX_SIZE, "{\"type\":\"update_playlist\"}"); pss->queue_version = queue_version; pss->do_send &= ~DO_SEND_PLAYLIST; } @@ -78,25 +87,20 @@ int callback_ympd(struct libwebsocket_context *context, pss->do_send |= DO_SEND_PLAYLIST; else if(!strcmp((const char *)in, MPD_API_GET_TRACK_INFO)) pss->do_send |= DO_SEND_TRACK_INFO; - else if(!strcmp((const char *)in, MPD_API_UPDATE_DB)) { - mpd_send_update(conn, NULL); - mpd_response_finish(conn); - } - else if(!strcmp((const char *)in, MPD_API_SET_PAUSE)) { - mpd_send_toggle_pause(conn); - mpd_response_finish(conn); - } - else if(!strcmp((const char *)in, MPD_API_SET_PREV)) { - mpd_send_previous(conn); - mpd_response_finish(conn); - } - else if(!strcmp((const char *)in, MPD_API_SET_NEXT)) { - mpd_send_next(conn); - mpd_response_finish(conn); - } - else if(!strcmp((const char *)in, MPD_API_RM_ALL)) { + else if(!strcmp((const char *)in, MPD_API_UPDATE_DB)) + mpd_run_update(conn, NULL); + else if(!strcmp((const char *)in, MPD_API_SET_PAUSE)) + mpd_run_toggle_pause(conn); + else if(!strcmp((const char *)in, MPD_API_SET_PREV)) + mpd_run_previous(conn); + else if(!strcmp((const char *)in, MPD_API_SET_NEXT)) + mpd_run_next(conn); + else if(!strcmp((const char *)in, MPD_API_SET_PLAY)) + mpd_run_play(conn); + else if(!strcmp((const char *)in, MPD_API_SET_STOP)) + mpd_run_stop(conn); + else if(!strcmp((const char *)in, MPD_API_RM_ALL)) mpd_run_clear(conn); - } else if(!strncmp((const char *)in, MPD_API_RM_TRACK, sizeof(MPD_API_RM_TRACK)-1)) { unsigned id; if(sscanf(in, "MPD_API_RM_TRACK,%u", &id)) @@ -129,9 +133,15 @@ int callback_ympd(struct libwebsocket_context *context, } else if(!strncmp((const char *)in, MPD_API_SET_VOLUME, sizeof(MPD_API_SET_VOLUME)-1)) { unsigned int volume; - if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume < 100) + if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume <= 100) mpd_run_set_volume(conn, volume); } + else if(!strncmp((const char *)in, MPD_API_SET_SEEK, sizeof(MPD_API_SET_SEEK)-1)) { + unsigned int seek, songid; + if(sscanf(in, "MPD_API_SET_SEEK,%u,%u", &songid, &seek)) { + mpd_run_seek_id(conn, songid, seek); + } + } else if(!strncmp((const char *)in, MPD_API_GET_BROWSE, sizeof(MPD_API_GET_BROWSE)-1)) { char *dir; if(sscanf(in, "MPD_API_GET_BROWSE,%m[^\t\n]", &dir) && dir != NULL) { @@ -146,6 +156,10 @@ int callback_ympd(struct libwebsocket_context *context, free(uri); } } + + if(mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) + pss->do_send |= DO_SEND_ERROR; + break; default: @@ -283,8 +297,12 @@ int mpd_put_playlist(char *buffer) if (!mpd_send_list_queue_meta(conn)) { lwsl_err("MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(conn)); - mpd_conn_state = MPD_FAILURE; - return 0; + cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", + mpd_connection_get_error_message(conn)); + + if (!mpd_connection_clear_error(conn)) + mpd_conn_state = MPD_FAILURE; + return cur - buffer; } cur += snprintf(cur, end - cur, "{\"type\": \"playlist\", \"data\": [ "); @@ -319,8 +337,12 @@ int mpd_put_browse(char *buffer, char *path) if (!mpd_send_list_meta(conn, path)) { lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn)); - mpd_conn_state = MPD_FAILURE; - return 0; + cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", + mpd_connection_get_error_message(conn)); + + if (!mpd_connection_clear_error(conn)) + mpd_conn_state = MPD_FAILURE; + return cur - buffer; } cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ "); diff --git a/src/mpd_client.h b/src/mpd_client.h index 9320209..287ecc1 100644 --- a/src/mpd_client.h +++ b/src/mpd_client.h @@ -9,6 +9,8 @@ #define DO_SEND_PLAYLIST (1 << 1) #define DO_SEND_TRACK_INFO (1 << 2) #define DO_SEND_BROWSE (1 << 3) +#define DO_SEND_ERROR (1 << 4) + #define MPD_API_GET_SEEK "MPD_API_GET_SEEK" #define MPD_API_GET_PLAYLIST "MPD_API_GET_PLAYLIST"