Skip to content

Commit

Permalink
Merge branch 'dev' at 1.4.0-alpha.
Browse files Browse the repository at this point in the history
Fixes and improves streaming, and introduces usability improvements.

Features:
  Mute state persists in cookie.
  No longer streams audio when muted longer than one song; saves energy, bandwidth.
  Added playback gap setting, which pads start and end of song playback.
  Confirmation dialog if you try to navigate away while you're a DJ.

Modifications:
  Simplified segment streaming, should fix initial playback skips.
  Queue items show song duration instead of album name.
  Made playback progress bar thinner in middle.
  Added placeholder text to search results when query is blank.

Fixed:
  Fixed users' names not updating from login info.
  Fixed sending superfluous room:song:stop messages when song ends.
  Fixed upload button not disabling when disconnected.

Package Changes:
  Upgraded fluent-ffmpeg from v2rc2 to v2rc3.
  • Loading branch information
dag10 committed Jan 18, 2015
2 parents a5e3fe2 + 10afd0f commit 1d01713
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 76 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2014 Drew Gottlieb
Copyright (c) 2015 Drew Gottlieb

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ it should be easily deployable elsewhere.
It's still a work in progress. Here's a screenshot:
![Screenshot](/screenshot.png)

For a standalone client, see [DJ Listener](https://github.com/dag10/DJ-Listener).

Installation
--
First, setup a new mysql database.
Expand Down
3 changes: 3 additions & 0 deletions config.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ module.exports = {
// If true and debug is true, socket.io debug messages will be logged.
debug_socketio: false,

// Seconds of delay between starting the next song.
playback_gap: 1,

// Maximum duration (in minutes) of songs that can be added.
max_duration: 10,

Expand Down
28 changes: 25 additions & 3 deletions less/room.less
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@
@box-shadow: inset 0 0 5px 0 rgba(0,0,0,0.05);
-webkit-box-shadow: @box-shadow;
box-shadow: @box-shadow;
overflow: hidden;

.played {
position: absolute;
Expand All @@ -1062,9 +1063,30 @@
right: 0;
width: 0%;
background-color: @color-purple;
@box-shadow: inset 0 0 3px 0 rgba(0,0,0,0.1);
-webkit-box-shadow: @box-shadow;
box-shadow: @box-shadow;
}

.shape-top, .shape-bottom {
background-color: @nav-background;
display: block;
position: absolute;
@side-offset: -32px;
left: @side-offset;
right: @side-offset;
border-radius: ~"80px /" (@inner-height / 2);
height: @inner-height;

@shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.06);
-webkit-box-shadow: @shadow;
-moz-box-shadow: @shadow;
box-shadow: @shadow;
}

.shape-top {
top: -@inner-height * 9/10;
}

.shape-bottom {
bottom: -@inner-height * 9/10;
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions logic/auth/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ exports.getSessionUser = function(req, res) {
if (user) {
user_info.id = user.id;

user.firstName = user_info.firstName;
user.lastName = user_info.lastName;
user.fullName = user_info.fullName;

user.lastVisitedAt = new Date();
user.admin = user.admin || (config.superadmin == user_info.username);

Expand Down Expand Up @@ -186,6 +190,8 @@ function handleLoginGetRequest(req, res) {
/**
* Handles a login post request.
*
* This function sanitizes the user data.
*
* @param req Express request object.
* @param res Express response object.
*/
Expand Down
2 changes: 1 addition & 1 deletion logic/connection/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = Backbone.Model.extend({
room.get('playback').on('play', function() {
this.sendSongPlayback(room.get('playback'));
}, this);
room.get('playback').on('end', function() {
room.get('playback').on('stop', function() {
this.sendSongPlaybackStopped();
}, this);
if (room.get('playback').playing()) {
Expand Down
33 changes: 27 additions & 6 deletions logic/song/song_playback.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = Backbone.Model.extend({
segments_loaded: false,
played_segments: 0
});
this.once('segment_load', this.sendSegment, this);
this.once('segment_load', this.sendDelayedSegment, this);
},

/* Playback Control */
Expand All @@ -41,7 +41,8 @@ module.exports = Backbone.Model.extend({

this.set({
finishedTimeout: setTimeout(
_.bind(this.finished, this), this.millisecondsRemaining())
_.bind(this.finished, this),
this.millisecondsRemaining() + (config.playback_gap * 1000))
});

this.startStream();
Expand Down Expand Up @@ -222,7 +223,7 @@ module.exports = Backbone.Model.extend({
if (segment_timeout) {
clearTimeout(segment_timeout);
this.unset('segment_timeout');
this.once('segment_load', this.sendSegment, this);
this.once('segment_load', this.sendDelayedSegment, this);
}

if (fileStream) {
Expand Down Expand Up @@ -254,22 +255,42 @@ module.exports = Backbone.Model.extend({
this.trigger('stream_end');
},

/**
* Sents a timeout to sendSegment in half of the playback_gap time from now.
*
* This is to give clients a chance to start actually streaming the song
* without missing the first segment or two.
*/
sendDelayedSegment: function() {
if (this.get('segment_timeout')) {
clearTimeout(this.get('segment_timeout'));
}

this.set({
segment_timeout: setTimeout(
_.bind(this.sendSegment, this), (config.playback_gap * 1000 / 2))
});
},

sendSegment: function() {
var segment = this.segments().shift();
var segments = this.segments();
var played_segments = this.get('played_segments');

if (!segment) {
if (played_segments >= segments.length) {
this.once('segment_load', this.sendSegment, this);
return;
}

var segment = segments[played_segments];
var sampleRate = this.encoder().sampleRate;
var segment_duration = segment.num_samples / sampleRate * 1000;

this.set({
segment_timeout: setTimeout(
_.bind(this.sendSegment, this), segment_duration),
played_segments: this.get('played_segments') + 1
played_segments: played_segments + 1
});

this.trigger('segment', segment.data);
}
});
Expand Down
25 changes: 17 additions & 8 deletions logic/web/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ exports.init = function(app) {
'Expires': '0'
});

var segments_sent = playback.get('played_segments');
var segment_index = playback.get('played_segments');
var ended = false;

// Create a pass-through stream that pipes to the response. This is
Expand Down Expand Up @@ -196,18 +196,27 @@ exports.init = function(app) {
// Function to send as many segments as possible until the stream
// fills up, or we sent as many segments that have been loaded so far.
var send_segments = function() {
var segments = playback.segments();
var played_segments = playback.get('played_segments');
if (!playback.song()) {
end();
}

var segments = playback.segments();
var segments_sent = 0;

while (!ended) {
var index = segments_sent - played_segments;
if (index < 0) index = 0;
if (index < segments.length) {
segments_sent++;
if (!res_stream.write(segments[index].data)) {
// Send at most 10 segments at a time.
if (segments_sent++ > 10) {
setTimeout(send_segments, 0);
break;
}

if (segment_index < segments.length) {
// We increment the segment before the write attempt because
// even if the write attempt "fails", the segement will still be
// in the queue to send. If we send it again (by not incrementing),
// the user will hear segments repeat.
segment_index++;
if (!res_stream.write(segments[segment_index - 1].data)) {
res_stream.once('drain', send_segments);
break;
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "DJ",
"description": "A virtual DJ website, where you can play and listen to music together. Inspired by turntable.fm.",
"version": "1.3.2-alpha",
"version": "1.4.0-alpha",
"author": "Drew Gottlieb (dag10) <[email protected]>",
"repository": {
"type": "git",
Expand All @@ -18,7 +18,7 @@
"winston": "0.7.2",
"sanitizer": "0.1.1",
"multiparty": "git://github.com/andrewrk/node-multiparty.git#3.3.0",
"fluent-ffmpeg": "2.0.0-rc2",
"fluent-ffmpeg": "2.0.0-rc3",
"socket.io": "0.9.15",
"underscore": "1.5.2",
"backbone": "1.1.0",
Expand Down
117 changes: 117 additions & 0 deletions scripts/jquery.cookie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {

var pluses = /\+/g;

function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}

function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}

function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}

function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}

try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}

function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}

var config = $.cookie = function (key, value, options) {

// Write

if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);

if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}

return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}

// Read

var result = key ? undefined : {};

// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];

for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');

if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}

// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}

return result;
};

config.defaults = {};

$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}

// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};

}));
8 changes: 5 additions & 3 deletions scripts/room.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
$(function() {
window.cookies = new models.Cookies();
window.bodyView = new views.Body();

var connection = new Connection({
room_shortname: window.room.shortname
});

window.bodyView = new views.Body();

if (window.user) {
connection.set({
Expand All @@ -15,7 +16,7 @@ $(function() {
connection: connection
});

new views.Queue({
var queueView = new views.Queue({
collection: connection.get('queue'),
connection: connection,
el: $('#queue-column')[0]
Expand All @@ -33,6 +34,7 @@ $(function() {
adder: songAdder,
connection: connection
}),
queueView: queueView,
el: $('#queue-column')[0]
});
}
Expand Down
Loading

0 comments on commit 1d01713

Please sign in to comment.