JS.LA
Jukebox Home Jukebox Demo Jukebox on Github About Me
Contents
- App Requirements
- Technology Strategy
- HTML5 App Features & Limitations
- System Architecture
- RESTful server, socketio, and static files
- Backbone Client App
- Organizing a backbone project
- Chat & sockets
- Audio in the browser using aurora.js
- Metadata from audio, album art
- HTML sliders and progress bars, drag and drop
- Uploading with xhr2
- Vizualizing audio using dancer.js & d3
- Mobile approach using media queries and user agent
- Where to go from here?
App Requirements
- HTML5 Web App
- Audio Playback without Flash
- Chat Rooms via Sockets
- User Authentication
- Media Server
- Library Data
- Queue Data
Technology Strategy
Open Source. Not targeting massive deployment to production. Experimenting with the technology.
HTML5 App Features & Limitations
Applications targeting the browser. HTML5, JS & CSS.
- Browser compatability
- URLs & SEO
- App Stores
- Device screen resolutions
- Network connection, Offline & Caching
Resources
System Architecture
Server
- House on Node.js
- Users & Auth
- Media Uploads
- Library Data & Queue
- Chat Room Sockets
Client
- Backbone App View
- Nav View
- Media Player View
- Player Controls
- Player Information
- Player Visualization
- Library View
- Song List
- Song Row
- Song Upload
- Song Search
- Song List
- Queue View
- Song Queue List
- Song Played List
- Chat View
- Room List
- New Room Form
- Open Room
- Member List
- Message List
- Message Row
- User Avatar & Name
- Msg Txt
- New Message Form
- Room List
RESTful server, socketio, and static files
Backbone friendly REST endpoints:
- Authentication
- Media Files
- Song Library
- Song Queue
- Songs Played
- Chat Rooms REST & socket server
Backbone application static files served from apps/jukebox/web to /jukebox
Backbone Client App
Organizing a backbone project
Example Bootstrap:
<html>
<body>
<div id="jukebox"></div>
<script src="require.js"></script>
<script>
require(['jquery.js'], function(){
require(['index.js'], function(webApp){
webApp.init(function(jukebox){
jukebox.setEl($('#jukebox'));
});
});
});
</script>
</body>
</html>
// index.js
(function(){
var app = {};
app.init = function($el, callback) {
require(['underscore.js'], function(){
require(['backbone.js'], function(){
require(['jukebox.js'], function(jukebox) {
if(callback) callback(jukebox);
});
});
});
}
// use require module pattern
if(define) {
define(function () {
return app;
});
}
})();
Backbone Components
Understand how Views, Models and Collections work together:
- Views have a var $el, a render() function and can listen to dom events
- Views often reference a Collection or Model. ex. ListView (has col) and RowView (has model).
- Collections contain Models
Example:
var MyListView = Backbone.View.extend({
initialize: function() {
this.collection = new MyCollection();
// listen for new documents to add to the list
this.collection.on('add', function(doc){
self.$el.append(doc.getView().el);
});
// load initial data from the server
this.collection.fetch();
},
render: function() {
this.$el.html('<ul></ul>');
return this;
},
events: {
"click": "alert"
},
alert: function() {
alert('you clicked on the view el');
}
});
var view = new MyView({el: $('#div-id')});
view.render();
Chat & sockets
var io = house.io.of('/socket.io/chat');
// user connection to chat
io.on('connection', function (socket) {
// Ask for the room status
socket.on('info', function(room_id, callback) {
callback(roomInfo[room_id]);
});
// Manually advance the queue for a room
socket.on('skip', function(data) {
advanceRoomSongQ(data.roomId, true);
});
// Request to join a room
socket.on('join', function(roomId) {
// subscribe this socket (user) to this room
socket.join(roomId);
// tell the others in the room
io.in(roomId).emit('entered', {room_id: roomId, user: roomUsers[roomId][socket.handshake.session.id]});
socket.on('disconnect', function () {
io.in(roomId).emit('exited', {room_id: roomId, user: roomUsers[roomId][socket.handshake.session.id]});
socket.leave(roomId);
});
});
});
// example of saving a message on the server and emitting it to the chat room
var newMsg = {room_id, msg, user, at};
insertMessage(newMsg, function(err, data) {
io.in(room_id).emit('message', newMsg);
});
// Backbone listens for messages
chat.MessageListView = Backbone.View.extend({
initialize: function() {
this.collection.on('add', function(doc, col) {
$ul.append(doc.getView().render().el);
});
}
});
var socket = io.connect('http://localhost/socket.io/chat');
// join a room
socket.emit('join', room.get('id'));
// listen for messages
socket.on('message', function (data) {
self.rooms[data.room_id].messageCollection.add(data);
});
Audio in the browser using aurora.js
- Can I Use?: Audio API
- Aurora on github
- mp3, flac, alac, m4a, aac, ogg
Example:
var player = Player.fromURL('http://mysite.com/audio.wav');
var player = Player.fromFile(file);
player.play();
player.on('format', function(format){
/*
bitrate: 320000
channelsPerFrame: 2
formatID: "mp3"
sampleRate: 44100
*/
});
player.on('duration', function(msecs){
// update current song time
});
Metadata from audio, album art
Metadata from aurora:
player.on('metadata', function(metadata){
/*
album
artist
genre
title
trackNumber
year
*/
var src = window.URL.createObjectURL(metadata.cover.toBlob());
$('.coverArt').append('<img src="' + src + '" />');
});
Uploading with xhr2
Example:
var formData = new FormData();
var xhr = new XMLHttpRequest();
formData.append('files', blobOrFile);
xhr.open('POST', '/api/files', true);
// Listen to the upload progress.
var progressBar = $row.find('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.val((e.loaded / e.total) * 100);
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.onload = function(e) {
console.log('upload complete');
var data = JSON.parse(e.target.response);
};
xhr.send(formData);
HTML sliders and progress bars, drag and drop
- Can I Use?: Input range
- Can I Use?: Progress Meter
- Can I Use?: DragnDrop
- http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-selecting-files-dnd
Example:
<input class="rating" type="range" min="0" max="100" title="Rating" value="50">
<meter min="0.0" max="100.0" value="33.33"></meter>
Result:
Vizualizing audio using dancer.js & d3
- Dancer API
- Dancer patch to work with Aurora forked on github
- Basic D3 Sample
- Dancer.js & Aurora Example
Example:
var dancer = new Dancer();
var kick = dancer.createKick({
onKick: function ( mag ) {
console.log('Kick!');
drawRadParticles(mag);
},
offKick: function ( mag ) {
console.log('no kick :(');
drawSadParticles(mag);
}
}).on();
Mobile approach using media queries and user agent
- Target screen sizes using css media queries
- Use browser user agent to determain iphone for limiting visualization
- Not currently targeting network connection for media format & compression
Example of css media query to format app for smaller screens:
@media only screen and (min-width: 767px) {
nav li:hover {
color: green;
}
}
Example:
if(window.navigator.userAgent.indexOf('iPhone') !== -1) {
pageSize = 100;
} else {
pageSize = 10; // load less on iphone
}
Where to go from here?
Other cool HTML5
- Routing URLs
- Can I Use?: History
- Web Workers
TODO
- Visualizations as plug-ins
- Skins
- Podcast player
- Drag and drop images to chat
- DJ more than the audio (visual, skin, etc)
- Drag and drop queue
- Round Robin Queue
- Compress HTML app css & js.
- Reference files remotely, without CORS
- Youtube API
- Last.fm
Thank you
Tell me what you think @comster and git the code at https://github.com/comster