2 Pitchfork Music Player Daemon Client
3 Copyright (C) 2007 Roger Bystrøm
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 var position_txt_node = null;
20 var bitrate_txt_node = null;
21 var ALBUMART_URL = "metadata.php?pic=";
22 var OUTPUT_ID = "output_";
24 var DIR_SEPARATOR = "/";
26 var STATUS_DEFAULT_TIMEOUT = 10;
28 /* todo: put these in an object, no reason for them to be lying around here */
29 var last_update_cmd_id;
30 var problem_update_delay = 5;
35 var playing = new Object();
39 playing.pl_version = 0;
46 playing.image = ""; //
48 playing.random = 0; // random on/off
49 playing.repeat = 0; // repeat on/off
50 playing.xfade = 0; // crossfade seconds
51 playing.update = false; // dbupdate
52 playing.show_node = null; // node that is supposed to have playing status
53 playing.is_stream = false; // true if the playing song is a stream
54 playing.error = false;
55 playing.error_time = 0;
59 playing.TIME_ELAPSED = 0;
60 playing.TIME_REMAINING = 1;
62 playing.time_dtype = playing.TIME_ELAPSED;
64 /* various elements found around the document */
65 playing.disp_info = null;
66 playing.pp_button = null;
67 playing.disp_artist = null;
68 playing.disp_title = null;
69 playing.disp_album = null;
70 playing.disp_year = null;
71 playing.albumart = null;
73 var last_pl_selected = true;
75 var output_toggle_id = null;
77 var pl_overlay_id = -1;
78 var pl_overlay_write = null;
80 var status_bar = null;
82 var send_command_rm_status = false;
83 var pl_entry_clone = null;
85 var settings_time = 10;
87 var need_info_arr = null;
89 var init_failed = false;
91 var playlist_add_popup = null;
92 var playlist_save_popup = null;
94 var pagination = new Object();
95 pagination.max = 0; // max pr. page
96 pagination.page = 0; // what page we currently are on
97 pagination.pages = 0; // number of pages being advertised
98 pagination.need_update = false; // indicates wheter we need an update
99 pagination.list = null; // reference to the displaying area
100 pagination.container = null; // reference to the container
102 function init_player() {
104 status_bar = new StatusBar();
109 update_delay = update_delay * 1000;
111 else window.update_delay = 1000;
113 possliderid = setup_slider(document.getElementById('posslider'), position_adjust, LANG.POSITION);
114 set_slider_pos(possliderid, 0);
116 volsliderid = setup_slider(document.getElementById('volslider'), volume_adjust, LANG.VOLUME);
117 set_slider_pos(volsliderid, 0);
119 playlist_element = document.getElementById('playlist')
121 var pltmp_id = setup_node_move(playlist_element, playlist_move, playlist_get_move_txt);
122 add_move_doubleclick(pltmp_id, playlist_dblclick);
123 set_selection_change_handler(pltmp_id, pl_selection_changed);
125 pl_overlay_id = setup_overlay(playlist_element, new Array(10, 10, 300, 300 ), pl_overlay_open_cb, pl_overlay_close_cb);
126 pl_overlay_write = get_overlay_write_area(pl_overlay_id);
128 playing.disp_info = document.getElementById('disp_info');
129 playing.disp_artist = document.getElementById('disp_artist');
130 playing.disp_title = document.getElementById('disp_title');
131 playing.disp_album = document.getElementById('disp_album');
132 playing.disp_year = document.getElementById('disp_year');
133 playing.albumart = document.getElementById("albumart");
134 playing.pp_button = document.getElementById("pp_button");
136 pagination.list = document.getElementById('pagination_list');
137 pagination.container = document.getElementById('pagination');
138 pagination.max = pagination_max; // nice :S
140 var tmp = setting_get("time_dtype");
143 setting_set("time_dtype", playing.time_dtype);
146 if(tmp==playing.TIME_ELAPSED)
147 playing.time_dtype = playing.TIME_ELAPSED;
148 else if(tmp==playing.TIME_REMAINING)
149 playing.time_dtype = playing.TIME_REMAINING;
160 if(typeof(window.metadata_init)=='function')
162 if(typeof(window.recommend_init)=='function')
168 debug(LANG.E_INIT +": " + e.message, true);
172 /* arg-list: command to send, command to call when result is return, show status message when working,
173 don't request status update with this sendcommand,
174 post data if we should use that, if it should form-urlencoded content type should be set */
175 function send_command(command, result_callback, status_msg, nostatus, do_post, form_urlencoded) {
179 var http = new XMLHttpRequest();
180 var url = "command.php?" + command;
183 url+="&plchanges=" + playing.pl_version;
184 if(pagination.max>0) {
185 url+="&pmax=" + pagination.max + "&page=" + pagination.page;
186 //debug("pmax: " + pagination.max + ", page: " + pagination.page);
190 if(send_command_rm_status) {
192 send_command_rm_status = false;
195 http.onreadystatechange = function() {
196 if(http.readyState==4) {
198 if(http.responseText&&(resp = evaluate_json(http.responseText))) {
199 if(resp['connection']&&resp['connection']=="failed") {
200 last_update = get_time();
201 show_status_bar(LANG.E_CONNECT);
202 send_command_rm_status = true;
204 result_callback("failed");
211 show_status_bar(LANG.E_INVALID_RESPONSE);
212 send_command_rm_status = true;
213 last_update = get_time();
215 result_callback("failed");
221 if(http.status==200) {
222 var res = resp['result'];
223 var stat = resp["status"];
224 var plchanges = resp["plchanges"];
225 var has_plchanges = plchanges && stat && playing.pl_version != stat.playlist;
227 if(res&&result_callback) {
228 result_callback(res);
232 current_status_handler(stat, has_plchanges);
233 last_update = get_time();
237 playing.pl_version = stat.playlist;
238 plchanges_handler3(plchanges, stat.playlistlength);
239 /* the currently playing song might have changed if it's a stream */
240 if(playing.is_stream) {
245 /* don't remove if there's no message or a timer */
246 if(status_msg&&!status_bar.timer)
250 if(result_callback) {
251 result_callback("server operation failed");
253 show_status_bar(LANG.NO_RESPONSE); // maybe add a 10 second delay here and reconnect!
254 send_command_rm_status = true;
260 show_status_bar(status_msg, true);
265 http.open("POST", url);
266 if(form_urlencoded) {
267 http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
272 http.open("GET", url);
277 function update_callback(res) {
278 var time = update_delay;
280 time = problem_update_delay*update_delay;
281 last_update_cmd_id = setTimeout(need_update, time);
284 function reschedule_update_now() {
285 clearTimeout(last_update_cmd_id);
286 // make sure it get's run immidiately
287 last_update_cmd_id = setTimeout(need_update, 0, -update_delay);
290 function need_update(delay) {
293 var now = get_time();
294 if(now>last_update+update_delay+delay-20) { // giving it a little slack
295 send_command("ping", update_callback);
298 delay = last_update+update_delay-now;
302 last_update_cmd_id = setTimeout(need_update, delay);
307 function handle_fatal_error(txt) {
308 show_status_bar(txt);
309 clearTimeout(last_update_cmd_id);
312 /* current status handler, info is stats
313 * if has_plchanges it is assumed the data also carries plchanges
314 * which will handle resizing of the playlist */
315 function current_status_handler(info, has_plchanges) {
317 // something is wrong
318 set_slider_pos(possliderid, 0);
322 var tmp = info.playlistlength;
323 if((tmp = parseInt(tmp))>=0) {
324 if(playing.pl_size!=tmp) {
325 playing.pl_size = tmp;
326 if(pagination.max>0) {
327 // make sure size fits inside what we have as number of pages
328 var s = pagination.max * (pagination.pages);
329 if(tmp < s-pagination.max || tmp>=s) {
330 pagination_update_list(tmp);
333 playlist_resize(tmp);
336 else if(!has_plchanges) { // if it doesn't carry plchanges we have to do it here
337 playlist_resize(tmp);
342 tmp = info.updating_db;
345 if(playing.update!=tmp) {
346 playing.update = tmp;
347 show_status_bar(LANG.WAIT_UPDATING_DB, true);
350 else if(playing.update) {
351 playing.update = false;
355 var state = info["state"];
356 var volume = info[ "volume"];
357 if(volume!=null&&volume!=playing.volume) {
358 set_slider_pos(volsliderid, volume);
359 playing.volume = volume;
362 playing.repeat = info.repeat;
363 playing.random = info.random;
364 playing.xfade = info.xfade;
368 var pos = info["time"].split(":");
369 set_slider_pos(possliderid, (pos[0]*100)/pos[1]);
370 playing.length = pos[1];
372 /* complex, but seems to be somewhat better in firefox */
373 var tmp = info.bitrate;
374 if(tmp&&playing.bitrate!=tmp) {
375 var disp_info = playing.disp_info
376 if(bitrate_txt_node==null) {
377 bitrate_txt_node = document.createTextNode(LANG.BITRATE);
378 disp_info.appendChild(bitrate_txt_node);
379 if(disp_info.normalize)
380 disp_info.normalize();
381 bitrate_txt_node = disp_info.firstChild.splitText(LANG.BITRATE.length);
383 var rep = document.createTextNode(tmp);
384 disp_info.replaceChild(rep, bitrate_txt_node);
385 bitrate_txt_node = rep;
386 playing.bitrate = tmp;
389 var postxt = get_slider_txt(possliderid);
391 if(position_txt_node==null) {
392 position_txt_node = postxt.firstChild.splitText(LANG.POSITION.length);
394 var rep = create_txt(format_playing_time(pos[0], pos[1]));
395 postxt.replaceChild(rep, position_txt_node);
396 position_txt_node = rep;
399 else if(playing.state!="stop") { // state == stop and last state wasn't stop
400 set_slider_pos(possliderid, 0);
401 var disp_info = playing.disp_info;
402 var rep = document.createTextNode("0");
403 var postxt = get_slider_txt(possliderid);
404 if(bitrate_txt_node&&bitrate_txt_node!=null) {
405 disp_info.replaceChild(rep, bitrate_txt_node);
406 bitrate_txt_node = rep;
409 rep = document.createTextNode("");
410 if(position_txt_node&&position_txt_node!=null) {
411 postxt.replaceChild(rep, position_txt_node);
412 position_txt_node = rep;
415 if(state!=playing.state) {
416 playing.state = state;
417 var bt = playing.pp_button;
418 if (bt) /* we must be in read write mode to show this */ {
420 bt.src = IMAGE.BUTTON_PAUSE;
421 if(typeof(window.streaming_try_autoplay)=='function')
422 streaming_try_autoplay();
425 bt.src = IMAGE.BUTTON_PLAY;
426 if(typeof(window.streaming_try_autostop)=='function')
427 streaming_try_autostop();
432 if(typeof info.error != 'undefined' || playing.error) {
433 if(playing.error && get_time() - 10000 > playing.error_time) {
434 playing.error = false;
438 else if(typeof info.error != 'undefined' && playing.error != info.error) {
439 playing.error = info.error;
440 playing.error_time = get_time();
441 show_status_bar("MPD error: " + playing.error);
445 var c_play = info.songid;
446 if(typeof(c_play)=='undefined') {
450 select_playing_song();
452 else if(c_play!=playing.id) {
454 playing.pos = parseInt(info.song);
455 select_playing_song();
456 if(pagination_is_following()) {
457 playlist_scroll_to_playing();
459 /* this is last because it needs the id */
462 else if(playing.pos!=info.song) {
463 playing.pos = parseInt(info.song);
467 function format_playing_time(pos, total) {
468 if(playing.time_dtype==playing.TIME_REMAINING) {
469 return convert_minsec(pos-total) + "/" + convert_minsec(total);
471 else { // dtype == playing.TIME_ELAPSED || something wrong
472 return convert_minsec(pos) + "/" + convert_minsec(total);
476 function request_song_info() {
479 remove_children(playing.disp_artist);
480 remove_children(playing.disp_title);
481 remove_children(playing.disp_album);
482 if(playing.disp_year)
483 remove_children(playing.disp_year);
485 remove_children(playing.albumart);
493 playing.is_stream = false;
498 send_command("currentsong", update_current_song, false, true);
502 function update_current_song(info) {
503 var artist = info[ "Artist"];
504 var title = info["Title"];
505 var album = info[ "Album"];
506 var year = (info["Year"] == null) ? info["Date"] : info["Year"];
507 var a = playing.disp_artist;
508 var t = playing.disp_title;
509 var alb = playing.disp_album;
510 var y = playing.disp_year;
511 var new_thumb = false;
513 if(typeof(title)=='undefined')
515 if(typeof(album)=='undefined')
517 if(typeof(artist)=='undefined')
519 if(typeof(year)=='undefined')
524 if(artist!=playing.artist) {
525 playing.artist = artist;
528 a.appendChild(create_txt(artist));
530 if(playing.album != album) {
531 playing.album = album;
533 remove_children(alb);
534 alb.appendChild(create_txt(album));
536 if((playing.year != year) && y) {
540 y.appendChild(create_txt(year));
543 if(typeof(info['file'])!='undefined') {
544 var f = info['file'];
545 if(f&&f.indexOf("http://")==0)
546 playing.is_stream = true;
547 else playing.is_stream = false;
551 playing.title = title;
553 if(title==null||title=="") {
554 title = info["file"];
556 title = title.substring(title.lastIndexOf(DIR_SEPARATOR)+1);
558 t.appendChild(create_txt(title));
560 set_playing_title(artist, title);
562 if(new_thumb&&typeof(window.request_thumbnail) == 'function') {
563 setTimeout(request_thumbnail, 1);
567 function set_playing_title(artist, title) {
568 if(typeof(artist)=='undefined'||artist==null)
570 if(typeof(title)=='undefined'||title==null)
573 var wt = "Pitchfork MPD Client";
574 if(artist.length||title.length) {
582 function volume_adjust(vol) {
583 send_command("volume=" + parseInt(vol));
586 function position_adjust(pos) {
587 send_command("position=" + parseInt((pos* parseInt(playing.length))/100) + "&id=" + playing.id);
590 function convert_minsec(sec) {
591 var min = parseInt(sec/60);
592 var s = Math.abs(sec%60);
593 return (sec<0&&min==0?"-" + min:min) + ":" + (s<10?"0" + s:s);
596 function buttons_init() {
599 var elem = document.getElementById('pp_button');
600 if (elem) /* we must be in read write mode to show these buttons */
602 elem.src = IMAGE.BUTTON_PLAY;
603 add_listener(elem, "click", send_play_pause);
604 if(window.stop_button) {
605 elem = document.getElementById('stop_button');
606 elem.style.display = "";
607 elem.src = IMAGE.BUTTON_STOP;
608 add_listener(elem, "click", send_stop_cmd);
609 elem.parentNode.style.marginLeft = "-15px";
612 elem = document.getElementById("next_button");
613 elem.src = IMAGE.BUTTON_NEXT;
614 add_listener(elem, "click", send_next_song);
615 elem = document.getElementById("previous_button");
616 elem.src = IMAGE.BUTTON_PREVIOUS;
617 add_listener(elem, "click", send_previous_song);
620 /* left menu buttons */
621 elem = document.getElementById("open_directory_button");
622 elem.src = IMAGE.MENU_ITEM_DIRECTORY;
623 add_listener(elem, "click", open_pl_overlay);
624 elem = document.getElementById("crop_items_button");
625 elem.src = IMAGE.MENU_ITEM_CROP;
626 add_listener(elem, "click", remove_songs_event);
627 elem = document.getElementById("remove_items_button");
628 elem.src = IMAGE.MENU_ITEM_REMOVE;
629 add_listener(elem, "click", remove_songs_event);
631 /* server settings */
632 elem = document.getElementById("settings_header");
633 add_listener(elem, "click", open_close_settings);
634 add_listener(elem, "mousedown", stop_event);
637 elem = document.getElementById("playlist_add");
638 add_listener(elem, "click", playlist_add_button);
639 elem = document.getElementById("playlist_save");
640 add_listener(elem, "click", playlist_save_button);
643 elem = document.getElementById("status_bar_img");
644 elem.src = IMAGE.WORKING;
646 /* streaming if applicable */
647 elem = document.getElementById("streaming_open");
649 add_listener(elem, "click", streaming_open);
653 elem = get_slider_txt(possliderid);
655 add_listener(elem, "click", change_pos_dtype);
659 elem = document.getElementById("pagination");
661 add_listener(elem, "click", pagination_change_page);
662 add_listener(elem, "mousemove", pagination_scroll_view);
663 add_listener(elem, "mouseout", pagination_scroll_stop);
665 elem = document.getElementById("pagination_jump_current");
667 elem.src = IMAGE.JUMP_CURRENT;
668 add_listener(elem, "click", playlist_scroll_to_playing);
669 elem.title = LANG.JUMP_CURRENT;
671 elem = document.getElementById("pagination_follow_current");
673 add_listener(elem, "click", pagination_toggle_following);
676 elem = document.getElementById("playlist_search_btn");
678 add_listener(elem, "click", plsearch_open);
681 // set it to nothing selected
682 pl_selection_changed(false);
685 function send_play_pause(e) {
688 if(playing.state=="stop") {
691 act+="&id=" + playing.id;
693 send_command("act=" + act);
695 function send_stop_cmd(e) {
697 send_command("act=stop");
699 function send_next_song(e) {
701 send_command("act=next");
703 function send_previous_song(e) {
705 send_command("act=previous");
708 function send_update_db_cmd(e) {
710 send_command("updatedb");
713 function send_clear_error() {
714 send_command("clearerror", false, false, true);
717 function remove_songs_event(e) {
718 var inv = 'crop_items_button'==e.target.id;
719 var sel = get_pl_selection_range(inv);
721 /* nothing selected if we just removed it,
722 * or at least in theory */
723 pl_selection_changed(false);
728 send_command("remove=" + encodeURIComponent(sel), remove_songs_cb, LANG.WAIT_REMOVING);
731 function remove_songs_cb(response) {
733 show_status_bar(LANG.E_REMOVE);
734 hide_status_bar(STATUS_DEFAULT_TIMEOUT);
738 function open_close_settings(e) {
739 var sc = document.getElementById('settings_container');
741 if(sc.style.display == "block") { /* not perfect but I think there's enough vars at the top */
742 sc.firstChild.style.display = "none";
743 remove_listener(sc, "mousedown", stop_event);
744 remove_listener(sc, "click", settings_click_handler);
745 setTimeout(open_close_settings_timer, settings_time, sc, false, new Array(0, 200));
749 var dst_height = sc.scrollHeight; // || innerHeight
750 sc.style.height = "50px";
751 sc.style.overflow = "hidden";
752 remove_children(sc.firstChild);
753 sc.firstChild.style.display = "none";
754 sc.firstChild.appendChild(document.createTextNode("Loading.."));
755 sc.style.display = "block";
756 add_listener(sc, "mousedown", stop_event);
757 add_listener(sc, "click", settings_click_handler);
758 setTimeout(open_close_settings_timer, settings_time, sc, true, new Array(0, 200));
759 send_command("outputs", open_settings_cb);
763 function open_close_settings_timer(sc, isOpening, heights) {
767 else heights[1] -=ad;
769 if(heights[0]<heights[1]) {
770 sc.style.height = (isOpening?heights[0]:heights[1]) + "px";
771 setTimeout(open_close_settings_timer, settings_time, sc, isOpening, heights);
775 //sc.style.overflow = "auto";
776 sc.firstChild.style.display = "block";
777 sc.style.height = heights[1] + "px";
780 sc.style.display = "none";
781 sc.style.height = heights[0] + "px";
787 function create_settings_status_image(stat) {
788 var img = create_node("img");
789 img.className = "server_settings";
790 if(stat==1||stat=="1") {
791 img.src = IMAGE.SERVER_SETTINGS_ENABLED;
794 img.src = IMAGE.SERVER_SETTINGS_DISABLED;
799 function open_settings_cb(response) {
800 var txt = document.getElementById('settings_content');
801 remove_children(txt);
802 var img = create_node("img");
803 img.className = "server_settings";
805 function add_entry(id, stat, text, no_img) {
806 var span = create_node("span", id);
807 span.className = "server_settings";
809 var im = create_settings_status_image(stat);
811 span.appendChild(im);
813 span.appendChild(create_txt(" " + text));
814 txt.appendChild(span);
815 txt.appendChild(create_node("br"));
819 add_entry("repeat_toggle", playing.repeat, LANG.REPEAT);
820 add_entry("random_toggle", playing.random, LANG.RANDOM);
821 var xfade = add_entry("xfade_entry", playing.xfade, LANG.XFADE, true);
822 var xe = create_node("img");
824 xe.className = "server_settings";
825 xe.src = IMAGE.SERVER_SETTINGS_XFADE_DOWN;
826 xfade.appendChild(xe);
827 var i_right = xe.cloneNode(true);
828 i_right.name = "right";
829 i_right.src = IMAGE.SERVER_SETTINGS_XFADE_UP;
830 xe = create_node("span", "xfade_adjust_txt", " " + playing.xfade + " ");
831 xfade.appendChild(xe);
832 xfade.appendChild(i_right);
834 var tmp = create_node("hr");
835 tmp.className = "server_settings";
836 txt.appendChild(tmp);
837 txt.appendChild(create_txt("Outputs:"));
838 txt.appendChild(create_node("br"));
840 var outputs = response['outputs'];
841 for(var i in outputs) {
842 var id = outputs[i]["outputid"];
843 var enabled = outputs[i]["outputenabled"];
844 var s = add_entry(OUTPUT_ID + id, enabled, outputs[i]["outputname"]);
845 s.setAttribute("outputenabled", enabled);
850 txt.appendChild(create_txt(LANG.E_NO_OUTPUTS));
854 function settings_click_handler(e) {
855 for(var n = e.target; n.parentNode; n=n.parentNode) {
857 if(n.id.indexOf(OUTPUT_ID)==0&&n.id.indexOf("img")<0) {
861 else if(n.id=="repeat_toggle") {
865 else if(n.id=="random_toggle") {
869 else if(n.id=="xfade_entry") {
872 else if(n.id=="settings_container") {
880 function toggle_repeat(e) {
881 send_command("repeat=" + (parseInt(playing.repeat)==0?1:0), toggle_repeat_cb);
883 function toggle_random(e) {
884 send_command("random=" + (parseInt(playing.random)==0?1:0), toggle_random_cb);
886 function toggle_output(e) {
888 output_toggle_id = target.id;
889 id = target.id.substring(OUTPUT_ID.length);
891 if(target.getAttribute("outputenabled")==1)
895 send_command(cmd, output_change_cb);
898 function xfade_adjust(node, ev) {
899 if(!ev.target.name) {
902 var name = ev.target.name;
903 if(name!="left"&&name!="right") {
906 var xfade= parseInt(playing.xfade) + ("left"==name?-1:+1);
909 send_command("xfade=" + xfade);
910 var x = document.getElementById("xfade_adjust_txt");
912 x.firstChild.nodeValue = " " + xfade + " ";
916 function toggle_repeat_cb(response) {
917 var n = document.getElementById("repeat_toggle_img");
918 var img = create_settings_status_image(response);
919 replace_node(img, n);
920 img.id = "repeat_toggle_img";
922 function toggle_random_cb(response) {
923 var n = document.getElementById("random_toggle_img");
924 var img = create_settings_status_image(response);
925 replace_node(img, n);
926 img.id = "random_toggle_img";
928 function output_change_cb(response) {
929 if(output_toggle_id==null)
931 var n = document.getElementById(output_toggle_id);
934 var o_img = document.getElementById(output_toggle_id + "_img");
935 n.setAttribute("outputenabled", response);
936 var img = create_settings_status_image(response);
938 replace_node(img, o_img);
939 output_toggle_id = null;
943 function send_play_pos(pos) {
944 send_command("act=play&pos=" + pos);
947 function open_pl_overlay(e) {
948 if(open_overlay_idx<0) {
949 open_overlay_fixed(pl_overlay_id);
952 close_overlay(pl_overlay_id);
956 function StatusBar(txt) {
957 this.txt = document.getElementById('status_bar_txt');
958 this.img = document.getElementById('status_bar_img');
959 this.main = document.getElementById('status_bar');
963 /* status bar (could be put in toolkit though */
964 function show_status_bar(txt, working) {
965 txt = create_txt(txt);
966 remove_children(status_bar.txt);
967 status_bar.txt.appendChild(txt);
969 status_bar.img.setAttribute("working", "yeah");
972 if(status_bar.img.hasAttribute("working"))
973 status_bar.img.removeAttribute("working");
975 status_bar.main.style.display = "block";
976 /* to prevent it from disappearing again if it is showing */
977 if(status_bar.timer) {
978 clearTimeout(status_bar.timer);
979 status_bar.timer = false;
983 /* hides status-bar after optional number of seconds */
984 function hide_status_bar(time) {
985 if(typeof(time)!='undefined'&&time&&time>0) {
986 status_bar.timer = setTimeout(hide_status_bar, time*1000, false);
989 remove_children(status_bar.txt);
990 if(browser_is_opera()) {
991 opera_quirk_set_display_none(status_bar.main);
994 status_bar.main.style.display = "none";
996 status_bar.timer = false;
1000 function setup_keys() {
1002 keyboard_register_listener(send_play_pause, "k", KEYBOARD_NO_KEY, true);
1003 keyboard_register_listener(send_previous_song, "j", KEYBOARD_NO_KEY, true);
1004 keyboard_register_listener(send_next_song, "l", KEYBOARD_NO_KEY, true);
1005 keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true);
1006 /* for browsers where ctrl+shift does something else */
1007 keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_ALT_KEY, true);
1008 keyboard_register_listener(playlist_scroll_to_playing, " " , KEYBOARD_NO_KEY, true);
1010 var qa = document.getElementById('quickadd');
1011 qa.setAttribute("autocomplete", "off");
1012 add_listener(qa, "keydown", quickadd_keydown_handler); // stop it from getting to the keylisteners!
1013 add_listener(qa, "keyup", quickadd_keyup_handler);
1014 add_listener(qa, "focus", quickadd_focus);
1015 add_listener(qa, "blur", quickadd_blur);
1016 qa.title = LANG.QUICK_ADD + " [Ctrl+Shift+S]";
1019 function change_pos_dtype() {
1020 playing.time_dtype++;
1021 if(playing.time_dtype>=playing.TIME_END) {
1022 playing.time_dtype = 0;
1024 setting_set("time_dtype", playing.time_dtype);