mpd.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. /* ympd
  2. (c) 2013-2014 Andrew Karpow <andy@ndyk.de>
  3. This project's homepage is: https://www.ympd.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; version 2 of the License.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License along
  12. with this program; if not, write to the Free Software Foundation, Inc.,
  13. Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  14. */
  15. var socket;
  16. var last_state;
  17. var last_outputs;
  18. var current_app;
  19. var pagination = 0;
  20. var browsepath;
  21. var lastSongTitle = "";
  22. var current_song = new Object();
  23. var MAX_ELEMENTS_PER_PAGE = 512;
  24. var dirble_selected_cat = "";
  25. var dirble_catid = "";
  26. var dirble_page = 1;
  27. var isTouch = Modernizr.touch ? 1 : 0;
  28. var filter = undefined;
  29. var dirble_api_token = "";
  30. var dirble_stations = false;
  31. var app = $.sammy(function() {
  32. function runBrowse() {
  33. current_app = 'queue';
  34. $('#breadcrump').addClass('hide');
  35. $('#filter').addClass('hide');
  36. $('#salamisandwich').removeClass('hide').find("tr:gt(0)").remove();
  37. $('#dirble_panel').addClass('hide');
  38. socket.send('MPD_API_GET_QUEUE,'+pagination);
  39. $('#panel-heading').text("Queue");
  40. $('#queue').addClass('active');
  41. }
  42. function prepare() {
  43. $('#nav_links > li').removeClass('active');
  44. $('.page-btn').addClass('hide');
  45. $('#add-all-songs').hide();
  46. pagination = 0;
  47. browsepath = '';
  48. }
  49. this.get(/\#\/(\d+)/, function() {
  50. prepare();
  51. pagination = parseInt(this.params['splat'][0]);
  52. runBrowse();
  53. });
  54. this.get(/\#\/browse\/(\d+)\/(.*)/, function() {
  55. prepare();
  56. browsepath = this.params['splat'][1];
  57. pagination = parseInt(this.params['splat'][0]);
  58. current_app = 'browse';
  59. $('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/0/\" onclick=\"set_filter()\">root</a></li>");
  60. $('#filter').removeClass('hide');
  61. $('#salamisandwich').removeClass('hide').find("tr:gt(0)").remove();
  62. $('#dirble_panel').addClass('hide');
  63. socket.send('MPD_API_GET_BROWSE,'+pagination+','+(browsepath ? browsepath : "/"));
  64. // Don't add all songs from root
  65. if (browsepath) {
  66. var add_all_songs = $('#add-all-songs');
  67. add_all_songs.off(); // remove previous binds
  68. add_all_songs.on('click', function() {
  69. socket.send('MPD_API_ADD_TRACK,'+browsepath);
  70. });
  71. add_all_songs.show();
  72. }
  73. $('#panel-heading').text("Browse database: "+browsepath);
  74. var path_array = browsepath.split('/');
  75. var full_path = "";
  76. $.each(path_array, function(index, chunk) {
  77. if(path_array.length - 1 == index) {
  78. $('#breadcrump').append("<li class=\"active\">"+ chunk + "</li>");
  79. return;
  80. }
  81. full_path = full_path + chunk;
  82. $('#breadcrump').append("<li><a href=\"#/browse/0/" + full_path + "\">"+chunk+"</a></li>");
  83. full_path += "/";
  84. });
  85. $('#browse').addClass('active');
  86. });
  87. this.get(/\#\/search\/(.*)/, function() {
  88. current_app = 'search';
  89. $('#salamisandwich').find("tr:gt(0)").remove();
  90. $('#dirble_panel').addClass('hide');
  91. var searchstr = this.params['splat'][0];
  92. $('#search > div > input').val(searchstr);
  93. socket.send('MPD_API_SEARCH,' + searchstr);
  94. $('#panel-heading').text("Search: "+searchstr);
  95. });
  96. this.get(/\#\/dirble\/(\d+)\/(\d+)/, function() {
  97. prepare();
  98. current_app = 'dirble';
  99. $('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/dirble/\">Categories</a></li><li>"+dirble_selected_cat+"</li>");
  100. $('#salamisandwich').addClass('hide');
  101. $('#dirble_panel').removeClass('hide');
  102. $('#dirble_loading').removeClass('hide');
  103. $('#dirble_left').find("tr:gt(0)").remove();
  104. $('#dirble_right').find("tr:gt(0)").remove();
  105. $('#panel-heading').text("Dirble");
  106. $('#dirble').addClass('active');
  107. $('#next').addClass('hide');
  108. if (this.params['splat'][1] > 1) $('#prev').removeClass('hide');
  109. else $('#prev').addClass('hide');
  110. dirble_catid = this.params['splat'][0];
  111. dirble_page = this.params['splat'][1];
  112. dirble_stations = true;
  113. if (dirble_api_token) { dirble_load_stations(); }
  114. });
  115. this.get(/\#\/dirble\//, function() {
  116. prepare();
  117. current_app = 'dirble';
  118. $('#breadcrump').removeClass('hide').empty().append("<li>Categories</li>");
  119. $('#salamisandwich').addClass('hide');
  120. $('#dirble_panel').removeClass('hide');
  121. $('#dirble_loading').removeClass('hide');
  122. $('#dirble_left').find("tr:gt(0)").remove();
  123. $('#dirble_right').find("tr:gt(0)").remove();
  124. $('#panel-heading').text("Dirble");
  125. $('#dirble').addClass('active');
  126. dirble_stations = false;
  127. if (dirble_api_token) { dirble_load_categories(); }
  128. });
  129. this.get("/", function(context) {
  130. context.redirect("#/0");
  131. });
  132. });
  133. $(document).ready(function(){
  134. webSocketConnect();
  135. $("#volumeslider").slider(0);
  136. $("#volumeslider").on('slider.newValue', function(evt,data){
  137. socket.send("MPD_API_SET_VOLUME,"+data.val);
  138. });
  139. $('#progressbar').slider(0);
  140. $("#progressbar").on('slider.newValue', function(evt,data){
  141. if(current_song && current_song.currentSongId >= 0) {
  142. var seekVal = Math.ceil(current_song.totalTime*(data.val/100));
  143. socket.send("MPD_API_SET_SEEK,"+current_song.currentSongId+","+seekVal);
  144. }
  145. });
  146. $('#addstream').on('shown.bs.modal', function () {
  147. $('#streamurl').focus();
  148. })
  149. $('#addstream form').on('submit', function (e) {
  150. addStream();
  151. });
  152. if(!notificationsSupported())
  153. $('#btnnotify').addClass("disabled");
  154. else
  155. if ($.cookie("notification") === "true")
  156. $('#btnnotify').addClass("active")
  157. add_filter();
  158. });
  159. function webSocketConnect() {
  160. if (typeof MozWebSocket != "undefined") {
  161. socket = new MozWebSocket(get_appropriate_ws_url());
  162. } else {
  163. socket = new WebSocket(get_appropriate_ws_url());
  164. }
  165. try {
  166. socket.onopen = function() {
  167. console.log("connected");
  168. $('.top-right').notify({
  169. message:{text:"Connected to ympd"},
  170. fadeOut: { enabled: true, delay: 500 }
  171. }).show();
  172. app.run();
  173. /* emit initial request for output names */
  174. socket.send('MPD_API_GET_OUTPUTS');
  175. /* emit initial request for dirble api token */
  176. socket.send('MPD_API_GET_DIRBLEAPITOKEN');
  177. }
  178. socket.onmessage = function got_packet(msg) {
  179. if(msg.data === last_state || msg.data.length == 0)
  180. return;
  181. var obj = JSON.parse(msg.data);
  182. switch (obj.type) {
  183. case 'queue':
  184. if(current_app !== 'queue')
  185. break;
  186. $('#salamisandwich > tbody').empty();
  187. for (var song in obj.data) {
  188. var minutes = Math.floor(obj.data[song].duration / 60);
  189. var seconds = obj.data[song].duration - minutes * 60;
  190. $('#salamisandwich > tbody').append(
  191. "<tr trackid=\"" + obj.data[song].id + "\"><td>" + (obj.data[song].pos + 1) + "</td>" +
  192. "<td>" + obj.data[song].artist + "<br /><span>" + obj.data[song].album + "</span></td>" +
  193. "<td>" + obj.data[song].title + "</td>" +
  194. "<td>" + minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
  195. "</td><td></td></tr>");
  196. }
  197. if(obj.data.length && obj.data[obj.data.length-1].pos + 1 >= pagination + MAX_ELEMENTS_PER_PAGE)
  198. $('#next').removeClass('hide');
  199. if(pagination > 0)
  200. $('#prev').removeClass('hide');
  201. if ( isTouch ) {
  202. $('#salamisandwich > tbody > tr > td:last-child').append(
  203. "<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
  204. "onclick=\"trash($(this).parents('tr'));\">" +
  205. "<span class=\"glyphicon glyphicon-trash\"></span></a>");
  206. } else {
  207. $('#salamisandwich > tbody > tr').on({
  208. mouseover: function(){
  209. var doomed = $(this);
  210. if ( $('#btntrashmodeup').hasClass('active') )
  211. doomed = $("#salamisandwich > tbody > tr:lt(" + ($(this).index() + 1) + ")");
  212. if ( $('#btntrashmodedown').hasClass('active') )
  213. doomed = $("#salamisandwich > tbody > tr:gt(" + ($(this).index() - 1) + ")");
  214. $.each(doomed, function(){
  215. if($(this).children().last().has("a").length == 0)
  216. $(this).children().last().append(
  217. "<a class=\"pull-right btn-group-hover\" href=\"#/\" " +
  218. "onclick=\"trash($(this).parents('tr'));\">" +
  219. "<span class=\"glyphicon glyphicon-trash\"></span></a>")
  220. .find('a').fadeTo('fast',1);
  221. });
  222. },
  223. mouseleave: function(){
  224. var doomed = $(this);
  225. if ( $('#btntrashmodeup').hasClass('active') )
  226. doomed = $("#salamisandwich > tbody > tr:lt(" + ($(this).index() + 1) + ")");
  227. if ( $('#btntrashmodedown').hasClass('active') )
  228. doomed = $("#salamisandwich > tbody > tr:gt(" + ($(this).index() - 1) + ")");
  229. $.each(doomed, function(){$(this).children().last().find("a").stop().remove();});
  230. }
  231. });
  232. };
  233. $('#salamisandwich > tbody > tr').on({
  234. click: function() {
  235. $('#salamisandwich > tbody > tr').removeClass('active');
  236. socket.send('MPD_API_PLAY_TRACK,'+$(this).attr('trackid'));
  237. $(this).addClass('active');
  238. },
  239. });
  240. //Helper function to keep table row from collapsing when being sorted
  241. var fixHelperModified = function(e, tr) {
  242. var $originals = tr.children();
  243. var $helper = tr.clone();
  244. $helper.children().each(function(index)
  245. {
  246. $(this).width($originals.eq(index).width())
  247. });
  248. return $helper;
  249. };
  250. //Make queue table sortable
  251. $('#salamisandwich > tbody').sortable({
  252. helper: fixHelperModified,
  253. stop: function(event,ui) {renumber_table('#salamisandwich',ui.item)}
  254. }).disableSelection();
  255. break;
  256. case 'search':
  257. $('#wait').modal('hide');
  258. case 'browse':
  259. if(current_app !== 'browse' && current_app !== 'search')
  260. break;
  261. /* The use of encodeURI() below might seem useless, but it's not. It prevents
  262. * some browsers, such as Safari, from changing the normalization form of the
  263. * URI from NFD to NFC, breaking our link with MPD.
  264. */
  265. if ($('#salamisandwich > tbody').is(':ui-sortable')) {
  266. $('#salamisandwich > tbody').sortable('destroy');
  267. }
  268. for (var item in obj.data) {
  269. switch(obj.data[item].type) {
  270. case 'directory':
  271. var clazz = 'dir';
  272. if (filter !== undefined) {
  273. var first = obj.data[item].dir[0];
  274. if (filter === "#" && isNaN(first)) {
  275. clazz += ' hide';
  276. } else if (filter >= "A" && filter <= "Z" && first.toUpperCase() !== filter) {
  277. clazz += ' hide';
  278. } else if (filter === "||") {
  279. clazz += ' hide';
  280. }
  281. }
  282. $('#salamisandwich > tbody').append(
  283. "<tr uri=\"" + encodeURI(obj.data[item].dir) + "\" class=\"" + clazz + "\">" +
  284. "<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" +
  285. "<td colspan=\"2\"><a>" + basename(obj.data[item].dir) + "</a></td>" +
  286. "<td></td><td></td></tr>"
  287. );
  288. break;
  289. case 'playlist':
  290. var clazz = 'plist';
  291. if (filter !== "||") {
  292. clazz += ' hide';
  293. }
  294. $('#salamisandwich > tbody').append(
  295. "<tr uri=\"" + encodeURI(obj.data[item].plist) + "\" class=\"" + clazz + "\">" +
  296. "<td><span class=\"glyphicon glyphicon-list\"></span></td>" +
  297. "<td colspan=\"2\"><a>" + basename(obj.data[item].plist) + "</a></td>" +
  298. "<td></td><td></td></tr>"
  299. );
  300. break;
  301. case 'song':
  302. var minutes = Math.floor(obj.data[item].duration / 60);
  303. var seconds = obj.data[item].duration - minutes * 60;
  304. if (typeof obj.data[item].artist === 'undefined') {
  305. var details = "<td colspan=\"2\">" + obj.data[item].title + "</td>";
  306. } else {
  307. var details = "<td>" + obj.data[item].artist + "<br /><span>" + obj.data[item].album + "</span></td><td>" + obj.data[item].title + "</td>";
  308. }
  309. $('#salamisandwich > tbody').append(
  310. "<tr uri=\"" + encodeURI(obj.data[item].uri) + "\" class=\"song\">" +
  311. "<td><span class=\"glyphicon glyphicon-music\"></span></td>" + details +
  312. "<td>" + minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
  313. "</td><td></td></tr>"
  314. );
  315. break;
  316. case 'wrap':
  317. if(current_app == 'browse') {
  318. $('#next').removeClass('hide');
  319. } else {
  320. $('#salamisandwich > tbody').append(
  321. "<tr><td><span class=\"glyphicon glyphicon-remove\"></span></td>" +
  322. "<td colspan=\"2\">Too many results, please refine your search!</td>" +
  323. "<td></td><td></td></tr>"
  324. );
  325. }
  326. break;
  327. }
  328. if(pagination > 0)
  329. $('#prev').removeClass('hide');
  330. }
  331. function appendClickableIcon(appendTo, onClickAction, glyphicon) {
  332. $(appendTo).append(
  333. "<a role=\"button\" class=\"pull-right btn-group-hover\">" +
  334. "<span class=\"glyphicon glyphicon-" + glyphicon + "\"></span></a>")
  335. .find('a').click(function(e) {
  336. e.stopPropagation();
  337. socket.send(onClickAction + "," + decodeURI($(this).parents("tr").attr("uri")));
  338. $('.top-right').notify({
  339. message:{
  340. text: "\"" + $('td:nth-last-child(3)', $(this).parents("tr")).text() + "\" added"
  341. } }).show();
  342. }).fadeTo('fast',1);
  343. }
  344. if ( isTouch ) {
  345. appendClickableIcon($("#salamisandwich > tbody > tr.dir > td:last-child"), 'MPD_API_ADD_TRACK', 'plus');
  346. appendClickableIcon($("#salamisandwich > tbody > tr.song > td:last-child"), 'MPD_API_ADD_TRACK', 'play');
  347. } else {
  348. $('#salamisandwich > tbody > tr').on({
  349. mouseenter: function() {
  350. if($(this).is(".dir"))
  351. appendClickableIcon($(this).children().last(), 'MPD_API_ADD_TRACK', 'plus');
  352. else if($(this).is(".song"))
  353. appendClickableIcon($(this).children().last(), 'MPD_API_ADD_PLAY_TRACK', 'play');
  354. },
  355. mouseleave: function(){
  356. $(this).children().last().find("a").stop().remove();
  357. }
  358. });
  359. };
  360. $('#salamisandwich > tbody > tr').on({
  361. click: function() {
  362. switch($(this).attr('class')) {
  363. case 'dir':
  364. pagination = 0;
  365. browsepath = $(this).attr("uri");
  366. $("#browse > a").attr("href", '#/browse/'+pagination+'/'+browsepath);
  367. app.setLocation('#/browse/'+pagination+'/'+browsepath);
  368. break;
  369. case 'song':
  370. socket.send("MPD_API_ADD_TRACK," + decodeURI($(this).attr("uri")));
  371. $('.top-right').notify({
  372. message:{
  373. text: "\"" + $('td:nth-last-child(3)', this).text() + "\" added"
  374. }
  375. }).show();
  376. break;
  377. case 'plist':
  378. socket.send("MPD_API_ADD_PLAYLIST," + decodeURI($(this).attr("uri")));
  379. $('.top-right').notify({
  380. message:{
  381. text: "\"" + $('td:nth-last-child(3)', this).text() + "\" added"
  382. }
  383. }).show();
  384. break;
  385. }
  386. }
  387. });
  388. break;
  389. case 'state':
  390. updatePlayIcon(obj.data.state);
  391. updateVolumeIcon(obj.data.volume);
  392. if(JSON.stringify(obj) === JSON.stringify(last_state))
  393. break;
  394. current_song.totalTime = obj.data.totalTime;
  395. current_song.currentSongId = obj.data.currentsongid;
  396. var total_minutes = Math.floor(obj.data.totalTime / 60);
  397. var total_seconds = obj.data.totalTime - total_minutes * 60;
  398. var elapsed_minutes = Math.floor(obj.data.elapsedTime / 60);
  399. var elapsed_seconds = obj.data.elapsedTime - elapsed_minutes * 60;
  400. $('#volumeslider').slider(obj.data.volume);
  401. var progress = Math.floor(100*obj.data.elapsedTime/obj.data.totalTime);
  402. $('#progressbar').slider(progress);
  403. $('#counter')
  404. .text(elapsed_minutes + ":" +
  405. (elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
  406. total_minutes + ":" + (total_seconds < 10 ? '0' : '') + total_seconds);
  407. $('#salamisandwich > tbody > tr').removeClass('active').css("font-weight", "");
  408. $('#salamisandwich > tbody > tr[trackid='+obj.data.currentsongid+']').addClass('active').css("font-weight", "bold");
  409. if(obj.data.random)
  410. $('#btnrandom').addClass("active")
  411. else
  412. $('#btnrandom').removeClass("active");
  413. if(obj.data.consume)
  414. $('#btnconsume').addClass("active")
  415. else
  416. $('#btnconsume').removeClass("active");
  417. if(obj.data.single)
  418. $('#btnsingle').addClass("active")
  419. else
  420. $('#btnsingle').removeClass("active");
  421. if(obj.data.crossfade)
  422. $('#btncrossfade').addClass("active")
  423. else
  424. $('#btncrossfade').removeClass("active");
  425. if(obj.data.repeat)
  426. $('#btnrepeat').addClass("active")
  427. else
  428. $('#btnrepeat').removeClass("active");
  429. last_state = obj;
  430. break;
  431. case 'outputnames':
  432. $('#btn-outputs-block button').remove();
  433. if (obj.data.length > 1) {
  434. $.each(obj.data, function(id, name){
  435. var btn = $('<button id="btnoutput'+id+'" class="btn btn-default" onclick="toggleoutput(this, '+id+')"><span class="glyphicon glyphicon-volume-up"></span> '+name+'</button>');
  436. btn.appendTo($('#btn-outputs-block'));
  437. });
  438. } else {
  439. $('#btn-outputs-block').addClass('hide');
  440. }
  441. /* remove cache, since the buttons have been recreated */
  442. last_outputs = '';
  443. break;
  444. case 'outputs':
  445. if(JSON.stringify(obj) === JSON.stringify(last_outputs))
  446. break;
  447. $.each(obj.data, function(id, enabled){
  448. if (enabled)
  449. $('#btnoutput'+id).addClass("active");
  450. else
  451. $('#btnoutput'+id).removeClass("active");
  452. });
  453. last_outputs = obj;
  454. break;
  455. case 'disconnected':
  456. if($('.top-right').has('div').length == 0)
  457. $('.top-right').notify({
  458. message:{text:"ympd lost connection to MPD "},
  459. type: "danger",
  460. fadeOut: { enabled: true, delay: 1000 },
  461. }).show();
  462. break;
  463. case 'update_queue':
  464. if(current_app === 'queue')
  465. socket.send('MPD_API_GET_QUEUE,'+pagination);
  466. break;
  467. case 'song_change':
  468. $('#album').text("");
  469. $('#artist').text("");
  470. $('#btnlove').removeClass("active");
  471. $('#currenttrack').text(" " + obj.data.title);
  472. var notification = "<strong><h4>" + obj.data.title + "</h4></strong>";
  473. if(obj.data.album) {
  474. $('#album').text(obj.data.album);
  475. notification += obj.data.album + "<br />";
  476. }
  477. if(obj.data.artist) {
  478. $('#artist').text(obj.data.artist);
  479. notification += obj.data.artist + "<br />";
  480. }
  481. if ($.cookie("notification") === "true")
  482. songNotify(obj.data.title, obj.data.artist, obj.data.album );
  483. else
  484. $('.top-right').notify({
  485. message:{html: notification},
  486. type: "info",
  487. }).show();
  488. break;
  489. case 'mpdhost':
  490. $('#mpdhost').val(obj.data.host);
  491. $('#mpdport').val(obj.data.port);
  492. if(obj.data.passwort_set)
  493. $('#mpd_password_set').removeClass('hide');
  494. break;
  495. case 'dirbleapitoken':
  496. dirble_api_token = obj.data;
  497. if (dirble_api_token) {
  498. $('#dirble').removeClass('hide');
  499. if (dirble_stations) { dirble_load_stations(); }
  500. else { dirble_load_categories(); }
  501. } else {
  502. $('#dirble').addClass('hide');
  503. }
  504. break;
  505. case 'error':
  506. $('.top-right').notify({
  507. message:{text: obj.data},
  508. type: "danger",
  509. }).show();
  510. default:
  511. break;
  512. }
  513. }
  514. socket.onclose = function(){
  515. console.log("disconnected");
  516. $('.top-right').notify({
  517. message:{text:"Connection to ympd lost, retrying in 3 seconds "},
  518. type: "danger",
  519. onClose: function () {
  520. webSocketConnect();
  521. }
  522. }).show();
  523. }
  524. } catch(exception) {
  525. alert('<p>Error' + exception);
  526. }
  527. }
  528. function get_appropriate_ws_url()
  529. {
  530. var pcol;
  531. var u = document.URL;
  532. var separator;
  533. /*
  534. /* We open the websocket encrypted if this page came on an
  535. /* https:// url itself, otherwise unencrypted
  536. /*/
  537. if (u.substring(0, 5) == "https") {
  538. pcol = "wss://";
  539. u = u.substr(8);
  540. } else {
  541. pcol = "ws://";
  542. if (u.substring(0, 4) == "http")
  543. u = u.substr(7);
  544. }
  545. u = u.split('#');
  546. if (/\/$/.test(u[0])) {
  547. separator = "";
  548. } else {
  549. separator = "/";
  550. }
  551. return pcol + u[0] + separator + "ws";
  552. }
  553. var updateVolumeIcon = function(volume)
  554. {
  555. $("#volume-icon").removeClass("glyphicon-volume-off");
  556. $("#volume-icon").removeClass("glyphicon-volume-up");
  557. $("#volume-icon").removeClass("glyphicon-volume-down");
  558. if(volume == 0) {
  559. $("#volume-icon").addClass("glyphicon-volume-off");
  560. } else if (volume < 50) {
  561. $("#volume-icon").addClass("glyphicon-volume-down");
  562. } else {
  563. $("#volume-icon").addClass("glyphicon-volume-up");
  564. }
  565. }
  566. var updatePlayIcon = function(state)
  567. {
  568. $("#play-icon").removeClass("glyphicon-play")
  569. .removeClass("glyphicon-pause");
  570. $('#track-icon').removeClass("glyphicon-play")
  571. .removeClass("glyphicon-pause")
  572. .removeClass("glyphicon-stop");
  573. if(state == 1) { // stop
  574. $("#play-icon").addClass("glyphicon-play");
  575. $('#track-icon').addClass("glyphicon-stop");
  576. } else if(state == 2) { // pause
  577. $("#play-icon").addClass("glyphicon-pause");
  578. $('#track-icon').addClass("glyphicon-play");
  579. } else { // play
  580. $("#play-icon").addClass("glyphicon-play");
  581. $('#track-icon').addClass("glyphicon-pause");
  582. }
  583. }
  584. function updateDB() {
  585. socket.send('MPD_API_UPDATE_DB');
  586. $('.top-right').notify({
  587. message:{text:"Updating MPD Database... "}
  588. }).show();
  589. }
  590. function clickPlay() {
  591. if($('#track-icon').hasClass('glyphicon-stop'))
  592. socket.send('MPD_API_SET_PLAY');
  593. else
  594. socket.send('MPD_API_SET_PAUSE');
  595. }
  596. function trash(tr) {
  597. if ( $('#btntrashmodeup').hasClass('active') ) {
  598. socket.send('MPD_API_RM_RANGE,0,' + (tr.index() + 1));
  599. tr.remove();
  600. } else if ( $('#btntrashmodesingle').hasClass('active') ) {
  601. socket.send('MPD_API_RM_TRACK,' + tr.attr('trackid'));
  602. tr.remove();
  603. } else if ( $('#btntrashmodedown').hasClass('active') ) {
  604. socket.send('MPD_API_RM_RANGE,' + tr.index() + ',-1');
  605. tr.remove();
  606. };
  607. }
  608. function renumber_table(tableID,item) {
  609. was = item.children("td").first().text();//Check if first item exists!
  610. is = item.index() + 1;//maybe add pagination
  611. if (was != is) {
  612. socket.send("MPD_API_MOVE_TRACK," + was + "," + is);
  613. socket.send('MPD_API_GET_QUEUE,'+pagination);
  614. }
  615. }
  616. function basename(path) {
  617. return path.split('/').reverse()[0];
  618. }
  619. function clickLove() {
  620. socket.send("MPD_API_SEND_MESSAGE,mpdas," + ($('#btnlove').hasClass('active') ? "unlove" : "love"));
  621. if ( $('#btnlove').hasClass('active') )
  622. $('#btnlove').removeClass("active");
  623. else
  624. $('#btnlove').addClass("active");
  625. }
  626. $('#btnrandom').on('click', function (e) {
  627. socket.send("MPD_API_TOGGLE_RANDOM," + ($(this).hasClass('active') ? 0 : 1));
  628. });
  629. $('#btnconsume').on('click', function (e) {
  630. socket.send("MPD_API_TOGGLE_CONSUME," + ($(this).hasClass('active') ? 0 : 1));
  631. });
  632. $('#btnsingle').on('click', function (e) {
  633. socket.send("MPD_API_TOGGLE_SINGLE," + ($(this).hasClass('active') ? 0 : 1));
  634. });
  635. $('#btncrossfade').on('click', function(e) {
  636. socket.send("MPD_API_TOGGLE_CROSSFADE," + ($(this).hasClass('active') ? 0 : 1));
  637. });
  638. $('#btnrepeat').on('click', function (e) {
  639. socket.send("MPD_API_TOGGLE_REPEAT," + ($(this).hasClass('active') ? 0 : 1));
  640. });
  641. function toggleoutput(button, id) {
  642. socket.send("MPD_API_TOGGLE_OUTPUT,"+id+"," + ($(button).hasClass('active') ? 0 : 1));
  643. }
  644. $('#trashmode').children("button").on('click', function(e) {
  645. $('#trashmode').children("button").removeClass("active");
  646. $(this).addClass("active");
  647. });
  648. $('#btnnotify').on('click', function (e) {
  649. if($.cookie("notification") === "true") {
  650. $.cookie("notification", false);
  651. } else {
  652. Notification.requestPermission(function (permission) {
  653. if(!('permission' in Notification)) {
  654. Notification.permission = permission;
  655. }
  656. if (permission === "granted") {
  657. $.cookie("notification", true, { expires: 424242 });
  658. $('btnnotify').addClass("active");
  659. }
  660. });
  661. }
  662. });
  663. function getHost() {
  664. socket.send('MPD_API_GET_MPDHOST');
  665. function onEnter(event) {
  666. if ( event.which == 13 ) {
  667. confirmSettings();
  668. }
  669. }
  670. $('#mpdhost').keypress(onEnter);
  671. $('#mpdport').keypress(onEnter);
  672. $('#mpd_pw').keypress(onEnter);
  673. $('#mpd_pw_con').keypress(onEnter);
  674. }
  675. $('#search').submit(function () {
  676. app.setLocation("#/search/"+$('#search > div > input').val());
  677. $('#wait').modal('show');
  678. setTimeout(function() {
  679. $('#wait').modal('hide');
  680. }, 10000);
  681. return false;
  682. });
  683. $('.page-btn').on('click', function (e) {
  684. switch ($(this).text()) {
  685. case "Next":
  686. if (current_app == "dirble") dirble_page++;
  687. else pagination += MAX_ELEMENTS_PER_PAGE;
  688. break;
  689. case "Previous":
  690. if (current_app == "dirble") dirble_page--
  691. else {
  692. pagination -= MAX_ELEMENTS_PER_PAGE;
  693. if(pagination <= 0)
  694. pagination = 0;
  695. }
  696. break;
  697. }
  698. switch(current_app) {
  699. case "queue":
  700. app.setLocation('#/'+pagination);
  701. break;
  702. case "browse":
  703. app.setLocation('#/browse/'+pagination+'/'+browsepath);
  704. break;
  705. case "dirble":
  706. app.setLocation("#/dirble/"+dirble_catid+"/"+dirble_page);
  707. break;
  708. }
  709. e.preventDefault();
  710. });
  711. function addStream() {
  712. if($('#streamurl').val().length > 0) {
  713. socket.send('MPD_API_ADD_TRACK,'+$('#streamurl').val());
  714. }
  715. $('#streamurl').val("");
  716. $('#addstream').modal('hide');
  717. }
  718. function saveQueue() {
  719. if($('#playlistname').val().length > 0) {
  720. socket.send('MPD_API_SAVE_QUEUE,'+$('#playlistname').val());
  721. }
  722. $('#savequeue').modal('hide');
  723. }
  724. function confirmSettings() {
  725. if($('#mpd_pw').val().length + $('#mpd_pw_con').val().length > 0) {
  726. if ($('#mpd_pw').val() !== $('#mpd_pw_con').val())
  727. {
  728. $('#mpd_pw_con').popover('show');
  729. setTimeout(function() {
  730. $('#mpd_pw_con').popover('hide');
  731. }, 2000);
  732. return;
  733. } else
  734. socket.send('MPD_API_SET_MPDPASS,'+$('#mpd_pw').val());
  735. }
  736. socket.send('MPD_API_SET_MPDHOST,'+$('#mpdport').val()+','+$('#mpdhost').val());
  737. $('#settings').modal('hide');
  738. }
  739. $('#mpd_password_set > button').on('click', function (e) {
  740. socket.send('MPD_API_SET_MPDPASS,');
  741. $('#mpd_pw').val("");
  742. $('#mpd_pw_con').val("");
  743. $('#mpd_password_set').addClass('hide');
  744. })
  745. function notificationsSupported() {
  746. return "Notification" in window;
  747. }
  748. function songNotify(title, artist, album) {
  749. /*var opt = {
  750. type: "list",
  751. title: title,
  752. message: title,
  753. items: []
  754. }
  755. if(artist.length > 0)
  756. opt.items.push({title: "Artist", message: artist});
  757. if(album.length > 0)
  758. opt.items.push({title: "Album", message: album});
  759. */
  760. //chrome.notifications.create(id, options, creationCallback);
  761. var textNotification = "";
  762. if(typeof artist != 'undefined' && artist.length > 0)
  763. textNotification += " " + artist;
  764. if(typeof album != 'undefined' && album.length > 0)
  765. textNotification += "\n " + album;
  766. var notification = new Notification(title, {icon: 'assets/favicon.ico', body: textNotification});
  767. setTimeout(function(notification) {
  768. notification.close();
  769. }, 3000, notification);
  770. }
  771. $(document).keydown(function(e){
  772. if (e.target.tagName == 'INPUT') {
  773. return;
  774. }
  775. switch (e.which) {
  776. case 37: //left
  777. socket.send('MPD_API_SET_PREV');
  778. break;
  779. case 39: //right
  780. socket.send('MPD_API_SET_NEXT');
  781. break;
  782. case 32: //space
  783. clickPlay();
  784. break;
  785. default:
  786. return;
  787. }
  788. e.preventDefault();
  789. });
  790. function dirble_load_categories() {
  791. dirble_page = 1;
  792. $.getJSON( "https://api.dirble.com/v2/categories?token=" + dirble_api_token, function( data ) {
  793. $('#dirble_loading').addClass('hide');
  794. data = data.sort(function(a, b) {
  795. return (a.title > b.title) ? 1 : 0;
  796. });
  797. var max = data.length - data.length%2;
  798. for(i = 0; i < max; i+=2) {
  799. $('#dirble_left > tbody').append(
  800. "<tr><td catid=\""+data[i].id+"\">"+data[i].title+"</td></tr>"
  801. );
  802. $('#dirble_right > tbody').append(
  803. "<tr><td catid=\""+data[i+1].id+"\">"+data[i+1].title+"</td></tr>"
  804. );
  805. }
  806. if (max != data.length) {
  807. $('#dirble_left > tbody').append(
  808. "<tr><td catid=\""+data[max].id+"\">"+data[max].title+"</td></tr>"
  809. );
  810. }
  811. $('#dirble_left > tbody > tr > td').on({
  812. click: function() {
  813. dirble_selected_cat = $(this).text();
  814. dirble_catid = $(this).attr("catid");
  815. app.setLocation("#/dirble/"+dirble_catid+"/"+dirble_page);
  816. }
  817. });
  818. $('#dirble_right > tbody > tr > td').on({
  819. click: function() {
  820. dirble_selected_cat = $(this).text();
  821. dirble_catid = $(this).attr("catid");
  822. app.setLocation("#/dirble/"+dirble_catid+"/"+dirble_page);
  823. }
  824. });
  825. });
  826. }
  827. function dirble_load_stations() {
  828. $.getJSON( "https://api.dirble.com/v2/category/"+dirble_catid+"/stations?page="+dirble_page+"&per_page=20&token=" + dirble_api_token, function( data ) {
  829. $('#dirble_loading').addClass('hide');
  830. if (data.length == 20) $('#next').removeClass('hide');
  831. var max = data.length - data.length%2;
  832. for(i = 0; i < max; i+=2) {
  833. $('#dirble_left > tbody').append(
  834. "<tr><td radioid=\""+data[i].id+"\">"+data[i].name+"</td></tr>"
  835. );
  836. $('#dirble_right > tbody').append(
  837. "<tr><td radioid=\""+data[i+1].id+"\">"+data[i+1].name+"</td></tr>"
  838. );
  839. }
  840. if (max != data.length) {
  841. $('#dirble_left > tbody').append(
  842. "<tr><td radioid=\""+data[max].id+"\">"+data[max].name+"</td></tr>"
  843. );
  844. }
  845. $('#dirble_left > tbody > tr > td').on({
  846. click: function() {
  847. var _this = $(this);
  848. $.getJSON( "https://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=" + dirble_api_token, function( data ) {
  849. socket.send("MPD_API_ADD_TRACK," + data.streams[0].stream);
  850. $('.top-right').notify({
  851. message:{
  852. text: _this.text() + " added"
  853. }
  854. }).show();
  855. });
  856. },
  857. mouseenter: function() {
  858. var _this = $(this);
  859. $(this).last().append(
  860. "<a role=\"button\" class=\"pull-right btn-group-hover\">" +
  861. "<span class=\"glyphicon glyphicon-play\"></span></a>").find('a').click(function(e) {
  862. e.stopPropagation();
  863. $.getJSON( "https://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=" + dirble_api_token, function( data ) {
  864. socket.send("MPD_API_ADD_PLAY_TRACK," + data.streams[0].stream);
  865. $('.top-right').notify({
  866. message:{
  867. text: _this.text() + " added"
  868. }
  869. }).show();
  870. });
  871. }).fadeTo('fast',1);
  872. },
  873. mouseleave: function(){
  874. $(this).last().find("a").stop().remove();
  875. }
  876. });
  877. $('#dirble_right> tbody > tr > td').on({
  878. click: function() {
  879. var _this = $(this);
  880. $.getJSON( "https://api.dirble.com/v2/station/"+$(this).attr("radioid")+"?token=" + dirble_api_token, function( data ) {
  881. socket.send("MPD_API_ADD_TRACK," + data.streams[0].stream);
  882. $('.top-right').notify({
  883. message:{
  884. text: _this.text() + " added"
  885. }
  886. }).show();
  887. });
  888. },
  889. mouseenter: function() {
  890. var _this = $(this);
  891. $(this).last().append(
  892. "<a role=\"button\" class=\"pull-right btn-group-hover\">" +
  893. "<span class=\"glyphicon glyphicon-play\"></span></a>").find('a').click(function(e) {
  894. e.stopPropagation();
  895. $.getJSON( "https://api.dirble.com/v2/station/"+_this.attr("radioid")+"?token=" + dirble_api_token, function( data ) {
  896. socket.send("MPD_API_ADD_PLAY_TRACK," + data.streams[0].stream);
  897. $('.top-right').notify({
  898. message:{
  899. text: _this.text() + " added"
  900. }
  901. }).show();
  902. });
  903. }).fadeTo('fast',1);
  904. },
  905. mouseleave: function(){
  906. $(this).last().find("a").stop().remove();
  907. }
  908. });
  909. });
  910. }
  911. function set_filter (c) {
  912. filter = c;
  913. $.each($('#salamisandwich > tbody > tr.dir'), function(i, line) {
  914. var first = $(line).attr('uri')[0];
  915. if (filter === undefined) {
  916. $(line).removeClass('hide');
  917. }
  918. else if (filter === "#") {
  919. if (!isNaN(first)) {
  920. $(line).removeClass('hide');
  921. } else {
  922. $(line).addClass('hide');
  923. }
  924. }
  925. else if (filter >= "A" && filter <= "Z") {
  926. if (first.toUpperCase() === filter) {
  927. $(line).removeClass('hide');
  928. } else {
  929. $(line).addClass('hide');
  930. }
  931. }
  932. else if (filter === "||") {
  933. $(line).addClass('hide');
  934. }
  935. });
  936. $.each($('#salamisandwich > tbody > tr.plist'), function(i, line) {
  937. if (filter === undefined) {
  938. $(line).removeClass('hide');
  939. } else if (filter === "||") {
  940. $(line).removeClass('hide');
  941. } else {
  942. $(line).addClass('hide');
  943. }
  944. });
  945. }
  946. function add_filter () {
  947. $('#filter').append('&nbsp;<a onclick="set_filter(\'#\')" href="#/browse/0/">#</a>');
  948. for (i = 65; i <= 90; i++) {
  949. var c = String.fromCharCode(i);
  950. $('#filter').append('&nbsp;<a onclick="set_filter(\'' + c + '\')" href="#/browse/0/">' + c + '</a>');
  951. }
  952. $('#filter').append('&nbsp;<a onclick="set_filter(\'||\')" href="#/browse/0/" class="glyphicon glyphicon-list"></a>');
  953. }