├── README.md ├── index.html ├── overview.png ├── player.css ├── player.css.php ├── player.js └── playlist.php /README.md: -------------------------------------------------------------------------------- 1 | This is a basic HTML5 music player
2 | This was made to be a simple music player with all the basic features a music player should have as well as have a mobile friendly layout
3 | 4 | Features:
5 | Loop: Play the current track over and over again.
6 | Shuffle: Play tracks at random instead of in order.
7 | Repeat: Allow tracks to repeat (When un-checked every track much be played once before it can be played again; saved between sessions; Played tracks are marked as ✔ in the playlist)
8 | Next/Back: This feature now uses a play history log, this is not saved between sessions (max length is the size of your playlist)
9 | Note: Clicking on a track after using the back feature will bump your historic playback to the present, for example say you hit back 5 times(history at -5) and you click a track, your history will be bumped back up to the present(history at 0)
10 | Note: Changing the number of tracks in the playlist will reset session data
11 | Note: Tracks will not mark a track as played unless it is at least 15% complete
12 | Note: ID3 (meta data from audio files) data is not displayed on very low screen widths (tiny smart phone)
13 | Note: Playback can be restricted to a single folder by double clicking it 14 | 15 | Overview:
16 | 17 | 18 | Keyboard shortcuts:
19 |
20 | pause          - Spacebar
21 | volume up      - Plus on the number keypad
22 | volume down    - Minus on the number keypad
23 | next song      - Left arrow key
24 | previous song  - Right arrow key
25 | skip 5s        - Up arrow key
26 | rewind 5s      - Down arrow key
27 | toggle shuffle - S key
28 | toggle repeat  - R key
29 | toggle loop    - L key
30 | 31 | Setup:
32 | 1. Add the index.html, player.js, player.css, and playlist.php files to a folder on your web server
33 | 2. Create a folder called library in that folder, this folder should contain your music
34 | this folder can be a symlink to your main music folder
35 | 3. If you would like to have have ID3 support look in playlist.php for the notes (it is a optional feature) 36 | 37 | Any files/folders starting with a "." will be ignored in addition to ".txt" files
38 | Cover images should be named cover (not case sensitive), they should be in png, jpg/jpeg, or gif format, basically anything a web browser can display
39 | Cover images are optional
40 | Any file not called cover will be treated as a audio file
41 | All files should have a file extension (eg .png, .mp3, .ogg, etc)
42 | This uses 5 icons from the apache web server, if you are using a different web service, grab these icons and save them as the following:
43 | /icons/open.folder.png
44 | /icons/folder.png
45 | /icons/sound2.png
46 | /icons/small/back.png
47 | /icons/down.png 48 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Music Player 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 🎵 16 |
17 |

18 | 19 |
20 | 21 |
22 |
23 | Title:
24 | Artist:
25 | Album:
26 | Year: 27 |
28 | 29 | 41 |
42 | 43 | 44 | /   45 | 46 | 47 |
48 |

49 |
50 |
Shuffle:
51 |
Repeat:
52 |
Loop:
53 |
54 |
55 |
56 |
57 |
58 |
Library
59 |
Search
60 |
History
61 |
62 | 63 | Track Action: 67 | 68 |
69 |
70 |
71 |
72 | Find: 73 | Case Sensitive 74 |
75 | Match 79 | 80 |
81 |
82 | 83 |
84 |
85 | Current loaded track: 86 |
    87 |
    88 |
    89 |
    90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GM-Script-Writer-62850/HTML5-Music-Player/1489223fb1d772deae2a2a87c7cce9969a89d6a5/overview.png -------------------------------------------------------------------------------- /player.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | /* 3 | * Striped down to the 3 characters I am using 4 | * https://github.com/iconic/open-iconic 5 | */ 6 | font-family: Open Iconic Volume; 7 | src: url('data:font/truetype;base64,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'); 8 | } 9 | *:not(input){ 10 | margin:0; 11 | padding:0; 12 | } 13 | hr{ 14 | margin-top:8px; 15 | margin-bottom:8px; 16 | } 17 | body{ 18 | background:#383838; 19 | color:white; 20 | font-size:16px; 21 | font-family:DejaVu Serif, FreeSerif; 22 | font-variant:normal; 23 | font-style:normal; 24 | text-align:center; 25 | text-rendering:optimizeLegibility; 26 | } 27 | body > div{ 28 | border:1px solid white; 29 | border-radius:5px; 30 | width:50%; 31 | min-width:320px; 32 | max-width:800px; 33 | display:inline-flex; 34 | align-content:stretch; 35 | align-items:flex-start; 36 | flex-direction:row; 37 | flex-wrap:wrap; 38 | justify-content:flex-start; 39 | box-sizing:border-box; 40 | margin-top:10px; 41 | } 42 | body > div:not(#tab_container), 43 | #tab_content > div{ 44 | padding:10px; 45 | } 46 | #player > div{ 47 | display:inline-block; 48 | flex:1; 49 | } 50 | #player #crl{ 51 | width:195px; 52 | } 53 | #player #opts{ 54 | display:flex; 55 | flex:none; 56 | width:100%; 57 | } 58 | #player #opts div{ 59 | display:inline-block; 60 | width:100%; 61 | } 62 | #player #cover{ 63 | width:100px; 64 | height:100px; 65 | background:white; 66 | border-radius:5px; 67 | margin:0 5px 5px 0; 68 | align-self:auto; 69 | line-height:100px; 70 | font-size:100px; 71 | } 72 | #player #title{ 73 | width:100%; 74 | margin:0; 75 | display:inline-block; 76 | text-align:center; 77 | word-wrap:break-word; 78 | } 79 | /*#player #opts span:has(> input:not([disabled])),/* Unsupported as of 2018.03.17: https://caniuse.com/#feat=css-has */ 80 | #player #opts span:not(.true), 81 | #player input:not([disabled]){ 82 | cursor:pointer; 83 | } 84 | #player input[type="button"], 85 | #find input[type="submit"], 86 | #tab_container select, 87 | #find input[type="text"]{/* Coloring credit: http://devgrow.com/dark-button-navigation-using-css3 */ 88 | font-family:Arial, DejaVu Serif, FreeSerif; 89 | height:32px; 90 | font-size:17px; 91 | color:#9FA8B0; 92 | background-image:linear-gradient(to bottom, #3D4850 3%, #313d45 4%, #232B30 100%); 93 | box-shadow:1px 1px 1px rgba(0,0,0,0.2); 94 | border:1px solid #1c252b; 95 | border-radius:10px; 96 | outline:none; 97 | } 98 | select option{ 99 | background-color:#383838; 100 | } 101 | #player input[type="button"]:hover, 102 | #player input[type="button"]:focus, 103 | #tab_container select:focus, 104 | #tab_container select:hover, 105 | #find input[type="text"]:focus, 106 | #find input[type="submit"]:hover{ 107 | background-image:linear-gradient(to bottom, #4C5A64 3%, #404F5A 4%, #2E3940 100%); 108 | color:white; 109 | } 110 | #player input[type="button"]:active, 111 | #tab_container select:active, 112 | #find input[type="submit"]:active{ 113 | background-image:linear-gradient(to bottom, #20282D 3%, #252E34 51%, #222A30 100%); 114 | box-shadow:1px 1px 1px rgba(255,255,255,0.1) 115 | } 116 | #player #back, 117 | #player #next{ 118 | margin:6px 0 6px; 119 | width:80px; 120 | } 121 | #player #error{ 122 | width:100%; 123 | height:30px; 124 | margin:0; 125 | color:red; 126 | font-weight:bold; 127 | text-align:center; 128 | display:none; 129 | } 130 | #player #error.open{ 131 | display:block; 132 | } 133 | #player audio{ /* for IE 11 */ 134 | width:100%; 135 | max-height:30px; 136 | } 137 | #player audio::-webkit-media-controls-play-button, 138 | #player audio::-webkit-media-controls-mute-button{ 139 | cursor:pointer; 140 | } 141 | #player audio::-webkit-media-controls-volume-slider, 142 | #player audio::-webkit-media-controls-timeline{ 143 | cursor:ew-resize; 144 | } 145 | #player audio::-webkit-media-controls-panel{ 146 | background-color:#272525; 147 | border-radius:5px; 148 | } 149 | #player audio::-webkit-media-controls-time-remaining-display, 150 | #player audio::-webkit-media-controls-current-time-display{ 151 | color:white; 152 | } 153 | #player #audioUI{ 154 | display:flex; 155 | flex:none; 156 | padding:8px 2px; 157 | width:calc(100% - 4px); 158 | background:#272525; 159 | border-radius:4px; 160 | align-items:center; 161 | height:15px; 162 | -moz-user-select:none; 163 | -webkit-user-select:none; 164 | -ms-user-select:none; 165 | user-select:none; 166 | } 167 | #player #audioUI > span:not([id]){ 168 | font-size:small; 169 | white-space:nowrap; 170 | } 171 | #player #audioUI > span[id]{ 172 | font-size:22px; 173 | cursor:pointer; 174 | padding:0 3px; 175 | color:gray; 176 | transition:color 0.25s; 177 | } 178 | #audioUI > span[id]:hover, 179 | #audioUI > span[id]:active{ 180 | color:white; 181 | } 182 | #audioUI #mute{ 183 | /* font-family:Segoe UI Symbol, Symbola, Quivira, Segoe UI Emoji, EmojiOne Mozilla; */ 184 | font-family:Open Iconic Volume; 185 | min-width:22px; 186 | text-align:left; 187 | } 188 | #audioUI input{ 189 | appearance:none; 190 | -moz-appearance:none; 191 | -webkit-appearance:none; 192 | background-color:transparent; 193 | outline:none; 194 | margin:0 5px; 195 | padding:0; 196 | flex:3; 197 | } 198 | #player #audioUI input::-moz-focus-outer{ 199 | border:0; 200 | } 201 | #player #audioUI input::-ms-track{ 202 | height:3px; 203 | background:transparent; 204 | } 205 | #player #audioUI input::-ms-fill-upper, 206 | #player #audioUI input::-ms-fill-lower{ 207 | background:#999; 208 | border-radius:3px; 209 | } 210 | #player #audioUI input::-ms-tooltip{ 211 | display:none; 212 | } 213 | #player #audioUI input::-ms-thumb{ 214 | margin-top:0; 215 | } 216 | #player #audioUI #volume{ 217 | min-width:30px; 218 | max-width:75px; 219 | flex:1; 220 | } 221 | #tab_container{ 222 | border:none; 223 | } 224 | #tab_container.playlist #playlist, 225 | #tab_container.find #find, 226 | #tab_container.hst #hst{ 227 | display:block; 228 | } 229 | #tab_container.playlist #tabs .playlist, 230 | #tab_container.find #tabs .find, 231 | #tab_container.hst #tabs .hst{ 232 | border-bottom-color:#383838; 233 | position:relative; 234 | } 235 | #tab_container.hst > span{ 236 | display:none; 237 | } 238 | #tab_container > span{ 239 | margin-left:auto; 240 | } 241 | #tab_container select{ 242 | border-radius:5px; 243 | height:23px; 244 | font-size:15px; 245 | padding:0 18px 0 5px; 246 | background-image:url('/icons/down.png'),linear-gradient(to bottom, #3D4850 3%, #313d45 4%, #232B30 100%); 247 | background-position:calc(100% - 5px) center,top left; 248 | background-repeat:no-repeat; 249 | background-size:13px,auto; 250 | appearance:none; 251 | -moz-appearance:none; 252 | -webkit-appearance:none; 253 | } 254 | #tab_container #find select{ 255 | margin-top:5px; 256 | } 257 | #tab_container select:focus, 258 | #tab_container select:hover{ 259 | background-image:url('/icons/down.png'),linear-gradient(to bottom, #4C5A64 3%, #404F5A 4%, #2E3940 100%); 260 | color:#FFF; 261 | } 262 | #tab_container select:active{ 263 | background-image:url('/icons/down.png'),linear-gradient(to bottom, #20282D 3%, #252E34 51%, #222A30 100%); 264 | } 265 | #tabs{ 266 | margin-bottom:-1px; 267 | margin-left:4px; 268 | text-align:left; 269 | } 270 | #tabs > div{ 271 | display:inline-block; 272 | margin:0 0 0 3px; 273 | padding:5px 10px; 274 | border:1px solid white; 275 | border-radius:5px 5px 0 0; 276 | cursor:pointer; 277 | } 278 | #tab_content{ 279 | width:100%; 280 | } 281 | #tab_content > div{ 282 | border:1px solid white; 283 | display:none; 284 | min-height:160px; 285 | border-radius:5px; 286 | width:100%; 287 | box-sizing:border-box; 288 | text-align:left; 289 | overflow:auto; 290 | } 291 | #playlist{/* It just does not look right to me */ 292 | padding-top:0!important; 293 | padding-bottom:0!important; 294 | } 295 | #playlist > ul{ 296 | cursor:pointer; 297 | width:100%; 298 | } 299 | #playlist li{ 300 | background-repeat:no-repeat; 301 | background-position:left 2px; 302 | padding-left:25px; 303 | line-height:25px; 304 | } 305 | #playlist ul{ 306 | list-style:none; 307 | padding-left:0; 308 | } 309 | #playlist > ul ul{ 310 | display:none; 311 | } 312 | #playlist li:hover > ul, /* This is the line that makes the folder open on mouse over */ 313 | #playlist li.open > ul{ 314 | display:block; 315 | } 316 | #playlist .back{ 317 | background-image:url(/icons/small/back.png), url(/icons/folder.png); 318 | background-position:1px 7px,left 2px; 319 | } 320 | #playlist .folder, 321 | #playlist .folder.open, 322 | #playlist .song{ 323 | background-position:left 2px; 324 | } 325 | #playlist .folder{ 326 | background-image:url(/icons/folder.png); 327 | } 328 | #playlist .folder.open{ 329 | background-image:url(/icons/folder.open.png); 330 | } 331 | #playlist .song, 332 | #find #search li{ 333 | background-image:url(/icons/sound2.png); 334 | } 335 | li[played]:before{ 336 | content:"\2714 "; 337 | color:aqua; /* http://i.imgur.com/2pLPx59.jpg */ 338 | font-style:normal; 339 | } 340 | #playlist li{ 341 | font-style:normal; 342 | color:white; 343 | } 344 | #playlist li.playing{ 345 | font-style:italic; 346 | color:lime; 347 | } 348 | #find form > span{ 349 | display:inline-block; 350 | } 351 | #find form > span:before{ 352 | content:"("; 353 | } 354 | #find form > span:after{ 355 | content:")"; 356 | } 357 | #find input[type="text"]{ 358 | height:25px; 359 | color:#FFF; 360 | border-radius:5px; 361 | width:175px; 362 | } 363 | #find #search li, 364 | #hst li{ 365 | cursor:pointer; 366 | } 367 | #find #search{ 368 | padding:0 0 5px; 369 | margin:0; 370 | list-style:none; 371 | } 372 | #find #search li{ 373 | padding-left:25px; 374 | background-repeat:no-repeat; 375 | background-position:left center; 376 | } 377 | #find #search span{ 378 | text-decoration:underline; 379 | } 380 | #find input[type="submit"]{ 381 | float:right; 382 | } 383 | #hst ol{ 384 | margin:5px 0 0; 385 | padding-left:40px; 386 | } 387 | @media (max-width:1150px){ 388 | body > div{ 389 | width:75%; 390 | } 391 | } 392 | @media (max-width:850px){ 393 | body > div{ 394 | width:80%; 395 | } 396 | } 397 | @media (max-width:800px){ 398 | body > div{ 399 | width:85%; 400 | } 401 | } 402 | @media (max-width:750px){ 403 | body > div{ 404 | margin-top:5px; 405 | } 406 | body > div:not(#tab_container), 407 | #tab_content > div{ 408 | padding:5px; 409 | } 410 | } 411 | @media (max-width:690px){ 412 | body > div{ 413 | width:90%; 414 | } 415 | #player input[type="button"]{ 416 | height:25px; 417 | font-size:16px; 418 | } 419 | #id3 .year{ 420 | display:none; 421 | } 422 | } 423 | @media (max-width:630px){ 424 | #player #id3{ 425 | display:none; 426 | } 427 | #player input[type="button"]{ 428 | height:32px; 429 | font-size:17px; 430 | } 431 | } 432 | @media (max-width:550px){ 433 | #tabs, 434 | #tab_container > span{ 435 | font-size:12px; 436 | } 437 | } 438 | @media (max-width:475px){ 439 | #player input[type="button"]{ 440 | height:25px; 441 | font-size:16px; 442 | } 443 | #tab_container > span > span:first-child{ 444 | display:none; 445 | } 446 | } 447 | @media (max-width:450px){ 448 | #find form > span{ 449 | margin-left:45px; 450 | } 451 | #find form > span:before, 452 | #find form > span:after{ 453 | content:""; 454 | } 455 | } 456 | @media (max-width:400px){ 457 | #tab_container > span > span{ 458 | display:none; 459 | } 460 | } 461 | @media (max-width:320px){ 462 | body > div{ 463 | margin:0; 464 | } 465 | #player{ 466 | margin-bottom:2px; 467 | } 468 | body > br{ 469 | display:none; 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /player.css.php: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /player.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var track,audio,audioUI,pic,title,ID3,shuffle,repeat,loop,err,playlist,tabs,unPlayed,trackAction, 3 | log=false,// Show messages in browser console 4 | music=Array(), 5 | spam={ 6 | "block":false, 7 | "lock":function(){ 8 | if(spam.block){ 9 | console.warn("No Spamming"); 10 | return true; 11 | } 12 | spam.block=true; 13 | setTimeout(function(){spam.block=false;},500);// miliseconds between actions 14 | return false; 15 | } 16 | }, 17 | hst={ 18 | "log":[], 19 | "indx":0, 20 | "nav":false, 21 | "add":function(id){ 22 | var li, 23 | trim=hst.log.length; 24 | id=id===undefined?track:id; 25 | if(isNaN(audio.duration)) 26 | return log("Do not add",id,"to history"); 27 | log('Add track',id,'to history'); 28 | hst.log.push(id); 29 | li=document.createElement('li'); 30 | li.setAttribute('track',id); 31 | li.title=music[id].getAttribute('file').substr(library.path.length); 32 | li.textContent=music[id].textContent; 33 | li.addEventListener('click',function(){ 34 | if(hst.indx==hst.log.length){ 35 | hst.add(); 36 | } 37 | hst.indx=this.parentNode.children.length-whichChildAmI(this)-1; 38 | log('History:',hst.indx,'-',hst.log); 39 | hst.nav=false; 40 | sendEvt(music[hst.log[hst.indx]],'click'); 41 | },false); 42 | if(hst.que.children.length==0) 43 | hst.que.appendChild(li); 44 | else 45 | hst.que.insertBefore(li,hst.que.children[0]) 46 | hst.log=hst.log.slice(music.length*-1); 47 | if(hst.indx>hst.log.length) 48 | hst.indx=hst.log.length; 49 | if(hst.que.children.length>hst.log.length) 50 | hst.que.removeChild(hst.que.children[hst.que.children.length-1]); 51 | return trim==hst.log.length; 52 | }, 53 | "tracking":{ 54 | "update":function(){ 55 | var i=hst.log.length-hst.indx; 56 | hst.tracking.ele.textContent=i; 57 | hst.tracking.style.textContent='#queue li:nth-child('+i+'){color:lime;font-style:italic;}'; 58 | } 59 | } 60 | }; 61 | function getId(id){ 62 | return document.getElementById(id); 63 | } 64 | function whichChildAmI(me){ 65 | var i,p=me.parentNode.children; 66 | for(i=p.length-1;i>-1;i--){ 67 | if(p[i]==me) return i; 68 | } 69 | } 70 | function randInt(min,max){ 71 | max++; 72 | var rand=Math.round(Math.random()*(max-min)+min); 73 | rand=rand==max?min:rand; 74 | log("RNG",{"min":min,"max":max-1,"int":rand}); 75 | return rand; 76 | } 77 | function escapeRegExp(string){// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions 78 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 79 | } 80 | function sendEvt(element,event){ 81 | log('Send',event,'event to',element); 82 | //element.dispatchEvent(new Event(event));//This does not work in IE11 83 | var evt = document.createEvent("HTMLEvents"); 84 | evt.initEvent(event, true, true ); 85 | return !element.dispatchEvent(evt); 86 | } 87 | function sec2time(t){ 88 | var min=Math.floor(t/60), 89 | sec=Math.round(t-min*60); 90 | if(sec==60){ 91 | min++; 92 | sec=0 93 | } 94 | if(sec<10){ 95 | sec='0'+sec; 96 | } 97 | return min+':'+sec; 98 | } 99 | function extToMime(ext){ 100 | ext=ext.toLowerCase(); 101 | log('File Extension:',ext); 102 | switch(ext){ 103 | case "mp3":return "audio/mpeg"; 104 | case "m4a":return "audio/mpeg"; 105 | case "ogg":return "audio/ogg"; 106 | case "oga":return "audio/ogg"; 107 | case "aac":return "audio/aac"; 108 | case "wav":return "audio/wave"; 109 | case "webm":return "audio/webm"; 110 | case "flac":return "audio/flac"; 111 | default: return "audio/"+ext; 112 | } 113 | } 114 | function play(){ 115 | var promise=audio.play(); 116 | if(promise!==undefined){ 117 | promise.then(function(){ 118 | log('Audio playback started!'); 119 | }).catch(function(){ 120 | console.warn('Audio playback was prevented by browser. See https://goo.gl/xX8pDD');// Note that using => breaks IE 121 | sendEvt(audio,'pause'); 122 | }); 123 | } 124 | } 125 | function reloadUnPlayed(init){ 126 | log('Reset UnPlayed'); 127 | var x,i=0; 128 | unPlayed=[]; 129 | for(x in music){ 130 | unPlayed.push(i); 131 | if(!init){ 132 | music[x].removeAttribute('played'); 133 | } 134 | i++; 135 | } 136 | repeat.parentNode.title="Played: 0/"+music.length; 137 | } 138 | function setPlayed(i,test){ 139 | if(test===false){ 140 | log('Track',i,'has been played'); 141 | music[i].setAttribute('played','yes'); 142 | return; 143 | } 144 | i=unPlayed.indexOf(i); 145 | if(unPlayed.length>0){ 146 | log('Tracks not played:',unPlayed.length); 147 | if(audio.currentTime/audio.duration >= 0.15){// At least 15% of file was played 148 | setPlayed(track,false); 149 | if(i>-1){ 150 | unPlayed.splice(i,1); 151 | repeat.parentNode.title="Played: "+(music.length-unPlayed.length)+"/"+music.length; 152 | } 153 | else{ 154 | log('Said track was played already'); 155 | } 156 | } 157 | else{ 158 | log('Track',i,'was Skipped'); 159 | i++; 160 | } 161 | } 162 | i=i==unPlayed.length?0:i; 163 | if(unPlayed.length==0){ 164 | reloadUnPlayed(false); 165 | } 166 | return i; 167 | } 168 | function applyID3(id3){ 169 | var unown=''; 170 | /* if(id3.title){ 171 | title.head.textContent=id3.title+" | "+title.page; 172 | }*/ 173 | ID3.title.textContent=id3.title?id3.title:unown; 174 | ID3.artist.textContent=id3.artist?id3.artist:unown; 175 | ID3.album.textContent=id3.album?id3.album:unown; 176 | if(!id3.year&&id3.recording_time){ 177 | id3.year=id3.recording_time; 178 | } 179 | ID3.year.textContent=id3.year?id3.year:unown; 180 | } 181 | function populateList(arr,e,dir){ 182 | var li,i,x,cover; 183 | for(i in arr){ 184 | if(i!="/"){ 185 | log('Found Folder:',i); 186 | li=document.createElement('li'); 187 | li.className="folder"; 188 | li.setAttribute('path',dir+i); 189 | li.textContent=i; 190 | e.appendChild(li); 191 | li.addEventListener('click',function(event){ 192 | event.stopPropagation(); 193 | if(this.className.indexOf("open")==-1){ 194 | this.className="open "+this.className; 195 | } 196 | else{ 197 | this.className=this.className.substr(5); 198 | } 199 | },false); 200 | li.addEventListener('dblclick',function(event){ 201 | event.stopPropagation(); 202 | var p=this.getAttribute('path'); 203 | p=p.substr(p.indexOf('/')+1); 204 | if(confirm('Only play from:\n\t'+p)){ 205 | // Yes, it is possible to accomplish this via JavaScript, but this was quick and easy implementation for a feature I do not care about 206 | document.location.href=document.location.pathname+'?folder='+encodeURIComponent(p); 207 | } 208 | },false); 209 | li.appendChild(document.createElement('ul')) 210 | populateList(arr[i],li.childNodes[1],dir+i+'/'); 211 | } 212 | else{ 213 | cover=false; 214 | for(x in arr[i]){ 215 | if(!arr[i][x] && x.slice(0,x.lastIndexOf('.')).toLowerCase()=="cover"){ 216 | log('Found Cover:',x); 217 | cover=x; 218 | delete(arr[i][x]); 219 | break; 220 | } 221 | } 222 | for(x in arr[i]){ 223 | log('Found Audio:',x); 224 | li=document.createElement('li'); 225 | li.id=music.length; 226 | li.className="song"; 227 | li.textContent=x.substr(0,x.lastIndexOf('.'));//title 228 | if(arr[i][x]){// id3 tags 229 | li.setAttribute('id3',JSON.stringify(arr[i][x])); 230 | } 231 | li.setAttribute('file',dir+x); 232 | li.setAttribute('cover',cover?dir+cover:'/icons/sound2.png'); 233 | li.addEventListener('click',function(event){ 234 | function safeURI(src){// Discussion here: https://github.com/ltGuillaume/MusicFolderPlayer/issues/1 235 | return encodeURI(src).replace(/[(\?=&#]/g,function(char){return escape(char);}); 236 | } 237 | event.stopPropagation(); 238 | if(hst.nav){ 239 | log('Manual navigation detected'); 240 | if(trackAction==1){ 241 | if(hst.log.length>0&&hst.log[hst.log.length-1]==this.id){ 242 | if(!confirm('Add this track to queue consecutively?')) return; 243 | } 244 | if(hst.indx==hst.log.length) hst.add(); 245 | hst.add(parseInt(this.id)); 246 | hst.tracking.update(); 247 | return; 248 | } 249 | hst.add(); 250 | hst.indx=hst.log.length; 251 | setPlayed(track,true); 252 | } 253 | hst.nav=true; 254 | hst.tracking.update(); 255 | var file=this.getAttribute('file'), 256 | cover=this.getAttribute('cover'), 257 | s=document.createElement('source'), 258 | mime=extToMime(file.substr(file.lastIndexOf('.')+1)), 259 | last=music[track], 260 | id3=this.getAttribute('id3'); 261 | log("Now Playing:",file); 262 | if(track==this.id && !isNaN(audio.duration)){ 263 | audio.currentTime=0; 264 | last=music[track]; 265 | return play(); 266 | } 267 | if(last.className.indexOf('playing')>-1){ 268 | while(last.id!='playlist'){ 269 | last.className=last.className.slice(0,-8); 270 | last=last.parentNode.parentNode; 271 | } 272 | } 273 | pic.src=safeURI(cover); 274 | title.player.textContent=this.textContent; 275 | title.head.textContent=this.textContent+" | "+title.page; 276 | if(id3!==null){ 277 | id3=JSON.parse(id3); 278 | applyID3(id3); 279 | } 280 | else if(library.id3===true){ 281 | applyID3({}); 282 | } 283 | showError(audio.canPlayType(mime)?false:"This browser does not support "+mime.substr(mime.indexOf('/')+1).toUpperCase()+" audio."); 284 | s.type=mime; 285 | s.src=safeURI(file); 286 | audio.appendChild(s); 287 | if(audio.childNodes.length>1){ 288 | audio.removeChild(audio.childNodes[0]); 289 | } 290 | audio.load();// Let autoplay handle it 291 | track=parseInt(this.id); 292 | last=music[track]; 293 | while(last.id!='playlist'){ 294 | last.className+=' playing'; 295 | last=last.parentNode.parentNode; 296 | } 297 | sendEvt(window,'resize'); 298 | if(library.id3===true&&id3===null){ 299 | var httpRequest=new XMLHttpRequest(); 300 | httpRequest.onreadystatechange=function(){ 301 | if(httpRequest.readyState==4){ 302 | if(httpRequest.status==200){ 303 | log("HTTP Request:",httpRequest.responseURL,"\n",httpRequest.responseText); 304 | if(httpRequest.responseText.length==0){ 305 | return console.error("Non-UTF8 character in ID3 data at:\n", 306 | decodeURIComponent(httpRequest.responseURL.slice(httpRequest.responseURL.indexOf('=')+1,httpRequest.responseURL.indexOf('&')))); 307 | } 308 | var r=JSON.parse(httpRequest.responseText); 309 | if(!r.id3){ 310 | return; 311 | } 312 | music[r.track].setAttribute('id3',JSON.stringify(r.id3)); 313 | if(track==r.track){ 314 | applyID3(r.id3); 315 | } 316 | } 317 | else if(httpRequest.status==404){ 318 | showError("This Tack no longer exist!"); 319 | var r=JSON.parse(httpRequest.responseText); 320 | log("404 Error:",music[r.track].getAttribute('file')); 321 | } 322 | else{ 323 | console.error("HTTP Status Code:"+httpRequest.status+"\n"+httpRequest.responseText); 324 | } 325 | } 326 | }; 327 | httpRequest.open('GET','playlist.php?file='+encodeURIComponent(file)+'&track='+track); 328 | httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 329 | httpRequest.send(null); 330 | } 331 | },false); 332 | e.appendChild(li); 333 | music.push(li); 334 | } 335 | } 336 | } 337 | } 338 | function showError(msg){ 339 | var a=audioUI?audioUI.ui:audio; 340 | if(!msg){ 341 | if(!!a.style.display){ 342 | a.removeAttribute('style'); 343 | } 344 | if(!!err.className) 345 | err.removeAttribute('class'); 346 | } 347 | else{ 348 | console.error(msg); 349 | a.style.display='none'; 350 | err.className="open"; 351 | err.textContent=msg; 352 | } 353 | } 354 | function init(){ 355 | log=log?console.log:function(){}; 356 | log('Begin Main JavaScript File'); 357 | var i,lst, 358 | config=localStorage.getItem("HTML5_music"), 359 | path=library.path.split('/'), 360 | ul=document.createElement('ul'), 361 | base={// Default player settings 362 | "volume":.25, 363 | "track":0, 364 | "state":false, 365 | "time":0, 366 | "shuffle":true, 367 | "repeat":true, 368 | "loop":false, 369 | "tracks":0, 370 | "unPlayed":[], 371 | "action":0 372 | }; 373 | audio=getId('audio'); 374 | hst.que=getId('queue'); 375 | hst.tracking.ele=getId('index'); 376 | hst.tracking.style=document.createElement('style'); 377 | hst.tracking.style.type="text/css"; 378 | document.body.previousElementSibling.appendChild(hst.tracking.style); 379 | title={ 380 | "player":getId('title'), 381 | "head":getId('pageTitle'), 382 | } 383 | title.page=title.head.textContent; 384 | ID3={ 385 | "title":getId('id3_title'), 386 | "artist":getId('id3_artist'), 387 | "album":getId('id3_album'), 388 | "year":getId('id3_year') 389 | }; 390 | pic=getId('cover'); 391 | shuffle=getId('shuffle'); 392 | loop=getId('loop'); 393 | repeat=getId('repeat'); 394 | err=getId('error'); 395 | err.addEventListener('click',function(){ 396 | showError(false); 397 | },false); 398 | playlist=getId('playlist'); 399 | playlist.appendChild(ul); 400 | if(path.length>2){ 401 | log('Create partent directory folder'); 402 | var path=library.path.split('/'), 403 | li=document.createElement('li'); 404 | li.className="back"; 405 | path=path.slice(1,-2); 406 | path=path.join('/'); 407 | li.setAttribute('path',path); 408 | path=path.substr(path.lastIndexOf('/')+1); 409 | li.textContent=path==''?'All Music':path; 410 | ul.appendChild(li); 411 | li.addEventListener('click',function(event){ 412 | event.stopPropagation(); 413 | var p=this.getAttribute('path'); 414 | if(confirm('Play from:\n\t'+(p==''?'All Music':p))){ 415 | p=p==''?p:'?folder='+encodeURIComponent(p); 416 | document.location.href=document.location.pathname+p; 417 | } 418 | },false); 419 | } 420 | populateList(library.music,ul,library.path); 421 | if(!library.id3){ 422 | getId('id3').style.display='none'; 423 | } 424 | //config=config==null?base:JSON.parse(config); 425 | config=JSON.parse(config)||{}; 426 | for(i in base){ 427 | if(config[i]===undefined){ 428 | config[i]=base[i]; 429 | } 430 | } 431 | log('Config Data:',config); 432 | unPlayed=config.unPlayed; 433 | if(config.tracks!=music.length){ 434 | log('Playlist has changed'); 435 | config.time=base.time; 436 | config.track=base.track; 437 | config.state=base.state; 438 | reloadUnPlayed(true); 439 | } 440 | else if(unPlayed.length==0){ 441 | reloadUnPlayed(true); 442 | } 443 | trackAction=getId('action'); 444 | trackAction.addEventListener('change',function(){ 445 | trackAction=this.selectedIndex; 446 | },false); 447 | trackAction.selectedIndex=config.action; 448 | sendEvt(trackAction,'change'); 449 | if(unPlayed.length!=music.length){ 450 | for(i in music){ 451 | i=parseInt(music[i].id); 452 | if(unPlayed.indexOf(i)==-1){ 453 | setPlayed(i,false); 454 | } 455 | } 456 | } 457 | audio.addEventListener("stalled",function(){ 458 | console.warn("The stream of",music[track].getAttribute('file'),"has stalled!!!"); 459 | showError("This stream has stalled!!!"); 460 | audio.addEventListener('progress',function fn(e){ 461 | e.target.removeEventListener(e.type, fn); 462 | console.log('This stream has resumed!!!'); 463 | if(err.textContent=="This stream has stalled!!!") 464 | showError(false); 465 | },false); 466 | },false); 467 | audio.addEventListener("error",function(e){ 468 | console.error( 469 | "https://developer.mozilla.org/en-US/docs/Web/API/MediaError", 470 | "\n", 471 | this.error 472 | ); 473 | showError([ 474 | this.error.message, 475 | "The fetching of the associated resource was aborted by the user's request.", 476 | "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.", 477 | "Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.", 478 | "The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable." 479 | ][this.error.message==""?this.error.code:0] 480 | ); 481 | },false); 482 | audio.addEventListener("ended",function(){ 483 | if(music.length==0){ 484 | return showError(library.error||"Playlist is empty"); 485 | } 486 | log('End Track:',track); 487 | var next=track, 488 | indx=setPlayed(track,true); 489 | if(loop.checked){ 490 | log('Track Loop'); 491 | audio.currentTime=0; 492 | return play(); 493 | } 494 | hst.nav=false; 495 | hst.indx++; 496 | if(hst.log.length>hst.indx){ 497 | log('Back feature was used:',hst.indx,'of',hst.log.length); 498 | sendEvt(music[hst.log[hst.indx]],'click'); 499 | return true; 500 | } 501 | else if(hst.indx>hst.log.length){ 502 | hst.add(); 503 | } 504 | if(shuffle.checked){ 505 | log('Shuffle Tracks'); 506 | if(repeat.checked){ 507 | log('Allow Repeats'); 508 | next=randInt(0,music.length-1); 509 | } 510 | else{ 511 | next=randInt(0,unPlayed.length-1); 512 | next=unPlayed[next]; 513 | } 514 | } 515 | else if(!repeat.checked){ 516 | log('Ordered Tracks w/ No Repeats'); 517 | next=unPlayed[indx>-1?indx:0]; 518 | } 519 | else{ 520 | log('Ordered Tracks w/ Repeats'); 521 | if(track+1 < music.length){ 522 | next++; 523 | } 524 | else{ 525 | next=0; 526 | } 527 | } 528 | if(next==track&&!!audio.childNodes[0].tagName){ 529 | audio.currentTime=0; 530 | return play(); 531 | } 532 | log("Next Track ID is: #",next); 533 | sendEvt(music[next],'click'); 534 | },false); 535 | audioUI=getId('audioUI'); 536 | if(!String.fromCodePoint&&audioUI){// The input event does not work in IE; Only IE does not have String.fromCodePoint 537 | audioUI.parentNode.removeChild(audioUI); 538 | audioUI=false; 539 | } 540 | if(audioUI){ 541 | audioUI={ 542 | "ui":audioUI, 543 | "state":getId('playPause'), 544 | "time":getId('current'), 545 | "now":getId('time'), 546 | "end":getId('length'), 547 | "mute":getId('mute'), 548 | "volume":getId('volume'), 549 | "seeking":false, 550 | "autoPause":false 551 | }; 552 | // Play/Pause 553 | audioUI.state.addEventListener('click',function(){ 554 | audio[audio.paused?'play':'pause'](); 555 | },false); 556 | audio.addEventListener('pause',function(){ 557 | audioUI.state.textContent=String.fromCharCode(9654); 558 | audioUI.state.title="Play"; 559 | },false); 560 | sendEvt(audio,'pause');// this is for chrome 561 | audio.addEventListener('play',function(){ 562 | audioUI.state.textContent=String.fromCharCode(10074,10074); 563 | audioUI.state.title="Pause"; 564 | },false); 565 | // Time Slider and track Time 566 | audio.addEventListener('timeupdate',function(){ 567 | if(audioUI.seeking) return; 568 | audioUI.time.value=(audio.currentTime/audio.duration).toFixed(8); 569 | audioUI.now.textContent=sec2time(audio.currentTime); 570 | },false); 571 | audioUI.time.addEventListener('change',function(){ 572 | audio.currentTime=audio.duration*this.value; 573 | audioUI.seeking=false; 574 | if(audioUI.autoPause){ 575 | play(); 576 | audioUI.autoPause=false; 577 | } 578 | },false); 579 | audioUI.time.addEventListener('mouseup',function(){ 580 | // Possible to not fire change if user leaves slider in the same point they started 581 | audioUI.seeking=false; 582 | },false); 583 | audioUI.time.addEventListener('input',function(){ 584 | if(!audio.paused&&audio.duration-audio.currentTime<1){ 585 | audioUI.autoPause=true; 586 | audio.pause(); 587 | } 588 | audioUI.seeking=true; 589 | audioUI.now.textContent=sec2time(audio.duration*this.value); 590 | },false); 591 | audio.addEventListener('loadedmetadata',function(){ 592 | audioUI.end.textContent=sec2time(audio.duration); 593 | },false); 594 | //Mute Button 595 | audioUI.mute.addEventListener('click',function(){ 596 | audio.muted=!audio.muted; 597 | },false); 598 | // Volume Slider 599 | audio.addEventListener('volumechange',function(){ 600 | audioUI.volume.value=audio.volume; 601 | if(audio.muted){ 602 | audioUI.volume.title='Muted'; 603 | //audioUI.mute.textContent=String.fromCodePoint(128264); 604 | audioUI.mute.textContent=String.fromCharCode(57559); 605 | } 606 | else{ 607 | audioUI.volume.title=Math.round(audio.volume*100)+'%'; 608 | //audioUI.mute.textContent=String.fromCodePoint(Math.round(audio.volume)==0?128265:128266); 609 | audioUI.mute.textContent=String.fromCharCode(Math.round(audio.volume)==0?57558:57557); 610 | } 611 | },false); 612 | audioUI.volume.addEventListener('input',function(){ 613 | audio.volume=this.value; 614 | audio.muted=false; 615 | },false); 616 | audioUI.volume.addEventListener('dblclick',function(){ 617 | var p=prompt('Desired volume level as a percentage',Math.round(audio.volume*100)); 618 | if(!p) return; 619 | p=parseInt(p); 620 | if(isNaN(p)) return alert('Sorry that is not a whole number'); 621 | if(p>=0&&p<=100){ 622 | audio.volume=p/100; 623 | } 624 | else{ 625 | alert(p+' is a percentage, it must be between 0 and 100'); 626 | } 627 | },false); 628 | // Make sure volume slider matches player volume 629 | if(config.volume==1) sendEvt(audio,'volumechange'); 630 | } 631 | else{ 632 | audioUI=false; 633 | audio.controls=true; 634 | } 635 | tabs=getId('tabs'); 636 | for(i=tabs.children.length-1;i>-1;i--){ 637 | tabs.children[i].addEventListener('click',function(){ 638 | this.parentNode.parentNode.className=this.className; 639 | },false) 640 | } 641 | getId('next').addEventListener("click",function(){ 642 | if(spam.lock()) return; 643 | sendEvt(audio,'ended'); 644 | },false); 645 | getId('back').addEventListener("click",function(){ 646 | if(spam.lock()) return; 647 | if(hst.indx==0){ 648 | log('History:',hst.indx,'-',hst.log); 649 | return alert('Out of History'); 650 | } 651 | if(hst.indx==hst.log.length){ 652 | if(hst.add()){ 653 | hst.indx--; 654 | } 655 | } 656 | hst.indx--; 657 | log('History:',hst.indx,'-',hst.log); 658 | hst.nav=false; 659 | sendEvt(music[hst.log[hst.indx]],'click'); 660 | },false); 661 | getId('wipe').addEventListener("click",function(){ 662 | if(music.length==unPlayed.length){ 663 | return alert('No tracks have been played!'); 664 | } 665 | if(confirm("All tracks will be marked as unplayed.\n\nTracks marked as played: "+(music.length-unPlayed.length)+" of "+music.length)){ 666 | reloadUnPlayed(false); 667 | } 668 | },false); 669 | shuffle.checked=config.shuffle; 670 | loop.checked=config.loop; 671 | repeat.checked=config.repeat; 672 | repeat.parentNode.title="Played: "+(music.length-unPlayed.length)+"/"+music.length; 673 | if(loop.checked){ 674 | repeat.disabled=true; 675 | shuffle.disabled=true; 676 | } 677 | loop.addEventListener('click',function(event){ 678 | event.stopPropagation(); 679 | repeat.disabled=this.checked; 680 | shuffle.disabled=this.checked; 681 | repeat.parentNode.className=this.checked; 682 | shuffle.parentNode.className=this.checked; 683 | },false); 684 | lst=[shuffle,repeat,loop]; 685 | for(i in lst){// checkboxes are annoying on smart phones 686 | lst[i].parentNode.addEventListener('click',function(){ 687 | this.childNodes[1].click(); 688 | },false); 689 | if(lst[i]!==loop){ 690 | lst[i].addEventListener('click',function(event){ 691 | event.stopPropagation(); 692 | },false); 693 | } 694 | } 695 | track=config.track>music.length-1?0:config.track; 696 | if(config.time==0&&track==0){ 697 | hst.indx--; 698 | sendEvt(audio,'ended'); 699 | } 700 | else{ 701 | audio.addEventListener('canplay',function fn(e){ 702 | e.target.removeEventListener(e.type, fn); 703 | log('Restore track state from previous session'); 704 | audio.currentTime=config.time; 705 | if(config.state===false){//audio[config.state===false?'play':'pause'](); 706 | play(); 707 | } 708 | else{ 709 | audio.pause();// needed for firefox to pause 710 | sendEvt(audio,'pause');// needed for chrome to show play button 711 | } 712 | },false); 713 | sendEvt(music[track],'click'); 714 | } 715 | audio.volume=config.volume; 716 | window.onunload=function(){ 717 | if( isNaN(track) || isNaN(audio.currentTime) || isNaN(audio.volume) ){ 718 | log('Abort save'); 719 | return; 720 | } 721 | localStorage.setItem("HTML5_music",JSON.stringify({ 722 | "volume":audio.volume, 723 | "track":track, 724 | "state":audio.paused===true, 725 | "time":audio.currentTime, 726 | "shuffle":shuffle.checked===true, 727 | "repeat":repeat.checked===true, 728 | "loop":loop.checked===true, 729 | "tracks":music.length, 730 | "unPlayed":unPlayed, 731 | "action":trackAction 732 | })); 733 | log('Session Saved'); 734 | } 735 | document.addEventListener('keyup',function(event){// keyboard shortcuts 736 | event.preventDefault();// Prevent scrolling main body 737 | switch(event.which){ 738 | case 32:audio[audio.paused?'play':'pause']();return;// spacebar 739 | case 107:audio.volume=(audio.volume+.1>1?1:audio.volume+.1);return;// + (num pad) 740 | case 109:audio.volume=(audio.volume-.1<0?0:audio.volume-.1);return;// - (num pad) 741 | case 37:getId('back').click();return;// left arrow 742 | case 39:getId('next').click();return;// right arrow 743 | case 38:audio.currentTime+=5;return;// up arrow 744 | case 40:audio.currentTime-=5;return;// down arrow 745 | case 83:if(!event.ctrlKey) shuffle.checked=!shuffle.checked;return;// s 746 | case 82:if(!event.ctrlKey) repeat.checked=!repeat.checked;return;// r 747 | case 76:if(!event.ctrlKey) loop.click();return;// l 748 | } 749 | return false; 750 | },false); 751 | audio.addEventListener('focus',function(event){// for firefox 752 | this.blur(); 753 | },false); 754 | document.search.str.addEventListener('keyup',function(event){ 755 | event.stopPropagation(); 756 | },false); 757 | document.search.addEventListener('submit',function(event){ 758 | event.preventDefault(); 759 | var out=getId('search'), 760 | i,li,txt,spn,res,loc,x, 761 | len=library.path.length, 762 | cmp=this.match.value=='name', 763 | src=new RegExp(escapeRegExp(this.str.value),this.case.checked?'':'i'); 764 | out.textContent=''; 765 | if(this.str.value=='') return; 766 | for(i in music){ 767 | if(cmp){//by name 768 | txt=music[i].textContent; 769 | } 770 | else{// by path 771 | txt=music[i].getAttribute('file').substr(len); 772 | } 773 | res=txt.match(src); 774 | if(res){ 775 | res=res.toString(); 776 | li=document.createElement('li'); 777 | li.setAttribute('trackId',i); 778 | 779 | loc=txt.indexOf(res); 780 | li.textContent=txt.substr(0,loc); 781 | spn=document.createElement('span'); 782 | spn.textContent=res; 783 | if(music[i].hasAttribute('played')) li.setAttribute('played','yes'); 784 | if(cmp) li.title=music[i].getAttribute('file').substr(len); 785 | li.appendChild(spn); 786 | li.appendChild(document.createTextNode(txt.substr(loc+res.length))); 787 | 788 | li.addEventListener('click',function(){ 789 | sendEvt(music[this.getAttribute('trackId')],'click'); 790 | },false); 791 | out.appendChild(li); 792 | } 793 | } 794 | },false); 795 | 796 | hst.debug=getId('debugHst');// Real time debug for queue/history (well as close as 10 FPS is) 797 | if(hst.debug){ 798 | hst.debugHst=function(e){ 799 | e.textContent='hst.indx = '+hst.indx; 800 | for(var i in hst.log){ 801 | e.appendChild(document.createElement('br')); 802 | e.appendChild(document.createTextNode(i+' : '+music[hst.log[i]].textContent)); 803 | } 804 | setTimeout(function(){hst.debugHst(e);},100); 805 | } 806 | log=console.log; 807 | hst.debugHst(hst.debug); 808 | playlist.setAttribute('style','max-height:120px;min-height:60px;');// Let me see what I'm doing 809 | } 810 | } 811 | window.onresize=function(){// This is to prevent a vertical scroll bar, well untill the min height comes into play 812 | var i, 813 | e=playlist.parentNode.children, 814 | h=window.innerWidth; 815 | h=h<=320?2:(h<=750?15:30);// offsetHeight does not include margin also simulate bottom margin for the tab_container 816 | h+=player.offsetHeight; 817 | h+=tabs.offsetHeight-1;// Has negative bottom margin 818 | h=window.innerHeight-h;// Remaining space available 819 | h+='px'; 820 | for(i=e.length-1;i>-1;i--){ 821 | e[i].style.maxHeight=h; 822 | } 823 | } 824 | -------------------------------------------------------------------------------- /playlist.php: -------------------------------------------------------------------------------- 1 | {$val}=$data[$val][0]; 24 | } 25 | } 26 | return $out; 27 | } 28 | if(isset($_GET['file'])&&isset($_GET['track'])){ 29 | header("content-type: application/json; charset=utf-8"); 30 | $file=explode('/',$_GET['file']); 31 | $track=(int)$_GET['track']; 32 | if(!file_exists($getID3)||array_search('..',$file)!==false||$file[0]!=$F){// Change in server config or theoretical attack on server 33 | die('{"id3":false,"track":'.$track.',"access":"denied"}'); 34 | } 35 | $file=$_GET['file']; 36 | if(!file_exists($file)){ 37 | header("HTTP/1.0 404 Not Found"); 38 | die('{"id3":false,"track":'.$track.'}'); 39 | } 40 | require_once($getID3); 41 | $getID3=new getID3; 42 | $getID3=processID3($getID3->analyze($file)); 43 | die(json_encode((object)array( 44 | "id3"=>$getID3, 45 | "track"=>$track 46 | ))); 47 | } 48 | header("content-type: application/javascript; charset=utf-8"); 49 | ?> 50 | var library={$f}=tree("$dir/$f",$depth+1,$getID3); 64 | } 65 | else{ 66 | $json->{$f}=array("Error: Too Deep"=>array("Infinite loop prevention"=>array())); 67 | } 68 | } 69 | else{ 70 | array_push($files,$f); 71 | } 72 | } 73 | if(count($files)>0){ 74 | $mfiles=(object)array(); 75 | foreach($files as $f){ 76 | if(!$getID3){ 77 | $mfiles->{$f}=false; 78 | } 79 | else if(strtolower(substr($f,0,-3))=='cover.'){ 80 | $mfiles->{$f}=false; 81 | } 82 | else{ 83 | $mfiles->{$f}=processID3($getID3->analyze("$dir/$f")); 84 | } 85 | } 86 | $json->{"/"}=$mfiles; 87 | } 88 | return $json; 89 | } 90 | $mtime=microtime(true); 91 | if($initGetID3==true && file_exists($getID3)){ 92 | require_once($getID3); 93 | $getID3=new getID3; 94 | } 95 | else{ 96 | $initGetID3=file_exists($getID3); 97 | $getID3=false; 98 | } 99 | if(isset($_SERVER['HTTP_REFERER'])){ 100 | parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $REF_GET); 101 | if(isset($REF_GET['folder'])){ 102 | $dir=$REF_GET['folder']; 103 | if(is_dir("$F/$dir")&&array_search('..',explode('/',$dir))===false){ 104 | $F="$F/$dir"; 105 | } 106 | } 107 | } 108 | $out=json_encode((object)array( 109 | 'music'=>tree($F,0,$getID3), 110 | 'path'=>"$F/", 111 | 'id3'=>$initGetID3||!is_bool($getID3), 112 | 'loadTime'=>microtime(true)-$mtime 113 | )); 114 | echo $out===null?'null':$out; 115 | ?>; 116 | if(console&&console.log){ 117 | console.log("Playlist load time: "+library.loadTime); 118 | } 119 | else{ 120 | var console={ 121 | "log":function(){}, 122 | "info":function(){}, 123 | "warn":function(){}, 124 | "error":function(){} 125 | }; 126 | } 127 | if(library===null){ 128 | console.error("Your library contains at least one NON-UTF8 character, Try setting initGetID3 to false. If the issue persist check your file/folder names."); 129 | library={"path":"","error":"Playlist is not UTF-8"}; 130 | } 131 | --------------------------------------------------------------------------------