User:Laurier Rochon/prototyping/jsplaylist: Difference between revisions
No edit summary |
No edit summary |
||
(One intermediate revision by the same user not shown) | |||
Line 4: | Line 4: | ||
''An example of the player loaded with Kid Koala's awesome live show /w P Love in Montreal.'' | ''An example of the player loaded with Kid Koala's awesome live show /w P Love in Montreal.'' | ||
[https://bitbucket.org/lr/bobby And I've also uploaded the newest version on BitBucket] | |||
Line 113: | Line 116: | ||
<script type="text/javascript"> | <script type="text/javascript"> | ||
var counter = 0 | function addBobby(url,element){ | ||
var | $.getJSON(url, function(data){ | ||
if(data){ | |||
Bobby(data,element) | |||
} | |||
}).error(function(){alert("Cannot find your JSON file '"+url+"'. Check it again?")}) | |||
} | |||
function Bobby(playlist,element){ | |||
var playlist | |||
var uuid = idgen() | |||
var format | |||
var counter = 0 | |||
var lastcounter | |||
function | init() | ||
function init(){ | |||
tempaudio = document.createElement("audio") | |||
canPlayMP3 = (typeof tempaudio.canPlayType === "function" && tempaudio.canPlayType("audio/mpeg") !== ""); | |||
canPlayOGG = (typeof tempaudio.canPlayType === "function" && tempaudio.canPlayType("audio/ogg") !== ""); | |||
//deterine what's playable / use OGG if both are available | |||
if(canPlayMP3){ format = "mp3" } | |||
if(canPlayOGG){ format = "ogg" } | |||
skeleton = '<div id="'+uuid+'">\ | |||
<audio src="'+playlist[counter]+'.'+format+'"></audio>\ | |||
<table cellpadding="6" cellspacing="6" class="player">\ | |||
<tr>\ | |||
<td class="top playtr"><a class="play" href="javascript:void(0)">Play</a></td>\ | |||
<td class="top timeleft"><span class="tleft">0:00</span></td>\ | |||
<td class="top nexttr"><a class="prev" href="javascript:void(0)">Prev</a></td>\ | |||
<td class="top nexttr"><a class="next" href="javascript:void(0)">Next</a></td>\ | |||
<td class="top"> </td>\ | |||
</tr>\ | |||
</table>\ | |||
</div>' | |||
if(!element){ | |||
$("body").append(skeleton) | |||
}else{ | |||
e = $(element) | |||
if(e.length==0){alert("Couldn't find element "+element+", check your html?")}else{ | |||
e.append(skeleton) | |||
} | |||
} | |||
$.each(playlist, function(index, value) { | |||
basename = value.split("/") | |||
$("#"+uuid+" table.player").append('<tr class="track"><td class="track'+index+' tracktitle" data-trackno="'+index+'" colspan="5">'+(index+1)+'. '+spaceout(basename[basename.length-1])+'</td></tr>') | |||
}); | |||
audio = $('#'+uuid+' audio').get(0); | |||
bind(audio) | |||
updateplaying() | |||
} | } | ||
function | function bind(audio){ | ||
$(audio).bind('timeupdate', function() { | |||
rem = parseInt(audio.duration - audio.currentTime, 10), | |||
pos = (audio.currentTime / audio.duration) * 100, | |||
mins = Math.floor(rem/60,10), | |||
secs = rem - mins*60; | |||
} | if(secs<10){ | ||
secs = "0"+secs | |||
} | |||
$("#"+uuid+" .tleft").html(mins+":"+secs); | |||
}); | |||
// | //add classes playing/not playing | ||
/ | $(audio).bind('play',function() { | ||
function | $("#"+uuid+" .play").addClass('playing'); | ||
}).bind('pause ended', function() { | |||
$("#"+uuid+" .play").removeClass('playing'); | |||
}).bind('ended', function() { | |||
next(audio, playlist) | |||
}); | |||
} | |||
//play individual tracks | |||
$("#"+uuid+" table td.tracktitle").bind('click',function() { | |||
trackno = $(this).data().trackno | |||
flipstate("Pause") | |||
counter = trackno | |||
index = trackno | |||
} | pushplay(audio, playlist[index]) | ||
}); | |||
function | //play/pause | ||
$("#"+uuid+" .play").bind('click',function() { | |||
if (audio.paused) { | |||
} | audio.play(); | ||
flipstate(); | |||
}else{ | |||
audio.pause(); | |||
flipstate(); | |||
} | |||
}); | |||
$("#"+uuid+" .next").bind('click',function() { | |||
next(audio, playlist) | |||
flipstate("Pause") | |||
}); | |||
$("#"+uuid+" .prev").bind('click',function() { | |||
prev(audio, playlist) | |||
flipstate("Pause") | |||
}); | |||
} | |||
} | |||
//taken from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript | |||
//id mainly for CSS reference | |||
function idgen() { | |||
} | var S4 = function() { | ||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1); | |||
}; | |||
return (S4()+S4()+S4()); | |||
} | |||
function spaceout(string){ | |||
string = string.replace(/_/g," ") | |||
return string | |||
} | |||
} | |||
function flipstate(state){ | function flipstate(state){ | ||
if(state){ | |||
$("#"+uuid+" .play").html(state) | |||
}else{ | }else{ | ||
$("#play").html("Pause") | if($("#"+uuid+" .play").html()=="Pause"){ | ||
$("#"+uuid+" .play").html("Play") | |||
}else{ | |||
$("#"+uuid+" .play").html("Pause") | |||
} | |||
} | } | ||
} | } | ||
function updateplaying(){ | function updateplaying(){ | ||
if(typeof lastcounter != 'undefined'){ | |||
current = $("#track"+ | current = $("#"+uuid+" .track"+lastcounter); | ||
current.css("background","# | current.css("background","#EFEFEF"); | ||
current.css("color","# | current.css("color","#000"); | ||
} | |||
current = $("body #"+uuid+" .track"+counter); | |||
current.css("background","#999"); | |||
current.css("color","#fff"); | |||
lastcounter = counter | |||
} | } | ||
function pushplay(audio, track){ | |||
$. | audio.setAttribute("src", track+"."+format); | ||
msg = $("#"+uuid+" .tleft") | |||
msg.html("loading...") | |||
res = audio.load() | |||
audio.play() | |||
updateplaying() | |||
} | |||
function next(audio){ | |||
if(counter<playlist.length-1){ | |||
counter++ | |||
} | pushplay(audio, playlist[counter]) | ||
} | |||
} | |||
function prev(audio){ | |||
if(counter!=0){ | |||
counter-- | |||
pushplay(audio, playlist[counter]) | |||
} | |||
} | |||
} | |||
} | |||
</script> | </script> |
Latest revision as of 23:57, 16 January 2012
HTML5 Audio player /w integrated playlist rendering
An example of the player loaded with Kid Koala's awesome live show /w P Love in Montreal.
And I've also uploaded the newest version on BitBucket
Live on the server here : http://pzwart3.wdka.hro.nl/~lrochon/playlist_1/
- I made a simple system that generates a playlist out of a JSON file.
- It's the first iteration, so it's a little shabby
- Dependencies : ffmpeg, php, bash, a recent browser
- Note : when running locally Chrome, beware of the allow-file-access bug. Run with "chromium-browser --allow-file-access-from-files" otherwise JSON loading via jQuery will break.
How it works
- You put MP3 files in a folder (it is assumed most people have mp3 files to start with)
- Run mk.php either locally or on the server, three things happen : A)It first invokes rename.sh which takes care of renaming the tracks to make sure there's no funny characters, spaces, etc. (within the same folder) B)for every track, an OGG version is created, using the same same C)a JSON file is created, which is then used for the playlist
- On loading the page :
- - Basic HTML code and controls are loaded, I replaced the default player with my own
- - JS checks if it can load MP3s or OGG files in the browser. This is then used throughout. OGG is given precedence if both can be played.
- - An <audio> tag is being created with the first element of the playlist in the source
- - For every track in the playlist, create a element which is clickable and bound to the play function
- - Most of whatever code is leftover is just binding and checking for states. For example the next/prev buttons, play/pause toggle, updating time, etc.
- - Small interesting thing : it seems like waiting for "data to finish loading" and attaching a callback sometimes flakes out in certain browsers, making it difficult to judge when to start playing the next track. Apparently using audio.load() before audio.play() helps prevent this. I've tried it and had no difficulties streaming the files off the server at all. Would need to test behavior on a slow connection.
Improvements
- Make this playlist an object so it is easy to have many on a single page. It's already somewhat crafted in that direction with a bunch of functions waiting to be methods and a block at the bottom which would easily be turned into the constructor. Then one could easily send the playlist URL to the constructor as a parameter and we're set. JS's prototype OOP system...hmm.
- Change the links to reference the MP3 files from a level higher (or more) if necessary. Just to avoid mass downloading of the files if they are copyrighted - especially if on a page with many of these playlists.
- More error checking. There is not much feedback given for error right now.
Soft
index.html
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="jquery1.7-min.js" type="text/javascript"></script>
<link href='http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700' rel='stylesheet' type='text/css'>
<style type="text/css">
#bts{
font-family: 'PT Sans Narrow', sans-serif;
width:500px;
overflow:hidden;
}
.top{
background:#eee;
font-weight:700;
}
.playing{
min-width:200px;
}
.tracktitle{
background:#efefef;
}
a{
text-decoration:none;
color:#940000;
}
#play,#prev,#next{
text-transform:uppercase;
}
.tracktitle{
cursor:pointer;
}
.tracktitle:hover{
background:#ddd;
}
</style>
</head>
<body>
<div id="bts">
<table cellpadding="6" cellspacing="6" id="player" width="500">
<tr>
<td class="top" width="50"><a id="play" href="javascript:void(0)">Play</a></td>
<!--<td class="top playing"><span id="playing"></span></td>-->
<td class="top" width="100"><span id="tleft">0:00</span></td>
<td class="top"><a id="prev" href="javascript:void(0);">Prev</a></td>
<td class="top"><a href="javascript:void(0);" id="next">Next</a></td>
</tr>
</table>
</div>
</body>
</html>
javascript
(externally linked or just within the HEAD)
<script type="text/javascript">
function addBobby(url,element){
$.getJSON(url, function(data){
if(data){
Bobby(data,element)
}
}).error(function(){alert("Cannot find your JSON file '"+url+"'. Check it again?")})
}
function Bobby(playlist,element){
var playlist
var uuid = idgen()
var format
var counter = 0
var lastcounter
init()
function init(){
tempaudio = document.createElement("audio")
canPlayMP3 = (typeof tempaudio.canPlayType === "function" && tempaudio.canPlayType("audio/mpeg") !== "");
canPlayOGG = (typeof tempaudio.canPlayType === "function" && tempaudio.canPlayType("audio/ogg") !== "");
//deterine what's playable / use OGG if both are available
if(canPlayMP3){ format = "mp3" }
if(canPlayOGG){ format = "ogg" }
skeleton = '<div id="'+uuid+'">\
<audio src="'+playlist[counter]+'.'+format+'"></audio>\
<table cellpadding="6" cellspacing="6" class="player">\
<tr>\
<td class="top playtr"><a class="play" href="javascript:void(0)">Play</a></td>\
<td class="top timeleft"><span class="tleft">0:00</span></td>\
<td class="top nexttr"><a class="prev" href="javascript:void(0)">Prev</a></td>\
<td class="top nexttr"><a class="next" href="javascript:void(0)">Next</a></td>\
<td class="top"> </td>\
</tr>\
</table>\
</div>'
if(!element){
$("body").append(skeleton)
}else{
e = $(element)
if(e.length==0){alert("Couldn't find element "+element+", check your html?")}else{
e.append(skeleton)
}
}
$.each(playlist, function(index, value) {
basename = value.split("/")
$("#"+uuid+" table.player").append('<tr class="track"><td class="track'+index+' tracktitle" data-trackno="'+index+'" colspan="5">'+(index+1)+'. '+spaceout(basename[basename.length-1])+'</td></tr>')
});
audio = $('#'+uuid+' audio').get(0);
bind(audio)
updateplaying()
}
function bind(audio){
$(audio).bind('timeupdate', function() {
rem = parseInt(audio.duration - audio.currentTime, 10),
pos = (audio.currentTime / audio.duration) * 100,
mins = Math.floor(rem/60,10),
secs = rem - mins*60;
if(secs<10){
secs = "0"+secs
}
$("#"+uuid+" .tleft").html(mins+":"+secs);
});
//add classes playing/not playing
$(audio).bind('play',function() {
$("#"+uuid+" .play").addClass('playing');
}).bind('pause ended', function() {
$("#"+uuid+" .play").removeClass('playing');
}).bind('ended', function() {
next(audio, playlist)
});
//play individual tracks
$("#"+uuid+" table td.tracktitle").bind('click',function() {
trackno = $(this).data().trackno
flipstate("Pause")
counter = trackno
index = trackno
pushplay(audio, playlist[index])
});
//play/pause
$("#"+uuid+" .play").bind('click',function() {
if (audio.paused) {
audio.play();
flipstate();
}else{
audio.pause();
flipstate();
}
});
$("#"+uuid+" .next").bind('click',function() {
next(audio, playlist)
flipstate("Pause")
});
$("#"+uuid+" .prev").bind('click',function() {
prev(audio, playlist)
flipstate("Pause")
});
}
//taken from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
//id mainly for CSS reference
function idgen() {
var S4 = function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
return (S4()+S4()+S4());
}
function spaceout(string){
string = string.replace(/_/g," ")
return string
}
function flipstate(state){
if(state){
$("#"+uuid+" .play").html(state)
}else{
if($("#"+uuid+" .play").html()=="Pause"){
$("#"+uuid+" .play").html("Play")
}else{
$("#"+uuid+" .play").html("Pause")
}
}
}
function updateplaying(){
if(typeof lastcounter != 'undefined'){
current = $("#"+uuid+" .track"+lastcounter);
current.css("background","#EFEFEF");
current.css("color","#000");
}
current = $("body #"+uuid+" .track"+counter);
current.css("background","#999");
current.css("color","#fff");
lastcounter = counter
}
function pushplay(audio, track){
audio.setAttribute("src", track+"."+format);
msg = $("#"+uuid+" .tleft")
msg.html("loading...")
res = audio.load()
audio.play()
updateplaying()
}
function next(audio){
if(counter<playlist.length-1){
counter++
pushplay(audio, playlist[counter])
}
}
function prev(audio){
if(counter!=0){
counter--
pushplay(audio, playlist[counter])
}
}
}
</script>
mk.php
<?php
function d($directory)
{
$results = array();
$handler = opendir($directory);
while ($file = readdir($handler)) {
if ($file != "." && $file != ".." && strstr($file,".mp3")) {
$results[] = $file;
}
}
closedir($handler);
return $results;
}
//clean up names first
shell_exec("./rename.sh");
$res = d(".");
$a = array();
sort($res);
foreach($res as $file){
$newname = substr_replace($file , 'ogg', strrpos($file , '.') +1);
$noext = substr_replace($file , '', strrpos($file , '.'));
echo "making file {$newname}\n";
shell_exec("ffmpeg -i ".$file." -y -acodec vorbis -aq 60 ".$newname."");
array_push($a,$noext);
}
$tracks = json_encode($a);
$f = fopen("tracks.json","w");
fwrite($f,$tracks);
fclose($f);
?>
rename.sh
#!/bin/bash
ls | while read -r FILE
do
mv -v "$FILE" `echo $FILE | tr ' ' '_' | tr -d '[{}(),\!]' | tr -d "\'" | tr '[A-Z]' '[a-z]' | sed 's/_-_/_/g'`
done
tracks.json
["04.kid_koala_+_p_love_interlude_1","05.kid_koala_+_p_love_nufonia_must_fall_opening_credits","06.kid_koala_+_p_love_nufonia_must_fall_track_1","07.kid_koala_+_p_love_interlude_2","08.kid_koala_+_p_love_nufonia_must_fall_trailer_music","09.kid_koala_+_p_love_interlude_3"]