1 <?xml version=
"1.0" encoding=
"utf-8" ?>
2 <!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.1//EN"
3 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
4 <html xmlns=
"http://www.w3.org/1999/xhtml"
5 xmlns:
svg=
"http://www.w3.org/2000/svg">
8 <meta http-equiv=
"Content-Type" content=
"text/html;charset=utf-8" />
9 <script type=
"text/javascript" charset=
"utf-8">
11 function getPlaybackStatusUpdate() {
12 var request = new XMLHttpRequest();
13 request.onreadystatechange = function() {
14 if (request.readyState ==
4) {
15 parseUpdateResponse(request);
18 if (lastUpdate && lastUpdate.revision) {
19 request.open(
"GET",
"/ctrl-int/1/playstatusupdate?revision-number="+lastUpdate.revision, true);
22 request.open(
"GET",
"/ctrl-int/1/playstatusupdate", true);
24 //XHR binary charset opt by Marcus Granado
2006 [http://mgran.blogspot.com]
25 request.overrideMimeType('text/plain; charset=x-user-defined');
29 function getPlaylistUpdate() {
30 var request = new XMLHttpRequest();
31 request.onreadystatechange = function() {
32 if (request.readyState ==
4) {
33 parsePlaylistResponse(request);
36 request.open(
"GET",
"/ctrl-int/1/playlist", true);
37 //XHR binary charset opt by Marcus Granado
2006 [http://mgran.blogspot.com]
38 request.overrideMimeType('text/plain; charset=x-user-defined');
42 function getAlbumArt(revision) {
43 document.getElementById(
"albumart").src =
"/ctrl-int/1/nowplayingartwork?revision="+revision;
46 function getPlaylists() {
47 var request = new XMLHttpRequest();
48 request.onreadystatechange = function() {
49 if (request.readyState ==
4) {
50 var text = request.responseText;
52 var tree = new Node();
53 parseNode(tree, text,
0);
54 tree = tree.children[
0];
56 var playlists = new Array();
58 for (node in tree.children) {
59 if (tree.children[node].name =
"mlcl") {
60 var list = tree.children[node];
61 for (item in list.children) {
62 playlists.push(new Container(list.children[item]));
67 var parent = document.getElementById(
"browse-playlists");
68 while (parent.childNodes.length
> 0) {
69 parent.removeChild(parent.firstChild);
72 for (p in playlists) {
73 var item = document.createElement(
"DIV");
74 item.
className=
"item";
76 item.appendChild(createNode(
"title", playlists[p].name));
77 item.appendChild(createNode(
"count", playlists[p].items));
79 parent.appendChild(item);
83 request.open(
"GET",
"/databases/1/containers", true);
84 //XHR binary charset opt by Marcus Granado
2006 [http://mgran.blogspot.com]
85 request.overrideMimeType('text/plain; charset=x-user-defined');
89 function getArtists() {
90 var request = new XMLHttpRequest();
91 request.onreadystatechange = function() {
92 if (request.readyState ==
4) {
93 var text = request.responseText;
95 var tree = new Node();
96 parseNode(tree, text,
0);
97 tree = tree.children[
0];
99 var artists = new Array();
101 for (node in tree.children) {
102 if (tree.children[node].name =
"abar") {
103 var list = tree.children[node];
104 for (item in list.children) {
105 artists.push(list.children[item].value);
110 var parent = document.getElementById(
"browse-artists");
111 while (parent.childNodes.length
> 0) {
112 parent.removeChild(parent.firstChild);
116 var item = document.createElement(
"DIV");
117 item.
className=
"item";
119 item.appendChild(createNode(
"title", artists[a]));
121 parent.appendChild(item);
125 request.open(
"GET",
"/databases/1/browse/artists", true);
126 //XHR binary charset opt by Marcus Granado
2006 [http://mgran.blogspot.com]
127 request.overrideMimeType('text/plain; charset=x-user-defined');
132 var request = new XMLHttpRequest();
133 request.open(
"GET",
"/ctrl-int/1/nextitem", true);
138 var request = new XMLHttpRequest();
139 request.open(
"GET",
"/ctrl-int/1/previtem", true);
144 var request = new XMLHttpRequest();
145 request.open(
"GET",
"/ctrl-int/1/playpause", true);
150 var request = new XMLHttpRequest();
151 request.open(
"GET",
"/ctrl-int/1/pause", true);
155 function jump(index) {
156 var request = new XMLHttpRequest();
157 request.open(
"GET",
"/ctrl-int/1/cue?command=play&index="+index);
161 function Node(name, length) {
163 this.length = length;
165 this.children = new Array();
166 this.print = function (indent) {
167 sysout(indent + this.name +
": " + this.value +
" (" + this.length +
")");
168 for (var i =
0; i < this.children.length; i++) {
169 this.children[i].print(indent+
" ");
174 function parseUpdateResponse(response) {
175 var text = response.responseText;
177 var name = text.substring(
0,
4);
178 var length = parseInt(text,
4);
180 var tree = new Node(name, length);
182 for (var i =
8; i < text.length;) {
183 i += parseNode(tree, text, i);
186 var up = new PlayStatusUpdate(tree);
190 if (!lastUpdate || up.revision != lastUpdate.revision) {
192 getAlbumArt(up.revision);
193 if ((!lastUpdate && up.status ==
4)
194 || (lastUpdate.status ==
2 && up.status ==
4)) {
195 document.body.className =
"playing";
200 getPlaybackStatusUpdate();
203 function parsePlaylistResponse(response) {
204 var text = response.responseText;
206 var name = text.substring(
0,
4);
207 var length = parseInt(text,
4);
209 var tree = new Node(name, length);
211 for (var i =
8; i < text.length;) {
212 i += parseNode(tree, text, i);
215 var playlist = new Playlist(tree);
217 updatePlaylist(playlist);
221 function parseNode(parent, text, index, debug) {
222 var name = text.substring(index, index +
4);
223 var length = parseInt(text, index +
4);
224 var node = new Node(name, length);
226 if (debug) sysout(name +
" " + length);
228 if (name ==
"mlit" && parent.name ==
"abar") {
229 //this is a hack caused by apple reusing mlit (list)
230 //as a string container in abar records
231 node.value = parseString(text, index+
8, length);
234 switch (types[name]) {
236 node.value = parseByte(text, index +
8);
239 node.value = parseShort(text, index +
8);
242 node.value = parseInt(text, index +
8);
245 node.value = parseLong(text, index +
8);
248 node.value = parseLongLong(text, index +
8);
251 node.value = parseString(text, index +
8, length);
254 node.value = parseDate(text, index +
8);
257 node.value = parseVersion(text, index +
8);
260 for ( var i =
0; i < length;) {
261 i += parseNode(node, text,
8 + index + i);
267 parent.children.push(node);
271 function parseByte(text, index) {
272 return text.charCodeAt(index);
275 function parseShort(text, index) {
276 var i = text.charCodeAt(index) &
255;
278 i += text.charCodeAt(index +
1) &
255;
282 function parseInt(text, index) {
283 var i = text.charCodeAt(index) &
255;
285 i += text.charCodeAt(index +
1) &
255;
287 i += text.charCodeAt(index +
2) &
255;
289 i += text.charCodeAt(index +
3) &
255;
293 function parseLong(text, index) {
294 var i = text.charCodeAt(index) &
255;
296 i += text.charCodeAt(index +
1) &
255;
298 i += text.charCodeAt(index +
2) &
255;
300 i += text.charCodeAt(index +
3) &
255;
302 i += text.charCodeAt(index +
4) &
255;
304 i += text.charCodeAt(index +
5) &
255;
306 i += text.charCodeAt(index +
6) &
255;
308 i += text.charCodeAt(index +
7) &
255;
312 function parseLongLong(text, index) {
314 i.push(parseInt(text, index));
315 i.push(parseInt(text, index +
4));
316 i.push(parseInt(text, index +
8));
317 i.push(parseInt(text, index +
12));
321 function parseString(text, index, length) {
322 //needs to decode utf-
8. This is slow :-(
326 for ( var i =
0; i < length;) {
327 var c = text.charCodeAt(index + i) &
255;
329 string += String.fromCharCode(c);
331 } else if (c
> 191 && c <
224) {
332 var c2 = text.charCodeAt(index + i +
1) &
255;
333 string += String.fromCharCode(((c &
31)) <<
6 | (c2 &
63));
336 var c2 = text.charCodeAt(index + i +
1) &
255;
337 var c3 = text.charCodeAt(index + i +
2) &
255;
338 string += String.fromCharCode(((c &
15)) <<
12 | (c2 &
63) <<
6
347 function parseDate(text, index) {
348 return parseInt(text, index);
351 function parseVersion(text, index) {
352 var i = text.charCodeAt(index) &
255;
354 i += text.charCodeAt(index +
1) &
255;
356 i += text.charCodeAt(index +
2) &
255;
358 i += text.charCodeAt(index +
3) &
255;
362 function sysout(text) {
363 var p = document.createElement(
"PRE");
364 p.className =
"debug";
365 p.appendChild(document.createTextNode(text));
366 document.body.appendChild(p);
371 function PlayStatusUpdate(tree) {
381 for (child in tree.children) {
382 var node = tree.children[child];
385 this.status = node.value;
388 this.revision = node.value;
391 this.title = node.value;
394 this.artist = node.value;
397 this.album = node.value;
400 this.genre = node.value;
403 this.id = node.value[
3];
409 function Container(tree) {
411 this.persistantId =
0;
417 for (child in tree.children) {
418 var node = tree.children[child];
421 this.id = node.value;
424 this.persistantId = node.value;
427 this.name = node.value;
430 this.base = node.value;
433 this.parent = node.value;
436 this.items = node.value;
442 function Playlist(tree) {
444 this.tracks = new Array();
448 for (child in tree.children) {
449 var node = tree.children[child];
460 for (child in list.children) {
461 var node = list.children[child];
464 this.tracks.push(new Track(node));
470 function Track(tree) {
478 for (child in tree.children) {
479 var node = tree.children[child];
482 this.album = node.value;
485 this.title = node.value;
488 this.artist = node.value;
491 this.genre = node.value;
494 this.id = node.value;
500 function updateStatus(update) {
502 if (update.status ==
2) {
503 document.getElementById(
"state").className =
"stopped";
505 document.getElementById(
"play").className =
"";
506 document.getElementById(
"pause").className =
"hidden";
507 } else if (update.status ==
3) {
508 document.getElementById(
"state").className =
"paused";
509 setText(document.getElementById(
"title"), update.title);
510 setText(document.getElementById(
"artist"), update.artist);
511 setText(document.getElementById(
"album"), update.album);
512 setText(document.getElementById(
"genre"), update.genre);
514 document.getElementById(
"play").className =
"";
515 document.getElementById(
"pause").className =
"hidden";
516 } else if (update.status ==
4) {
517 document.getElementById(
"state").className =
"playing";
518 setText(document.getElementById(
"title"), update.title);
519 setText(document.getElementById(
"artist"), update.artist);
520 setText(document.getElementById(
"album"), update.album);
521 setText(document.getElementById(
"genre"), update.genre);
523 document.getElementById(
"play").className =
"hidden";
524 document.getElementById(
"pause").className =
"";
528 function setText(node, text) {
529 while (node.childNodes.length
> 0) {
530 node.removeChild(node.firstChild);
532 node.appendChild(document.createTextNode(text));
535 function updatePlaylist(playlist) {
537 var list = document.getElementById(
"playlist");
540 var current = lastUpdate.id;
542 for (i in playlist.tracks) {
543 var track = playlist.tracks[i];
545 var node = document.createElement(
"DIV");
546 node.className =
"item";
548 node.appendChild(createNode(
"album", track.album));
549 node.appendChild(createNode(
"title", track.title));
550 node.appendChild(createNode(
"artist", track.artist));
551 node.appendChild(createNode(
"genre", track.genre));
552 node.appendChild(createNode(
"id", track.id));
554 if (current && track.id == current) {
555 node.className +=
" current";
558 while (list.childNodes.length
> 2) {
559 list.removeChild(list.childNodes[
0]);
563 list.appendChild(node);
567 function createNode(type, value) {
568 var node = document.createElement(
"DIV");
569 node.className = type;
570 node.appendChild(document.createTextNode(value));
574 function registerControls() {
576 document.getElementById(
"back").onclick = function() {
577 document.body.className =
"main";
579 document.getElementById(
"playing").onclick = function() {
580 document.body.className =
"playing";
583 document.body.className =
"main";
585 document.getElementById(
"previous").onclick = function() {
588 document.getElementById(
"play").onclick = function() {
591 document.getElementById(
"pause").onclick = function() {
594 document.getElementById(
"next").onclick = function() {
598 document.getElementById(
"playlist").onclick = function(e) {
600 while (!node.className.match(
"item")) {
601 node = node.parentNode;
602 if (node == document.body)
605 var playlist = document.getElementById(
"playlist");
607 for (child in playlist.childNodes) {
608 if (playlist.childNodes[child].className
609 && playlist.childNodes[child].className
616 for (child in playlist.childNodes) {
617 if (playlist.childNodes[child] == node) {
618 jump(child - offset);
629 document.getElementById(
"browse-menu-playlists").onclick = function() {
631 document.getElementById(
"browse").className =
"playlists";
633 document.getElementById(
"browse-menu-artists").onclick = function() {
635 document.getElementById(
"browse").className =
"artists";
637 document.getElementById(
"browse-menu-albums").onclick = function() {
639 document.getElementById(
"browse").className =
"albums";
641 document.getElementById(
"browse-menu-search").onclick = function() {
643 document.getElementById(
"browse").className =
"search";
646 document.getElementById(
"browse").className =
"playlists";
649 function replacePrev() {
650 var prev = document.getElementById(
"previous");
651 prev.removeChild(prev.firstChild);
653 var ns = 'http://www.w3.org/
2000/svg';
654 var svg = document.createElementNS(ns, 'svg');
655 svg.setAttribute('viewPort', '
0,
0,
33,
30');
656 svg.setAttribute('width', '
33');
657 svg.setAttribute('height', '
30');
659 var bar = document.createElementNS(ns, 'path');
660 bar.setAttribute('d', 'm
0,
3 4,
0 0,
24 -
4,
0 z');
661 bar.setAttribute('style', 'fill: #
222;');
662 svg.appendChild(bar);
664 var la = document.createElementNS(ns, 'path');
665 la.setAttribute('d', 'm
7,
15 10,
11 0,-
22 z');
667 .setAttribute('style',
668 'fill: #
222;stroke: #
222; stroke-width:
0.2em;stroke-linejoin:round;');
671 var ra = document.createElementNS(ns, 'path');
672 ra.setAttribute('d', 'm
20,
15 10,
11 0,-
22 z');
674 .setAttribute('style',
675 'fill: #
222;stroke: #
222; stroke-width:
0.2em;stroke-linejoin:round;');
678 prev.appendChild(svg);
681 function replacePlay() {
682 var play = document.getElementById(
"play");
683 play.removeChild(play.firstChild);
685 var ns = 'http://www.w3.org/
2000/svg';
686 var svg = document.createElementNS(ns, 'svg');
687 svg.setAttribute('viewPort', '
0,
0,
30,
30');
688 svg.setAttribute('width', '
30');
689 svg.setAttribute('height', '
30');
691 var p = document.createElementNS(ns, 'path');
692 p.setAttribute('d', 'm
5,
3 22,
12 -
22,
12 z');
694 .setAttribute('style',
695 'fill: #
222;stroke: #
222; stroke-width:
0.2em;stroke-linejoin:round;');
698 play.appendChild(svg);
701 function replacePause() {
702 var pause = document.getElementById(
"pause");
703 pause.removeChild(pause.firstChild);
705 var ns = 'http://www.w3.org/
2000/svg';
706 var svg = document.createElementNS(ns, 'svg');
707 svg.setAttribute('viewPort', '
0,
0,
17,
30');
708 svg.setAttribute('width', '
30');
709 svg.setAttribute('height', '
30');
711 var l = document.createElementNS(ns, 'path');
712 l.setAttribute('d', 'm
0,
2 6,
0 0,
26 -
6,
0 z');
713 l.setAttribute('style', 'fill: #
222;');
716 var r = document.createElementNS(ns, 'path');
717 r.setAttribute('d', 'm
11,
2 6,
0 0,
26 -
6,
0 z');
718 r.setAttribute('style', 'fill: #
222;');
721 pause.appendChild(svg);
724 function replaceNext() {
725 var next = document.getElementById(
"next");
726 next.removeChild(next.firstChild);
728 var ns = 'http://www.w3.org/
2000/svg';
729 var svg = document.createElementNS(ns, 'svg');
730 svg.setAttribute('viewPort', '
0,
0,
33,
30');
731 svg.setAttribute('width', '
33');
732 svg.setAttribute('height', '
30');
734 var la = document.createElementNS(ns, 'path');
735 la.setAttribute('d', 'm
1,
4 10,
11 -
10,
11 z');
737 .setAttribute('style',
738 'fill: #
222;stroke: #
222; stroke-width:
0.2em;stroke-linejoin:round;');
741 var ra = document.createElementNS(ns, 'path');
742 ra.setAttribute('d', 'm
15,
4 10,
11 -
10,
11 z');
744 .setAttribute('style',
745 'fill: #
222;stroke: #
222; stroke-width:
0.2em;stroke-linejoin:round;');
748 var bar = document.createElementNS(ns, 'path');
749 bar.setAttribute('d', 'm
27,
3 4,
0 0,
24 -
4,
0 z');
750 bar.setAttribute('style', 'fill: #
222;');
751 svg.appendChild(bar);
753 next.appendChild(svg);
758 <script type=
"text/javascript" charset=
"utf-8">
766 var dateVal =
10; //
4-byte int
767 var version =
11; //
4 single bytes or two ints
770 var types = new Array();
771 types[
"cmst"] = list;
772 types[
"mstt"] = intVal;
773 types[
"cmsr"] = intVal;
774 types[
"caps"] = byteVal;
775 types[
"cash"] = byteVal;
776 types[
"carp"] = byteVal;
777 types[
"caas"] = intVal;
778 types[
"caar"] = intVal;
779 types[
"canp"] = longlongVal;
780 types[
"cann"] = stringVal;
781 types[
"cana"] = stringVal;
782 types[
"canl"] = stringVal;
783 types[
"cang"] = stringVal;
784 types[
"asai"] = longVal;
785 types[
"cmmk"] = intVal;
786 types[
"cant"] = intVal;
787 types[
"cast"] = intVal;
789 types[
"muty"] = intVal;
790 types[
"mtco"] = intVal;
791 types[
"mrco"] = intVal;
792 types[
"mlcl"] = list;
793 types[
"mlit"] = list;
794 types[
"minm"] = stringVal;
795 types[
"asgn"] = stringVal;
796 types[
"asal"] = stringVal;
797 types[
"asar"] = stringVal;
798 types[
"mikd"] = byteVal;
799 types[
"assp"] = intVal;
800 types[
"assr"] = intVal;
801 types[
"asst"] = intVal;
802 types[
"astm"] = intVal;
803 types[
"asai"] = longVal;
804 types[
"miid"] = intVal;
806 types[
"aply"] = list;
807 types[
"abpl"] = byteVal;
808 types[
"mpco"] = intVal;
809 types[
"mimc"] = intVal;
810 types[
"mper"] = longVal;
812 types[
"abro"] = list;
813 types[
"abar"] = list;
817 <style type=
"text/css">
822 font-family:
"Geneva", sans-serif;
830 background-color: #
222;
837 .playing #navigation {
838 background-color: #
222;
842 background-color: #ccc;
872 border-bottom: solid
2px #ccc;
882 #state .album,#state .genre {
886 border-bottom: solid
1px #ccc;
890 .album,.artist,.title {
893 .album, .artist, .genre {
904 background-color: #eee;
919 background-color: #ccc;
946 background-color: #
222;
959 .playlists #browse-playlists {
962 .artists #browse-artists {
969 <body onload=
"registerControls(); getPlaybackStatusUpdate();">
970 <div id=
"navigation">
971 <div id=
"back">Back
</div>
972 <div id=
"playing">Now Playing
</div>
976 <img id=
"albumart" src=
"" alt=
"" />
977 <div id=
"title" class=
"title"></div>
978 <div id=
"album" class=
"album"></div>
979 <div id=
"artist" class=
"artist"></div>
980 <div id=
"genre" class=
"genre"></div>
983 <div id=
"previous">previous
</div>
984 <div id=
"play">play
</div>
985 <div id=
"pause">pause
</div>
986 <div id=
"next">next
</div>
987 <div id=
"volume">volume
</div>
989 <div id=
"playlist" class=
"content">
993 <div id=
"browse-menu">
994 <div id=
"browse-menu-playlists">Playlists
</div>
995 <div id=
"browse-menu-artists">Artists
</div>
996 <div id=
"browse-menu-albums">Albums
</div>
997 <div id=
"browse-menu-search">Search
</div>
999 <div id=
"browse-playlists" class=
"content"></div>
1000 <div id=
"browse-artists" class=
"content"></div>
1001 <div id=
"browse-albums" class=
"content"></div>
1002 <div id=
"browse-search" class=
"content"></div>