diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.text.erb
index d412a7b92..85f6a7f9f 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.text.erb
@@ -1,3 +1,3 @@
-Welcome to JamKazam, <%= @user.first_name %>!
+Welcome to JamKazam<%= @user.anonymous? ? '!' : ", #{@user.first_name}!" %>
To confirm this email address, please go to the signup confirmation page at: <%= @signup_confirm_url %>.
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb
index be9aa27fb..65bf1295f 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.html.erb
@@ -1,5 +1,7 @@
<% provide(:title, 'New Musicians You Should Check Out') %>
-Hi <%= @user.first_name %>,
+<% if !@user.anonymous? %>
+
Hi <%= @user.first_name %>,
+<% end %>
The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb
index 05fbbd268..ee100dbff 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/new_musicians.text.erb
@@ -1,7 +1,8 @@
New Musicians You Should Check Out
+<% if !@user.anonymous? %>
Hi <%= @user.first_name %>,
-
+<% end %>
The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
<% @new_musicians.each do |user| %>
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb
index 2fdc11256..769b5bcda 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.html.erb
@@ -1,7 +1,8 @@
<% provide(:title, @title) %>
-
Hello <%= @user.first_name %> --
-
+<% if !@user.anonymous? %>
+
Hi <%= @user.first_name %>,
+<% end %>
The following new sessions have been posted within the last 24 hours, and you have good or acceptable latency to the organizer of each session below. If a session looks interesting, click the Details link to see the session page. You can RSVP to a session from the session page, and you'll be notified if/when the session organizer approves your RSVP.
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.text.erb
index 282c6dd90..3d1e15346 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_daily.text.erb
@@ -1,7 +1,8 @@
<% provide(:title, @title) %>
-Hello <%= @user.first_name %> --
-
+<% if !@user.anonymous? %>
+Hi <%= @user.first_name %>,
+<% end %>
The following new sessions have been posted within the last 24 hours, and you have good or acceptable latency to the organizer of each session below. If a session looks interesting, click the Details link to see the session page. You can RSVP to a session from the session page, and you'll be notified if/when the session organizer approves your RSVP.
GENRE | NAME | DESCRIPTION | LATENCY
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb
index b72d3c133..cf4816bf1 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb
@@ -1,18 +1,20 @@
<% provide(:title, 'JamKazam Session Reminder') %>
-
+<% if !@user.anonymous? %>
+
Hi <%= @user.first_name %>,
-
+
+<% end %>
-
+
This is a reminder that your JamKazam session<%= @session_name %>is scheduled for tomorrow. We hope you have fun!
-
+
-
+
Best Regards,
Team JamKazam
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb
index c3f0576bf..333acdb74 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb
@@ -1,5 +1,6 @@
+<% if !@user.anonymous? %>
Hi <%= @user.first_name %>,
-
+<% end %>
This is a reminder that your JamKazam session <%=@session_name%> is scheduled for tomorrow. We hope you have fun!
Best Regards,
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb
index 4fbc59ace..119d57b16 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb
@@ -1,17 +1,20 @@
<% provide(:title, 'Your JamKazam session starts in 1 hour!') %>
-
+<% if !@user.anonymous? %>
+
Hi <%= @user.first_name %>,
-
+
+<% end %>
+
-
+
This is a reminder that your JamKazam session<%= @session_name %>starts in 1 hour. We hope you have fun!
-
+
-
+
Best Regards,
Team JamKazam
-
+
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb
index 70726a9e6..d4719bd4c 100644
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb
@@ -1,4 +1,6 @@
+<% if !@user.anonymous? %>
Hi <%= @user.first_name %>,
+<% end %>
This is a reminder that your JamKazam session
<%=@session_name%>
diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb
index 43f77bdb7..a3fae99bb 100644
--- a/ruby/lib/jam_ruby/jam_track_importer.rb
+++ b/ruby/lib/jam_ruby/jam_track_importer.rb
@@ -981,6 +981,44 @@ module JamRuby
sorted_tracks
end
+ # this will put original_audio_s3_path on each jam_track_track
+ def associate_tracks_with_original_stems(jam_track, s3_path)
+ attempt_to_match_existing_tracks = true
+
+ # find all wav files in the JamTracks s3 bucket
+ wav_files = fetch_important_files(s3_path)
+
+ tracks = []
+
+ wav_files.each do |wav_file|
+
+ if attempt_to_match_existing_tracks
+ # try to find a matching track from the JamTrack based on the name of the 44.1 path
+ basename = File.basename(wav_file)
+ ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg"
+
+ found_track = nil
+ jam_track.jam_track_tracks.each do |jam_track_track|
+
+ if jam_track_track["url_44"] && jam_track_track["url_44"].end_with?(ogg_44100_filename)
+ # found a match!
+ found_track = jam_track_track
+ break
+ end
+ end
+
+ if found_track
+ @@log.debug("found a existing track to reuse")
+ found_track.original_audio_s3_path = wav_file
+ tracks << found_track
+ next
+ end
+ end
+ end
+
+ tracks
+ end
+
def synchronize_audio(jam_track, metadata, s3_path, skip_audio_upload)
attempt_to_match_existing_tracks = true
@@ -1104,6 +1142,7 @@ module JamRuby
begin
Dir.mktmpdir do |tmp_dir|
+ generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
jam_track.jam_track_tracks.each do |track|
basename = File.basename(track.original_audio_s3_path)
@@ -1204,6 +1243,87 @@ module JamRuby
return true
end
+ def generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
+ jam_track.jam_track_tracks.each do |track|
+
+ if track.original_audio_s3_path.nil?
+
+ @@log.error("jam_track #{jam_track.name} has empty stem. stem: #{track.id}")
+ next
+ end
+
+ basename = File.basename(track.original_audio_s3_path)
+ s3_dirname = File.dirname(track.original_audio_s3_path)
+
+ # make a 44100 version, and a 48000 version
+ mp3_48000_filename = File.basename(basename, ".wav") + "-48000.mp3"
+ aac_48000_filename = File.basename(basename, ".wav") + "-48000.aac"
+
+ mp3_48000_s3_path = track.filename(mp3_48000_filename)
+ aac_48000_s3_path = track.filename(aac_48000_filename)
+
+ track.skip_uploader = true
+
+ if skip_audio_upload
+ track["url_mp3_48"] = mp3_48000_filename
+ track["md5_mp3_48"] = 'md5'
+ track["length_mp3_48"] = 1
+
+ track["url_aac_48"] = aac_48000_filename
+ track["md5_aac_48"] = 'md5'
+ track["length_aac_48"] = 1
+
+ # we can't fake the preview as easily because we don't know the MD5 of the current item
+ #track["preview_md5"] = 'md5'
+ #track["preview_mp3_md5"] = 'md5'
+ #track["preview_url"] = track.preview_filename('md5', 'ogg')
+ #track["preview_length"] = 1
+ #track["preview_mp3_url"] = track.preview_filename('md5', 'mp3')
+ #track["preview_mp3_length"] = 1
+ #track["preview_start_time"] = 0
+ else
+ wav_file = File.join(tmp_dir, basename)
+
+ # the wave file might already be on the system...
+
+ # don't bother with the same track twice
+
+ next if track["url_mp3_48"] && track["url_aac_48"]
+
+ # bring the original wav file down from S3 to local file system
+ JamTrackImporter::song_storage_manager.download(track.original_audio_s3_path, wav_file) unless File.exists?(wav_file)
+
+ mp3_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.mp3")
+ aac_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.aac")
+
+ `ffmpeg -i "#{wav_file}" -ar 48000 -ab 192k "#{mp3_48000}"`
+
+ `ffmpeg -i "#{wav_file}" -c:a libfdk_aac -b:a 192k "#{aac_48000}"`
+
+ # upload the new ogg files to s3
+ @@log.debug("uploading mp3 48000 to #{mp3_48000_s3_path}")
+
+ jamkazam_s3_manager.upload(mp3_48000_s3_path, mp3_48000)
+
+ @@log.debug("uploading aac 48000 to #{aac_48000_s3_path}")
+
+ jamkazam_s3_manager.upload(aac_48000_s3_path, aac_48000)
+
+ mp3_48000_digest = ::Digest::MD5.file(mp3_48000)
+ # and finally update the JamTrackTrack with the new info
+ track["url_mp3_48"] = mp3_48000_s3_path
+ track["md5_mp3_48"] = mp3_48000_digest.hexdigest
+ track["length_mp3_48"] = File.new(mp3_48000).size
+
+ track["url_aac_48"] = aac_48000_s3_path
+ track["md5_aac_48"] = ::Digest::MD5.file(aac_48000).hexdigest
+ track["length_aac_48"] = File.new(aac_48000).size
+ track.save
+ end
+ end
+ end
+
+
def synchronize_duration(jam_track, ogg_44100)
duration_command = "soxi -D \"#{ogg_44100}\""
output = `#{duration_command}`
@@ -1527,6 +1647,11 @@ module JamRuby
id
end
+ def is_default_storage?
+ assert_storage_set
+ @storage_format == 'default'
+ end
+
def is_tency_storage?
assert_storage_set
@storage_format == 'Tency'
@@ -1929,6 +2054,20 @@ module JamRuby
importer
end
+ def generate_mp3_aac_stem(jam_track)
+ importer = JamTrackImporter.new
+ importer.name = jam_track.name
+
+ Dir.mktmpdir do |tmp_dir|
+
+ audio_path = jam_track.metalocation[0...-"/meta.yml".length]
+ importer.associate_tracks_with_original_stems(jam_track, audio_path)
+ importer.generate_mp3_aac_stem(jam_track, tmp_dir, false)
+ end
+
+ importer
+ end
+
def download_masters
importers = []
@@ -1958,6 +2097,67 @@ module JamRuby
filename.tr('/&@:,$=+?;\^`><{}[]#%~|', '')
end
+ def generate_mp3_aac_stems(format)
+ importers = []
+
+ jam_tracks = []
+
+ tency = JamTrackLicensor.find_by_name('Tency Music')
+
+ @@log.info("processing storage #{@storage_format}")
+ if is_tency_storage?
+ tency = JamTrackLicensor.find_by_name!('Tency Music')
+ jam_tracks = JamTrack.where(licensor_id: tency.id)
+ elsif is_default_storage?
+ # XXX IF WE ADD ANOTHER STORAGE, UPDATE THE WHERE TO EXCLUDE IT AS WELL
+ jam_tracks = JamTrack.where('licensor_id is null OR licensor_id != ?', tency.id )
+ else
+ raise 'unknown storage format!'
+ end
+
+ jam_tracks.each do |jam_track|
+
+ if ENV['NODE_COUNT']
+ node_count = ENV['NODE_COUNT'].to_i
+ node_number = ENV['NODE_NUMBER'].to_i
+ raise "NO NODE_COUNT" if node_count == 0
+
+ jam_track_id = jam_track.id.to_i
+ jam_track_id = jam_track_id + node_number
+ if jam_track_id == 0
+ @@log.warn("skipping #{jam_track_id} because non-numeric ID")
+ next
+ elsif jam_track_id % node_count == 0
+ @@log.warn("starting JamTrack #{jam_track.id} (#{jam_track_id})")
+ importers << generate_mp3_aac_stem(jam_track)
+ else
+ @@log.warn("skipping #{jam_track_id}")
+ next
+ end
+ else
+ importers << generate_mp3_aac_stem(jam_track)
+ end
+
+ end
+
+ @@log.info("SUMMARY")
+ @@log.info("-------")
+ importers.each do |importer|
+ if importer
+ if importer.reason == "success"
+ @@log.info("#{importer.name} #{importer.reason}")
+ else
+ @@log.error("#{importer.name} failed to download.")
+ @@log.error("#{importer.name} reason=#{importer.reason}")
+ @@log.error("#{importer.name} detail=#{importer.detail}")
+ end
+ else
+ @@log.error("NULL IMPORTER")
+ end
+
+ end
+ end
+
def generate_slugs
JamTrack.all.each do |jam_track|
jam_track.generate_slug
diff --git a/ruby/lib/jam_ruby/models/affiliate_partner.rb b/ruby/lib/jam_ruby/models/affiliate_partner.rb
index 4408ffa42..42b689db2 100644
--- a/ruby/lib/jam_ruby/models/affiliate_partner.rb
+++ b/ruby/lib/jam_ruby/models/affiliate_partner.rb
@@ -50,6 +50,14 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
record.entity_type ||= ENTITY_TYPES.first
end
+ def display_name
+ partner_name || (partner_user ? partner_user.name : 'abandoned')
+ end
+
+ def admin_url
+ APP_CONFIG.admin_root_url + "/admin/affiliates/" + id
+ end
+
# used by admin
def self.create_with_params(params={})
raise 'not supported'
diff --git a/ruby/lib/jam_ruby/models/jam_track_mixdown.rb b/ruby/lib/jam_ruby/models/jam_track_mixdown.rb
index 71a2ca0a1..3551dcdba 100644
--- a/ruby/lib/jam_ruby/models/jam_track_mixdown.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_mixdown.rb
@@ -16,7 +16,7 @@ module JamRuby
validates :jam_track, presence: true
validates :settings, presence: true
- validates_uniqueness_of :name, scope: :user_id
+ validates_uniqueness_of :name, scope: [:user_id, :jam_track_id]
validate :verify_settings
validate :verify_max_mixdowns
diff --git a/ruby/lib/jam_ruby/models/jam_track_mixdown_package.rb b/ruby/lib/jam_ruby/models/jam_track_mixdown_package.rb
index 687727f4c..648bdfffd 100644
--- a/ruby/lib/jam_ruby/models/jam_track_mixdown_package.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_mixdown_package.rb
@@ -7,9 +7,10 @@ module JamRuby
@@log = Logging.logger[JamTrackMixdownPackage]
# these are used as extensions for the files stored in s3
+ FILE_TYPE_MP3 = 'mp3'
FILE_TYPE_OGG = 'ogg'
FILE_TYPE_AAC = 'aac'
- FILE_TYPES = [FILE_TYPE_OGG, FILE_TYPE_AAC]
+ FILE_TYPES = [FILE_TYPE_MP3, FILE_TYPE_OGG, FILE_TYPE_AAC]
SAMPLE_RATE_44 = 44
SAMPLE_RATE_48 = 48
@@ -135,8 +136,11 @@ module JamRuby
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
# but the url is short lived enough so that it wouldn't be easily shared
- def sign_url(expiration_time = 120)
- s3_manager.sign_url(self['url'], {:expires => expiration_time, :secure => true})
+ def sign_url(expiration_time = 120, content_type = nil, response_content_disposition = nil)
+ options = {:expires => expiration_time, :secure => true}
+ options[:response_content_type] = content_type if content_type
+ options[:response_content_disposition] = response_content_disposition if response_content_disposition
+ s3_manager.sign_url(self['url'], options)
end
diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb
index f48ceb498..808593322 100644
--- a/ruby/lib/jam_ruby/models/jam_track_right.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_right.rb
@@ -12,6 +12,7 @@ module JamRuby
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right
+ belongs_to :last_stem, class_name: 'JamRuby::JamTrackTrack', foreign_key: 'last_stem_id', inverse_of: :jam_track_right
validates :user, presence: true
validates :jam_track, presence: true
diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb
index 16f00cd21..22e8f17f3 100644
--- a/ruby/lib/jam_ruby/models/jam_track_track.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_track.rb
@@ -34,6 +34,7 @@ module JamRuby
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy
+ has_one :jam_track_right, class_name: 'JamRuby::JamTrackRight', foreign_key: 'last_stem_id', inverse_of: :last_stem
# create storage directory that will house this jam_track, as well as
def store_dir
@@ -79,6 +80,18 @@ module JamRuby
end
end
+ def display_name
+ if track_type == 'Master'
+ 'Master Mix'
+ else
+ display_part = ''
+ if part
+ display_part = "-(#{part})"
+ end
+ "#{instrument.description}#{display_part}"
+ end
+ end
+
def manually_uploaded_filename(mounted_as)
if track_type == 'Master'
filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
@@ -102,6 +115,18 @@ module JamRuby
def sign_url(expiration_time = 120, sample_rate=48)
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
end
+
+ def web_download_sign_url(expiration_time = 120, type='mp3', content_type = nil, response_content_disposition = nil)
+ options = {:expires => expiration_time, :secure => true}
+ options[:response_content_type] = content_type if content_type
+ options[:response_content_disposition] = response_content_disposition if response_content_disposition
+
+ url_field = self['url_' + type + '_48']
+ url_field = self['url_48'] if type == 'ogg' # ogg has different column format in database
+
+
+ s3_manager.sign_url(url_field, options)
+ end
def can_download?(user)
# I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
diff --git a/ruby/lib/jam_ruby/models/music_session_user_history.rb b/ruby/lib/jam_ruby/models/music_session_user_history.rb
index 86d132025..c8b8c0834 100644
--- a/ruby/lib/jam_ruby/models/music_session_user_history.rb
+++ b/ruby/lib/jam_ruby/models/music_session_user_history.rb
@@ -21,6 +21,10 @@ module JamRuby
.first
end
+ def name
+ user.name
+ end
+
def music_session
@msh ||= JamRuby::MusicSession.find_by_music_session_id(self.music_session_id)
end
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index ea72b803c..a9c46801a 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -176,11 +176,13 @@ module JamRuby
has_one :musician_search, :class_name => 'JamRuby::MusicianSearch'
has_one :band_search, :class_name => 'JamRuby::BandSearch'
+ before_save :default_anonymous_names
before_save :create_remember_token, :if => :should_validate_password?
before_save :stringify_avatar_info , :if => :updating_avatar
- validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
- validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
+ validates :first_name, length: {maximum: 50}, no_profanity: true
+ validates :last_name, length: {maximum: 50}, no_profanity: true
+ validates :last_name, length: {maximum: 50}, no_profanity: true
validates :biography, length: {maximum: 4000}, no_profanity: true
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email
@@ -295,8 +297,16 @@ module JamRuby
online?
end
+ def anonymous?
+ first_name == 'Anonymous' && last_name == 'Anonymous'
+ end
+
def name
- "#{first_name} #{last_name}"
+ if anonymous?
+ 'Anonymous'
+ else
+ "#{first_name} #{last_name}"
+ end
end
def location
@@ -575,9 +585,7 @@ module JamRuby
def to_s
return email unless email.nil?
- if !first_name.nil? && !last_name.nil?
- return first_name + ' ' + last_name
- end
+ return name unless name.nil?
id
end
@@ -1680,14 +1688,17 @@ module JamRuby
else
false
end
-
-
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
+ def default_anonymous_names
+ self.first_name = 'Anonymous' if self.first_name.nil?
+ self.last_name = 'Anonymous' if self.last_name.nil?
+ end
+
def stringify_avatar_info
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
diff --git a/ruby/lib/jam_ruby/models/user_event.rb b/ruby/lib/jam_ruby/models/user_event.rb
new file mode 100644
index 000000000..21446c005
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/user_event.rb
@@ -0,0 +1,8 @@
+module JamRuby
+ class UserEvent < ActiveRecord::Base
+
+ belongs_to :user, class_name: 'JamRuby::User'
+
+ validates :name, presence: true
+ end
+end
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb b/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb
index 1d54bf67d..445c228db 100644
--- a/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb
+++ b/ruby/lib/jam_ruby/resque/jam_track_mixdown_packager.rb
@@ -352,9 +352,15 @@ module JamRuby
# otherwise we need to convert from lastly created file to correct
output = File.join(tmp_dir, "output.#{@mixdown_package.file_type}")
- raise 'unknown file_type' if @mixdown_package.file_type != JamTrackMixdownPackage::FILE_TYPE_AAC
+ if @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_AAC
+ cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
+ elsif @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_MP3
+ cmd("ffmpeg -i \"#{@speed_mix_file}\" -ab 192k \"#{output}\"", 'convert_mp3')
+ else
+ raise 'unknown file_type'
+ end
+
- cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
output
end
end
diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb
index 0fb434442..89abe6e16 100644
--- a/ruby/spec/jam_ruby/models/user_spec.rb
+++ b/ruby/spec/jam_ruby/models/user_spec.rb
@@ -101,12 +101,12 @@ describe User do
describe "when first name is not present" do
before { @user.first_name = " " }
- it { should_not be_valid }
+ it { should be_valid }
end
describe "when last name is not present" do
before { @user.last_name = " " }
- it { should_not be_valid }
+ it { should be_valid }
end
describe "when email is not present" do
diff --git a/web/app/assets/images/content/icon_download.png b/web/app/assets/images/content/icon_download.png
new file mode 100644
index 000000000..d56bde46c
Binary files /dev/null and b/web/app/assets/images/content/icon_download.png differ
diff --git a/web/app/assets/images/content/icon_download@2X.png b/web/app/assets/images/content/icon_download@2X.png
new file mode 100644
index 000000000..5473ff7df
Binary files /dev/null and b/web/app/assets/images/content/icon_download@2X.png differ
diff --git a/web/app/assets/images/content/icon_download@3X.png b/web/app/assets/images/content/icon_download@3X.png
new file mode 100644
index 000000000..13c2a1fd1
Binary files /dev/null and b/web/app/assets/images/content/icon_download@3X.png differ
diff --git a/web/app/assets/images/landing/Andy Crowley - Avatar.png b/web/app/assets/images/landing/Andy Crowley - Avatar.png
new file mode 100644
index 000000000..4a675f607
Binary files /dev/null and b/web/app/assets/images/landing/Andy Crowley - Avatar.png differ
diff --git a/web/app/assets/images/landing/Andy Crowley - Speech Bubble.png b/web/app/assets/images/landing/Andy Crowley - Speech Bubble.png
new file mode 100644
index 000000000..9dc63b79b
Binary files /dev/null and b/web/app/assets/images/landing/Andy Crowley - Speech Bubble.png differ
diff --git a/web/app/assets/images/landing/Andy Crowley - YouTube.png b/web/app/assets/images/landing/Andy Crowley - YouTube.png
new file mode 100644
index 000000000..0dad41bf8
Binary files /dev/null and b/web/app/assets/images/landing/Andy Crowley - YouTube.png differ
diff --git a/web/app/assets/images/landing/Carl Brown - Avatar.png b/web/app/assets/images/landing/Carl Brown - Avatar.png
new file mode 100644
index 000000000..a991220a3
Binary files /dev/null and b/web/app/assets/images/landing/Carl Brown - Avatar.png differ
diff --git a/web/app/assets/images/landing/Carl Brown - Speech Bubble.png b/web/app/assets/images/landing/Carl Brown - Speech Bubble.png
new file mode 100644
index 000000000..071f8c40f
Binary files /dev/null and b/web/app/assets/images/landing/Carl Brown - Speech Bubble.png differ
diff --git a/web/app/assets/images/landing/Carl Brown - YouTube.png b/web/app/assets/images/landing/Carl Brown - YouTube.png
new file mode 100644
index 000000000..289b170d3
Binary files /dev/null and b/web/app/assets/images/landing/Carl Brown - YouTube.png differ
diff --git a/web/app/assets/images/landing/JK_FBAd_Guitar_with_Keys.png b/web/app/assets/images/landing/JK_FBAd_Guitar_with_Keys.png
new file mode 100644
index 000000000..61d12e9aa
Binary files /dev/null and b/web/app/assets/images/landing/JK_FBAd_Guitar_with_Keys.png differ
diff --git a/web/app/assets/images/landing/Julie Bonk - Avatar.png b/web/app/assets/images/landing/Julie Bonk - Avatar.png
new file mode 100644
index 000000000..0b5019588
Binary files /dev/null and b/web/app/assets/images/landing/Julie Bonk - Avatar.png differ
diff --git a/web/app/assets/images/landing/Ryan Jones - Avatar.png b/web/app/assets/images/landing/Ryan Jones - Avatar.png
new file mode 100644
index 000000000..09b908884
Binary files /dev/null and b/web/app/assets/images/landing/Ryan Jones - Avatar.png differ
diff --git a/web/app/assets/images/landing/Ryan Jones - PianoKeyz - YouTube.png b/web/app/assets/images/landing/Ryan Jones - PianoKeyz - YouTube.png
new file mode 100644
index 000000000..5c40b4727
Binary files /dev/null and b/web/app/assets/images/landing/Ryan Jones - PianoKeyz - YouTube.png differ
diff --git a/web/app/assets/images/landing/Ryan Jones - Speech Bubble.png b/web/app/assets/images/landing/Ryan Jones - Speech Bubble.png
new file mode 100644
index 000000000..9cf7c90d9
Binary files /dev/null and b/web/app/assets/images/landing/Ryan Jones - Speech Bubble.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 1.png b/web/app/assets/images/landing/Top 10 Image - Number 1.png
new file mode 100644
index 000000000..82e1a3847
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 1.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 10.png b/web/app/assets/images/landing/Top 10 Image - Number 10.png
new file mode 100644
index 000000000..23f1fd862
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 10.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 2.png b/web/app/assets/images/landing/Top 10 Image - Number 2.png
new file mode 100644
index 000000000..9d79989d7
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 2.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 3.png b/web/app/assets/images/landing/Top 10 Image - Number 3.png
new file mode 100644
index 000000000..7084feaf4
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 3.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 4.png b/web/app/assets/images/landing/Top 10 Image - Number 4.png
new file mode 100644
index 000000000..48f40e9be
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 4.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 5.png b/web/app/assets/images/landing/Top 10 Image - Number 5.png
new file mode 100644
index 000000000..d50812947
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 5.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 6.png b/web/app/assets/images/landing/Top 10 Image - Number 6.png
new file mode 100644
index 000000000..b76dd8555
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 6.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 7.png b/web/app/assets/images/landing/Top 10 Image - Number 7.png
new file mode 100644
index 000000000..0de272f34
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 7.png differ
diff --git a/web/app/assets/images/landing/Top 10 Image - Number 9.png b/web/app/assets/images/landing/Top 10 Image - Number 9.png
new file mode 100644
index 000000000..8fdcb8d46
Binary files /dev/null and b/web/app/assets/images/landing/Top 10 Image - Number 9.png differ
diff --git a/web/app/assets/images/landing/jamtrack_landing_arrow_1.png b/web/app/assets/images/landing/jamtrack_landing_arrow_1.png
new file mode 100644
index 000000000..c19bc6faf
Binary files /dev/null and b/web/app/assets/images/landing/jamtrack_landing_arrow_1.png differ
diff --git a/web/app/assets/images/landing/jamtrack_landing_arrow_2.png b/web/app/assets/images/landing/jamtrack_landing_arrow_2.png
new file mode 100644
index 000000000..0cb8cb55a
Binary files /dev/null and b/web/app/assets/images/landing/jamtrack_landing_arrow_2.png differ
diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js
index fb16ffa71..b893ef7c7 100644
--- a/web/app/assets/javascripts/JamServer.js
+++ b/web/app/assets/javascripts/JamServer.js
@@ -226,7 +226,7 @@
app.clientId = payload.client_id;
- if (isClientMode()) {
+ if (isClientMode() && context.jamClient) {
// tell the backend that we have logged in
context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
$.cookie('client_id', payload.client_id);
@@ -321,7 +321,7 @@
function socketClosed(in_error) {
// tell the backend that we have logged out
- context.jamClient.OnLoggedOut();
+ if (context.jamClient) context.jamClient.OnLoggedOut();
}
@@ -593,7 +593,10 @@
clientType = context.JK.clientType();
}
if(!mode) {
- mode = context.jamClient.getOperatingMode ? context.jamClient.getOperatingMode() : 'client';
+ mode = 'client'
+ if (context.jamClient && context.jamClient.getOperatingMode) {
+ mode = context.jamClient.getOperatingMode()
+ }
}
connectDeferred = new $.Deferred();
diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js
index 811277c13..2153fb9c1 100644
--- a/web/app/assets/javascripts/application.js
+++ b/web/app/assets/javascripts/application.js
@@ -61,6 +61,7 @@
//= require web/tracking
//= require webcam_viewer
//= require react-components
+//= require playbackControls
//= require_directory .
//= require_directory ./dialog
//= require_directory ./wizard
diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
index d350841e8..97d746144 100644
--- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
@@ -15,12 +15,21 @@
// remove all display errors
$('#recording-finished-dialog form .error-text').remove()
$('#recording-finished-dialog form .error').removeClass("error")
- console.log("save video?", recording)
if(recording.video) {
- $dialog.find('.save-video').show()
+ if(recording.owner.id == context.JK.currentUserId) {
+ // only the owner of the video gets to see video options
+ $dialog.find('.save-video').show()
+ $dialog.find('.upload-to-youtube').show()
+ }
+ else {
+ $dialog.find('.save-video').hide()
+ $dialog.find('.upload-to-youtube').hide()
+ }
+
}
else {
$dialog.find('.save-video').hide()
+ $dialog.find('.upload-to-youtube').hide()
}
removeGoogleLoginErrors()
}
@@ -209,6 +218,8 @@
var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
var recording_id = recording.id
+ var recording_video = recording.video
+
rest.claimRecording({
id: recording.id,
name: name,
@@ -222,7 +233,7 @@
$dialog.data('result', {keep:true});
app.layout.closeDialog('recordingFinished');
context.JK.GA.trackMakeRecording();
- if(save_video && upload_to_youtube) {
+ if(recording_video && save_video && upload_to_youtube) {
// you have to have elected to save video to have upload to youtube have
context.VideoUploaderActions.showUploader(recording_id);
}
diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js
index 43a844a32..42a0f410d 100644
--- a/web/app/assets/javascripts/everywhere/everywhere.js
+++ b/web/app/assets/javascripts/everywhere/everywhere.js
@@ -185,6 +185,7 @@
context.stats.write = context.stats.writePoint;
}
+
function initializeStun(app) {
stun = new context.JK.Stun(app);
context.JK.StunInstance = stun;
diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js
index 396e30be4..c085e548b 100644
--- a/web/app/assets/javascripts/globals.js
+++ b/web/app/assets/javascripts/globals.js
@@ -59,7 +59,8 @@
context.JK.PLAYBACK_MONITOR_MODE = {
MEDIA_FILE: 'MEDIA_FILE',
JAMTRACK: 'JAMTRACK',
- METRONOME: 'METRONOME'
+ METRONOME: 'METRONOME',
+ BROWSER_MEDIA: 'BROWSER_MEDIA'
}
context.JK.ALERT_NAMES = {
NO_EVENT : 0,
diff --git a/web/app/assets/javascripts/helpBubbleHelper.js b/web/app/assets/javascripts/helpBubbleHelper.js
index 0a6774631..b348ed629 100644
--- a/web/app/assets/javascripts/helpBubbleHelper.js
+++ b/web/app/assets/javascripts/helpBubbleHelper.js
@@ -131,4 +131,8 @@
return context.JK.prodBubble($element, 'jamtrack-browse-cta', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent}))
}
+ helpBubble.jamtrackWebPlay = function($element, $offsetParent) {
+ return context.JK.prodBubble($element, 'jamtrack-web-play', {}, bigHelpOptions({positions:['bottom'], offsetParent: $offsetParent}))
+ }
+
})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index ab4c37751..2dcaabfc3 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -540,6 +540,11 @@
processData: false
});
}
+ if(detail) {
+ detail.done(function(user) {
+ window.UserActions.loaded(user)
+ })
+ }
return detail;
}
@@ -1134,6 +1139,29 @@
});
}
+ function userOpenedJamTrackWebPlayer(options) {
+ var id = getId(options);
+
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: "/api/users/progression/opened_jamtrack_web_player",
+ processData: false
+ });
+ }
+
+ function postUserEvent(options) {
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: "/api/users/event/record",
+ data: JSON.stringify(options),
+ processData: false
+ });
+ }
+
function signout() {
return $.ajax({
type: "DELETE",
@@ -1144,11 +1172,12 @@
}
function updateUser(options) {
+ options = options || {};
var id = getId(options);
delete options['id'];
- return $.ajax({
+ var deferred = $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
@@ -1156,6 +1185,13 @@
data: JSON.stringify(options),
processData: false
});
+
+ deferred.done(function(user) {
+ window.UserActions.loaded(user)
+ })
+
+ return deferred;
+
}
function startRecording(options) {
@@ -2058,6 +2094,8 @@
this.userDownloadedClient = userDownloadedClient;
this.userCertifiedGear = userCertifiedGear;
this.userSocialPromoted = userSocialPromoted;
+ this.userOpenedJamTrackWebPlayer = userOpenedJamTrackWebPlayer;
+ this.postUserEvent = postUserEvent;
this.createJoinRequest = createJoinRequest;
this.updateJoinRequest = updateJoinRequest;
this.updateUser = updateUser;
diff --git a/web/app/assets/javascripts/minimal/minimal.js b/web/app/assets/javascripts/minimal/minimal.js
index 70bfd5f0b..0c216c297 100644
--- a/web/app/assets/javascripts/minimal/minimal.js
+++ b/web/app/assets/javascripts/minimal/minimal.js
@@ -9,17 +9,39 @@
//= require jquery.icheck
//= require jquery.easydropdown
//= require jquery.metronomePlaybackMode
+//= require jquery.cookie
+//= require influxdb-latest
+//= require howler.core.js
//= require classnames
//= require reflux
//= require AAC_underscore
//= require AAA_Log
//= require globals
+//= require AAB_message_factory
//= require jam_rest
//= require ga
+//= require layout
+//= require jamkazam
//= require utils
-//= require playbackControls
+//= require subscription_utils
//= require webcam_viewer
+//= require JamServer
//= require react
//= require react_ujs
//= require react-init
-//= require react-components
\ No newline at end of file
+//= require react-components
+//= require playbackControls
+
+function initializeInfluxDB() {
+ window.stats = new InfluxDB({
+ "host" : gon.global.influxdb_host,
+ "port" : gon.global.influxdb_port,
+ "username" : gon.global.influxdb_username,
+ "password" : gon.global.influxdb_password,
+ "database" : gon.global.influxdb_database
+ });
+
+ window.stats.write = window.stats.writePoint;
+}
+
+initializeInfluxDB()
\ No newline at end of file
diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js
index ec36d0a03..0ef6557e6 100644
--- a/web/app/assets/javascripts/playbackControls.js
+++ b/web/app/assets/javascripts/playbackControls.js
@@ -2,8 +2,25 @@
* Playback widget (play, pause , etc)
*/
+
+
(function (context, $) {
+
+ // this check ensures we attempt to listen if this component is created in a popup
+ var reactContext = window.opener ? window.opener : window
+ // make sure this is actually us opening the window, not someone else (by checking for MixerStore)
+ if (window.opener) {
+ try {
+ m = window.opener.MixerStore
+ }
+ catch (e) {
+ reactContext = window
+ }
+ }
+
+ var BrowserMediaStore = reactContext.BrowserMediaStore
+
"use strict";
var PlaybackMode = {
@@ -75,7 +92,6 @@
}
else {
$self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode});
-
}
if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
@@ -241,6 +257,9 @@
else if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) {
$parentElement.addClass('metronome-mode');
}
+ else if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA) {
+ $parentElement.addClass('mediafile-mode');
+ }
else {
throw "unknown playbackMonitorMode: " + playbackMonitorMode;
}
@@ -294,14 +313,19 @@
var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs();
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
var durationMs = duration.media_len;
+ var isPlaying = context.jamClient.isSessionTrackPlaying();
+ }
+ else if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA) {
+ var positionMs = BrowserMediaStore.onGetPlayPosition() || 0
+ var durationMs = BrowserMediaStore.onGetPlayDuration() || 0;
+ var isPlaying = BrowserMediaStore.playing;
}
else {
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
+ var isPlaying = context.jamClient.isSessionTrackPlaying();
}
- var isPlaying = context.jamClient.isSessionTrackPlaying();
-
executeMonitor(positionMs, durationMs, isPlaying)
}
}
diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js
index 079093ac5..81da1bf5c 100644
--- a/web/app/assets/javascripts/react-components.js
+++ b/web/app/assets/javascripts/react-components.js
@@ -4,6 +4,7 @@
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
+//= require ./react-components/stores/BrowserMediaStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/VideoStore
//= require ./react-components/stores/SessionStore
@@ -11,11 +12,13 @@
//= require ./react-components/stores/JamTrackStore
//= require ./react-components/stores/SessionNotificationStore
//= require ./react-components/stores/MediaPlaybackStore
+//= require ./react-components/stores/BrowserMediaPlaybackStore
//= require ./react-components/stores/SessionMyTracksStore
//= require ./react-components/stores/SessionOtherTracksStore
//= require ./react-components/stores/SessionMediaTracksStore
//= require ./react-components/stores/PlatformStore
//= require ./react-components/stores/VideoUploaderStore
+//= require ./react-components/stores/JamTrackPlayerStore
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components
diff --git a/web/app/assets/javascripts/react-components/BrowserMediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/BrowserMediaControls.js.jsx.coffee
new file mode 100644
index 000000000..af39ab333
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/BrowserMediaControls.js.jsx.coffee
@@ -0,0 +1,132 @@
+context = window
+PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE
+EVENTS = context.JK.EVENTS
+logger = context.JK.logger
+
+mixins = []
+
+# this check ensures we attempt to listen if this component is created in a popup
+reactContext = if window.opener? then window.opener else window
+# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
+if window.opener?
+ try
+ m = window.opener.MixerStore
+ catch e
+ reactContext = window
+
+# temporarily..
+# reactContext = window
+
+MediaPlaybackActions = reactContext.MediaPlaybackActions
+BrowserMediaStore = reactContext.BrowserMediaStore
+BrowserMediaPlaybackStore = reactContext.BrowserMediaPlaybackStore
+BrowserMediaPlaybackActions = reactContext.BrowserMediaPlaybackActions
+
+mixins.push(Reflux.listenTo(BrowserMediaPlaybackStore,"onMediaStateChanged"))
+
+
+@BrowserMediaControls = React.createClass({
+
+ mixins: mixins
+ tempos : [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, 208 ]
+
+ onMediaStateChanged: (changes) ->
+ if changes.playbackStateChanged
+ if @state.controls?
+ if changes.playbackState == 'play_start'
+ @state.controls.onPlayStartEvent()
+ else if changes.playbackState == 'play_stop'
+ @state.controls.onPlayStopEvent()
+ else if changes.playbackState == 'play_pause'
+ @state.controls.onPlayPauseEvent();
+ if changes.positionUpdateChanged
+ if @state.controls?
+ @state.controls.executeMonitor(changes.positionMs, changes.durationMs, changes.isPlaying)
+ if changes.currentTimeChanged
+ @setState({time: changes.time})
+
+ monitorControls: (controls) ->
+
+ controls.startMonitor(PLAYBACK_MONITOR_MODE.BROWSER_MEDIA)
+
+ render: () ->
+
+
+ tempo_options = []
+ for tempo in @tempos
+ tempo_options.push(``)
+
+ `
- To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or visit the JamTracks section of your account.
-
+ The fastest way to start playing with your JamTracks is to open them below and use our custom mix feature to play them back in your browser. To access the full set of JamTrack features, install our free app. To learn more about all you can do with JamTracks, check out our JamTracks help docs.
+
`
+ playJamTracks = []
+
+ for jamTrack in @state.purchasedJamTracks
+ playJamTracks.push `
`
+
+ if @state.purchasedJamTracks.length < 5
+ # fill out the table with empty rows
+ for x in [@state.purchasedJamTracks.length...(6 - @state.purchasedJamTracks.length )] by 1
+ playJamTracks.push `
@@ -60,15 +86,20 @@ MIX_MODES = context.JK.MIX_MODES
JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each part of the master mix. Used with the free JamKazam app & Internet service, you can:
-
Solo just the part you want to play in order to hear and learn it
-
Mute just the part you want to play and play along with the rest
+
Choose your favorites from our catalog of 3,700+ songs
+
Listen to just the single part you want to play to learn it
+
Mute the part you want to play, and play along with the rest of the band
Slow down playback to practice without changing the pitch
Change the song key by raising or lowering pitch in half steps
-
Make audio recordings and share them via Facebook or URL
-
Make video recordings and share them via YouTube
-
And even go online to play with others live & in sync
+
Save your custom mixes for easy access, and export them to use anywhere
+
Apply VST & AU audio plugin effects to your live performance
+
Use MIDI with VST & AU instruments for keys, electronic drums, more
+
Make audio recordings and share via Facebook or URL
+
Make video recordings and share via YouTube
+
Play online live and in sync with others from different locations
+
JamTracks work in standard browser, with more features in Win/Mac apps
Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.
`
+
+ windowUnloaded: () ->
+ JamTrackPlayerActions.windowUnloaded() unless window.DontAutoCloseMedia
+
+ toggleMyMixes: (e) ->
+ e.preventDefault()
+ @setState({showMyMixes: !@state.showMyMixes})
+
+ toggleCustomMixes: (e) ->
+ e.preventDefault()
+
+
+ @setState({showCustomMixes: !@state.showCustomMixes})
+
+ downloadNotReady: (mixdown, e) ->
+ alert("This mix is not yet ready to download.")
+ e.preventDefault()
+
+ downloadMixdownReady: (mixdown, e) ->
+ e.preventDefault()
+
+ if mixdown.myPackage?.signing_state == 'SIGNED'
+ iframe = document.createElement("iframe")
+ iframe.src = window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{mixdown.id}/download.mp3?file_type=mp3&sample_rate=48&download=1"
+ iframe.style.display = "none"
+ document.body.appendChild(iframe);
+ else
+ alert("The mix is not yet ready to download")
+
+ activateStem: (e) ->
+ e.preventDefault()
+
+ $select = $(this.getDOMNode()).find('.active-stem-select')
+
+ selectedTrackId = $select.val()
+
+ if !selectedTrackId? || selectedTrackId == ''
+ alert("You must pick a track from the dropdown in order to play it.")
+ else
+
+ @setState({editingMixdownId: null})
+
+ e.preventDefault()
+
+ if @disableLoading
+ alert('Certain actions are disabled while a track is being loaded.')
+ return
+
+ # make this package the active one
+ JamTrackPlayerActions.openStem(selectedTrackId)
+
+ downloadStem: (e) ->
+ e.preventDefault()
+
+ $select = $(this.getDOMNode()).find('.active-stem-select')
+
+ selectedTrackId = $select.val()
+
+ if !selectedTrackId? || selectedTrackId == ''
+ alert("You must select a track in order to download.")
+ else
+ e.preventDefault()
+
+ iframe = document.createElement("iframe")
+ iframe.src = window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{@state.jamTrackState.jamTrack.id}/stems/#{selectedTrackId}/download.mp3?file_type=mp3&download=1"
+ iframe.style.display = "none"
+ document.body.appendChild(iframe);
+ stemChanged: () ->
+
+ mixdownPlay: (mixdown, e) ->
+ @setState({editingMixdownId: null})
+
+ e.preventDefault()
+
+ if @disableLoading
+ alert('Certain actions are disabled while a track is being loaded.')
+ return
+
+ # make this package the active one
+ JamTrackPlayerActions.openMixdown(mixdown)
+
+ jamTrackPlay: (jamtrack, e) ->
+ e.preventDefault()
+ # user wants to select the full track
+
+ if @disableLoading
+ alert('Certain actions are disabled while a track is being loaded.')
+ return
+
+ JamTrackPlayerActions.activateNoMixdown(jamtrack)
+
+ jamTrackDownload: (jamTrack, e) ->
+ e.preventDefault()
+
+ iframe = document.createElement("iframe")
+ iframe.src = window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{jamTrack.id}/stems/master/download.mp3?file_type=mp3&download=1"
+ iframe.style.display = "none"
+ document.body.appendChild(iframe);
+
+
+ onEditKeydown: (mixdown, e) ->
+ logger.debug("on edit keydown", e)
+ if e.keyCode == 13 # enter
+ @mixdownSave(mixdown, e)
+ else if e.keyCode == 27 # esc
+ @setState({editingMixdownId: null})
+
+ mixdownEdit: (mixdown) ->
+ @setState({editingMixdownId: mixdown.id})
+
+ mixdownSave: (mixdown, e) ->
+ e.preventDefault()
+ $input = $(this.getDOMNode()).find('input.edit-name')
+ newValue = $input.val()
+ logger.debug("editing mixdown name to be: " + newValue)
+ JamTrackPlayerActions.editMixdown({id: mixdown.id, name: newValue})
+ @setState({editingMixdownId: null})
+
+ mixdownDelete: (mixdown) ->
+ if @state.editingMixdownId?
+ @setState({editingMixdownId:null})
+ return
+
+ if confirm("Delete this custom mix?")
+ JamTrackPlayerActions.deleteMixdown(mixdown)
+
+
+ mixdownError: (mixdown) ->
+
+ myPackage = mixdown.myPackage
+
+ if myPackage?
+ switch myPackage.signing_state
+ when 'QUIET_TIMEOUT'
+ action = 'Custom mix never got created. Retry?'
+ when 'QUEUED_TIMEOUT'
+ action = 'Custom mix was never built. Retry?'
+ when 'SIGNING_TIMEOUT'
+ action = 'Custom mix took took long to build. Retry?'
+ when 'ERROR'
+ action = 'Custom mix failed to build. Retry?'
+ else
+ action = 'Custom mix never got created. Retry?'
+
+ return unless action?
+
+ if confirm(action)
+ JamTrackPlayerActions.enqueueMixdown(mixdown, @enqueueDone)
+
+ enqueueDone: (enqueued) ->
+ @promptEstimate(enqueued)
+
+ promptEstimate: (enqueued) ->
+ time = enqueued.queue_time
+
+ if time == 0
+ alert("Your custom mix will take about 1 minute to be created.")
+ else
+ guess = Math.ceil(time / 60.0)
+ if guess == 1
+ msg = '1 minute'
+ else
+ msg = "#{guess} minutes"
+ alert("Your custom mix will take about #{msg} to be created.")
+
+
+ createMix: (e) ->
+ e.preventDefault()
+
+ return if @state.creatingMix
+
+ $root = $(@getDOMNode())
+
+ name = $root.find('input[name="mix-name"]').val()
+ speed = $root.find('select[name="mix-speed"]').val()
+ pitch = $root.find('select[name="mix-pitch"]').val()
+
+ if name == null || name == ''
+ @setState({createMixdownErrors: {errors: {'Mix Name': ["can't be blank"]}}})
+ return
+
+ # sanitize junk out of speed/pitch
+ if speed == '' || speed.indexOf('separator') > -1
+ speed = undefined
+ else
+ speed = parseInt(speed)
+ if pitch == '' || pitch.indexOf('separator') > -1
+ pitch = undefined
+ else
+ pitch = parseInt(pitch)
+
+
+ # get mute state of all tracks
+ $mutes = $(@getDOMNode()).find('.stems .stem .stem-mute')
+
+ tracks = []
+ $mutes.each((i, mute) =>
+ $mute = $(mute)
+ stemId = $mute.attr('data-stem-id')
+ muted = $mute.is(':checked')
+
+ tracks.push({id: stemId, mute: muted})
+ )
+
+ mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, tracks:tracks}}
+
+ JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)
+
+ @setState({creatingMixdown: true, createMixdownErrors: null})
+
+ createMixdownDone: (created) ->
+ logger.debug("created (within PopupMediaControls)", created)
+ @setState({creatingMixdown: false, showCustomMixes: true, showMyMixes: true})
+
+ @promptEstimate(created)
+
+ createMixdownFail: (jqXHR) ->
+ logger.debug("create mixdown fail (within PopupMediaControls)", jqXHR.status)
+ @setState({creatingMixdown: false})
+ if jqXHR.status == 422
+ response = JSON.parse(jqXHR.responseText)
+ logger.warn("failed to create mixdown", response, jqXHR.responseText)
+
+ @setState({createMixdownErrors: response})
+
+ fetchJamTrackInfo: () ->
+ rest.getJamTrack({plan_code: gon.jamtrack_id})
+ .done((response) =>
+ JamTrackPlayerActions.open(response, false);
+ @fetchUserInfo()
+ )
+ .fail((jqXHR) =>
+ alert("Unable to fetch JamTrack information. Try logging in.")
+ )
+
+ fetchUserInfo: () ->
+ rest.getUserDetail()
+ .done((response) =>
+
+ rest.postUserEvent({name: 'jamtrack_web_player_open'})
+ context.stats.write('web.jamtrack_web_player.open', {
+ value: 1,
+ user_id: context.JK.currentUserId,
+ user_name: context.JK.currentUserName
+ })
+
+ if !response.first_opened_jamtrack_web_player
+ setTimeout((() =>
+ logger.debug("first time user")
+ rest.userOpenedJamTrackWebPlayer()
+ $root = $(@getDOMNode())
+ #context.JK.prodBubble($root.find('.create-mix-btn'), 'first-time-jamtrack-web-player', {}, {positions:['left'], offsetParent: $root})
+ ), 1500)
+ )
+
+
+
+ componentDidMount: () ->
+
+ $(window).unload(@windowUnloaded)
+
+ @root = jQuery(this.getDOMNode())
+
+ $loop = @root.find('input[name="loop"]')
+ context.JK.checkbox($loop)
+
+ $loop.on('ifChecked', () =>
+ # it doesn't matter if you do personal or master, because backend just syncs both
+ MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, true)
+ )
+ $loop.on('ifUnchecked', () =>
+ # it doesn't matter if you do personal or master, because backend just syncs both
+ MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, false)
+ )
+
+ @resizeWindow()
+
+ # this is necessary due to whatever the client's rendering behavior is.
+ setTimeout(@resizeWindow, 300)
+
+ @fetchJamTrackInfo()
+
+ componentDidUpdate: () ->
+ @resizeWindow()
+ setTimeout(@resizeWindow, 1000)
+
+ resizeWindow: () =>
+ $container = $('#minimal-container')
+ width = $container.width()
+ height = $container.height()
+
+ # there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
+ #mysteryTopMargin = 20
+ mysteryTopMargin = 0
+ # deal with chrome in real browsers
+ offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
+
+ # handle native client chrome that the above outer-inner doesn't catch
+ #if navigator.userAgent.indexOf('JamKazam') > -1
+
+ #offset += 25
+
+ window.resizeTo(450, height + offset)
+
+ computeDisableLoading: (state) ->
+ @disableLoading = false
+
+ return unless nextState?
+
+ selectedMixdown = state?.jamTrackState?.jamTrack?.activeMixdown
+
+ mixdownDownloading = false
+ if selectedMixdown?
+ switch selectedMixdown.client_state
+ when 'downloading'
+ mixdownDownloading = true
+
+ selectedStem = state?.jamTrackState?.jamTrack?.activeStem
+
+ stemDownloading = false
+ if selectedStem?
+ switch selectedStem.client_state
+ when 'downloading'
+ stemDownloading = true
+
+ @disableLoading = state?.jamTrackState?.jamTrack?.client_state == 'downloading' || mixdownDownloading || stemDownloading
+
+ componentWillMount: () ->
+ @computeDisableLoading(@state)
+
+ componentWillUpdate: (nextProps, nextState) ->
+ @computeDisableLoading(nextState)
+
+
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee
index 9aa0ccdf0..a1d6656a5 100644
--- a/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/PopupMediaControls.js.jsx.coffee
@@ -252,7 +252,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
if !selectedMixdown?
mixControls = `
-
Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.
+
Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.