Tag Archives: css

Yeah so my audio player in jQuery Mobile and NodeJS (part 2)

Ok so my prev post was about how to construct a playlist in jQuery Mobile with some simple NodeJS file serving. This one is to construct the audio player itself! Mine looks like this (cause I was kinda too lazy to do the styling properly):

My audio player

My audio player

Ok so it’s very basic: we got a toggle Play/Pause button, Next button, Prev button, the progress bar for download progress and a fake album art (cause I didn’t know how to extract mp3 metadata yet). The features that I implemented are pretty basic:

1. Play/Pause

2. Next/Prev Song

3. Progress bar for song buffering

4. Time Left

5. Auto-play the next one if this one ended

6. Header shows song name

The HTML structure itself is rather simple as jQuery Mobile does most of the styling for u:


<div data-role="page" class="player">

    <div data-role="header">
        <h1>My collection</h1>
    </div><!-- /header -->

    <div data-role="content">

	<div class='cover-art' style='text-align:center'>
		<audio src='music/kpop/ttl2.mp3' preload autoplay></audio>
		<img src='images/no-album-art.png' />
	</div>
    </div><!-- /content -->
    <div data-role='footer' style='text-align:center'>
	<p class='track-info'>
		<span class="song-progress">
			<input type="range" min="0" max="100" value="0" />
		</span>
		<span class="timeleft"></span>
	</p>
	<div class='playback' data-role="controlgroup" data-type='horizontal' style='text-align:center'>
<button class='playback-prev' data-icon='back'>Prev</button>
<button class='playback-play' >||</button>
<button class='playback-next' data-icon="forward" data-iconpos="right">Next</button>
</div>
	</div>
<script type="text/javascript">
	$('div.player').bind('pageshow', function(ev, ui) {
		if (!$(this).attr('data-init')) {
			Player.init('div.player.ui-page-active', $.getUrlVar($(this).attr('data-url'), 'song'));
			$(this).attr('data-init', 'true');
		}
	});
</script>
</div><!-- /page -->

So again, I bind some initialization to the “pageshow” event of the page and make sure it doesn’t get initialized twice. Since the href in each <li> points to the same page but different parameter, jQuery loads this again every single time even if it’s the same one. This only prevents the forward history button to reload the song. However, this does not prevent having multiple songs playing at the same time cause jQuery mobile loads those as different div. You can customize the changePage behavior when user clicks on a <li> but I didn’t, just to keep it simple.

The parameter is stored in the main player div (with selector “div.player”, class “ui-page-active” indicates its the active one) so $.getUrlVar just extract the parameter song from it (which indicates the song index):

$.extend({
	getUrlVars : function(string) {
		var vars = [];
		var hash;
		var href = string ? string : window.location.href;
		console.log(href);
		if (href.indexOf('#') > -1) {
			var hrefArr = href.split('#');
			href = hrefArr[hrefArr.length - 1];
		}
    		var hashes = href.slice(href.indexOf('?') + 1).split('&');
		for (var i = 0; i < hashes.length; i++) {
			hash = hashes[i].split('=');
			vars.push(hash[0]);
			vars[hash[0]] = hash[1];
		}
		return vars;
	},

	getUrlVar : function(string, name) {
		return $.getUrlVars(string)[name];
	}
});

Pretty simple, just basically splitting the data-url field into a map of parameter names and values. The Player.init function takes in the parent div selector (so that I can locate the control relative to the parent div) and the song index. I basically keep track of all the control DOM elements:

var $next = $(div + ' button.playback-next');
var $prev = $(div + ' button.playback-prev');
var $play = $(div + ' button.playback-play');
var $trackInfo = $(div + ' p.track-info');
var $songProgress = $trackInfo.find('.song-progress');
var $loading = $songProgress.find('.loading');
var $timeLeft = $trackInfo.find('.timeleft');
var $slider = $songProgress.find('.ui-slider');
var $handle = $slider.find('.ui-slider-handle');
$songProgress.find('input[type="number"]').hide();
var $title = $(div + ' h1.ui-title');
var $audio = $(div + ' audio');
var audio = $audio.get(0);

I have this habit of prefixing jQuery objects with $ to distinguish from actual DOM element ($audio is the jQuery-wrapped object of audio). Play/pause is really easy:

$play.click(function(ev) {
    var $buttonText = $(this).parent().find('.ui-btn-text');
    if (audio.paused) {
        $audio.attr('data-state', 'play');
        audio.play();
        $buttonText.text("||");
    }
    else {
        $audio.attr('data-state', 'pause');
        audio.pause();
        $buttonText.text("Play");
    }
});

Prev/Next is also straightforward:

$next.click(function(ev) {
    var state = $audio.attr('data-state');
    var current = parseInt($audio.attr('data-current'));
    Player.getSongPath(current + 1, $audio, $title, function() {
        $audio.attr('data-current', current + 1);
        if (state == 'play') {
            audio.play();
        }
    });
});
$prev.click(function(ev) {
    var state = $audio.attr('data-state');
    var current = parseInt($audio.attr('data-current'));
    Player.getSongPath(current - 1, $audio, $title, function() {
        $audio.attr('data-current', current - 1);
        if (state == 'play') {
            audio.play();
        }
    });
});

So we’ve done 1 and 2. Let’s jump to 5 cause its also easy:

$audio.bind('ended', function(ev) {
    $next.click();
});

I did 6 as a separate functionality that ping the server for the song’ path, change the audio source and also title:

getSongPath: function(index, $audio, $title, fn) {
    $.post('playlist?song=' + index, null, function(data) {
        console.log(data);
        $audio.attr('src', data.result);
        var filenameArr = data.result.split('/');
        var filename = filenameArr[filenameArr.length - 1];
        $title.text(filename);
        if ($.isFunction(fn)) {
            fn();
        }
    }, 'json');
}

For some reason I put null in the POST request data instead of the actual data (song=2) cause I wasn’t getting that data on the server side (I tried req.body, req.query and everything… didn’t seem to show up, will look into it a bit more). OK now lets get back to 3:

if (!$loading.get(0)) { //this inject the white loading bar before the handler
    $handle.before('<div class="ui-slider loading" style="width: 3%; float: left; top: 0; left: -3%; background-color: buttonface;"></div>');
    $loading = $slider.find('div.loading'); //update var
}
$audio.bind('progress', function() {
    var loaded = parseInt(((audio.buffered.end(0) / audio.duration) * 100) + 3, 10);
    $loading.css({
        width: loaded + '%' //change width accordingly
    });
});
var manualSeek = false;
var loaded = false;
$handle.css({
    top: '-50%' //somehow I think the styling of footer and handler conflicted and messed it up so I had to bump it up 50%
});

I actually didn’t know how to get the current time of the audio but after googling around and looking at audio attributes, things got a bit clearer. Here’s 4:

$audio.bind('timeupdate', function() {
    var rem = parseInt(audio.duration - audio.currentTime, 10),
        pos = Math.floor((audio.currentTime / audio.duration) * 100),
        mins = Math.floor(rem / 60, 10),
        secs = rem - mins * 60;
    $timeLeft.text('-' + mins + ':' + (secs > 9 ? secs : '0' + secs));
    if (!manualSeek) {
        $handle.css({
            left: pos + '%'
        });
    }
    if (!loaded) {
        loaded = true;
    }
});

Ok so that’s how I made a sorta functional audio player. There’re still problems with it but hopefully this DIY guide gave u some idea on how to control the audio element manually.

Advertisements
Tagged , , , , , , ,

What I’ve been up to… (a.k.a making an audio player using jQuery Mobile & NodeJS) Part 1

So I recently signed up for a VPS from AlienVPS at a ridiculously low price and guess what, it crashed on me twice today… -_- But $19/month is still pretty darn cheap. At least it gave me some sandbox to play around with NodeJS and jQuery Mobile.

OK so far NodeJS has been rather simple and straightforward. I actually use Express framework on top of NodeJS which ease the work a little bit. However, I can really see how this can become complicated really really fast. 1st of all, I sorta have to implement all the HTTP protocol code manually in NodeJS (except for 500 Internal Error and 200 OK I think). So that includes 404, 403, blah blah. Not that it matters that much except I wanna maximize my site traffic by taking advantage of Search Engine bots. Well I disallow everything in robots.txt right now so if those bots behave, I should be good. You can check it out at http://208.93.155.39:8080/playlist.html but please please don’t spread it around or I’m gonna have to shut it down due to my limited bandwidth. The app is still buggy since it’s a work in progress but a refresh should make it behave a bit better.

Anyway, now the 1st thing a web server should be able to do is to serve static web pages and it can be achieved pretty easily:

var app = require('express').createServer();
var fs = require('fs');
var public_path = 'public/';
var PORT = 8080;
app.get('/', function(req, res) {
        res.sendfile(public_path + 'index.html');
});
app.get('/*', function(req, res) {
        var page = req.params[0];
        res.sendfile(public_path + page);
});

Easy enuf… Now I want to query some specific stuff like, idk my KOREAN POP playlist!!

var songs;
app.post('/playlist.html', function(req, res){
        Controller.handlePlaylist(req, res);
});
var Playlist = {
        get : function(name) {
                return fs.readdirSync(public_path + MUSIC_PATH + name);
        }
};
var Controller = {
        handlePlaylist : function(req, res) {
                if (!songs) { //lazy-initialize this
                        songs = Playlist.get(req.param('playlist'));
                }
                var index = req.param('song');
                if (index && index >=0 && index < songs.length) { //if I query a specific song number, give me the path to the song
                        res.send({ 'result' : MUSIC_PATH + 'kpop/' + songs[index] });
                } else { //otherwise give me the whole list
                        res.send({ 'filenames' : songs });
                }
        }
};

Now when I hit up playlist.html?playlist=kpop with POST, I can get my playlist and playlist.html?song=1 with POST gives me the 2nd song. This is a simple enuf song serving mechanism that will help me build my audio player.

Playlist Page

Playlist Page

Since I’m not using any view rendering engine, in playlist.html I actually have to use the trick of loading the file 1st, then make an ajax call to populate the data. This gets very very tricky with jQuery mobile since it doesn’t have a lot of control events on when it’s done rendering and what not. This combines with ambiguous timing from AJAX callbacks can lead to a pretty disruptive UX (I’m still having trouble with synchronizing stuff in JavaScript). But anyway, the playlist.html has a pretty simple structure:

<div data-role="page" class="playlist">
    <div data-role="header">
        <h1>My collection</h1>
    </div><!-- /header -->

    <div data-role="content">
        <ul data-role="listview" data-inset="true">
                <li data-role="list-divider">Kpop</li>
        </ul>
    </div><!-- /content -->
    <script type='text/javascript'> 
    $('div.playlist').bind('pageshow', function() {
        var $page = $(this); // to use inside callback since "this" in the callback function is different
        if (!($page.attr('data-init'))) { // Initialize once
                $.post('playlist.html?playlist=kpop', null, function(data) { //retrieve the data
                        var i;
                        var filenames = data['filenames'];
                        var $playlist = $page.find('ul[data-role="listview"]');
                        for (i in filenames) { //populate the list of songs
                                $playlist.append('<li><a href="player.html?song=' + i + '">' + filenames[i] + '</a></li>'); 
                        }
                        $page.attr('data-init', 'true');
                        $playlist.listview('refresh'); //refresh the view
                }, 'json');
        }
    });
    </script>

</div><!-- /page -->

The HTML structure itself is simple the the JavaScript is kind of a hack. “Pageshow” event in jQuery Mobile gets invoked after page has been initialized (a.k.a after jQuery converts basic elements into its themed mobile looks). Why not “pagecreate” or “pagebeforecreate”? Because the callback is actually an AJAX call to grab the data and listview can only be refresh after it’s been initialized (also not guaranteed in the previous 2 event hooks). If I were to use a view rendering engine to populate the data, then send across the wire, I wouldn’t have had this problem so… something to look at next time.

OK so that’s the easy part, I write next time about how to actually make the player cause that took me like 3 days… >.< sleep now!

Tagged , , , , , ,

Redesigning my site!!!

So I’ve been a bit late in blogging today (normally I blogged at like 2a.m) cause I’ve been working on a new skin for my website. I got bored after looking at it for a while so a redesign would also help keep my creativity fresh. Also there’re a lot of awesome professional web designs out there that brought to my attention so I’ll dedicate this post to some of them. So this’s what I have so far as the prototype:

Prototype Site

Prototype Site

So I’ve tried to simplify it and keep focus on certain parts of the site. I also tried to minimize all the descriptions and stuff since reading is not really that fun in general. Hmm what else, oh brighter background and tetradic color scheme. The glasses in the background are the closest ones to mine that I can find on stock xchng. BTW, if you want high quality plain images of objects, go to stock xchng at http://www.sxc.hu/. They have really really high-res pictures. Of course it’s non-commercial and for personal use only.

I also found out that my domain is surprisingly close to another domain called azndesign (mine is azndezign). Coincidence lol! The guy is also Vietnamese and is a graphics & web design. He’s much more experienced than me though. Anyway, besides stock xchng, you can also visit http://colorschemedesigner.com/ for color scheme generator (wait I think I mentioned that).

For inspirational purpose, you can check out some of the top web designs right here:

http://www.thebestdesigns.com/

Also here are the best CSS website collection:

http://www.csselite.com/

Some of my favorite ones are here:

Aaron Beall

Aaron Beall

Morphix

Morphix

N-Design Studio

N-Design Studio

Oh btw CSS websites refer to non-flash web. I myself am not a big fan of flash although it’s a very strong animation format. Well first of all Mac can’t run flash, yeah Flash will burn your Mac processor. It’s an architecture problem so it’s unlikely its gonna get fixed soon.

Flash is also not compatible with iPhone so you have to maintain 2 versions of your site, which is a pain. Flash also requires flash plugin and that leads to some additional security issues. Yup so stick with XHTML/CSS + JavaScript. Aaron Beall’s site is just too awesome I gotta make an exception for it. Kk guys have fun and keep on designing!

Tagged , , , , , , , , , ,

Web 2.0 Stuff… hmm Part 1 I guess – FONT!!

Now in case you don’t know what Web 2.0 is, it’s pretty much everything you use nowadays including social media like Facebook, YouTube and all that. It’s a new trend of web design both in terms of visual interface and technology. The technology part I believe is mainly the development of AJAX (Asynchronous JavaScript And XML), which gives you the ability to push/pull specific part of your website without reloading the page, like what I did with my personal homepage right here: http://www.azndezign.com

Anyway to me AJAX is really cool since you might have realized a big part of a website might be its template. Well it’s also useless to reload the template cause it’s not gonna be changed until the webmaster upgraded the site itself (like Facebook changed its template 125816 times in like 3 months). Thus, web becomes more efficient, data gets packed into its very raw form and then made beautiful once its gets to your Firefox (or IE/Chrome/Safari) browser, saving you Internet traffic bill every month (yes!).

The more impressive part of web 2.0 is the significant change in visual interface, particularly the elimination of using tables with “spacer.gif” (yeah I hate spacer.gif so much) and switch to liquid/fixed/hybrid div tags which are cross-browser compatible and visually stunning also. In this 1st part I’ll talk about FONT!!!! Yes web 2.0 font is nice and elegant for sure.

So let’s see… I believe there’re 2 main font family: serif and sans-serif. What is serif? serif indicates the stroke at the tail of the letter like Times New Roman font:

Serif fonts

Serif fonts

I saw serif fonts more often in headlines and sans-serif in the body, probably because too many strokes confuse your eyes. You can read the details on Wikipedia so let’s not dive into history too much: http://en.wikipedia.org/wiki/Serif

So what is sans-serif? sans-serif fonts don’t have those strokes. They look more fresh and creative than the traditional mentioned fonts. Arial is 1 example of sans-serif but for web 2.0 I see a lot of people using Helvetica.

Helvetica Font

Helvetica Font

Most computers do support this font. Otherwise, Verdana is also equally nice and is a web-safe font just like Times New Roman. In general I believe there’re 9 web-safe fonts that are on both Windows and Mac OS X:

9 Web Safe Fonts

9 Web Safe Fonts

As you can see, there’re only 3 serif fonts out of 9, which means sans-serif is taking over (soon).

Besides choosing a nice font-family, a reasonable font size is also necessary. 12pt seems to be the common standard that works in most screen resolution. You don’t want the font size to be too large, which can cause pixelation and make you page look totally ugly. Of course too small means people can’t read your page properly.

In addition, you can incorporate your own fonts using CSS (Cascading Style Sheet). For example I use Ubuntu font for my navigation bar in my homepage:

@font-face {
font-family: "Ubuntu";
src: url(fonts/ubuntu.ttf) format("truetype");
}

Ubuntu Font

Ubuntu Font

This font looks kinda cute, clearly sans-serif but requires additional rendering from the browser. Also it’s a good practice to fall back on a traditional font if your computers/web browsers do not support those. You can do that in CSS:
font-family: Helvetica, Verdana, Arial, sans-serif;

What this does is it tries to render the web page using those fonts in priority. 1st priority is Helvetica (my favorite), if Helvetica is not available, then Verdana, then Arial, then any type of generic sans-serif fonts that computer has. You can change this weight by adjusting
font-weight: bold;

if the font supports bold text. Now if you’re good at graphics design and typography and all, you can even make your own font. Making a new font is rather complicated since you must follow a specific set of rules

Anatomy of Letterform

Anatomy of Letterform

So there you go, pick your favorite font or make your own and keep on designing!!

Tagged , , , , , , , ,

How to make a combobox with jQuery

So jQuery came out with a new version 1.4.2 recently and jQuery UI is trying catch up with its 1.8rc3 since 1.7.2 is not compatible with the new jQuery library itself. Since jQuery UI starts incorporating its own theme builder tool, I’ve been using it and it seems to be very nice and elegant itself.

jQueryUI has its own autocomplete feature and button so I just wanna combine them together to make a combobox, which acts as both a select box and an autocomplete field.

So the HTML is like this:

<input id="example-text-field" type="text" /><button></button>

Well you can dynamically adding the button and even the text field using jQuery manipulation method

We start with configuring the autocomplete text field first.

var $input = $('#example-text-field');
$input.autocomplete({
   source: 'ExampleAutocompleteServerHandler.do', //you obviously need the source, it can be a JavaScript array or string or some handler from the server
   minLength: 0, //important! You need to set minLength to 0 for the button to work
   delay: 0 //make it a bit faster since there's no delay
})
.css('float', 'left');

So the text field is done, now let’s move on to the button next to it:

var $btn = $input.siblings('button:first');
$btn.button({
   icons: {
      primary: "ui-icon-triangle-1-s" //this puts the down arrow into the button
   },
   text: false //no text inside the button, just the arrow
})
.removeClass("ui-corner-all")  //disable rounded corner, it's optional
.addClass("ui-button-icon")   //put in styling as a button icon since the default button is pretty big
.css({
   height: "17px",    //make it as high as the text field
   width: "18px",    //this is optional, you can make it as wide as you want
   float: 'left'    //float it to the left so that it's right next to the text field
})
.unbind('click')   //it's optional but I like to clear all events associated to it before binding new click event
.click(function() {   //set up click binding event
   if ($input.autocomplete("widget").is(":visible")) {   //close the displaying result if it's visible already
      $input.autocomplete("close");
      return false;
   }
   $input.autocomplete("search", ""); //pass empty string as search query, this is why you need to set minLength = 0 so that it still searches, but displays all possible choices
   $input.focus();  //focus on the text field
   return false;  //disable form submit
});

$input.parents(':first').find('.ui-autocomplete') //configure result display so that it won't shift down the content
.css('width', '150px')
.css('height', '100px')
.css('overflow', 'auto');

There you go, combobox is done, you can pack it into a plugin with jQuery or inside your main JavaScript file, up to you.

Tagged , , , , , ,