Merge branch 'master' of bitbucket.org:jamkazam/jam-web

This commit is contained in:
Brian Smith 2013-09-01 09:50:34 -04:00
commit a47349f4c8
27 changed files with 438 additions and 120 deletions

View File

@ -18,6 +18,7 @@
LEAVE_MUSIC_SESSION : "LEAVE_MUSIC_SESSION",
LEAVE_MUSIC_SESSION_ACK : "LEAVE_MUSIC_SESSION_ACK",
HEARTBEAT : "HEARTBEAT",
HEARTBEAT_ACK : "HEARTBEAT_ACK",
FRIEND_UPDATE : "FRIEND_UPDATE",
SESSION_INVITATION : "SESSION_INVITATION",
JOIN_REQUEST : "JOIN_REQUEST",

View File

@ -52,6 +52,15 @@
server.socket.onclose = server.onClose;
};
server.close = function(in_error) {
logger.log("closing websocket");
server.socket.close();
if(in_error) {
context.JK.CurrentSessionModel.onWebsocketDisconnected();
}
}
server.rememberLogin = function() {
var token, loginMessage;
token = $.cookie("remember_token");
@ -95,7 +104,8 @@
context.jamClient.connected = false;
}
// TODO: reconnect
context.JK.CurrentSessionModel.onWebsocketDisconnected();
};
server.send = function(message) {

View File

@ -0,0 +1,56 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.Banner = (function() {
var self = this;
var logger = context.JK.logger;
// responsible for updating the contents of the update dialog
// as well as registering for any event handlers
function show(options) {
var text = options.text;
var html = options.html;
var newContent = null;
if (html) {
newContent = $('#banner .dialog-inner').html(html);
}
else if(text) {
newContent = $('#banner .dialog-inner').html(text);
}
else {
console.error("unable to show banner for empty message")
return newContent;
}
$('#banner').show()
$('#banner_overlay').show()
// return the core of the banner so that caller can attach event handlers to newly created HTML
return newContent;
}
function hide() {
$('#banner').hide();
$('#banner_overlay .dialog-inner').html("");
$('#banner_overlay').hide();
}
function initialize() {
return self;
}
// Expose publics
var me = {
initialize: initialize,
show : show,
hide : hide
}
return me;
})();
})(window,jQuery);

View File

@ -76,6 +76,10 @@
function settingsInit() {
jamClient.FTUEInit();
setLevels(0);
// Always reset the driver select box to "Choose..." which forces everything
// to sync properly when the user reselects their driver of choice.
// VRFS-375 and VRFS-561
$('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val("");
}
function setLevels(db) {
@ -266,7 +270,29 @@
ftueSave(false);
}
function videoLinkClicked(evt) {
var myOS = jamClient.GetOSAsString();
var link;
if (myOS === 'MacOSX') {
link = $(evt.currentTarget).attr('external-link-mac');
} else if (myOS === 'Win32') {
link = $(evt.currentTarget).attr('external-link-win');
}
if (link) {
context.jamClient.OpenSystemBrowser(link);
}
}
function events() {
$('.ftue-video-link').hover(
function(evt) { // handlerIn
$(this).addClass('hover');
},
function(evt) { // handlerOut
$(this).removeClass('hover');
}
);
$('.ftue-video-link').on('click', videoLinkClicked);
$('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged);
$('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged);
$('#btn-asio-control-panel').on('click', openASIOControlPanel);

View File

@ -144,6 +144,15 @@
});
}
/** check if the server is alive */
function serverHealthCheck(options) {
return $.ajax({
url: window.host,
cache: false,
dataType: "html"
});
}
function getId(options) {
var id = options && options["id"]
@ -201,6 +210,7 @@
this.getClientDownloads = getClientDownloads
this.createInvitation = createInvitation;
this.postFeedback = postFeedback;
this.serverHealthCheck = serverHealthCheck;
return this;
};

View File

@ -20,7 +20,9 @@
var app;
var logger = context.JK.logger;
var heartbeatInterval=null;
var heartbeatMS=null;
var inBadState=false;
var lastHeartbeatAckTime=null;
var opts = {
layoutOpts: {}
@ -59,6 +61,15 @@
if (app.heartbeatActive) {
var message = context.JK.MessageFactory.heartbeat();
context.JK.JamServer.send(message);
// check if the server is still sending heartbeat acks back down
// this logic equates to 'if we have not received a heartbeat within 2 heartbeat intervals, then get upset
if(new Date().getTime() - lastHeartbeatAckTime.getTime() > heartbeatMS * 2) {
logger.error("no heartbeat ack received from server after twice heartbeat interval. giving up");
clearInterval(heartbeatInterval); // stop future heartbeats
context.JK.JamServer.close(true);
}
}
}
@ -67,9 +78,14 @@
$.cookie('client_id', payload.client_id);
// $.cookie('remember_token', payload.token); // removed per vrfs-273/403
var heartbeatMS = payload.heartbeat_interval * 1000;
heartbeatMS = payload.heartbeat_interval * 1000;
logger.debug("jamkazam.js.loggedIn(): clientId now " + app.clientId + "; Setting up heartbeat every " + heartbeatMS + " MS");
heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
}
function heartbeatAck(header, payload) {
lastHeartbeatAckTime = new Date();
}
/**
@ -99,6 +115,12 @@
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, loggedIn);
}
function registerHeartbeatAck() {
logger.debug("register for heartbeatAck");
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, heartbeatAck);
}
function registerBadStateError() {
logger.debug("register for server_bad_state_error");
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError);
@ -218,6 +240,7 @@
this.layout = new context.JK.Layout();
this.layout.initialize(this.opts.layoutOpts);
registerLoginAck();
registerHeartbeatAck();
registerBadStateRecovered();
registerBadStateError();
events();

View File

@ -121,22 +121,37 @@
}
function afterCurrentUserLoaded() {
sessionModel = new context.JK.SessionModel(
// It seems the SessionModel should be a singleton.
// a client can only be in one session at a time,
// and other parts of the code want to know at any certain times
// about the current session, if any (for example, reconnect logic)
context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel(
context.JK.JamServer,
context.jamClient,
context.JK.userMe
context.jamClient
);
sessionModel.subscribe('sessionScreen', sessionChanged);
sessionModel.joinSession(sessionId);
sessionModel.joinSession(sessionId)
.fail(function(xhr, textStatus, errorMessage) {
if(xhr.status == 404) {
// we tried to join the session, but it's already gone. kick user back to join session screen
window.location = "#/findSession"
app.notify(
{ title: "Unable to Join Session",
text: "The session you attempted to join is over."
},
{ no_cancel: true });
}else {
app.ajaxError(xhr, textStatus, errorMessage);
}
})
}
function beforeHide(data) {
// track that the screen is inactive, to disable body-level handlers
screenActive = false;
sessionModel.leaveCurrentSession(sessionId);
// 'unregister' for callbacks
context.jamClient.SessionRegisterCallback("");
context.jamClient.SessionSetAlertCallback("");
sessionModel.leaveCurrentSession()
.fail(app.ajaxError)
}
function sessionChanged() {
@ -364,7 +379,6 @@
]);
if (mixer) {
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var muteClass = "enabled";
@ -412,6 +426,25 @@
});
}
function connectTrackToMixer(trackSelector, clientId, mixerId, gainPercent) {
var vuOpts = $.extend({}, trackVuOpts);
var faderOpts = $.extend({}, trackFaderOpts);
faderOpts.faderId = mixerId;
var vuLeftSelector = trackSelector + " .track-vu-left";
var vuRightSelector = trackSelector + " .track-vu-right";
var faderSelector = trackSelector + " .track-gain";
var $track = $('div.track[client-id="' + clientId + '"]');
// Set mixer-id attributes and render VU/Fader
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
$track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul');
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
$track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur');
context.JK.FaderHelpers.renderFader(faderSelector, faderOpts);
// Set gain position
context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent);
context.JK.FaderHelpers.subscribe(mixerId, faderChanged);
}
// Function called on an interval when participants change. Mixers seem to
// show up later, so we render the tracks from participants, but keep track
// of the ones there weren't any mixers for, and continually try to find them
@ -428,32 +461,14 @@
ChannelGroupIds.PeerAudioInputMusicGroup
]);
if (mixer) {
var vuOpts = $.extend({}, trackVuOpts);
var faderOpts = $.extend({}, trackFaderOpts);
faderOpts.faderId = mixer.id;
var baseSelector = 'div.track[client-id="' + key + '"]';
var vuLeftSelector = baseSelector + " .track-vu-left";
var vuRightSelector = baseSelector + " .track-vu-right";
var faderSelector = baseSelector + " .track-gain";
keysToDelete.push(key);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var trackSelector = 'div.track[client-id="' + key + '"]';
connectTrackToMixer(trackSelector, key, mixer.id, gainPercent);
var $track = $('div.track[client-id="' + key + '"]');
// Set mixer-id attributes and render VU/Fader
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
$track.find('.track-vu-left').attr('mixer-id', mixer.id + '_vul');
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
$track.find('.track-vu-right').attr('mixer-id', mixer.id + '_vur');
context.JK.FaderHelpers.renderFader(faderSelector, faderOpts);
context.JK.FaderHelpers.subscribe(mixer.id, faderChanged);
$track.find('.track-icon-mute').attr('mixer-id', mixer.id);
$track.find('.track-icon-settings').attr('mixer-id', mixer.id);
// Set gain position
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent);
// Set mute state
_toggleVisualMuteControl($track.find('.track-icon-mute'), mixer.mute);
}
@ -513,25 +528,9 @@
$destination.append(newTrack);
// Render VU meters and gain fader
// Find the last child (just-appended):
var trackCount = $(parentSelector + ' .session-track').length;
var selectorPrefix = parentSelector + ' .session-track:nth-child(' + String(trackCount) + ')';
var vuOpts = $.extend({}, trackVuOpts);
var faderOpts = $.extend({}, trackFaderOpts);
faderOpts.faderId = trackData.mixerId;
var vuLeftSelector = selectorPrefix + ' .track-vu-left';
var vuRightSelector = selectorPrefix + ' .track-vu-right';
var faderSelector = selectorPrefix + ' .track-gain';
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
context.JK.FaderHelpers.renderFader(faderSelector, faderOpts);
context.JK.FaderHelpers.subscribe(trackData.mixerId, faderChanged);
// Visually update fader to underlying mixer start value.
if (trackData.gainPercent) {
context.JK.FaderHelpers.setFaderValue(trackData.mixerId, trackData.gainPercent);
} else {
context.JK.FaderHelpers.setFaderValue(trackData.mixerId, 0);
}
var trackSelector = parentSelector + ' .session-track[client-id="' + trackData.clientId + '"]';
var gainPercent = trackData.gainPercent || 0;
connectTrackToMixer(trackSelector, trackData.clientId, trackData.mixerId, gainPercent);
var $closeButton = $('#div-track-close', 'div[track-id="' + trackData.trackId + '"]');
if (index === 0) {
@ -564,11 +563,12 @@
function handleVolumeChangeCallback(mixerId, isLeft, value) {
// Visually update mixer
// There is no need to actually set the back-end mixer value as the
// back-end will already have updated the audio mixer directly prior to sending
// me this event. I simply need to visually show the new fader position.
// TODO: Use mixer's range
var faderValue = percentFromMixerValue(-80, 20, value);
context.JK.FaderHelpers.setFaderValue(mixerId, faderValue);
fillTrackVolumeObject(mixerId, false); // don't broadcast
setMixerVolume(mixerId, faderValue);
}
function handleBridgeCallback() {
@ -593,6 +593,8 @@
}
_updateVU(mixerId, vuVal);
} else if (eventName === 'add' || eventName === 'remove') {
//logger.dbg('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
// TODO - _renderSession. Note I get streams of these in
// sequence, so have Nat fix, or buffer/spam protect
// Note - this is already handled from websocket events.

View File

@ -7,12 +7,13 @@
context.JK = context.JK || {};
var logger = context.JK.logger;
context.JK.SessionModel = function(server, client, currentUser) {
context.JK.SessionModel = function(server, client) {
var clientId = client.clientID;
var currentSessionId = null; // Set on join, prior to setting currentSession.
var currentSession = null;
var subscribers = {};
var users = {}; // User info for session participants
var rest = context.JK.Rest();
function id() {
return currentSession.id;
@ -32,24 +33,54 @@
function joinSession(sessionId) {
currentSessionId = sessionId;
logger.debug("SessionModel.joinSession(" + sessionId + ")");
joinSessionRest(sessionId, function() {
refreshCurrentSession();
});
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
var deferred = joinSessionRest(sessionId);
deferred
.done(function(){
logger.debug("calling jamClient.JoinSession");
client.JoinSession({ sessionID: sessionId });
refreshCurrentSession();
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
});
return deferred;
}
/**
* Leave the current session
* Leave the current session, if there is one.
* callback: called in all conditions; either after an attempt is made to tell the server that we are leaving,
* or immediately if there is no session
*/
function leaveCurrentSession() {
logger.debug("SessionModel.leaveCurrentSession()");
// TODO - sessionChanged will be called with currentSession = null
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
leaveSessionRest(currentSessionId, sessionChanged);
currentSession = null;
currentSessionId = null;
var deferred;
if(currentSessionId) {
logger.debug("SessionModel.leaveCurrentSession()");
// TODO - sessionChanged will be called with currentSession = null
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
// leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long
// time, for that entire duration you'll still be sending voice data to the other users.
// this may be bad if someone decides to badmouth others in the left-session during this time
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: currentSessionId });
deferred = leaveSessionRest(currentSessionId);
deferred.done(function() {
sessionChanged();
});
// 'unregister' for callbacks
context.jamClient.SessionRegisterCallback("");
context.jamClient.SessionSetAlertCallback("");
currentSession = null;
currentSessionId = null;
}
else {
deferred = rest.serverHealthCheck();
}
return deferred;
}
/**
@ -268,7 +299,7 @@
* Make the server calls to join the current user to
* the session provided.
*/
function joinSessionRest(sessionId, callback) {
function joinSessionRest(sessionId) {
var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
var data = {
client_id: clientId,
@ -277,38 +308,77 @@
tracks: tracks
};
var url = "/api/sessions/" + sessionId + "/participants";
$.ajax({
return $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: url,
async: false,
data: JSON.stringify(data),
processData:false,
success: function(response) {
logger.debug("calling jamClient.JoinSession");
client.JoinSession({ sessionID: sessionId });
callback();
},
error: ajaxError
processData:false
});
}
function leaveSessionRest(sessionId, callback) {
function leaveSessionRest(sessionId) {
var url = "/api/participants/" + clientId;
$.ajax({
return $.ajax({
type: "DELETE",
url: url,
async: false,
success: function (response) {
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: sessionId });
callback();
},
error: ajaxError
async: false
});
}
function reconnect() {
window.location.reload();
}
function registerReconnect(content) {
$('a.disconnected-reconnect', content).click(function() {
var template = $('#template-reconnecting').html();
var templateHtml = context.JK.fillTemplate(template, null);
var content = context.JK.Banner.show({
html : template
});
context.JK.CurrentSessionModel.leaveCurrentSession()
.done(function() {
reconnect();
})
.fail(function(xhr, textStatus, errorThrown) {
console.log("leaveCurrentSession failed: ", arguments);
if(xhr && xhr.status >= 100) {
// we could connect to the server, and it's alive
reconnect();
}
else {
var template = $('#template-could-not-reconnect').html();
var templateHtml = context.JK.fillTemplate(template, null);
var content = context.JK.Banner.show({
html : template
});
registerReconnect(content);
}
});
});
}
function onWebsocketDisconnected() {
var template = $('#template-disconnected').html();
var templateHtml = context.JK.fillTemplate(template, null);
var content = context.JK.Banner.show({
html : template
}) ;
// kill the streaming of the session immediately
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
client.LeaveSession({ sessionID: currentSessionId });
registerReconnect(content);
}
function ajaxError(jqXHR, textStatus, errorMessage) {
logger.error("Unexpected ajax error: " + textStatus);
}
@ -324,6 +394,7 @@
this.addTrack = addTrack;
this.updateTrack = updateTrack;
this.deleteTrack = deleteTrack;
this.onWebsocketDisconnected = onWebsocketDisconnected;
this.getCurrentSession = function() {
return currentSession;
};

View File

@ -147,12 +147,16 @@
});
}
context.JK.trimString = function(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};
context.JK.padString = function(str, max) {
var retVal = '' + str;
while (retVal.length < max) {
retVal = '0' + retVal;
}
return retVal;
}

View File

@ -0,0 +1,8 @@
#banner {
display:none;
}
#banner h2 {
font-weight:bold;
font-size:x-large;
}

View File

@ -30,6 +30,7 @@
*= require ./genreSelector
*= require ./sessionList
*= require ./searchResults
*= require ./banner
*= require ./clientUpdate
*= require jquery.Jcrop
*/

View File

@ -18,12 +18,16 @@ div.dialog.ftue .ftue-inner div[layout-wizard-step="1"] {
li {
text-align:center;
width: 33%;
width: 31%;
height: 170px;
margin:0px;
padding:0px;
/*padding:0px;*/
list-style: none;
float:left;
}
li.first {
width: 34%;
}
}
div.dialog.ftue .ftue-inner div[layout-wizard-step="3"] {
h5 {
@ -421,3 +425,14 @@ table.audiogeartable {
font-size:15px;
color:#aaa;
}
.ftue-video-link {
padding:4px;
cursor:pointer;
background-color:#333;
border: 1px solid #333;
}
.ftue-video-link.hover {
background-color:#444;
border: 1px solid #555;
}

View File

@ -53,22 +53,23 @@ class ApiUsersController < ApiController
end
def update
@user = User.save(params[:id],
current_user.id,
params[:first_name],
params[:last_name],
nil, # Don't allow changing email here, since updating email is something that must be done through it's own API
nil, # Don't allow changing password here, since we want to prompt again for the old password
nil,
params[:musician],
params[:gender],
params[:birth_date],
params[:internet_service_provider],
params[:city],
params[:state],
params[:country],
params[:instruments].nil? ? [] : params[:instruments], # we have to convert nil to empty []. background: http://stackoverflow.com/questions/14647731/rails-converts-empty-arrays-into-nils-in-params-of-the-request
params[:photo_url])
@user = User.find(params[:id])
@user.first_name = params[:first_name] if params.has_key?(:first_name)
@user.last_name = params[:last_name] if params.has_key?(:last_name)
@user.gender = params[:gender] if params.has_key?(:gender)
@user.birth_date = Date.strptime(params[:birth_date], '%m-%d-%Y') if params.has_key?(:birth_date)
@user.city = params[:city] if params.has_key?(:city)
@user.state = params[:state] if params.has_key?(:state)
@user.country = params[:country] if params.has_key?(:country)
@user.musician = params[:musician] if params.has_key?(:musician)
@user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments)
puts params[:birth_date]
@user.save
puts @user.birth_date.inspect
if @user.errors.any?
respond_with @user, :status => :unprocessable_entity

View File

@ -0,0 +1,7 @@
module MetaHelper
def version()
"web=#{::JamWeb::VERSION} lib=#{JamRuby::VERSION} db=#{JamDb::VERSION} pb=#{Jampb::VERSION}"
end
end

View File

@ -65,7 +65,7 @@
<td valign="top">
<div class="field">
Gender:<br />
<select name="gender" data-value="{gender}" class="w80"><option value='M'>Male</option><option value='F'>Female</option><option value="">-</option></select><br />
<select name="gender" data-value="{gender}" class="w80"><option value='M'>Male</option><option value='F'>Female</option><option >-</option></select><br />
<br />
</div>
</td>

View File

@ -0,0 +1,18 @@
<!-- generic banner for use by an code -->
<div class="overlay" id="banner_overlay"></div>
<div id="banner" class="dialog-overlay-sm">
<!-- dialog header -->
<div class="content-head">
<%= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon") %><h1>alert</h1>
</div>
<div class="dialog-inner">
<!-- contents are replaced by caller to f-->
</div>
<!-- end right column -->
<br clear="all">
</div>

View File

@ -10,35 +10,37 @@
<!-- First screen of the FTUE wizard -->
<div layout-wizard-step="1" dialog-title="welcome!" style="display:block;">
<p class="intro">
Please identify which of the three types of audio gear below you are going to use with the JamKazam
service, and click either the Windows or Mac link under the appropriate gear to watch a video on how
to navigate this initial setup and testing process. After watching the video, click the 'NEXT'
button to get started.
Please identify which of the three types of audio gear below you
are going to use with the JamKazam service, and click one to
watch a video on how to navigate this initial setup and testing
process. After watching the video, click the 'NEXT' button to
get started. If you don't have your audio gear handy now, click
Cancel.
</p>
<ul class="device_type">
<li>
<li class="ftue-video-link first"
external-link-win="http://www.youtube.com/watch?v=b1JrwGeUcOo"
external-link-mac="http://www.youtube.com/watch?v=TRzb7OTlO-Q">
AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS<br/>
<p><%= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} %></p>
<p><a href="http://www.youtube.com/watch?v=jM5_MWtlxxE" rel="external">Windows Video</a><br/>
<a href="http://www.youtube.com/watch?v=TRzb7OTlO-Q" rel="external">Mac Video</a></p>
</li>
<li>
<li class="ftue-video-link"
external-link-win="http://www.youtube.com/watch?v=IDrLa8TOXwQ"
external-link-mac="http://www.youtube.com/watch?v=vIs7ArrjMpE">
USB MICROPHONE<br/>
<p><%= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} %></p>
<p><a href="http://www.youtube.com/watch?v=IDrLa8TOXwQ" rel="external">Windows Video</a><br/>
<a href="http://www.youtube.com/watch?v=vIs7ArrjMpE" rel="external">Mac Video</a></p>
</li>
<li>
<li class="ftue-video-link"
external-link-win="http://www.youtube.com/watch?v=PCri4Xed4CA"
external-link-mac="http://www.youtube.com/watch?v=Gatmd_ja47U">
COMPUTER'S BUILT-IN MIC &amp; SPEAKERS/HEADPHONES<br/>
<p><%= image_tag "content/computer_ftue.png", {:width => 118, :height => 105} %></p>
<p><a href="http://www.youtube.com/watch?v=PCri4Xed4CA" rel="external">Windows Video</a><br/>
<a href="http://www.youtube.com/watch?v=Gatmd_ja47U" rel="external">Mac Video</a></p>
</li>
</ul>
<div class="right mr30 buttonbar">
<!-- <a class="button-grey" layout-action="close">REGISTER AS A FAN</a>&nbsp; -->
<a class="button-grey" layout-action="close">CANCEL</a>&nbsp;
<a class="button-orange" layout-wizard-link="2">NEXT</a>
</div>

View File

@ -0,0 +1,42 @@
<script type="text/template" id="template-disconnected">
<h2 align="center">Disconnected from Server</h2>
<br />
<br />
<br />
<div align="center">
You have been disconnected from JamKazam.
</div>
<br clear="all" /><br />
<div class="right">
<a href="#" class="button-orange disconnected-reconnect">RECONNECT</a>
</div>
</script>
<script type="text/template" id="template-reconnecting">
<h2 align="center">Reconnecting to Server</h2>
<br />
<br />
<br />
<div align="center">
Attempting to reestablish connection to the server...
</div>
<br clear="all" /><br />
</script>
<script type="text/template" id="template-could-not-reconnect">
<h2 align="center">Unable to Reconnect</h2>
<br />
<br />
<br />
<div align="center">
We were not able to reconnect to JamKazam. <br/></br/>
Please check your internet connection, then try again later.
</div>
<br clear="all" /><br />
<div class="right">
<a href="#" class="button-orange disconnected-reconnect">TRY AGAIN TO RECONNECT</a>
</div>
</script>

View File

@ -29,6 +29,8 @@
<%= render "account_audio_profile" %>
<%= render "notify" %>
<%= render "client_update" %>
<%= render "banner" %>
<%= render "clients/banners/disconnected" %>
<%= render "overlay_small" %>
<script type="text/javascript">
@ -82,6 +84,8 @@
var clientUpdate = new JK.ClientUpdate()
clientUpdate.initialize().check()
// Some things can't be initialized until we're connected. Put them here.
function _initAfterConnect() {
@ -118,6 +122,7 @@
// This is a helper class with a singleton. No need to instantiate.
JK.GenreSelectorHelper.initialize();
JK.Banner.initialize();
var createSessionScreen = new JK.CreateSessionScreen(JK.app);
createSessionScreen.initialize();
@ -155,6 +160,8 @@
JK.app = JK.JamKazam();
JK.app.initialize();
JK.JamServer.connect(); // singleton here defined in JamServer.js
// this ensures that there is always a CurrentSessionModel, even if it's for a non-active session
JK.CurrentSessionModel = new JK.SessionModel(JK.JamServer, window.jamClient);
// Run a check to see if we're logged in yet. Only after that should
// we initialize the other screens.
@ -173,4 +180,6 @@
})
</script>
<!-- version info: <%= version %> -->

View File

@ -46,6 +46,7 @@
<!-- footer links -->
<div id="footer-links"><%= link_to "about", corp_about_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "news", corp_news_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "media", corp_media_center_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "contact", corp_contact_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "privacy", corp_privacy_path %>&nbsp;&nbsp;|&nbsp;&nbsp;<%= link_to "terms of service", corp_terms_path %>&nbsp;&nbsp;|&nbsp;<%= link_to "help", corp_help_path %></div>
<div><%= version %></div>
</div>
</div>

View File

@ -32,5 +32,7 @@
<div id="footer-container">
<%= render "clients/footer" %>
</div>
<!-- version info: <%= version %> -->
</body>
</html>

7
build
View File

@ -81,6 +81,13 @@ if [ -n "$PACKAGE" ]; then
exit 1
fi
cat > lib/jam_web/version.rb << EOF
module JamWeb
VERSION = "0.1.$BUILD_NUMBER"
end
EOF
type -P dpkg-architecture > /dev/null

View File

@ -29,8 +29,7 @@ if defined?(Bundler)
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib/managers)
config.autoload_paths += %W(#{config.root}/lib)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.

3
lib/jam_web/version.rb Normal file
View File

@ -0,0 +1,3 @@
module JamWeb
VERSION = "0.0.1"
end