From 458e3f50401a0fff24eaa403d4280ecb36d64021 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Tue, 6 Jan 2015 19:19:43 -0500 Subject: [PATCH 01/19] Upgrade fluent-ffmpeg to v2 rc3. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ef2c8e..7219694 100644 --- a/package.json +++ b/package.json @@ -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", From a2b6953d4d4768314c8e262e7fa700ad6d77480a Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Wed, 7 Jan 2015 01:45:22 -0500 Subject: [PATCH 02/19] Simplified segment streaming, should fix initial playback skips. --- logic/song/song_playback.js | 9 ++++++--- logic/web/handlers.js | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/logic/song/song_playback.js b/logic/song/song_playback.js index 55d1959..d0e5ab9 100644 --- a/logic/song/song_playback.js +++ b/logic/song/song_playback.js @@ -255,21 +255,24 @@ module.exports = Backbone.Model.extend({ }, 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); } }); diff --git a/logic/web/handlers.js b/logic/web/handlers.js index 9fda216..64be9d7 100644 --- a/logic/web/handlers.js +++ b/logic/web/handlers.js @@ -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 @@ -197,21 +197,27 @@ exports.init = function(app) { // 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'); + //var played_segments = playback.get('played_segments'); if (!playback.song()) { end(); } + 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) { + if (!res_stream.write(segments[segment_index].data)) { res_stream.once('drain', send_segments); break; } + segment_index++; } else if (playback.get('segments_loaded')) { + winston.debug('ALL SEGMENTS SENT!'.white.greenBG); end(); break; } else { From a154ca0e26691533399be8643b2d0d1f561af290 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 8 Jan 2015 18:37:22 -0500 Subject: [PATCH 03/19] Fixed repeating segments, cleaned up debug messages. --- logic/web/handlers.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/logic/web/handlers.js b/logic/web/handlers.js index 64be9d7..66470cb 100644 --- a/logic/web/handlers.js +++ b/logic/web/handlers.js @@ -196,13 +196,13 @@ 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) { // Send at most 10 segments at a time. if (segments_sent++ > 10) { @@ -211,13 +211,16 @@ exports.init = function(app) { } if (segment_index < segments.length) { - if (!res_stream.write(segments[segment_index].data)) { + // 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; } - segment_index++; } else if (playback.get('segments_loaded')) { - winston.debug('ALL SEGMENTS SENT!'.white.greenBG); end(); break; } else { From c659b2051980bec0748e2acbc2774bec841ef7c7 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 8 Jan 2015 18:48:43 -0500 Subject: [PATCH 04/19] Restored updating user's name from login info. --- logic/auth/dev.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/logic/auth/dev.js b/logic/auth/dev.js index cce7437..4b8f9e6 100644 --- a/logic/auth/dev.js +++ b/logic/auth/dev.js @@ -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); @@ -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. */ From b48fcd0350fe4e876f82f3de8a1c149de370639a Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 8 Jan 2015 23:50:28 -0500 Subject: [PATCH 05/19] Added playback gap setting, which pads start and end of song playback. --- config.example.js | 3 +++ logic/song/song_playback.js | 26 ++++++++++++++++++++++---- utils/load_config.js | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/config.example.js b/config.example.js index aedadca..77e8996 100644 --- a/config.example.js +++ b/config.example.js @@ -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, diff --git a/logic/song/song_playback.js b/logic/song/song_playback.js index d0e5ab9..72b07b7 100644 --- a/logic/song/song_playback.js +++ b/logic/song/song_playback.js @@ -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 */ @@ -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(); @@ -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) { @@ -254,11 +255,28 @@ 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 segments = this.segments(); var played_segments = this.get('played_segments'); - if (played_segments > segments.length) { + if (played_segments >= segments.length) { this.once('segment_load', this.sendSegment, this); return; } diff --git a/utils/load_config.js b/utils/load_config.js index 4dff41e..7083604 100644 --- a/utils/load_config.js +++ b/utils/load_config.js @@ -14,6 +14,7 @@ var default_values = { superadmin: 'dag10', debug: true, debug_socketio: false, + playback_gap: 1, max_duration: 10, transcoding: { max_concurrent_jobs: 2, From 94eee735db38866fb03301ae3772b6b9171a0d91 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Fri, 9 Jan 2015 02:17:04 -0500 Subject: [PATCH 06/19] Updated copyright year to 2015. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 6e75da0..6f34c4c 100644 --- a/LICENSE +++ b/LICENSE @@ -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 From c896ccef21931512bf15ed6f6b796fc3709f3493 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Wed, 14 Jan 2015 18:26:31 -0500 Subject: [PATCH 07/19] Don't send superfluous room:song:stop messages when song ends. --- logic/connection/connection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/connection/connection.js b/logic/connection/connection.js index 2717d2b..4caf221 100644 --- a/logic/connection/connection.js +++ b/logic/connection/connection.js @@ -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()) { From 228442dbae28db41a2248083ac92b9f84c85ff0a Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Wed, 14 Jan 2015 23:56:30 -0500 Subject: [PATCH 08/19] Destroy Audio object if muted past end of song to conserve energy. Also gets rid of silly fix to non-problem where I append a random number as a get parameter to the current-song stream URL. --- scripts/room_models.js | 53 +++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/scripts/room_models.js b/scripts/room_models.js index f255dbb..8125949 100644 --- a/scripts/room_models.js +++ b/scripts/room_models.js @@ -20,10 +20,6 @@ $(function() { }, initialize: function() { - var audio = new Audio(); - audio.autoplay = true; - this.set({ audio: audio }); - this.on('change:song', this.songChanged, this); this.on('change:muted', this.mutedChanged, this); }, @@ -70,6 +66,22 @@ $(function() { this.stopAudio(); }, + createAudio: function() { + if (this.has('audio')) return; + + var audio = new Audio(); + audio.autoplay = true; + audio.muted = this.get('muted'); + this.set({ audio: audio }); + }, + + destroyAudio: function() { + if (!this.has('audio')) return; + + this.get('audio').src = ''; + this.unset('audio'); + }, + startAudio: function() { this.stopAudio(); this.updateProgress(); @@ -77,19 +89,38 @@ $(function() { if (!this.has('song')) return; - var audio = this.get('audio'); - audio.src = '/stream/' + this.get('room').get('shortname') + - '/current?' + Math.floor(Math.random() * 100); - console.log('Setting audio src:', audio.src); + // If there is no audio object and we're not muted, create one. + if (!this.has('audio') && !this.get('muted')) { + this.createAudio(); + } + + if (this.has('audio')) { + var audio = this.get('audio'); + audio.src = ''; + audio.src = '/stream/' + this.get('room').get('shortname') + + '/current'; + } }, stopAudio: function() { - var audio = this.get('audio'); - audio.src = ''; + if (this.has('audio')) { + var audio = this.get('audio'); + audio.src = ''; + + // Destroy audio if we're stopping audio and it's currently muted. + if (this.get('muted')) { + this.destroyAudio(); + } + } }, mutedChanged: function() { - this.get('audio').muted = this.get('muted'); + if (this.has('audio')) { + this.get('audio').muted = this.get('muted'); + } else if (!this.get('muted')) { + // There's no audio object but we're being unmuted, so let's make one. + this.startAudio(); + } }, updateProgress: function() { From f466d665105c6bf6e003fde031505b0645631167 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 00:39:19 -0500 Subject: [PATCH 09/19] Added placeholder text to search results when query is blank. --- scripts/room_views.js | 16 +++++++++++++++- views/room.ejs | 5 ++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/room_views.js b/scripts/room_views.js index eaddee4..a0b444c 100644 --- a/scripts/room_views.js +++ b/scripts/room_views.js @@ -179,7 +179,7 @@ $(function() { render: function() { var $ul = this.$('ul'); - var $placeholder = this.$('.section-empty'); + var $placeholder = this.$('#queue-placeholder'); var scrollTop = this.el.scrollTop; @@ -858,6 +858,7 @@ $(function() { this.$('#search-input').focus(); this.$('#search-results-list').show(); this.$('#queue-list').hide(); + this.updateResultsPlaceholder(); return false; }, @@ -877,6 +878,7 @@ $(function() { this.$('#search-input').val(''); this.$('#queue-list').show(); this.$('#search-results-list').hide(); + this.$('#search-results-placeholder').hide(); this.model.set('query', ''); }, this)); }, @@ -889,6 +891,7 @@ $(function() { } else { _.defer(_.bind(function() { this.model.set({ query: this.$('#search-input').val().trim() }); + this.updateResultsPlaceholder(); }, this)); } }, @@ -896,6 +899,7 @@ $(function() { searchPaste: function(event) { _.defer(_.bind(function() { this.model.set({ query: this.$('#search-input').val().trim() }); + this.updateResultsPlaceholder(); }, this)); }, @@ -930,6 +934,16 @@ $(function() { return this; }, + updateResultsPlaceholder: function() { + if (this.$('#search-input').val().trim().length > 0) { + this.$('#search-results-list').show(); + this.$('#search-results-placeholder').hide(); + } else { + this.$('#search-results-list').hide(); + this.$('#search-results-placeholder').show(); + } + }, + updateSearchButton: function() { if (this.connection.get('connected')) { this.$btn_search.show(); diff --git a/views/room.ejs b/views/room.ejs index e917430..87667cd 100644 --- a/views/room.ejs +++ b/views/room.ejs @@ -66,12 +66,15 @@
    -
    +
    Your queue is empty.

    Search for songs or drag files here to get started.
    +
    + Type to search for a song, artist, or album. +
    From fdfe040b032a2b8cea4fc395a3f370dd48fb4dd9 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 03:19:12 -0500 Subject: [PATCH 10/19] Fixed "current DJs" placeholder always being visible. --- scripts/room_views.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/room_views.js b/scripts/room_views.js index a0b444c..acb8747 100644 --- a/scripts/room_views.js +++ b/scripts/room_views.js @@ -179,7 +179,7 @@ $(function() { render: function() { var $ul = this.$('ul'); - var $placeholder = this.$('#queue-placeholder'); + var $placeholder = this.$('.section-empty'); var scrollTop = this.el.scrollTop; From c1593983226d12dd4d3b239c216e070f1dd7c4f9 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 03:21:14 -0500 Subject: [PATCH 11/19] Added link to DJ Listener to README.md. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e92e7a..6f68855 100644 --- a/README.md +++ b/README.md @@ -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. From c609b3435ef03877e363dac076879a5a663476af Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 19:04:45 -0500 Subject: [PATCH 12/19] Fixed conflicts between queue and search placeholder texts. --- scripts/room.js | 3 +- scripts/room_views.js | 73 ++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/scripts/room.js b/scripts/room.js index 38da7ea..7f4e48d 100644 --- a/scripts/room.js +++ b/scripts/room.js @@ -15,7 +15,7 @@ $(function() { connection: connection }); - new views.Queue({ + var queueView = new views.Queue({ collection: connection.get('queue'), connection: connection, el: $('#queue-column')[0] @@ -33,6 +33,7 @@ $(function() { adder: songAdder, connection: connection }), + queueView: queueView, el: $('#queue-column')[0] }); } diff --git a/scripts/room_views.js b/scripts/room_views.js index acb8747..e704b33 100644 --- a/scripts/room_views.js +++ b/scripts/room_views.js @@ -666,6 +666,7 @@ $(function() { }, initialize: function(opts) { + this.ui_hidden = false; this.views = []; this.connection = opts.connection; @@ -716,6 +717,20 @@ $(function() { })[0]; }, + hideUI: function() { + this.ui_hidden = true; + this.$('.queue-header').hide(); + this.$('#queue-list').hide(); + this.updatePlaceholder(); + }, + + showUI: function() { + this.ui_hidden = false; + this.$('.queue-header').show(); + this.$('#queue-list').show(); + this.updatePlaceholder(); + }, + render: function() { var $ul = this.$('#queue-list'); @@ -729,12 +744,14 @@ $(function() { }, updatePlaceholder: function() { - var $placeholder = this.$('.section-empty'); + var $placeholder = this.$('#queue-placeholder'); - if (this.connection.get('connected') && this.collection.length === 0) + if (!this.ui_hidden && this.connection.get('connected') && + this.collection.length === 0) { $placeholder.show(); - else + } else { $placeholder.hide(); + } }, sorted: function(event, model, position) { @@ -823,7 +840,8 @@ $(function() { 'mousedown #search-input': 'searchInputMouseDown' }, - initialize: function() { + initialize: function(opts) { + this.ui_hidden = true; this.listMousePressed = false; this.searchInputMousePressed = false; window.bodyView.on('mouseup', function() { @@ -831,6 +849,8 @@ $(function() { this.searchInputMousePressed = false; }, this); + this.queueView = opts.queueView; + this.section_views = []; this.sections = this.model.get('sections'); this.sections.on('add', this.sectionAdded, this); @@ -852,14 +872,30 @@ $(function() { search: function(event) { if (event) event.preventDefault(); + this.showUI(); + return false; + }, + + showUI: function() { + console.log('queueView:', this.queueView); + this.queueView.hideUI(); + + this.ui_hidden = false; this.$('#btn-search').tooltip('hide'); this.$('.search-header').show(); - this.$('.queue-header').hide(); this.$('#search-input').focus(); this.$('#search-results-list').show(); - this.$('#queue-list').hide(); this.updateResultsPlaceholder(); - return false; + }, + + hideUI: function() { + this.ui_hidden = true; + this.$('.search-header').hide(); + this.$('#search-input').val(''); + this.$('#search-results-list').hide(); + this.$('#search-results-placeholder').hide(); + + this.queueView.showUI(); }, searchBlurred: function() { @@ -873,13 +909,8 @@ $(function() { endSearch: function() { _.defer(_.bind(function() { - this.$('.queue-header').show(); - this.$('.search-header').hide(); - this.$('#search-input').val(''); - this.$('#queue-list').show(); - this.$('#search-results-list').hide(); - this.$('#search-results-placeholder').hide(); this.model.set('query', ''); + this.hideUI(); }, this)); }, @@ -916,8 +947,11 @@ $(function() { render: function() { this.$search_results_list = this.$('#search-results-list'); this.$btn_search_placeholder = this.$('#btn-search-placeholder'); + this.$results_placeholder = this.$('#search-results-placeholder'); this.$btn_search = this.$('#btn-search'); + this.$results_placeholder.hide(); + this.$btn_search.tooltip('destroy'); this.$btn_search.tooltip({ title: 'Search', @@ -935,12 +969,15 @@ $(function() { }, updateResultsPlaceholder: function() { - if (this.$('#search-input').val().trim().length > 0) { - this.$('#search-results-list').show(); - this.$('#search-results-placeholder').hide(); + if (this.ui_hidden) { + this.$search_results_list.hide(); + this.$results_placeholder.hide(); + } else if (this.$('#search-input').val().trim().length > 0) { + this.$search_results_list.show(); + this.$results_placeholder.hide(); } else { - this.$('#search-results-list').hide(); - this.$('#search-results-placeholder').show(); + this.$search_results_list.hide(); + this.$results_placeholder.show(); } }, From 4c3731c48288c993f788c7d5a333bf4704d25e16 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 19:52:18 -0500 Subject: [PATCH 13/19] Added cookie manager to room. --- scripts/jquery.cookie.js | 117 +++++++++++++++++++++++++++++++++++++++ scripts/room.js | 5 +- scripts/room_models.js | 34 ++++++++++++ views/room.ejs | 1 + 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100755 scripts/jquery.cookie.js diff --git a/scripts/jquery.cookie.js b/scripts/jquery.cookie.js new file mode 100755 index 0000000..c7f3a59 --- /dev/null +++ b/scripts/jquery.cookie.js @@ -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); + }; + +})); diff --git a/scripts/room.js b/scripts/room.js index 7f4e48d..e05a5f0 100644 --- a/scripts/room.js +++ b/scripts/room.js @@ -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({ diff --git a/scripts/room_models.js b/scripts/room_models.js index 8125949..fa966f0 100644 --- a/scripts/room_models.js +++ b/scripts/room_models.js @@ -4,6 +4,40 @@ $(function() { var progressInterval = 10; var search_throttle_ms = 200; + // Model managing cookies. + models.Cookies = Backbone.Model.extend({ + defaults: { + muted: false + }, + + initialize: function() { + Object.keys(this.attributes).forEach(_.bind(function(key) { + var value = $.cookie(key); + if (typeof value !== 'undefined') { + if (value.toLowerCase() === 'true') { + value = true; + } else if (value.toLowerCase() === 'false') { + value = false; + } else if (typeof this.defaults[key] === 'number') { + value = Number.parseFloat(value); + } + + this.set(key, value); + } else { + $.cookie(key, this.get(key)); + } + }, this)); + + this.on('change', this.onChange, this); + }, + + onChange: function() { + Object.keys(this.changedAttributes()).forEach(_.bind(function(key) { + $.cookie(key, this.get(key)); + }, this)); + } + }); + // Model representing a song's information. models.Song = Backbone.Model.extend({ defaults: { diff --git a/views/room.ejs b/views/room.ejs index 87667cd..42432ab 100644 --- a/views/room.ejs +++ b/views/room.ejs @@ -6,6 +6,7 @@ script('/scripts/backbone-min.js'); script('/scripts/handlebars.js'); script('/scripts/jquery-ui.custom.min.js'); + script('/scripts/jquery.cookie.js'); script('/scripts/moment.min.js'); script('/scripts/room_models.js'); script('/scripts/room_views.js'); From 6be9403edfefc0f39e66535b35379a281241203e Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 19:54:23 -0500 Subject: [PATCH 14/19] Save mute state in cookie. --- scripts/room_models.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/room_models.js b/scripts/room_models.js index fa966f0..c4ab6fc 100644 --- a/scripts/room_models.js +++ b/scripts/room_models.js @@ -54,6 +54,8 @@ $(function() { }, initialize: function() { + this.set({ muted: cookies.get('muted') }); + this.on('change:song', this.songChanged, this); this.on('change:muted', this.mutedChanged, this); }, @@ -149,12 +151,16 @@ $(function() { }, mutedChanged: function() { + var muted = this.get('muted'); + if (this.has('audio')) { - this.get('audio').muted = this.get('muted'); - } else if (!this.get('muted')) { + this.get('audio').muted = muted; + } else if (!muted) { // There's no audio object but we're being unmuted, so let's make one. this.startAudio(); } + + cookies.set({ muted: muted }); }, updateProgress: function() { @@ -757,16 +763,18 @@ $(function() { // Model representing the state of the current room. models.Room = Backbone.Model.extend({ defaults: { - activities: new models.Activities(), anonymous_listeners: 0, connected: false, - listeners: new models.Users(), - dj: false, - djs: new models.Users(), - playback: new models.Playback() + dj: false }, initialize: function() { + this.set({ + activities: new models.Activities(), + listeners: new models.Users(), + djs: new models.Users(), + playback: new models.Playback() + }); this.get('activities').room = this; this.get('playback').set({ room: this }); this.get('listeners').comparator = 'username'; From 7a74055dbcfba0bf5b8c10d60c31fe4396f91695 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Thu, 15 Jan 2015 20:09:44 -0500 Subject: [PATCH 15/19] Disable upload button when disconnected. --- scripts/room_models.js | 11 ++++++++++- scripts/room_views.js | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/scripts/room_models.js b/scripts/room_models.js index c4ab6fc..8a06d95 100644 --- a/scripts/room_models.js +++ b/scripts/room_models.js @@ -479,6 +479,14 @@ $(function() { models.SongAdder = Backbone.Model.extend({ initialize: function() { this.set({ adds: new models.SongAdds() }); + this.get('connection').on( + 'change:connected', this.connectedChanged, this); + }, + + connectedChanged: function() { + if (!this.get('connection').get('connected')) { + this.get('adds').reset(); + } }, songUploadAdded: function(event, data) { @@ -781,8 +789,9 @@ $(function() { this.get('djs').comparator = 'djOrder'; this.set({ username: this.get('connection').get('username') }); this.on('change:connected', function() { - if (this.get('connected')) + if (this.get('connected')) { this.unset('kick_message'); + } }, this); }, diff --git a/scripts/room_views.js b/scripts/room_views.js index e704b33..dcb4c25 100644 --- a/scripts/room_views.js +++ b/scripts/room_views.js @@ -534,6 +534,14 @@ $(function() { this.$el.removeClass('dragging'); e.preventDefault(); }, this)); + + this.connection.on('change:connected', function() { + if (this.connection.get('connected')) { + this.$btnupload.removeAttr('disabled'); + } else { + this.$btnupload.attr('disabled', 'disabled'); + } + }, this); } }); From aeae92dc1612c7fd056cfe1ed9dc92220f8f9be5 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Sat, 17 Jan 2015 15:38:12 -0500 Subject: [PATCH 16/19] Added leave confirmation if you're a DJ. --- scripts/room_models.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/room_models.js b/scripts/room_models.js index 8a06d95..9a71780 100644 --- a/scripts/room_models.js +++ b/scripts/room_models.js @@ -793,6 +793,7 @@ $(function() { this.unset('kick_message'); } }, this); + window.onbeforeunload = _.bind(this.unloadConfirmation, this); }, reset: function() { @@ -873,6 +874,20 @@ $(function() { endDJ: function() { this.get('connection').endDJ(); + }, + + unloadConfirmation: function() { + if (this.get('dj')) { + if (this.get('playback').get('selfIsDJ')) { + return 'You\'re currently playing music. If you navigate away, ' + + 'your music will stop.'; + } else { + return 'You\'re currently a DJ. If you navigate away, you will ' + + 'lose your spot in the queue.'; + } + } + + return null; } }); }); From 5f42fbf7fdd31315cdac30aa9c87f6a3e71bfabf Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Sat, 17 Jan 2015 18:31:14 -0500 Subject: [PATCH 17/19] Replaced album with duration in queue. --- scripts/room_views.js | 42 +++++++++++++++++++++++------------------- views/room.ejs | 5 +---- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/room_views.js b/scripts/room_views.js index dcb4c25..d4f6a4b 100644 --- a/scripts/room_views.js +++ b/scripts/room_views.js @@ -1,4 +1,20 @@ $(function() { + function secondsToTimestamp(seconds) { + if (seconds === 0) return '0:00'; + seconds = Math.floor(seconds); + + var remainingSeconds = seconds % 60; + var retStr = ''; + + retStr += Math.floor(seconds/60); + retStr += ':'; + if (remainingSeconds < 10) + retStr += '0'; + retStr += remainingSeconds; + + return retStr; + } + var views = window.views = {}; views.Body = Backbone.View.extend({ @@ -646,8 +662,12 @@ $(function() { } } + var attributes = this.model.attributes; + + attributes.formattedDuration = secondsToTimestamp(attributes.duration); + this.undelegateEvents(); - this.$el.html(this.template(this.model.attributes)); + this.$el.html(this.template(attributes)); this.delegateEvents(); this.$el.disableSelection(); @@ -1016,31 +1036,15 @@ $(function() { this.render(); }, - secondsToTimestamp: function(seconds) { - if (seconds === 0) return '0:00'; - seconds = Math.floor(seconds); - - var remainingSeconds = seconds % 60; - var retStr = ''; - - retStr += Math.floor(seconds/60); - retStr += ':'; - if (remainingSeconds < 10) - retStr += '0'; - retStr += remainingSeconds; - - return retStr; - }, - progressTimestamp: function() { var progress = this.model.get('progress') || 0; - return this.secondsToTimestamp(progress); + return secondsToTimestamp(progress); }, durationTimestamp: function() { var song = this.model.get('song'); var duration = song ? song.get('duration') || 0 : 0; - return this.secondsToTimestamp(duration); + return secondsToTimestamp(duration); }, updateProgress: function() { diff --git a/views/room.ejs b/views/room.ejs index 42432ab..7f41595 100644 --- a/views/room.ejs +++ b/views/room.ejs @@ -277,10 +277,7 @@ class="fg" title=""{{title}}"{{#if artist}} by {{artist}}{{/if}}{{#if album}} on {{album}}{{/if}}"> {{#if playing}}► {{/if}}{{title}} - {{artist}} - {{#if album}} - - {{album}} - {{/if}} + {{formattedDuration}} - {{artist}}
    {{#unless playing}} From 25072d8a6c34a1cc05dbb2e63a4de6b66626b988 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Sat, 17 Jan 2015 19:13:37 -0500 Subject: [PATCH 18/19] Made playback progress bar thinner in middle. --- less/room.less | 28 +++++++++++++++++++++++++--- views/room.ejs | 2 ++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/less/room.less b/less/room.less index a85a810..c15063a 100644 --- a/less/room.less +++ b/less/room.less @@ -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; @@ -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; } } } diff --git a/views/room.ejs b/views/room.ejs index 7f41595..96537bf 100644 --- a/views/room.ejs +++ b/views/room.ejs @@ -326,6 +326,8 @@
    +
    +
    From 10afd0f21c12c839abd8fccb07af28f71b085021 Mon Sep 17 00:00:00 2001 From: Drew Gottlieb Date: Sat, 17 Jan 2015 19:18:05 -0500 Subject: [PATCH 19/19] Incremented version to 1.4.0-alpha. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7219694..590d01e 100644 --- a/package.json +++ b/package.json @@ -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) ", "repository": { "type": "git",