mpd.js 42 KB

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