fix crash when seeking without song, new slider

This commit is contained in:
Andrew Karpow 2014-01-16 18:32:20 +01:00
parent 13b921553f
commit cd865b2cef
10 changed files with 1176 additions and 1021 deletions

View file

@ -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;
}

View file

@ -12,7 +12,7 @@
position: relative;
}
.slider.slider-horizontal {
width: 210px;
width: 100%;
height: 20px;
}
.slider.slider-horizontal .slider-track {

View file

@ -13,7 +13,6 @@
<link href="css/bootstrap.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/slider.css" rel="stylesheet">
<link href="css/mpd.css" rel="stylesheet">
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
@ -40,7 +39,7 @@
<ul id="nav_links" class="nav navbar-nav">
<li id="playlist"><a href="#/">Playlist</a></li>
<li id="browse"><a href="#/browse/">Browse database</a></li>
<li><a href="#" data-toggle="modal" data-target="#about">About</a></li>
<li><a href="#" data-toggle="modal" data-target="#about" onclick="getVersion();">About</a></li>
</ul>
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
@ -48,6 +47,9 @@
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');">
<span class="glyphicon glyphicon-backward"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');">
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PAUSE');">
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
</button>
@ -58,8 +60,7 @@
<div class="btn-group">
<div class="btn btn-toolbar btn-default">
<span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
&nbsp;&nbsp;
<input type="text" class="span2" value="0" data-slider-min="0" data-slider-max="100" data-slider-step="5" id="volumeslider" data-slider-tooltip="hide">
<div id="volumeslider"></div>
</div>
</div>
</div>
@ -71,7 +72,7 @@
<div class="row">
<div class="col-md-10 col-xs-12">
<div id="alert" class="alert hide"></div>
<div class="notifications top-right"></div>
<div class="panel panel-primary">
<!-- Default panel contents -->
@ -87,10 +88,9 @@
</h4>
<p id="counter" class="text pull-right">&nbsp;&nbsp;</p>
<div class="progress progress-striped active">
<div id="progressbar" class="progress-bar navbar-left" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
<div id="progressbar"></div>
</div><!-- /.panel-body -->
<ol id="breadcrump" class="breadcrumb">
@ -112,7 +112,7 @@
</div><!-- /.col-md-10 -->
<div class="col-md-2 col-xs-12" >
<div data-spy="affix" data-offset-bottom="10">
<div class="btn-toolbar">
<div class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
<button id="btnrandom" type="button" class="btn btn-default">
<span class="glyphicon glyphicon-random"></span> Random
@ -128,13 +128,15 @@
</button>
</div>
<button type="button" class="btn btn-block btn-default btn-lg dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-wrench"></span> Options <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#/" onclick="updateDB();"><span class="glyphicon glyphicon-refresh"></span> Update Database</a></li>
<li><a href="#/" onclick="socket.send('MPD_API_RM_ALL');"><span class="glyphicon glyphicon-trash"></span> Clear queue</a></li>
</ul>
<div class="btn-group-vertical btn-block btn-group-lg">
<button type="button" class="btn btn-default" onclick="updateDB();">
<span class="glyphicon glyphicon-refresh"></span> Update DB
</button>
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_RM_ALL');">
<span class="glyphicon glyphicon-trash"></span> Clear queue
</button>
</div>
</div>
</div><!-- /.col-md-2 -->
</div><!-- /.row -->
@ -150,9 +152,13 @@
</div>
<div class="modal-body">
<h4><span class="glyphicon glyphicon-play-circle"></span> ympd&nbsp;&nbsp;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<br/>
<span class="glyphicon glyphicon-play-circle"></span> ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.
<h5><span class="glyphicon glyphicon-play-circle"></span> ympd uses following excellent software:</h5>
<p>
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
<p class="text-muted">
ympd <span id="ympd_version"></span><br/>
libmpdclient <span id="mpd_version"></span><br/>
</p>
<h5>ympd uses following excellent software:</h5>
<h6><a href="http://libwebsockets.org">libWebSockets</a> <small>LGPL2.1 + static link exception</small></h6>
<h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
<br/>
@ -162,7 +168,7 @@
<strong>Andrew Karpow</strong><br>
<a href="mailto:andy@ympd.org">andy@ympd.org</a><br/>
<a href="http://www.ympd.org">www.ympd.org</a><br/>
XMPP: <a href="xmpp:andy@jabber.ccc.de?subscribe">andy_@jabber.ccc.de</a>
XMPP: <a href="xmpp:andy_@jabber.ccc.de?subscribe">andy_@jabber.ccc.de</a>
</address>
</blockquote>
</div>
@ -179,7 +185,8 @@
<!-- Placed at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/bootstrap-slider.js"></script>
<script src="js/bootstrap-notify.js"></script>
<script src="js/boostrap-slider.js"></script>
<script src="js/sammy.js"></script>
<script src="js/mpd.js"></script>
</body>

585
htdocs/js/bootstrap-notify.js vendored Normal file
View file

@ -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: "<div class=\"" + pluginClassName + "-wrapper\">\n <div class=\"" + pluginClassName + "-arrow\"></div>\n <div class=\"" + pluginClassName + "-container\"></div>\n</div>",
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 + "></" + 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, '<br/>');
}
}
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: "<div>\n<span data-notify-text></span>\n</div>",
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",
}
}
});

View file

@ -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( $ ) {
(function($){
var Slider = function(element, options) {
this.element = $(element);
this.picker = $('<div class="slider">'+
'<div class="slider-track">'+
'<div class="slider-selection"></div>'+
'<div class="slider-handle"></div>'+
'<div class="slider-handle"></div>'+
'</div>'+
'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'+
'</div>')
.insertBefore(this.element)
.append(this.element);
this.id = this.element.data('slider-id')||options.id;
if (this.id) {
this.picker[0].id = this.id;
}
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 = [];
if (typeof Modernizr !== 'undefined' && Modernizr.touch) {
this.touchCapable = true;
}
$(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);
}
var tooltip = this.element.data('slider-tooltip')||options.tooltip;
return $(this).each (function() {
var self=$(this);
this.tooltip = this.picker.find('.tooltip');
this.tooltipInner = this.tooltip.find('div.tooltip-inner');
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;
}
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;
}
self.addClass("slider-wrapper-jq")
.append($("<div class='progress' style='position:relative;left:0'/>")
.append("<div class='progress-bar' style='position:width: 30%;background-color: "+
options.barColor+"; -webkit-transition:none; transition:none;' />")
.append("<div class='btn btn-default ' style='position:absolute;height:100%;padding:6px 10px;margin-left:-10px;vertical-align: top'>"));
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;
}
self.find('.progress').on('mousedown', function(evt){
self.data("dragSlider","true")
.data("startPoint",evt.pageX)
.data("endPoint",evt.pageX);
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;
if(!$(evt.target).hasClass("btn")){
self.slider._setWidthFromEvent.call(self,evt.pageX,null,true);
}
else{
self.data("btnTarget","true");
}
evt.preventDefault();
evt.stopPropagation();
});
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;
$(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);
}
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
}
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();
}
});
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
];
self.slider._setValue.call(self,options.val);
});
this.offset = this.picker.offset();
this.size = this.picker[0][this.sizePos];
}
this.formater = options.formater;
var defaults={
val:50,
barColor:"#428bca"
},
_setWidthFromEvent = function(pageX,reqVals,supress) {
if(!reqVals){
reqVals = this.slider._getRequiredValues.call(this);
}
else{
reqVals = null;
}
this.layout();
var width = pageX - reqVals.pbar.offset().left,
perc = ((100.0*width) / (reqVals.progw));
if (this.touchCapable) {
// Touch: Bind touch events:
this.picker.on({
touchstart: $.proxy(this.mousedown, this)
});
} else {
this.picker.on({
mousedown: $.proxy(this.mousedown, this)
});
}
return this.slider._setValue.call(this,perc,reqVals,supress);
},
_setValue = function (val,reqVals,supress) {
if(!reqVals){
reqVals = this.slider._getRequiredValues.call(this);
}
if (tooltip === 'show') {
this.picker.on({
mouseenter: $.proxy(this.showTooltip, this),
mouseleave: $.proxy(this.hideTooltip, this)
});
} else {
this.tooltip.addClass('hide');
}
};
val = ((val<0)?0:((val>100)?100:val));
var adjVal= ((val*(100-reqVals.pbutp)/100) + (reqVals.pbutp/2));
Slider.prototype = {
constructor: Slider,
this.data("sliderValue",val);
reqVals.pbar.css({width:adjVal+"%"});
this.find('div.btn').css('left',adjVal+"%");
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;
},
if(supress !== true){
this.trigger("slider.newValue",{val:Math.round(val)});
}
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';
}
},
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);
mousedown: function(ev) {
return {
pbar:pbar,
progw:progw,
pbutp:pbutp
};
};
// Touch: Get the original event:
if (this.touchCapable && ev.type === 'touchstart') {
ev = ev.originalEvent;
}
$.fn.slider = slider;
$.fn.slider.defaults = defaults;
$.fn.slider._getRequiredValues = _getRequiredValues ;
$.fn.slider._setWidthFromEvent = _setWidthFromEvent;
$.fn.slider._setValue = _setValue;
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 );
})(jQuery);

View file

@ -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: '&hellip;',
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 : $('<ul></ul>').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('<li class="disabled"><span class="ellipse">' + o.ellipseText + '</span></li>');
} 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('<li class="disabled"><span class="ellipse">' + o.ellipseText + '</span></li>');
} 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 = $('<li></li>'), $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 = $('<span class="current">' + (options.text) + '</span>');
} else {
$link = $('<a href="' + o.hrefTextPrefix + (pageIndex + 1) + o.hrefTextSuffix + '" class="page-link">' + (options.text) + '</a>');
$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);

View file

@ -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.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");
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');
});
$('#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 = "/";
this.get(/\#\/browse\/(.*)/, function() {
current_app = 'browse';
$('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/\">root</a></li>");
$('#salamisandwich').find("tr:gt(0)").remove();
var path = this.params['splat'][0];
//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("<li class=\"active\">"+ chunk + "</li>");
return;
}
$.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
full_path = full_path + chunk;
$('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
full_path += "/";
});
$('#browse').addClass('active');
});
$('#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("<li class=\"active\">"+ chunk + "</li>");
return;
}
this.get("/", function(context) {
context.redirect("#/");
});
full_path = full_path + chunk;
$('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
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");
}
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();
try {
socket.onopen = function() {
$('.top-right').notify({
message:{text:"Connected"},
fadeOut: { enabled: true, delay: 1000 }
}).show();
/* Push Initial state on first visit */
//$(window).trigger("statechange")
}
app.run();
}
socket.onmessage =function got_packet(msg) {
console.log(typeof msg);
if(msg instanceof MessageEvent) {
if(msg.data === last_state)
return;
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;
}
var obj = JSON.parse(msg.data);
} else {
var obj = msg;
}
switch (obj.type) {
case "playlist":
if(current_app !== 'playlist')
break;
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').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(
"<tr trackid=\"" + obj.data[song].id + "\"><td>" + obj.data[song].pos + "</td>" +
"<td>"+ obj.data[song].title +"</td>" +
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
"</td></tr>");
}
$('#salamisandwich > tbody').append(
"<tr trackid=\"" + obj.data[song].id + "\"><td>" + obj.data[song].pos + "</td>" +
"<td>"+ obj.data[song].title +"</td>" +
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
"</td></tr>");
}
$('#salamisandwich > tbody > tr').on({
mouseover: function(){
if($(this).children().last().has("a").length == 0)
$(this).children().last().append(
"<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
"onclick=\"socket.send('MPD_API_RM_TRACK," + $(this).attr("trackid") +"'); $(this).parents('tr').remove();\">" +
"<span class=\"glyphicon glyphicon-trash\"></span></a>")
.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();
}
});
$('#salamisandwich > tbody > tr').on({
mouseover: function(){
if($(this).children().last().has("a").length == 0)
$(this).children().last().append(
"<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
"onclick=\"socket.send('MPD_API_RM_TRACK," + $(this).attr("trackid") +"'); $(this).parents('tr').remove();\">" +
"<span class=\"glyphicon glyphicon-trash\"></span></a>")
.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(
"<tr uri=\"" + obj.data[item].dir + "\">" +
"<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" +
"<td><a href=\"#/browse/"+ obj.data[item].dir +"\">" + basename(obj.data[item].dir) +"</a></td>" +
"<td></td></tr>");
break;
case "song":
var minutes = Math.floor(obj.data[item].duration / 60);
var seconds = obj.data[item].duration - minutes * 60;
$('#salamisandwich > tbody').append(
"<tr uri=\"" + obj.data[item].uri + "\">" +
"<td><span class=\"glyphicon glyphicon-music\"></span></td>" +
"<td>" + obj.data[item].title +"</td>" +
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"</td></tr>");
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(
"<a role=\"button\" class=\"pull-right btn-group-hover\" " +
"onclick=\"socket.send('MPD_API_ADD_TRACK," + $(this).attr("uri") +"'); $(this).parents('tr').addClass('active');\">" +
"<span class=\"glyphicon glyphicon-plus\"></span></a>")
.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");
break;
case "browse":
if(current_app !== 'browse')
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;
}
for (var item in obj.data) {
switch(obj.data[item].type) {
case "directory":
$('#salamisandwich > tbody').append(
"<tr uri=\"" + obj.data[item].dir + "\" class=\"dir\">" +
"<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" +
"<td><a>" + basename(obj.data[item].dir) + "</a></td>" +
"<td></td></tr>");
break;
case "song":
var minutes = Math.floor(obj.data[item].duration / 60);
var seconds = obj.data[item].duration - minutes * 60;
$('#salamisandwich > tbody').append(
"<tr uri=\"" + obj.data[item].uri + "\" class=\"song\">" +
"<td><span class=\"glyphicon glyphicon-music\"></span></td>" +
"<td>" + obj.data[item].title +"</td>" +
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"</td></tr>");
break;
case "playlist":
break;
}
}
$('#salamisandwich > tbody > tr').on({
mouseenter: function(){
if($(this).is(".dir")) {
$(this).children().last().append(
"<a role=\"button\" class=\"pull-right btn-group-hover\">" +
"Add Directory <span class=\"glyphicon glyphicon-plus\"></span></a>")
.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;
}
}
socket.onclose = function(){
console.log("Disconnected");
}
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();
}
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('<p>Error' + exception);
}
} catch(exception) {
alert('<p>Error' + exception);
}
}
function get_appropriate_ws_url()
{
var pcol;
var u = document.URL;
var pcol;
var u = document.URL;
/*
/* We open the websocket encrypted if this page came on an
/* https:// url itself, otherwise unencrypted
/*/
/*
/* 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);
}
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('/');
u = u.split('/');
return pcol + u[0];
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");
$("#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");
}
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);
});
}

View file

@ -2,6 +2,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <mpd/client.h>
@ -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);
/* Copy size to content-length field */
sprintf(buf, "%6lu", response_size);
memcpy(p + sizeof(http_header) - 11, buf, 6);
free(response_buffer);
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
*/

View file

@ -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\":[ ");

View file

@ -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"