Initial commit
61
README.md
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
# Winamp.js
|
||||
|
||||
A reimplementation of Winamp 2.x in jQuery and css. We use the actual skin
|
||||
assets so it *should* be compatible with other themes.
|
||||
|
||||
It comes preloaded with one track, but you can click to "eject" button to
|
||||
select your own mp3 file.
|
||||
|
||||
Looks the most polished in Chrome but works fine in modern versions of Safari
|
||||
and Firefox. Only tested on OS X.
|
||||
|
||||
## TODO
|
||||
|
||||
- Investigate real space text
|
||||
- Blog post
|
||||
- Test on IE 10
|
||||
- Handle "working" icon
|
||||
- Minimized "shade" view
|
||||
- Better presentation around it
|
||||
- Test other themes
|
||||
- Tool for selecting a theme
|
||||
- Maybe http://gildas-lormeau.github.io/zip.js/demos/demo2.html
|
||||
- Control loading state so it's not visible until it's loaded
|
||||
- Marquee effect when the title is too long
|
||||
- Check native app for behavior
|
||||
- When you load a track, do we auto-play? Do we update the time?
|
||||
- What does the scrubber do when we fastforward
|
||||
- Does it pause when we scrub?
|
||||
- What does the scrubber do when we get to the end of the file?
|
||||
- What does the top left button do?
|
||||
- What are the visualizer options?
|
||||
- What is the line graph version?
|
||||
- What is the bar graph version?
|
||||
- What do the clutter bar letters do?
|
||||
|
||||
## Someday Maybe
|
||||
|
||||
- Playlist window
|
||||
- Visualizer? (http://w-labs.at/experiments/audioviz/)
|
||||
- Actually read header info
|
||||
- Actually get kbps and khz
|
||||
- Actually get streo/mono
|
||||
- Drag and drop file selection
|
||||
- Make the window draggable
|
||||
- Responsive css so it looks reasonable on my phone
|
||||
|
||||
## Currently Impossible
|
||||
|
||||
- Equalizer and balance?
|
||||
|
||||
## Tested in modern versions of
|
||||
|
||||
- Chrome (OS X, Ubuntu) - Perfect
|
||||
- Firefox (OS X) - Perfect
|
||||
- Safari (OS X), Chrome & Safari (iOS) - Small glitch with the position handle
|
||||
|
||||
## Thanks to
|
||||
|
||||
- [skinspecs.pdf](http://members.xoom.it/skinart/tutorial/skinspecs..pdf)
|
||||
- After I started, I found someone else who did the same thing in 2002(!).
|
||||
Doesn't seem to work play in my modern browsers. http://forums.winamp.com/showthread.php?threadid=91850
|
||||
63
index.html
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Winamp.js - It really rips off the llama's ass</title>
|
||||
<link rel='stylesheet' type='text/css' href='winamp.css' />
|
||||
</head>
|
||||
<body>
|
||||
<audio id='player'></audio>
|
||||
<div id='winamp'>
|
||||
<div id='title-bar' class='selected'>
|
||||
<div id='option'></div>
|
||||
<div id='minimize'></div>
|
||||
<div id='shade'></div>
|
||||
<div id='close'></div>
|
||||
</div>
|
||||
<div class='status'>
|
||||
<div id='clutter-bar' class='disabled'></div>
|
||||
<div id='play-pause' class='stop'>
|
||||
<div id='work-indicator' class='selected'></div>
|
||||
</div>
|
||||
<div id='time'>
|
||||
<div id='minus-sign'></div>
|
||||
<div id='minute-first-digit'></div>
|
||||
<div id='minute-second-digit'></div>
|
||||
<div id='second-first-digit'></div>
|
||||
<div id='second-second-digit'></div>
|
||||
</div>
|
||||
<div id='visualization'></div>
|
||||
</div>
|
||||
<div class='media-info'>
|
||||
<div id='song-title'></div>
|
||||
<div id='kbps'></div>
|
||||
<div id='khz'></div>
|
||||
<div class='mono-stereo'>
|
||||
<div id='mono' class=''></div>
|
||||
<div id='stereo' class='selected'></div>
|
||||
</div>
|
||||
</div>
|
||||
<input id='volume' type='range' min='0' max='100' step='1' value='50' />
|
||||
<input id='balance' type='range' min='0' max='100' step='1' value='50' />
|
||||
<div class='windows'>
|
||||
<div id='equalizer'></div>
|
||||
<div id='playlist'></div>
|
||||
</div>
|
||||
<input id='position' type='range' min='0' max='100' step='1' value='0' />
|
||||
<div class='actions'>
|
||||
<div id='previous'></div>
|
||||
<div id='play'></div>
|
||||
<div id='pause'></div>
|
||||
<div id='stop'></div>
|
||||
<div id='next'></div>
|
||||
</div>
|
||||
<div id='eject'></div>
|
||||
<div class='shuffle-repeat'>
|
||||
<div id="shuffle"></div>
|
||||
<div id="repeat"></div>
|
||||
</div>
|
||||
<a id='about' target='_blank' href='http://jordaneldredge.com'></a>
|
||||
</div>
|
||||
<input id="file-input" type="file" />
|
||||
<script src="winamp.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
skins/default/AVS.BMP
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
skins/default/BALANCE.BMP
Executable file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
skins/default/CBUTTONS.BMP
Executable file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
skins/default/CLOSE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/EQCLOSE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/EQMAIN.BMP
Executable file
|
After Width: | Height: | Size: 57 KiB |
BIN
skins/default/EQNORMAL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/EQSLID.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/EQTITLE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/EQ_EX.BMP
Executable file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
skins/default/MAIN.BMP
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
skins/default/MAINMENU.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/MB.BMP
Executable file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
skins/default/MIN.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/MONOSTER.BMP
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
skins/default/NORMAL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/NUMBERS.BMP
Executable file
|
After Width: | Height: | Size: 2 KiB |
BIN
skins/default/PCLOSE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PLAYPAUS.BMP
Executable file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
skins/default/PLEDIT.BMP
Executable file
|
After Width: | Height: | Size: 22 KiB |
6
skins/default/PLEDIT.TXT
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
[Text]
|
||||
Normal=#00FF00
|
||||
Current=#FFFFFF
|
||||
NormalBG=#000000
|
||||
SelectedBG=#0000FF
|
||||
Font=Arial
|
||||
BIN
skins/default/PNORMAL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/POSBAR.BMP
Executable file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
skins/default/POSBAR.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PSIZE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PTBAR.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PVSCROLL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PWINBUT.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PWSNORM.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/PWSSIZE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
105
skins/default/REGION.TXT
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
; I stole this from the Complex skin, cause hey I thought it was cool :)
|
||||
; (and cause I'm too lazy to document it myself. :)
|
||||
; The original author is Adam Kennedy <adamk@iname.com>
|
||||
;
|
||||
; Hope you don't mind Adam :)
|
||||
;
|
||||
;
|
||||
; -Justin
|
||||
;
|
||||
;
|
||||
; P.S. you can use the section names [WindowShade] and [Equalizer]
|
||||
; for obvious purposes =)
|
||||
;
|
||||
;
|
||||
; REGION.TXT STRUCTURE FOR DEFINING SKIN MASKS
|
||||
;
|
||||
;The region information comes without any supporting documentation
|
||||
;so I thought I might as well make some.
|
||||
;
|
||||
;The region.txt in WinAmp 1.92 allows us to some interesting things with transparencies
|
||||
;But how does it work?
|
||||
;
|
||||
;Well, basically, it lets you define a set of polygons. Anything inside a polygon is drawn. Anything not in a polygon is not drawn. Simple heh?
|
||||
;For how to define it have a look below at the first block. Un-comment it now. Don't worry about commenting the actual one lower down, if WinAmp finds multiple definitions, it only does the first and ignores the rest
|
||||
;
|
||||
;How to make a mask
|
||||
;1. Type [Normal]. Real simple that one. (My guess is it is just for forward compatibility)
|
||||
;2. Just type the NumPoints= and leave the number blank, we'll fill it in later
|
||||
;3. OK, now for the fun part.
|
||||
; The co-ordinates you should type in are the OUTSIDE pixel co-ordinates in x,y format.
|
||||
; Start at the top lefthand corner and work your way CLOCKWISE around you polygon.
|
||||
; Now WinAmp ONLY accepts the PointList as one line of comma seperated variables.
|
||||
; You can use spaces, but DONT GO ONTO A NEW LINE. Clear? Good. :)
|
||||
; One last thing, don't type the first position again at the end, WinAmp joins them.
|
||||
; Putting a space between each pair is simply common sense, right?
|
||||
;4. Once your done, count the number of co-ords, and fill in that number for NumPoints.
|
||||
;
|
||||
;Oh, as a side note, the x variables go from 0 to 275, and the y from 0 to 116.
|
||||
;So if you look at the first example you can see I've gone across the second top line, gone down a bit, ducked in one pixel, gone down to the bottom, and across and back up the other side, putting another ledge on the other side.
|
||||
|
||||
;This does the outside border, and chops some bits out on the top and edges
|
||||
;[Normal]
|
||||
;NumPoints=8 ;Do this LAST!
|
||||
;PointList=0,1, 275,1, 275,14, 274,14, 274,115, 1,115, 1,14, 0,14
|
||||
|
||||
;Cool heh? Very subtle effect. Now lets try a more complex one, with multiple polygons
|
||||
;For your first hard(ish) mask, I suggest defining each element as a seperate mask.
|
||||
;It makes them a bit easier to think about.
|
||||
|
||||
;First, lets define an area that JUST does the titlebar.
|
||||
;[Normal]
|
||||
;NumPoints=4
|
||||
;PointList=0,1, 275,1, 275,14, 0,14
|
||||
|
||||
;Simple as can be :)
|
||||
;Go ahead, uncomment it and have a look. Just remember to recomment everything above it
|
||||
;Doing your areas one at a time does speed the process up a bit
|
||||
|
||||
;Now lets define JUST the area sort of inside the outer ring
|
||||
;[Normal]
|
||||
;NumPoints = 4
|
||||
;PointList = 3,15, 272,15, 272,113, 3,113
|
||||
|
||||
;Right, so say we want a mask that has, the titlebar AND the main area.
|
||||
;We just add them together
|
||||
; [Normal]
|
||||
; NumPoints = 4, 4 ;Make sure to get the order right(although it doesnt matter here =P )
|
||||
; PointList = 0,1, 275,1, 275,14, 0,14, 3,15, 272,15, 272,113, 3,113
|
||||
|
||||
;Dont forget to add that extra comma after the first set.
|
||||
; Having that bigger gap that lets you "keep it in your head" easier, it's a good idea
|
||||
|
||||
;OK, now lets try something tricky. We are going to make a mask for just one green line around the outside of the skin
|
||||
;This introduces the other "big thing" with masks. Have a look at the first three co-ordinates below. You can see the first two moves we make are to go one to the side and then go down to the bottom. Looking at the last co-ordinate you can see we will come back up at the end, making a line two wide, right?
|
||||
;WRONG!! Here comes the big important phrase.
|
||||
;IF YOU ARNT FOLLOWING THE OUTSIDE EDGE, YOUR CO-ORDINATES MARK TRANSPARENT SPACE LIMITS
|
||||
;That's right. Because I don't make a square, because I have to turn "inside" the box, I am only marking space, and what I end up with is a mask with one green line.
|
||||
;WARNING: Leave your skins dialog open when you do this one. :)
|
||||
;[Normal]
|
||||
;NumPoints = 8
|
||||
;PointList = 1,14, 2,14, 2,114, 273,114, 273,14, 274,14, 274,115, 1,115
|
||||
|
||||
;OK, as the final touch lets add lets the border we just made to the other two.
|
||||
;You should be able to work this out on your own
|
||||
;[Normal]
|
||||
;NumPoints = 4, 4, 8
|
||||
;PointList = 0,1, 275,1, 275,14, 0,14, 3,15, 272,15, 272,113, 3,113, 1,14, 2,14, 2,114, 273,114, 273,14, 274,14, 274,115, 1,115
|
||||
|
||||
;hmm... that still looks a bit tacky down the bottom right
|
||||
;So I'm going to modify it a bit
|
||||
;See if you can work out what I've had to alter just by looking at the display of it
|
||||
;[Normal]
|
||||
;NumPoints = 4, 4, 8
|
||||
;PointList = 0,1, 275,1, 275,14, 0,14, 3,15, 272,15, 272,80, 3,80, 1,14, 2,14, 2,81, 273,81, 273,14, 274,14, 274,115, 1,115
|
||||
|
||||
|
||||
; Justin's whacked trans skin :) in one, big lame messy line
|
||||
; [Normal]
|
||||
; NumPoints=20,4,4,4,8,4,4,6,6,4,4,4,4
|
||||
; PointList=0,0,19,0,19,11,114,11,114,0,156,0,156,11,243,11,243,0,275,0,275,13,266,13,266,22,264,22,264,13,111,13,111,22,109,22,109,13,0,13, 109,22,266,22,266,36,109,36, 16,88,130,88,130,105,16,105, 136,89,157,89,157,104,136,104, 22,13,22,62,102,62,102,13,100,13,100,24,24,24,24,13, 0,0,275,0,275,3,0,3, 16,72,264,72,264,81,16,81, 0,13,0,78,16,78,16,75,4,75,4,13, 275,13,275,78,264,78,264,75,272,75,272,13, 14,78,16,78,16,105,14,105, 130,81,132,81,132,105,130,105, 146,81,146,89,148,89,148,81, 130,96,136,96,136,98,130,98
|
||||
|
||||
;THE END
|
||||
;
|
||||
;Post-Script
|
||||
;The other good thing about doing your mask in bits and pieces like this is that can make multiple versions so you can change as your whims change
|
||||
BIN
skins/default/SHUFREP.BMP
Executable file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
skins/default/SONGNAME.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/TEXT.BMP
Executable file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
skins/default/TITLEBAR.BMP
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
skins/default/TITLEBAR.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
24
skins/default/VISCOLOR.TXT
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
0,0,0, // color 0 = black
|
||||
24,33,41, // color 1 = grey for dots
|
||||
239,49,16, // color 2 = top of spec
|
||||
206,41,16, // 3
|
||||
214,90,0, // 4
|
||||
214,102,0, // 5
|
||||
214,115,0, // 6
|
||||
198,123,8, // 7
|
||||
222,165,24, // 8
|
||||
214,181,33, // 9
|
||||
189,222,41, // 10
|
||||
148,222,33, // 11
|
||||
41,206,16, // 12
|
||||
50,190,16, // 13
|
||||
57,181,16, // 14
|
||||
49,156,8, // 15
|
||||
41,148,0, // 16
|
||||
24,132,8, // 17 = bottom of spec
|
||||
255,255,255, // 18 = osc 1
|
||||
214,214,222, // 19 = osc 2 (slightly dimmer)
|
||||
181,189,189, // 20 = osc 3
|
||||
160,170,175, // 21 = osc 4
|
||||
148,156,165, // 22 = osc 4
|
||||
150, 150, 150, // 23 = analyzer peak dots
|
||||
BIN
skins/default/VOLBAL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/VOLBAR.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/VOLUME.BMP
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
skins/default/WINBUT.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/WSCLOSE.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/WSMIN.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/WSNORMAL.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/WSPOSBAR.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
skins/default/WSWINBUT.CUR
Executable file
|
After Width: | Height: | Size: 766 B |
461
winamp.css
Executable file
|
|
@ -0,0 +1,461 @@
|
|||
/* Range input css reset */
|
||||
input[type=range]{ -webkit-appearance: none; margin: 0; padding: 0; background: none; }
|
||||
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; border: none; border-radius: 0; background: none; }
|
||||
input[type=range]::-moz-range-thumb { border: none; border-radius: 0; background: none; }
|
||||
input[type=range]::-moz-range-track { border: none; background: none; }
|
||||
input[type=range]:focus { outline: none; }
|
||||
input[type=range]::-moz-focus-outer { border: 0; }
|
||||
|
||||
#winamp {
|
||||
position: relative;
|
||||
background-image: url('skins/default/MAIN.BMP');
|
||||
height: 116px;
|
||||
width: 275px;
|
||||
cursor:url('skins/default/MAINMENU.CUR'), auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
top: 88px;
|
||||
left: 16px;
|
||||
height: 18px;
|
||||
width: 114px;
|
||||
}
|
||||
|
||||
#title-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 14px;
|
||||
width: 275px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
background-position: -27px -14px;
|
||||
cursor:url('skins/default/TITLEBAR.CUR'), auto;
|
||||
}
|
||||
|
||||
#title-bar.selected {
|
||||
background-position: -27px 0px;
|
||||
}
|
||||
|
||||
.llama #title-bar {
|
||||
background-position: -27px -61px;
|
||||
}
|
||||
|
||||
.llama #title-bar.selected {
|
||||
background-position: -27px -57px;
|
||||
}
|
||||
|
||||
#title-bar div {
|
||||
position: absolute;
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
top: 3px;
|
||||
cursor:url('skins/default/MAINMENU.CUR'), auto;
|
||||
}
|
||||
|
||||
#title-bar #option {
|
||||
left: 6px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
}
|
||||
#title-bar #option:active {
|
||||
background-position: 0 -9px;
|
||||
}
|
||||
|
||||
#title-bar #minimize {
|
||||
left: 244px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
background-position: -9px 0;
|
||||
}
|
||||
#title-bar #minimize:active {
|
||||
background-position: -9px -9px;
|
||||
}
|
||||
|
||||
#title-bar #shade {
|
||||
left: 254px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
background-position: 0px -18px;
|
||||
}
|
||||
#title-bar #shade:active {
|
||||
background-position: -9px -18px;
|
||||
}
|
||||
|
||||
#title-bar #close {
|
||||
left: 264px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
background-position: -18px 0px;
|
||||
cursor:url('skins/default/CLOSE.CUR'), auto;
|
||||
}
|
||||
#title-bar #close:active {
|
||||
background-position: -18px -9px;
|
||||
}
|
||||
|
||||
.status #clutter-bar {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
left: 10px;
|
||||
height: 43px;
|
||||
width: 8px;
|
||||
background-image: url('skins/default/TITLEBAR.BMP');
|
||||
background-position: -304px 0;
|
||||
}
|
||||
|
||||
.status #clutter-bar.disabled {
|
||||
background-position: -312px 0;
|
||||
}
|
||||
|
||||
.status #play-pause {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 24px;
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
background-image: url('skins/default/PLAYPAUS.BMP');
|
||||
}
|
||||
|
||||
.status #play-pause.play #work-indicator {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 9px;
|
||||
width: 3px;
|
||||
background-image: url('skins/default/PLAYPAUS.BMP');
|
||||
background-position: -39px 0;
|
||||
}
|
||||
.status #play-pause.play #work-indicator.selected {
|
||||
background-position: -36px 0;
|
||||
}
|
||||
|
||||
.status #play-pause.play { }
|
||||
.status #play-pause.pause { background-position: -9px 0; }
|
||||
.status #play-pause.stop { background-position: -18px 0; }
|
||||
.status #time {
|
||||
position: absolute;
|
||||
left: 48px;
|
||||
left: 40px;
|
||||
top: 26px;
|
||||
/* Just to make it clickable */
|
||||
height: 13px;
|
||||
width: 59px;
|
||||
}
|
||||
.status #time #minus-sign {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
background-color: red;
|
||||
width: 5px;
|
||||
height: 1px;
|
||||
background-image: url('skins/default/NUMBERS.BMP');
|
||||
background-position: -9px -6px;
|
||||
}
|
||||
.status #time.countdown #minus-sign {
|
||||
background-position: -20px -6px;
|
||||
}
|
||||
|
||||
.status #time #minute-first-digit {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
height: 13px;
|
||||
width: 9px;
|
||||
}
|
||||
.status #time #minute-second-digit {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
height: 13px;
|
||||
width: 9px;
|
||||
}
|
||||
.status #time #second-first-digit {
|
||||
position: absolute;
|
||||
left: 38px;
|
||||
height: 13px;
|
||||
width: 9px;
|
||||
}
|
||||
.status #time #second-second-digit {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
height: 13px;
|
||||
width: 9px;
|
||||
}
|
||||
|
||||
.media-info #song-title {
|
||||
position: absolute;
|
||||
left: 112px;
|
||||
top: 27px;
|
||||
width: 152px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-info #kbps {
|
||||
position: absolute;
|
||||
left: 111px;
|
||||
top: 43px;
|
||||
width: 15px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-info #khz {
|
||||
position: absolute;
|
||||
left: 156px;
|
||||
top: 43px;
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-info .mono-stereo {
|
||||
position: absolute;
|
||||
left: 212px;
|
||||
top: 41px;
|
||||
width: 57px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.media-info .mono-stereo div {
|
||||
position: absolute;
|
||||
height: 12px;
|
||||
background-image: url('skins/default/MONOSTER.BMP');
|
||||
}
|
||||
|
||||
.media-info .mono-stereo #mono {
|
||||
width: 29px;
|
||||
background-position: -29px -12px;
|
||||
}
|
||||
.media-info .mono-stereo #mono.selected {
|
||||
background-position: -29px 0;
|
||||
}
|
||||
|
||||
.media-info .mono-stereo #stereo {
|
||||
left: 27px;
|
||||
width: 29px;
|
||||
background-position: 0 -12px;
|
||||
}
|
||||
.media-info .mono-stereo #stereo.selected {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
#volume {
|
||||
position: absolute;
|
||||
left: 107px;
|
||||
top: 57px;
|
||||
height: 13px;
|
||||
width: 68px;
|
||||
background-image: url('skins/default/VOLUME.BMP');
|
||||
background-position: 0 0;
|
||||
}
|
||||
#volume::-webkit-slider-thumb {
|
||||
margin-top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
background-image: url('skins/default/VOLUME.BMP');
|
||||
background-position: -15px -422px;
|
||||
cursor:url('skins/default/VOLBAR.CUR'), auto;
|
||||
}
|
||||
#volume::-moz-range-thumb {
|
||||
margin-top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
background-image: url('skins/default/VOLUME.BMP');
|
||||
background-position: -15px -422px;
|
||||
cursor:url('skins/default/VOLBAR.CUR'), auto;
|
||||
}
|
||||
#volume:active::-webkit-slider-thumb {
|
||||
background-position: 0 -422px;
|
||||
}
|
||||
#volume:active::-moz-range-thumb {
|
||||
background-position: 0 -422px;
|
||||
}
|
||||
|
||||
#balance {
|
||||
position: absolute;
|
||||
left: 177px;
|
||||
top: 57px;
|
||||
height: 13px;
|
||||
width: 38px;
|
||||
background-image: url('skins/default/BALANCE.BMP');
|
||||
background-position: -9px 0;
|
||||
}
|
||||
|
||||
#balance::-webkit-slider-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
background-image: url('skins/default/VOLUME.BMP');
|
||||
background-position: -15px -422px;
|
||||
cursor:url('skins/default/POSBAR.CUR'), auto;
|
||||
}
|
||||
#balance::-moz-range-thumb {
|
||||
top: 1px;
|
||||
height: 11px;
|
||||
width: 14px;
|
||||
background-image: url('skins/default/VOLUME.BMP');
|
||||
background-position: -15px -422px;
|
||||
cursor:url('skins/default/POSBAR.CUR'), auto;
|
||||
}
|
||||
|
||||
#balance:active::-webkit-slider-thumb {
|
||||
background-position: 0 -422px;
|
||||
}
|
||||
#balance:active::-moz-range-thumb {
|
||||
background-position: 0 -422px;
|
||||
}
|
||||
|
||||
.windows {
|
||||
position: absolute;
|
||||
left: 219px;
|
||||
top: 58px;
|
||||
width: 46px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.windows div {
|
||||
position: absolute;
|
||||
width: 23px;
|
||||
height: 13px;
|
||||
background-image: url('skins/default/SHUFREP.BMP');
|
||||
}
|
||||
|
||||
.windows #equalizer {
|
||||
left: 0;
|
||||
background-position: 0 -61px;
|
||||
}
|
||||
.windows #equalizer:active {
|
||||
background-position: -46px -61px;
|
||||
}
|
||||
|
||||
.windows #playlist {
|
||||
left: 23px;
|
||||
background-position: -23px -61px;
|
||||
}
|
||||
.windows #playlist:active {
|
||||
background-position: -69px -61px;
|
||||
}
|
||||
|
||||
#position {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 72px;
|
||||
width: 248px;
|
||||
height: 10px;
|
||||
background-image: url('skins/default/POSBAR.BMP');
|
||||
}
|
||||
|
||||
#position::-webkit-slider-thumb {
|
||||
height: 10px;
|
||||
width: 29px;
|
||||
background-position: -248px 0;
|
||||
background-image: url('skins/default/POSBAR.BMP');
|
||||
cursor:url('skins/default/POSBAR.CUR'), auto;
|
||||
}
|
||||
#position::-moz-range-thumb {
|
||||
height: 10px;
|
||||
width: 29px;
|
||||
background-position: -248px 0;
|
||||
background-image: url('skins/default/POSBAR.BMP');
|
||||
cursor:url('skins/default/POSBAR.CUR'), auto;
|
||||
}
|
||||
#position:active::-webkit-slider-thumb {
|
||||
background-position: -278px 0;
|
||||
}
|
||||
#position:active::-moz-range-thumb {
|
||||
background-position: -278px 0;
|
||||
}
|
||||
|
||||
.actions div {
|
||||
background-image: url('skins/default/CBUTTONS.BMP');
|
||||
height: 18px;
|
||||
width: 23px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.actions #previous { background-position: 0 0; }
|
||||
.actions #previous:active { background-position: 0 -18px; }
|
||||
.actions #play { background-position: -23px 0; }
|
||||
.actions #play:active { background-position: -23px -18px; }
|
||||
.actions #pause { background-position: -46px 0; }
|
||||
.actions #pause:active { background-position: -46px -18px; }
|
||||
.actions #stop { background-position: -69px 0; }
|
||||
.actions #stop:active { background-position: -69px -18px; }
|
||||
.actions #next { background-position: -92px 0; width: 22px; }
|
||||
.actions #next:active { background-position: -92px -18px; }
|
||||
|
||||
#eject {
|
||||
position: absolute;
|
||||
top: 88px;
|
||||
left: 136px;
|
||||
background-image: url('skins/default/CBUTTONS.BMP');
|
||||
height: 16px;
|
||||
width: 22px;
|
||||
}
|
||||
#eject { background-position: -114px 0; }
|
||||
#eject:active { background-position: -114px -16px; }
|
||||
|
||||
.shuffle-repeat {
|
||||
position: absolute;
|
||||
top: 89px;
|
||||
left: 164px;
|
||||
width: 74px;
|
||||
}
|
||||
|
||||
.shuffle-repeat div {
|
||||
position: absolute;
|
||||
height: 15px;
|
||||
background-image: url('skins/default/SHUFREP.BMP');
|
||||
}
|
||||
|
||||
.shuffle-repeat #shuffle {
|
||||
width: 47px;
|
||||
background-position: -28px 0;
|
||||
}
|
||||
.shuffle-repeat #shuffle:active {
|
||||
background-position: -28px -15px;
|
||||
}
|
||||
|
||||
.shuffle-repeat #shuffle.selected {
|
||||
background-position: -28px -30px;
|
||||
}
|
||||
.shuffle-repeat #shuffle.selected:active {
|
||||
background-position: -28px -45px;
|
||||
}
|
||||
|
||||
.shuffle-repeat #repeat {
|
||||
left: 47px;
|
||||
width: 28px;
|
||||
}
|
||||
.shuffle-repeat #repeat:active {
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
|
||||
.shuffle-repeat #repeat.selected {
|
||||
background-position: 0 -30px;
|
||||
}
|
||||
.shuffle-repeat #repeat.selected:active {
|
||||
background-position: 0 -45px;
|
||||
}
|
||||
|
||||
#about {
|
||||
position: absolute;
|
||||
top: 91px;
|
||||
left: 253px;
|
||||
height: 15px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.character {
|
||||
display: block;
|
||||
float: left; /* Safari does not do inline-block well */
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
background-image: url('skins/default/TEXT.BMP');
|
||||
text-indent: -9999px;
|
||||
|
||||
}
|
||||
|
||||
.digit {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 13px;
|
||||
background-image: url('skins/default/NUMBERS.BMP');
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
#file-input { display: none; }
|
||||
309
winamp.js
Executable file
|
|
@ -0,0 +1,309 @@
|
|||
/* Helpful wrapper for the native <audio> element */
|
||||
function Media (audioId) {
|
||||
this.audio = document.getElementById(audioId);
|
||||
|
||||
/* Properties */
|
||||
this.timeElapsed = function() {
|
||||
return this.audio.currentTime;
|
||||
}
|
||||
this.timeRemaining = function() {
|
||||
return this.audio.duration - this.audio.currentTime;
|
||||
}
|
||||
this.timeElapsedObject = function() {
|
||||
return this._timeObject(this.timeElapsed());
|
||||
}
|
||||
this.timeRemainingObject = function() {
|
||||
return this._timeObject(this.timeRemaining());
|
||||
}
|
||||
this.percentComplete = function() {
|
||||
return (this.audio.currentTime / this.audio.duration) * 100;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
this.previous = function() {
|
||||
this.audio.currentTime = 0;
|
||||
};
|
||||
this.play = function() {
|
||||
this.audio.play();
|
||||
};
|
||||
this.pause = function() {
|
||||
this.audio.pause();
|
||||
};
|
||||
this.stop = function() {
|
||||
this.audio.pause();
|
||||
this.audio.currentTime = 0;
|
||||
};
|
||||
this.next = function() {
|
||||
this.audio.currentTime = this.audio.duration;
|
||||
};
|
||||
this.toggleRepeat = function() {
|
||||
this.audio.loop = !this.audio.loop;
|
||||
};
|
||||
this.toggleShuffle = function() {
|
||||
// Not implemented
|
||||
};
|
||||
|
||||
/* Actions with arguments */
|
||||
this.seekToPercentComplete = function(percent) {
|
||||
this.audio.currentTime = this.audio.duration * (percent/100);
|
||||
this.audio.play();
|
||||
};
|
||||
this.setVolume = function(volume) {
|
||||
this.audio.volume = volume;
|
||||
};
|
||||
this.loadFile = function(file) {
|
||||
this.audio.setAttribute('src', file);
|
||||
};
|
||||
|
||||
/* Listeners */
|
||||
this.addEventListener = function(event, callback) {
|
||||
this.audio.addEventListener(event, callback);
|
||||
};
|
||||
|
||||
/* Helpers */
|
||||
this._timeObject = function(seconds) {
|
||||
var minutes = seconds / 60;
|
||||
var seconds = seconds % 60;
|
||||
|
||||
return [
|
||||
Math.floor(minutes / 10),
|
||||
Math.floor(minutes % 10),
|
||||
Math.floor(seconds / 10),
|
||||
Math.floor(seconds % 10)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function Winamp () {
|
||||
self = this;
|
||||
this.media = new Media('player');
|
||||
this.media.setVolume(.5);
|
||||
|
||||
this.nodes = {
|
||||
'close': document.getElementById('close'),
|
||||
'position': document.getElementById('position'),
|
||||
'fileInput': document.getElementById('file-input'),
|
||||
'time': document.getElementById('time'),
|
||||
'previous': document.getElementById('previous'),
|
||||
'play': document.getElementById('play'),
|
||||
'pause': document.getElementById('pause'),
|
||||
'stop': document.getElementById('stop'),
|
||||
'next': document.getElementById('next'),
|
||||
'eject': document.getElementById('eject'),
|
||||
'repeat': document.getElementById('repeat'),
|
||||
'shuffle': document.getElementById('shuffle'),
|
||||
'volume': document.getElementById('volume'),
|
||||
'balance': document.getElementById('balance'),
|
||||
'playPause': document.getElementById('play-pause'),
|
||||
'winamp': document.getElementById('winamp'),
|
||||
};
|
||||
|
||||
this.nodes.close.onclick = function() {
|
||||
}
|
||||
|
||||
this.media.addEventListener('timeupdate', function() {
|
||||
self.nodes.position.value = self.media.percentComplete();
|
||||
self.updateTime();
|
||||
});
|
||||
|
||||
this.media.addEventListener('ended', function() {
|
||||
self.setStatus('stop');
|
||||
self.media.previous();
|
||||
});
|
||||
|
||||
this.nodes.time.onclick = function() {
|
||||
this.classList.toggle('countdown');
|
||||
self.updateTime();
|
||||
}
|
||||
|
||||
this.nodes.previous.onclick = function() {
|
||||
self.media.previous();
|
||||
}
|
||||
|
||||
this.nodes.play.onclick = function() {
|
||||
self.media.play();
|
||||
self.setStatus('play');
|
||||
}
|
||||
this.nodes.pause.onclick = function() {
|
||||
self.media.pause();
|
||||
self.setStatus('pause');
|
||||
}
|
||||
this.nodes.stop.onclick = function() {
|
||||
self.media.stop();
|
||||
self.setStatus('stop');
|
||||
}
|
||||
this.nodes.next.onclick = function() {
|
||||
self.media.next();
|
||||
}
|
||||
|
||||
this.nodes.eject.onclick = function() {
|
||||
self.nodes.fileInput.click();
|
||||
}
|
||||
|
||||
this.nodes.fileInput.onchange = function(e){
|
||||
var file = e.target.files[0];
|
||||
var objectUrl = URL.createObjectURL(file);
|
||||
self.loadFile(objectUrl, file.name);
|
||||
self.media.play();
|
||||
self.setStatus('play');
|
||||
}
|
||||
|
||||
this.nodes.volume.oninput = function() {
|
||||
setVolume( this.value / 100);
|
||||
}
|
||||
|
||||
this.nodes.position.onmousedown = function() {
|
||||
self.media.pause();
|
||||
}
|
||||
|
||||
this.nodes.position.onchange = function() {
|
||||
self.media.seekToPercentComplete(this.value);
|
||||
}
|
||||
|
||||
this.nodes.balance.oninput = function() {
|
||||
setBalance( this.value / 100);
|
||||
}
|
||||
this.nodes.repeat.onclick = function() {
|
||||
toggleRepeat();
|
||||
}
|
||||
this.nodes.shuffle.onclick = function() {
|
||||
toggleShuffle();
|
||||
}
|
||||
|
||||
this.setStatus = function(className) {
|
||||
self.nodes.playPause.removeAttribute("class");
|
||||
self.nodes.playPause.classList.add(className);
|
||||
}
|
||||
function setVolume(volume) {
|
||||
sprite = Math.round(volume * 28);
|
||||
offset = (sprite - 1) * 15;
|
||||
self.media.setVolume(volume);
|
||||
self.nodes.volume.style.backgroundPosition = '0 -' + offset + 'px';
|
||||
}
|
||||
|
||||
function setBalance(balance) {
|
||||
offset = Math.abs(balance - .5) * 2;
|
||||
sprite = Math.floor(offset * 28);
|
||||
offset = (sprite - 1) * 15;
|
||||
console.log(self.nodes.balance.style);
|
||||
self.nodes.balance.style.backgroundPosition = '-9px -' + offset + 'px';
|
||||
}
|
||||
|
||||
function toggleRepeat() {
|
||||
self.media.toggleRepeat();
|
||||
self.nodes.repeat.classList.toggle('selected');
|
||||
}
|
||||
|
||||
function toggleShuffle() {
|
||||
self.media.toggleShuffle();
|
||||
self.nodes.shuffle.classList.toggle('selected');
|
||||
}
|
||||
|
||||
this.updateTime = function() {
|
||||
if(this.nodes.time.classList.contains('countdown')) {
|
||||
digits = this.media.timeRemainingObject();
|
||||
} else {
|
||||
digits = this.media.timeElapsedObject();
|
||||
}
|
||||
html = digitHtml(digits[0]);
|
||||
document.getElementById('minute-first-digit').innerHTML = '';
|
||||
document.getElementById('minute-first-digit').appendChild(html);
|
||||
html = digitHtml(digits[1]);
|
||||
document.getElementById('minute-second-digit').innerHTML = '';
|
||||
document.getElementById('minute-second-digit').appendChild(html);
|
||||
html = digitHtml(digits[2]);
|
||||
document.getElementById('second-first-digit').innerHTML = '';
|
||||
document.getElementById('second-first-digit').appendChild(html);
|
||||
html = digitHtml(digits[3]);
|
||||
document.getElementById('second-second-digit').innerHTML = '';
|
||||
document.getElementById('second-second-digit').appendChild(html);
|
||||
}
|
||||
|
||||
this.loadFile = function(file, fileName) {
|
||||
this.media.loadFile(file);
|
||||
html = fontHtml(fileName);
|
||||
document.getElementById('song-title').innerHTML = '';
|
||||
document.getElementById('song-title').appendChild(html);
|
||||
html = fontHtml("128");
|
||||
document.getElementById('kbps').innerHTML = '';
|
||||
document.getElementById('kbps').appendChild(html);
|
||||
html = fontHtml("44");
|
||||
document.getElementById('khz').innerHTML = '';
|
||||
document.getElementById('khz').appendChild(html);
|
||||
this.updateTime();
|
||||
}
|
||||
|
||||
function digitHtml(digit) {
|
||||
horizontalOffset = digit * 9;
|
||||
div = document.createElement('div');
|
||||
div.classList.add('digit');
|
||||
div.style.backgroundPosition = '-' + horizontalOffset + 'px 0';
|
||||
div.innerHTML = digit;
|
||||
return div;
|
||||
}
|
||||
|
||||
function fontHtml(string) {
|
||||
parentDiv = document.createElement('div');
|
||||
for (var i = 0, len = string.length; i < len; i++) {
|
||||
char = string[i].toLowerCase();
|
||||
parentDiv.appendChild(charHtml(char));
|
||||
}
|
||||
return parentDiv;
|
||||
}
|
||||
|
||||
function charHtml(char) {
|
||||
position = charPosition(char);
|
||||
row = position[0];
|
||||
column = position[1];
|
||||
verticalOffset = row * 6;
|
||||
horizontalOffset = column * 5;
|
||||
|
||||
div = document.createElement('div');
|
||||
div.classList.add('character');
|
||||
x = '-' + horizontalOffset + 'px';
|
||||
y = '-' + verticalOffset + 'px'
|
||||
div.style.backgroundPosition = x + ' ' + y;
|
||||
div.innerHTML = char;
|
||||
return div;
|
||||
}
|
||||
|
||||
function charPosition(char) {
|
||||
position = fontLookup[char];
|
||||
if(!position) {
|
||||
return fontLookup[' '];
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
/* XXX There are too many " " and "_" characters */
|
||||
var fontLookup = {
|
||||
"a": [0,0], "b": [0,1], "c": [0,2], "d": [0,3], "e": [0,4], "f": [0,5],
|
||||
"g": [0,6], "h": [0,7], "i": [0,8], "j": [0,9], "k": [0,10],
|
||||
"l": [0,11], "m": [0,12], "n": [0,13], "o": [0,14], "p": [0,15],
|
||||
"q": [0,16], "r": [0,17], "s": [0,18], "t": [0,19], "u": [0,20],
|
||||
"v": [0,21], "w": [0,22], "x": [0,23], "y": [0,24], "z": [0,25],
|
||||
"\"": [0,26], "@": [0,27], " ": [0,29], "0": [1,0], "1": [1,1],
|
||||
"2": [1,2], "3": [1,3], "4": [1,4], "5": [1,5], "6": [1,6], "7": [1,7],
|
||||
"8": [1,8], "9": [1,9], " ": [1,10], "_": [1,11], ":": [1,12],
|
||||
"(": [1,13], ")": [1,14], "-": [1,15], "'": [1,16], "!": [1,17],
|
||||
"_": [1,18], "+": [1,19], "\\": [1,20], "/": [1,21], "[": [1,22],
|
||||
"]": [1,23], "^": [1,24], "&": [1,25], "%": [1,26], ".": [1,27],
|
||||
"=": [1,28], "$": [1,29], "#": [1,30], "Å": [2,0], "Ö": [2,1],
|
||||
"Ä": [2,2], "?": [2,3], "*": [2,4], " ": [2,5]
|
||||
};
|
||||
}
|
||||
|
||||
keylog = [];
|
||||
trigger = [78,85,76,27,76,27,83,79,70,84];
|
||||
// Easter Egg
|
||||
document.onkeyup = function(e){
|
||||
keylog.push(e.which);
|
||||
keylog = keylog.slice(-10);
|
||||
if(keylog.toString() == trigger.toString()) {
|
||||
document.getElementById('winamp').classList.toggle('llama');
|
||||
}
|
||||
}
|
||||
|
||||
winamp = new Winamp();
|
||||
winamp.loadFile('http://jordaneldredge.com/projects/winamp.js/llama.mp3', 'llama.mp3');
|
||||