diff --git a/admin/app/admin/affiliate_traffic_totals.rb b/admin/app/admin/affiliate_traffic_totals.rb new file mode 100644 index 000000000..2d1a3b391 --- /dev/null +++ b/admin/app/admin/affiliate_traffic_totals.rb @@ -0,0 +1,36 @@ +ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Stats' do + + menu :label => 'Daily Stats', :parent => 'Affiliates' + + config.sort_order = 'referral_user_count DESC' + config.batch_actions = false + config.clear_action_items! + config.filters = true + config.per_page = 50 + config.paginate = true + + filter :affiliate_partner + filter :day + filter :signups + filter :visits + + form :partial => 'form' + + scope("Active", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') } + + index do + + # default_actions # use this for all view/edit/delete links + + column 'Day' do |oo| oo.day end + column 'Partner' do |oo| link_to(oo.affiliate_partner, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end + column 'Signups' do |oo| oo.signups end + column 'Visits' do |oo| oo.visits end + + end + + + controller do + + end +end diff --git a/admin/app/admin/affiliate_users.rb b/admin/app/admin/affiliate_users.rb index b21d512eb..f500a2565 100644 --- a/admin/app/admin/affiliate_users.rb +++ b/admin/app/admin/affiliate_users.rb @@ -7,10 +7,10 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do config.filters = false index do - column 'User' do |oo| link_to(oo.name, "http://www.jamkazam.com/client#/profile/#{oo.id}", {:title => oo.name}) end + column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end column 'Email' do |oo| oo.email end column 'Created' do |oo| oo.created_at end - column 'Partner' do |oo| oo.affiliate_referral.partner_name end + column 'Partner' do |oo| oo.affiliate_referral.display_name end end controller do diff --git a/admin/app/admin/affiliates.rb b/admin/app/admin/affiliates.rb index 0409ee36c..db7bfa53d 100644 --- a/admin/app/admin/affiliates.rb +++ b/admin/app/admin/affiliates.rb @@ -6,10 +6,12 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do config.batch_actions = false # config.clear_action_items! config.filters = false + config.per_page = 50 + config.paginate = true form :partial => 'form' - scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL') } + scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') } scope("Unpaid") { |partner| partner.unpaid } index do diff --git a/admin/app/admin/user_progression.rb b/admin/app/admin/user_progression.rb index 6ea4bb285..aa72d8014 100644 --- a/admin/app/admin/user_progression.rb +++ b/admin/app/admin/user_progression.rb @@ -9,7 +9,7 @@ ActiveAdmin.register JamRuby::User, :as => 'User Progression' do config.filters = false index do - column :email do |user| link_to(truncate(user.email, {:length => 12}), resource_path(user), {:title => "#{user.first_name} #{user.last_name} (#{user.email})"}) end + column :email do |user| link_to(truncate(user.email, {:length => 12}), resource_path(user), {:title => "#{user.name} (#{user.email})"}) end column :updated_at do |uu| uu.updated_at.strftime(PROGRESSION_DATE) end column :created_at do |uu| uu.created_at.strftime(PROGRESSION_DATE) end column :city diff --git a/admin/app/views/email/dump_emailables.csv.erb b/admin/app/views/email/dump_emailables.csv.erb index efa44b0f8..f226e68fa 100644 --- a/admin/app/views/email/dump_emailables.csv.erb +++ b/admin/app/views/email/dump_emailables.csv.erb @@ -1,2 +1,2 @@ <%- headers = ['email', 'name', 'unsubscribe_token'] -%> -<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.first_name, user.unsubscribe_token]) %><%- end -%> \ No newline at end of file +<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.anonymous? ? '-' : user.first_name, user.unsubscribe_token]) %><%- end -%> \ No newline at end of file diff --git a/admin/config/application.rb b/admin/config/application.rb index eb5c1c612..be5ce3803 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -154,5 +154,8 @@ module JamAdmin config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep")) config.email_dump_code = 'rcAUyC3TZCbgGx4YQpznBRbNnQMXW5iKTzf9NSBfzMLsnw9dRQ' + + config.admin_port = ENV['ADMIN_PORT'] || 3333 + config.admin_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.admin_port == 80 || config.admin_port == 443) ? '' : ':' + config.admin_port.to_s}" end end diff --git a/db/manifest b/db/manifest index 42496dba9..82a23b66c 100755 --- a/db/manifest +++ b/db/manifest @@ -306,4 +306,5 @@ jam_track_slug.sql mixdown.sql aac_master.sql video_recording.sql +web_playable_jamtracks.sql jam_track_lang_idx.sql diff --git a/db/up/web_playable_jamtracks.sql b/db/up/web_playable_jamtracks.sql new file mode 100644 index 000000000..b3d6e8a4e --- /dev/null +++ b/db/up/web_playable_jamtracks.sql @@ -0,0 +1,16 @@ +ALTER TABLE users ADD COLUMN first_opened_jamtrack_web_player TIMESTAMP; +ALTER TABLE jam_track_rights ADD COLUMN last_stem_id VARCHAR(64) REFERENCES jam_track_tracks(id) ON DELETE SET NULL; +ALTER TABLE jam_track_tracks ADD COLUMN url_mp3_48 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN md5_mp3_48 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN length_mp3_48 BIGINT; +ALTER TABLE jam_track_tracks ADD COLUMN url_aac_48 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN md5_aac_48 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN length_aac_48 BIGINT; + +CREATE TABLE user_events ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL, + name VARCHAR(100) NOT NULL, + detail JSON, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 0d462a929..0f3e59b19 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -106,6 +106,7 @@ require "jam_ruby/models/max_mind_release" require "jam_ruby/models/genre_player" require "jam_ruby/models/genre" require "jam_ruby/models/user" +require "jam_ruby/models/user_event" require "jam_ruby/models/anonymous_user" require "jam_ruby/models/signup_hint" require "jam_ruby/models/machine_fingerprint" diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb index 6f760961e..a79e893e5 100644 --- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/confirm_email.html.erb @@ -1,5 +1,5 @@ <% provide(:title, 'Confirm Email') %> -

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.

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(``) + + `
+ +
+
+ Get Ready! +
+ +
+ + + + + + + +
+ +
+ +
+ +
+
+
+ + +
+
+ + +
+
+
+ +
{this.state.time}
+
+
+
+
0:00
+ +
0:00
+ +
+ + +
+
` + + + getInitialState: () -> + {controls: null, time: '0:00'} + + componentDidUpdate: (prevProps, prevState) -> + + componentDidMount: () -> + $root = jQuery(this.getDOMNode()) + controls = context.JK.PlaybackControls($root, {mediaActions: BrowserMediaPlaybackActions}) + controls.setDisabled(@props.disabled) + + @monitorControls(controls) + + @setState({controls:controls}) + + componentWillUpdate: (nextProps) -> + + @state.controls.setDisabled(nextProps.disabled) if @state.controls? +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee index eb2fa4198..63d5425c9 100644 --- a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee @@ -1,13 +1,17 @@ context = window MIX_MODES = context.JK.MIX_MODES +rest = context.JK.Rest() @JamTrackLandingScreen = React.createClass({ - mixins: [Reflux.listenTo(@AppStore,"onAppInit")] + mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")] getInitialState: () -> - {user: null} + {user: null, purchasedJamTracks: []} + + onUserChanged: (userState) -> + @onUser(userState.user) if userState.user render: () -> @@ -22,10 +26,31 @@ MIX_MODES = context.JK.MIX_MODES else howTo = `
- 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 `{jamTrack.name} by {jamTrack.original_artist}` + + 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 ` ` + + playableJamTracks = + `
+ + + + + + {playJamTracks} + +
JAMTRACKS
+
` `
@@ -33,6 +58,7 @@ MIX_MODES = context.JK.MIX_MODES

my jamtracks

{howTo} + {playableJamTracks}

search jamtracks

@@ -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:
- +
@@ -96,19 +127,50 @@ MIX_MODES = context.JK.MIX_MODES instrument = $root.find('select.instrument-list').val() context.JamTrackActions.requestFilter(genre, instrument) + processUrl: () -> + + if $.QueryString['redeemed_flow']? + @redeemedFlow = true + + if @redeemedFlow + + if window.history.replaceState #ie9 proofing + window.history.replaceState({}, "", "/client#/jamtrack") + + preparePlayJamTrackProd: () -> + setTimeout((() => + $element = $(this.getDOMNode()).find('.purchased-jam-tracks .play-jamtrack a').first() + $offsetParent = $element.closest('.screen') + + context.JK.HelpBubbleHelper.jamtrackWebPlay($element, $offsetParent) + ), 1500) + afterShow: (data) -> - if context.JK.currentUserId - @app.user().done(@onUser) - else + @processUrl() + + if !context.JK.currentUserId @onUser({free_jamtrack: context.JK.currentUserFreeJamTrack}) + + beforeShow: () -> @setState({user: null}) onUser:(user) -> @setState({user: user}) + rest.getPurchasedJamTracks({page:1, per_page:20}) + .done((purchasedJamTracks) => + if @redeemedFlow + @preparePlayJamTrackProd() + @redeemedFlow = false + @setState({purchasedJamTracks: purchasedJamTracks.jamtracks}) + ) + .fail((jqXHR, textStatus, errorMessage) => + @app.ajaxError(jqXHR, textStatus, errorMessage); + ) + # Get artist names and build links #@rest.getJamTrackArtists({group_artist: true, per_page:100}) #.done(this.buildArtistLinks) @@ -118,6 +180,24 @@ MIX_MODES = context.JK.MIX_MODES # artist_name #@bindArtistLinks() + customMixHelpClicked: (e) -> + e.preventDefault() + context.JK.popExternalLink($(e.target).attr('href')) + + downloadsClicked:(e) -> + e.preventDefault() + context.JK.popExternalLink($(e.target).attr('href')) + + jamTrackHelpClicked: (e) -> + e.preventDefault() + context.JK.popExternalLink($(e.target).attr('href')) + + onPlayJamTrack: (jamTrack, e) -> + e.preventDefault() + + # popup window + JamTrackPlayerActions.open(jamTrack) + onAppInit: (@app) -> @rest = context.JK.Rest() diff --git a/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee new file mode 100644 index 000000000..4687320f3 --- /dev/null +++ b/web/app/assets/javascripts/react-components/PopupJamTrackPlayer.js.jsx.coffee @@ -0,0 +1,750 @@ +context = window +logger = context.JK.logger +ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; +rest = context.JK.Rest() + +mixins = [] + + +# make sure this is actually us opening the window, not someone else (by checking for MixerStore) +# 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 + +AppActions = reactContext.AppActions +JamTrackPlayerActions = reactContext.JamTrackPlayerActions +JamTrackPlayerStore = reactContext.JamTrackPlayerStore + +mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged')) + + +@PopupJamTrackPlayer = React.createClass({ + + mixins: mixins + + + onJamTrackPlayerStoreChanged: (changes) -> + #logger.debug("PopupMediaControls: jamtrack changed", changes) + @setState({jamTrackState: changes}) + + showMetronome: (e) -> + e.preventDefault() + + SessionActions.showNativeMetronomeGui() + + getInitialState: () -> + state = {} + state.jamTrackState = JamTrackPlayerStore.getState() + state.showCustomMixes = true + return state + + close: () -> + window.close() + + help: (e) -> + e.preventDefault() + + AppActions.openExternalUrl($(e.target).attr('href')) + + render: () -> + + closeLinkText = null + header = null + extraControls = null + + jamTrack = @state.jamTrackState?.jamTrack + mediaType = "JamTrack" + mediaName = jamTrack?.name + closeLinkText = 'CLOSE JAMTRACK' + helpLink = 'https://jamkazam.desk.com/customer/portal/articles/2138903-using-custom-mixes-to-slow-tempo-change-pitch' + + selectedMixdown = jamTrack?.activeMixdown + selectedStem = jamTrack?.activeStem + + if selectedMixdown? + jamTrackTypeHeader = 'Custom Mix' + + disabled = true + if selectedMixdown.client_state? + switch selectedMixdown.client_state + when 'cant_open' + customMixName = `
{selectedMixdown.name}
` + when 'keying_timeout' + customMixName = `
{selectedMixdown.name}
` + when 'download_fail' + customMixName = `
{selectedMixdown.name}
` + when 'keying' + customMixName = `
Loading selected mix...
` + when 'downloading' + customMixName = `
Loading selected mix...
` + when 'ready' + customMixName = `
{selectedMixdown.name}
` + disabled = false + else + if selectedMixdown.myPackage + customMixName = `
Creating mixdown...
` + else + customMixName = `
{selectedMixdown.name}
` + + else if selectedStem? + if selectedStem.instrument + instrumentId = selectedStem.instrument.id + instrumentDescription = selectedStem.instrument.description + part = "(#{selectedStem.part})" if selectedStem.part? && selectedStem.part != instrumentDescription + part = '' unless part + + jamTrackTypeHeader = 'Track' + + trackName = "#{instrumentDescription} #{part}" + + disabled = true + if selectedStem.client_state? + switch selectedStem.client_state + when 'downloading' + customMixName = `
Loading {trackName}...
` + when 'ready' + customMixName = `
{trackName}
` + disabled = false + else + customMixName = `
{trackName}
` + + else + if jamTrack?.client_state == 'downloading' + downloader = `` + + jamTrackTypeHeader = `Full JamTrack {downloader}` + + header = ` +
+

{mediaType}: {mediaName}

+

{jamTrackTypeHeader}

+ {customMixName} +
` + + myMixes = null + if @state.showMyMixes + myMixdowns = [] + + boundPlayClick = this.jamTrackPlay.bind(this, jamTrack); + boundDownloadClick = this.jamTrackDownload.bind(this, jamTrack); + + active = jamTrack.last_mixdown_id == null && jamTrack.last_stem_id == null + + myMixdowns.push ` +
+
+ Full JamTrack +
+
+ + + + +
+
` + + for mixdown in jamTrack.mixdowns + boundPlayClick = this.mixdownPlay.bind(this, mixdown); + boundEditClick = this.mixdownEdit.bind(this, mixdown); + boundSaveClick = this.mixdownSave.bind(this, mixdown); + boundDeleteClick = this.mixdownDelete.bind(this, mixdown); + boundErrorClick = this.mixdownError.bind(this, mixdown); + boundEditKeydown = this.onEditKeydown.bind(this, mixdown); + + boundDownloadNotReadyClick = this.downloadNotReady.bind(this, mixdown) + boundDownloadReadyClick = this.downloadMixdownReady.bind(this, mixdown) + + mixdown_package = mixdown.myPackage + + active = mixdown.id == jamTrack.last_mixdown_id + + editing = mixdown.id == @state.editingMixdownId + + # if there is a package, check it's state; otherwise let the user enqueue it + if mixdown_package + switch mixdown_package.signing_state + when 'QUIET_TIMEOUT' + action = `` + when 'QUIET' + action = `` + when 'QUEUED' + action = `` + when 'QUEUED_TIMEOUT' + action = `` + when 'SIGNING' + action = `` + when 'SIGNING_TIMEOUT' + action = `` + when 'SIGNED' + action = `` + when 'ERROR' + action = `` + else + action = `` + + if editing + mixdownName = `` + editIcon = `` + else + mixdownName = mixdown.name + editIcon = `` + + # create hidden objects to deal with alginment issues using a table + if !editIcon + editIcon = `` + + download = `` + + myMixdowns.push ` +
+
+ {mixdownName} +
+
+ {action} + {download} + {editIcon} + + +
+
` + + active = jamTrack.last_stem_id? + trackOptions = [] + for track in jamTrack.tracks + if track.track_type == 'Track' + + + if track.instrument + instrumentId = track.instrument.id + instrumentDescription = track.instrument.description + if track.part? && track.part != instrumentDescription + part = "(#{track.part})" + else + part = '' + trackOptions.push(``) + + boundStemActivateClick = this.activateStem.bind(this) + boundStemPlayClick = this.downloadStem.bind(this) + boundStemChange = this.stemChanged.bind(this) + + action = `` + download = `` + + myMixdowns.push ` +
+
+ +
+
+ {action} + {download} + + +
+
` + + myMixes = `
{myMixdowns}
` + + mixControls = null + + if @state.showCustomMixes + + tracks = [] + if jamTrack? + for track in jamTrack.tracks + if track.track_type == 'Track' + if track.instrument + instrumentId = track.instrument.id + instrumentDescription = track.instrument.description + if track.part? && track.part != instrumentDescription + part = "(#{track.part})" + else + part = '' + + + tracks.push(` + + {instrumentDescription} {part} + + `) + + stems = `
+ + + + + + + {tracks} + +
TRACKSmute
+
` + + + nameClassData = {field: true} + if @state.createMixdownErrors? + + errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'}) + + createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown}) + + mixControls = ` +
+

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.

+ {stems} +
+ + +
+
+ + +
+
+ + +
+
+ CREATE MIX + {errorHtml} +
+
+ +
` + + + if @state.showMyMixes + showMyMixesText = `hide my mixes
` + else + showMyMixesText = `show my mixes
` + + if @state.showCustomMixes + showMixControlsText = `hide mix controls
` + else + showMixControlsText = `show mix controls
` + + extraControls = ` +
+

My Mixes {showMyMixesText}

+ + {myMixes} + +

Create Custom Mix {showMixControlsText}

+ + {mixControls} + +
` + + + + if helpLink? + helpButton = `HELP` + + `
+ {header} + + {extraControls} +
+ {helpButton} + {closeLinkText} +
+
` + + 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.

+ +
+ +
+ + {loggedOutPriceAdvisory} + + + or browse our catalog of 3,700+ songs +
` + + + `
+
+
+ screenshot of app +

{this.props.jam_track.name.toUpperCase()}

+

by {this.props.jam_track.original_artist.toUpperCase()}

+
+
+
+ + +
+ Preview JamTrack +
+
+

Click the play buttons below to preview the master mix and 20-second samples of all the isolated tracks.

+
+ +
+ {loggedInCtaButton} + {loggedInPriceAdvisory} +
+ + {register} +
+
+
+

+ JamTracks by JamKazam are the best way to play along with your favorite songs. Far better and different than traditional + backing tracks, our JamTracks are complete multi-track professional recordings, with fully isolated tracks for each part of the music. + And our free app and Internet service are packed with features that give you unmatched creative freedom to learn, practice, record, play with others, and share your performances. +

+
+
` + + getInitialState: () -> + {loginErrors: null, processing:false} + + privacyPolicy: (e) -> + e.preventDefault() + + context.JK.popExternalLink('/corp/privacy') + + termsClicked: (e) -> + e.preventDefault() + + context.JK.popExternalLink('/corp/terms') + + componentDidMount:() -> + $root = $(this.getDOMNode()) + $checkbox = $root.find('.terms-checkbox') + console.log("$checkbox", $checkbox) + context.JK.checkbox($checkbox) + + # add item to cart, create the user if necessary, and then place the order to get the free JamTrack. + ctaClick: (e) -> + e.preventDefault() + + return if @state.processing + + @setState({loginErrors: null}) + + loggedIn = context.JK.currentUserId? + + isFree = context.JK.currentUserFreeJamTrack + + + rest.addJamtrackToShoppingCart({id: @props.jam_track.id}).done((response) => + if isFree + if loggedIn + context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices + @setState({done: true}) + context.location = '/client?redeemed_flow=1#/jamtrack' + else + @createUser() + else + if loggedIn + @setState({done: true}) + context.location = '/client#/shoppingCart' + else + @createUser(true) + + ).fail((jqXHR, textStatus, errorMessage) => + if jqXHR.status == 422 + errors = JSON.parse(jqXHR.responseText) + cart_errors = errors?.errors?.cart_id + if cart_errors?.length == 1 && cart_errors[0] == 'has already been taken' + if loggedIn + @setState({done: true}) + context.location = '/client#/shoppingCart' + else + @createUser(true) + else + context.JK.app.ajaxError(jqXHR, textStatus, errorMessage) + @setState({processing:false}) + ) + + createUser: (redirectToShoppingCart) -> + $form = $('.jamtrack-signup-form') + email = $form.find('input[name="email"]').val() + password = $form.find('input[name="password"]').val() + terms = $form.find('input[name="terms"]').is(':checked') + + rest.signup({email: email, password: password, first_name: null, last_name: null, terms:terms}) + .done((response) => + + if redirectToShoppingCart + @setState({done: true}) + context.location = '/client#/shoppingCart' + return + + rest.placeOrder() + .done((response) => + this.setState({done: true}) + context.JK.Tracking.redeemCompleteTrack() + window.location = '/client?redeemed_flow=1#/jamtrack' + ) + .fail((jqXHR) => + logger.error("unable to place an older after creating the user") + window.reload() + ) + + ).fail((jqXHR) => + @setState({processing:false}) + if jqXHR.status == 422 + response = JSON.parse(jqXHR.responseText) + if response.errors + @setState({loginErrors: response.errors}) + else + context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText}) + else + context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up") + ) + + + @setState({processing:true}) + +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/BrowserMediaPlaybackStore.js.coffee b/web/app/assets/javascripts/react-components/stores/BrowserMediaPlaybackStore.js.coffee new file mode 100644 index 000000000..6c085aea1 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/BrowserMediaPlaybackStore.js.coffee @@ -0,0 +1,84 @@ +$ = jQuery +context = window +logger = context.JK.logger +PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE +RecordingActions = @RecordingActions +BrowserMediaActions = @BrowserMediaActions + +@BrowserMediaPlaybackStore = Reflux.createStore( + { + listenables: @BrowserMediaPlaybackActions + + playbackStateChanged: false + positionUpdateChanged: false + currentTimeChanged: false + playbackState: null + positionMs: 0 + durationMs: 0 + isRecording: false + sessionHelper: null + + init: () -> + this.listenTo(context.BrowserMediaStore, this.onBrowserMediaChange); + + onBrowserMediaChange: (changes) -> + @onPositionUpdate(PLAYBACK_MONITOR_MODE.BROWSER_MEDIA) + + onCurrentTimeChanged: (time) -> + @time = time + @currentTimeChanged = true + @issueChange() + + onMediaStartPlay: (data) -> + BrowserMediaActions.play() + + onMediaStopPlay: (data) -> + + if !data.endReached + BrowserMediaActions.stop() + + onMediaPausePlay: (data) -> + # if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording + if !data.endReached + BrowserMediaActions.pause() + + onMediaChangePosition: (data) -> + seek = data.positionMs; + + BrowserMediaActions.seek(seek); + + issueChange: () -> + + @state = + playbackState: @playbackState + playbackStateChanged: @playbackStateChanged + positionUpdateChanged: @positionUpdateChanged + currentTimeChanged: @currentTimeChanged + positionMs: @positionMs + durationMs: @durationMs + isPlaying: @isPlaying + time: @time + + this.trigger(@state) + @playbackStateChanged = false + @positionUpdateChanged = false + @currentTimeChanged = false + + onPlaybackStateChange: (text) -> + @playbackState = text + @playbackStateChanged = true + + @issueChange() + + onPositionUpdate: (playbackMode) -> + if playbackMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA + @positionMs = BrowserMediaStore.onGetPlayPosition() || 0 + @durationMs = BrowserMediaStore.onGetPlayDuration() || 0; + @isPlaying = BrowserMediaStore.playing; + else + raise 'Only BROWSER_MEDIA is supported' + @positionUpdateChanged = true + @issueChange() + + } +) diff --git a/web/app/assets/javascripts/react-components/stores/BrowserMediaStore.js.coffee b/web/app/assets/javascripts/react-components/stores/BrowserMediaStore.js.coffee new file mode 100644 index 000000000..0eec9aaa0 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/BrowserMediaStore.js.coffee @@ -0,0 +1,159 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() +EVENTS = context.JK.EVENTS + +BrowserMediaActions = @BrowserMediaActions + +@BrowserMediaStore = Reflux.createStore( + { + listenables: BrowserMediaActions + audio: null + loaded: false + loading: false + playing: false + paused: false + id: null + media_type: null + + playbackState:(position, time) -> + state = {} + if @playing + state.playbackState = 'play_start' + else if @paused + state.playbackState = 'play_pause' + else + state.playbackState = 'play_stop' + + state.playbackStateChanged = !@state? || @state.isPlaying != @playing || @state.paused != @paused + state.positionUpdateChanged = !@state? || @state.positionMs != position + state.currentTimeChanged = !@state? || @state.time != time + + state + + changed: () -> + position = @onGetPlayPosition() + time = context.JK.prettyPrintSeconds(parseInt(position / 1000)) + playbackState = @playbackState(position, time) + + # XXX: how to deal with duration? no mention in Howler API + + target = {id: @id, isPlaying: @playing, loaded: @loaded, paused: @paused, positionMs: position, time: time, durationMs: @onGetPlayDuration()} + $.extend(true, target, playbackState) + @state = target + @trigger(@state) + + onLoad: (id, urls, media_type) -> + if @audio + @audio.stop() + @audio.unload() + @loaded = false + @loading = false + @playing = false + @paused = false + + @id = id + @media_type = media_type + @loading = true + @playing = false + @paused = false + + console.log("URLS", urls) + @audio = new Howl({ + src: urls, + autoplay: false, + loop: false, + volume: 1, + preload: true, + onend: @onAudioEnded, + onload: @onAudioLoaded, + onpause: @onAudioPause, + onplay: @onAudioPlay + }) + + @changed() + + onPlay: () -> + if @audio + @playing = true + @paused = false + @audio.play() + + try + if !@audio.recorded_play + rest.postUserEvent({name: @media_type + '_play'}) if @media_type? + context.stats.write('web.' + @media_type + '.play', { + value: 1, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName + }) if @media_type + + @audio.recorded_play = true + catch e + logger.warn("BrowserMediaStore: unable to post user event") + + onPause: () -> + if @audio + @playing = false + @paused = true + @audio.pause() + + onStop: () -> + if @audio + @playing = false + @paused = false + @audio.pause() + @audio.seek(0) + + onSeek: (pos) -> + if @audio + console.log("seek time", pos) + @audio.seek(pos / 1000) + + onGetPlayPosition: () -> + if @audio + try + position = @audio.seek() + if position == @audio + return 0 + position * 1000 + catch e + return 0 + else + 0 + + onGetPlayDuration: () -> + if @audio + # XXX : how to determine duration? + try + duration = @audio.duration() + return Math.round(duration) * 1000 + catch e + return 0 + else + 0 + + onAudioEnded: () -> + logger.debug("onAudioEnded", this, arguments) + @playing = false + @changed() + + onAudioLoaded: () -> + logger.debug("onAudioLoaded") + @loaded = true + @changed() + + onAudioPause: () -> + logger.debug("onAudioPause") + + @changed() + + onAudioPlay: () -> + logger.debug("onAudioPlay") + @playing = true + @paused = false + @changed() + + } +) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/JamTrackPlayerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/JamTrackPlayerStore.js.coffee new file mode 100644 index 000000000..7f98b57f4 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/JamTrackPlayerStore.js.coffee @@ -0,0 +1,454 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() +EVENTS = context.JK.EVENTS + +JamTrackPlayerActions = @JamTrackPlayerActions +BrowserMediaStore = @BrowserMediaStore +BrowserMediaActions = @BrowserMediaActions + +@JamTrackPlayerStore = Reflux.createStore( + { + listenables: JamTrackPlayerActions + jamTrack: null + previous: null + requestedSearch: null + requestedFilter: null + subscriptions: {} + enqueuedMixdowns: {} + childWindow: null + watchedMixdowns: {} + + init: -> + # Register with the app store to get @app + this.listenTo(context.AppStore, this.onAppInit) + this.listenTo(context.BrowserMediaStore, this.onBrowserMediaChanged) + + onAppInit: (app) -> + @app = app + + onBrowserMediaChanged: (browserMediaState) -> + @browserMediaState = browserMediaState + + @changed() + + getState: () -> + @state + + onOpen: (jamTrack, popup=true) -> + + + if jamTrack.id == @jamTrack?.id && @childWindow? + logger.info("JamTrackPlayerStore: request to open already-opened JamTrack; ignoring") + return + + @enqueuedMixdowns = {} + @jamTrack = jamTrack + + sampleRate = 48 + @sampleRate = if sampleRate == 48 then 48 else 44 + + + if popup + if @childWindow? + @childWindow.close() + + logger.debug("opening JamTrackPlayer window") + @childWindow = window.open("/popups/jamtrack-player/" + @jamTrack.id, 'Media Controls', 'scrollbars=yes,toolbar=no,status=no,height=580,width=450') + + @changed() + + onWindowUnloaded: () -> + BrowserMediaActions.stop() + @childWindow = null + + pickMyPackage: () -> + + return unless @jamTrack? + + for mixdown in @jamTrack.mixdowns + + myPackage = null + for mixdown_package in mixdown.packages + if mixdown_package.file_type == 'mp3' && mixdown_package.encrypt_type == null && mixdown_package.sample_rate == @sampleRate + myPackage = mixdown_package + break + + mixdown.myPackage = myPackage + + subscriptionKey: (mixdown_package) -> + "mixdown-#{mixdown_package.id}" + + subscribe: (mixdown_package) -> + key = @subscriptionKey(mixdown_package) + + if !@watchedMixdowns[key]? + # we need to register + context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onMixdownSubscriptionEvent) + @watchedMixdowns[key] = {type:'mixdown', id: mixdown_package.id} + + unsubscribe: (mixdown_package) -> + key = @subscriptionKey(mixdown_package) + if @watchedMixdowns[key]? + context.JK.SubscriptionUtils.unsubscribe('mixdown', mixdown_package.id) + delete @watchedMixdowns[key] + + manageWatchedMixdowns: () -> + + if @jamTrack? + for mixdown in @jamTrack.mixdowns + if mixdown.myPackage + if mixdown.myPackage.signing_state == 'SIGNED' + @unsubscribe(mixdown.myPackage) + else + @subscribe(mixdown.myPackage) + + else + for key, subscription of @watchedMixdowns + logger.debug("unsubscribing bulk", key, subscription) + context.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id) + + # we cleared them all out; clear out storage + @watchedMixdowns = {} + + onMixdownSubscriptionEvent: (e, data) -> + logger.debug("JamTrackStore: subscription notification received: type:" + data.type, data) + + return unless @jamTrack? + + mixdown_package_id = data.id + + for mixdown in @jamTrack.mixdowns + for mixdown_package in mixdown.packages + if mixdown_package.id == mixdown_package_id + mixdown_package.signing_state = data.body.signing_state + mixdown_package.packaging_steps = data.body.packaging_steps + mixdown_package.current_packaging_step = data.body.current_packaging_step + logger.debug("updated package with subscription notification event") + + if mixdown_package.signing_state == 'SIGNING_TIMEOUT' || mixdown_package.signing_state == 'QUEUED_TIMEOUT' || mixdown_package.signing_state == 'QUIET_TIMEOUT' || mixdown_package.signing_state == 'ERROR' + @reportError(mixdown) + + @changed() + break + + # this drives the state engine required to get a Mixdown from 'available on the server' to + manageMixdownSynchronization: () -> + + + if @jamTrack + @jamTrack.activeMixdown = null + @jamTrack.activeStem = null + + # let's see if we have a mixdown active? + + #if !@jamTrack?.last_mixdown_id? + # logger.debug("JamTrackStore: no mixdown active") + + for mixdown in @jamTrack.mixdowns + if mixdown.id == @jamTrack.last_mixdown_id + @jamTrack.activeMixdown = mixdown + #logger.debug("JamTrackStore: mixdown active:", mixdown) + break + + for stem in @jamTrack.tracks + if stem.id == @jamTrack.last_stem_id + @jamTrack.activeStem = stem + break + + # let's check and see if we've asked the BrowserMediaStore to load this particular file or not + + if @jamTrack?.activeStem + + if @browserMediaState?.id != @jamTrack.activeStem.id + BrowserMediaActions.load(@jamTrack.activeStem.id, [window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{@jamTrack.id}/stems/#{@jamTrack.activeStem.id}/download.mp3?file_type=mp3"], 'jamtrack_web_player') + @jamTrack.activeStem.client_state = 'downloading' + else + if @browserMediaState.loaded + @jamTrack.activeStem.client_state = 'ready' + else + @jamTrack.activeStem.client_state = 'downloading' + + + else if @jamTrack?.activeMixdown + + # if we don't have this on the server yet, don't engage the rest of this logic... + return if @jamTrack.activeMixdown?.myPackage?.signing_state != 'SIGNED' + + activePackage = @jamTrack.activeMixdown.myPackage + + if activePackage? + if @browserMediaState?.id != activePackage.id + BrowserMediaActions.load(activePackage.id, [window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{@jamTrack.activeMixdown.id}/download.mp3?file_type=mp3&sample_rate=48"], 'jamtrack_web_player') + @jamTrack.activeMixdown.client_state = 'downloading' + else + if @browserMediaState.loaded + @jamTrack.activeMixdown.client_state = 'ready' + else + @jamTrack.activeMixdown.client_state = 'downloading' + + else if @jamTrack? + + masterTrack = null + for jamTrackTrack in @jamTrack.tracks + if jamTrackTrack.track_type == 'Master' + masterTrack = jamTrackTrack + break + + if @browserMediaState?.id != @jamTrack.id + BrowserMediaActions.load(@jamTrack.id, [masterTrack.preview_mp3_url], 'jamtrack_web_player') + @jamTrack.client_state = 'downloading' + else + if @browserMediaState.loaded + @jamTrack.client_state = 'ready' + else + @jamTrack.client_state = 'downloading' + else + logger.error("unknown condition when processing manageMixdownSynchronization") + + + changed: () -> + + @pickMyPackage() + @manageWatchedMixdowns() + @manageMixdownSynchronization() + + @state = { + jamTrack: @jamTrack, + opened: @previous == null && @jamTrack != null, + closed: @previous != null && @jamTrack == null, + fullTrackActivated: @previousMixdown != null && @jamTrack?.activeMixdown == null} + @previous = @jamTrack + @previousMixdown = @jamTrack?.activeMixdown + this.trigger(@state) + + onCreateMixdown: (mixdown, done, fail) -> + + #volumeSettings = context.jamClient.GetJamTrackSettings(); + + #track_settings = [] + + #for track in volumeSettings.tracks + # track_settings.push({id: track.id, pan: track.pan, vol: track.vol_l, mute: track.mute}) + + #mixdown.settings.tracks = track_settings + + logger.debug("creating mixdown", mixdown) + + rest.createMixdown(mixdown) + .done((created) => + + @addMixdown(created) + + logger.debug("created mixdown", created) + + @onEnqueueMixdown({id: created.id}, done, fail) + ) + .fail((jqxhr) => + fail(jqxhr) + ) + + + onEditMixdown: (mixdown) -> + logger.debug("editing mixdown", mixdown) + + rest.editMixdown(mixdown) + .done((updatedMixdown) => + logger.debug("edited mixdown") + @updateMixdown(updatedMixdown) + ).fail((jqxhr) => + @app.layout.notify({title:'Unable to Edit Custom Mix', text: 'The server was unable to edit this mix.'}) + ) + + onDeleteMixdown: (mixdown) -> + logger.debug("deleting mixdown", mixdown) + + rest.deleteMixdown(mixdown) + .done(() => + logger.debug("deleted mixdown") + + @deleteMixdown(mixdown) + ) + .fail((jqxhr) => + @app.layout.notify({title:'Unable to Deleted Custom Mix', text: 'The server was unable to delete this mix.'}) + ) + + stopPlaying: () -> + alert("stop playing") + + onOpenMixdown: (mixdown) -> + logger.debug("opening mixdown", mixdown) + + # check if it's already available in the backend or not + rest.markMixdownActive({id: @jamTrack.id, mixdown_id: mixdown.id}) + .done((edited) => + logger.debug("marked mixdown as active") + @jamTrack = edited + + BrowserMediaActions.stop() + + @changed() + ) + .fail((jqxhr) => + @app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'}) + ) + + onOpenStem: (stem_id) -> + logger.debug("opening stem", stem_id) + + # check if it's already available in the backend or not + rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null, stem_id: stem_id}) + .done((edited) => + logger.debug("marked stem as active") + @jamTrack = edited + + BrowserMediaActions.stop() + + @changed() + ) + .fail((jqxhr) => + @app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'}) + ) + + onActivateNoMixdown: (jamTrack) -> + logger.debug("activating no mixdown") + + rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null}) + .done((edited) => + logger.debug("marked JamTrack as active") + + @jamTrack = edited + @changed() + ) + .fail((jqxhr) => + @app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'}) + ) + + + onCloseMixdown: (mixdown) -> + logger.debug("closing mixdown", mixdown) + + onEnqueueMixdown: (mixdown, done, fail) -> + logger.debug("enqueuing mixdown", mixdown) + + package_settings = {file_type: 'mp3', encrypt_type: null, sample_rate: @sampleRate} + package_settings.id = mixdown.id + + rest.enqueueMixdown(package_settings) + .done((enqueued) => + + @enqueuedMixdowns[mixdown.id] = {} + + logger.debug("enqueued mixdown package", package_settings) + @addOrUpdatePackage(enqueued) + done(enqueued) if done + ) + .fail((jqxhr) => + @app.layout.notify({title:'Unable to Create Custom Mix', text: 'Click the error icon to retry.'}) + fail(jqxhr) if fail? + ) + + onDownloadMixdown: (mixdown) -> + logger.debug("download mixdown", mixdown) + + onRefreshMixdown: (mixdown) -> + logger.debug("refresh mixdown", mixdown) + + addMixdown: (mixdown) -> + if @jamTrack? + logger.debug("adding mixdown to JamTrackStore", mixdown) + @jamTrack.mixdowns.splice(0, 0, mixdown) + @changed() + else + logger.warn("no jamtrack to add mixdown to in JamTrackStore", mixdown) + + deleteMixdown: (mixdown) -> + if @jamTrack? + logger.debug("deleting mixdown from JamTrackStore", mixdown) + index = null + for matchMixdown, i in @jamTrack.mixdowns + if mixdown.id == matchMixdown.id + index = i + if index? + @jamTrack.mixdowns.splice(index, 1) + + if @jamTrack.activeMixdown?.id == mixdown.id + @onActivateNoMixdown(@jamTrack) + + @changed() + else + logger.warn("unable to find mixdown to delete in JamTrackStore", mixdown) + else + logger.warn("no jamtrack to delete mixdown for in JamTrackStore", mixdown) + + updateMixdown: (mixdown) -> + if @jamTrack? + logger.debug("editing mixdown from JamTrackStore", mixdown) + index = null + for matchMixdown, i in @jamTrack.mixdowns + if mixdown.id == matchMixdown.id + index = i + if index? + @jamTrack.mixdowns[index] = mixdown + + @changed() + else + logger.warn("unable to find mixdown to edit in JamTrackStore", mixdown) + else + logger.warn("no jamtrack to edit mixdown for in JamTrackStore", mixdown) + + addOrUpdatePackage: (mixdown_package) -> + if @jamTrack? + added = false + index = null + for mixdown in @jamTrack.mixdowns + existing = false + if mixdown_package.jam_track_mixdown_id == mixdown.id + for possiblePackage, i in mixdown.packages + if possiblePackage.id == mixdown_package.id + existing = true + index = i + break + + if existing + mixdown.packages[index] = mixdown_package + logger.debug("replacing mixdown package in JamTrackStore", mixdown_package) + else + mixdown.packages.splice(0, 0, mixdown_package) + logger.debug("adding mixdown package in JamTrackStore") + + added = true + @changed() + break + + if !added + logger.debug("couldn't find the mixdown associated with package in JamTrackStore", mixdown_package) + else + logger.warn("no mixdown to add package to in JamTrackStore", mixdown_package) + + + + reportError: (mixdown) -> + + enqueued = @enqueuedMixdowns[mixdown?.id] + + # don't double-report + if !enqueued? || enqueued.marked + return + + enqueued.marked = true + data = { + value: 1, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName, + result: "signing state: #{mixdown.myPackage?.signing_state}, client state: #{mixdown.client_state}", + mixdown: mixdown.id, + package: mixdown.myPackage?.id + detail: mixdown.myPackage?.error_reason + } + rest.createAlert("Mixdown Sync failed for #{context.JK.currentUserName}", data) + + context.stats.write('web.mixdown.error', data) + } +) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee b/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee new file mode 100644 index 000000000..133c3f87c --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/UserStore.js.coffee @@ -0,0 +1,18 @@ +$ = jQuery +context = window +logger = context.JK.logger + +@UserStore = Reflux.createStore( + { + user: null + + listenables: @UserActions + + onLoaded:(user) -> + @user = user + @changed() + + changed:() -> + @trigger({user: @user}) + } +) diff --git a/web/app/assets/javascripts/redeem_complete.js b/web/app/assets/javascripts/redeem_complete.js index 1300e97f4..c04289d4c 100644 --- a/web/app/assets/javascripts/redeem_complete.js +++ b/web/app/assets/javascripts/redeem_complete.js @@ -35,6 +35,9 @@ } function afterShow(data) { + + context.JK.Tracking.redeemCompleteTrack() + $noPurchasesPrompt.addClass('hidden') $purchasedJamTracks.empty() $thanksPanel.addClass("hidden") @@ -68,6 +71,7 @@ rest.placeOrder() .done(function(purchaseResponse) { context.JK.currentUserFreeJamTrack = false // make sure the user sees no more free notices without having to do a full page refresh + rest.updateUser() checkoutUtils.setLastPurchase(purchaseResponse) jamTrackUtils.checkShoppingCart() diff --git a/web/app/assets/javascripts/search.js b/web/app/assets/javascripts/search.js index 04e35767b..2f9edec08 100644 --- a/web/app/assets/javascripts/search.js +++ b/web/app/assets/javascripts/search.js @@ -110,7 +110,7 @@ function getItemHtml(item) { var replacements = { id: item.id, - name: item.first_name + " " + item.last_name, + name: item.name, image: item.photo_url, subtext: item.location }; diff --git a/web/app/assets/javascripts/user_dropdown.js b/web/app/assets/javascripts/user_dropdown.js index 342e3dc7e..5264c0d3f 100644 --- a/web/app/assets/javascripts/user_dropdown.js +++ b/web/app/assets/javascripts/user_dropdown.js @@ -81,7 +81,13 @@ } function updateHeader() { - $('#user').html(userMe.name); + if (userMe.first_name =='Anonymous' && userMe.last_name == 'Anonymous') { + $('#user').html(userMe.email); + } + else { + $('#user').html(userMe.name); + } + showAvatar(); } diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 5dfcbde51..68aa5b692 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -799,7 +799,7 @@ // * Unix context.JK.GetOSAsString = function() { if(!os) { - if(context.jamClient.IsNativeClient()) { + if(context.jamClient && context.jamClient.IsNativeClient()) { os = context.jamClient.GetOSAsString(); } else { @@ -868,6 +868,25 @@ } } + context.JK.getFullFirstError = function(fieldName, errors, field_mapper) { + + if (errors == null) return null; + + if (errors[fieldName] && errors[fieldName].length > 0) { + var displayField = fieldName + if (field_mapper) { + displayField = field_mapper[fieldName] + if (!displayField) { + // in case mapper has no data for this field + displayField = fieldName; + } + } + return displayField + ' ' + errors[fieldName][0] + } + else { + return null; + } + } /** * Returns a ul with an li per field name. * @param fieldName the name of the field diff --git a/web/app/assets/javascripts/web/individual_jamtrack.js b/web/app/assets/javascripts/web/individual_jamtrack.js index fc20baa68..94fba4ebd 100644 --- a/web/app/assets/javascripts/web/individual_jamtrack.js +++ b/web/app/assets/javascripts/web/individual_jamtrack.js @@ -3,7 +3,7 @@ "use strict"; context.JK = context.JK || {}; - context.JK.IndividualJamTrack = function (app) { + context.JK.IndividualJamTrack = function (app, dontPoke) { var rest = context.JK.Rest(); var logger = context.JK.logger; @@ -47,7 +47,7 @@ new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: true, color:'black', master_adds_line_break: false, preload_master:true}) - if(track.track_type =='Master') { + if(track.track_type =='Master' && !dontPoke) { context.JK.HelpBubbleHelper.rotateJamTrackLandingBubbles($element.find('.jam-track-preview'), $page.find('.one_by_two .watch-video'), $page.find('.checkout')); } }) diff --git a/web/app/assets/javascripts/web/tracking.js.coffee b/web/app/assets/javascripts/web/tracking.js.coffee index 8186066de..cb912a684 100644 --- a/web/app/assets/javascripts/web/tracking.js.coffee +++ b/web/app/assets/javascripts/web/tracking.js.coffee @@ -74,6 +74,9 @@ class Tracking @logger.debug("existing new recorded") context.JK.GA.virtualPageView('/client#/redeemSignup/new-user') + redeemCompleteTrack: () -> + context.JK.GA.virtualPageView('/client#/redeemed-successful') + context.JK.Tracking = new Tracking() diff --git a/web/app/assets/stylesheets/client/help.css.scss b/web/app/assets/stylesheets/client/help.css.scss index 1bd085fa4..7082d7d27 100644 --- a/web/app/assets/stylesheets/client/help.css.scss +++ b/web/app/assets/stylesheets/client/help.css.scss @@ -52,8 +52,17 @@ body.jam, body.web, .dialog{ font-size:20px; color: #ed3618; p {color:#ed3618} + + &.jamtrack-web-play { + p { + font-size:20px; + } + } } + + + .help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks { font-size:12px; diff --git a/web/app/assets/stylesheets/client/jamtrack_landing.css.scss b/web/app/assets/stylesheets/client/jamtrack_landing.css.scss index 37a0a3049..4090b3618 100644 --- a/web/app/assets/stylesheets/client/jamtrack_landing.css.scss +++ b/web/app/assets/stylesheets/client/jamtrack_landing.css.scss @@ -29,6 +29,31 @@ display:inline-block; } + .purchased-jam-tracks { + height:167px; + overflow:auto; + margin-top:20px; + } + + .jamtable { + + a { + text-decoration: underline !important; + color:#fc0 !important; + } + th { + font-size:14px; + padding:3px 10px; + } + td { + padding:4px 15px; + font-size:14px; + } + tbody { + + } + } + .search-area { .easydropdown-wrapper{ max-width:350px; diff --git a/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss index b9c28d460..c54f8bd2f 100644 --- a/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss +++ b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss @@ -1,81 +1,444 @@ -body.web.landing_jamtrack.individual_jamtrack { +@import "client/common"; - .landing-content { - .header { - text-align:center; +body.web.individual_jamtrack { + + $copy-color-on-dark: #b9b9b9; + $copy-color-on-white: #575757; + $cta-color: #e03d04; + $chunkyBorderWidth: 6px; + + .full-row { + width: 100%; + } + + .row { + width: 663px; + } + + .name-and-artist { + padding-top: 60px; + } + + .summary-text { + + p { + color: $copy-color-on-dark; + clear: both; + font-size: 20px; + line-height: 175%; + } + } + + .row.awesome { + h2 { + color: $copy-color-on-white; + font-size: 30px; + margin-bottom: 20px; } p { - width:100%; + color: $copy-color-on-white; + font-size: 18px; + line-height: 175%; } - h1 { - font-size:24px; + padding: 40px 0; + } + + .awesome-thing { + color: $copy-color-on-white; + margin-bottom: 20px; + + .awesome-item { + padding: 24px; + background-color: #dedede; + border-radius: 12px; + @include border_box_sizing; + width: 663px; } + + .video-wrapper { + float: right; + margin-left: 10px; + margin-bottom:80px; + + .cta-text { + margin-left: 10px; + text-align:center; + margin-top:20px; + } + &.left { + float: left; + margin-left: 0; + margin-right: 10px; + + .cta-text { + margin-left: 0; + margin-right: 10px; + } + } + } + + .video-container { + width: 420px; + padding-bottom: 53.33%; + + } + .awesome-image { + float: right; + margin-left: 10px; + + &.left { + float: left; + margin-left: 0; + margin-right: 10px; + } + } + + .awesome-number { + height: 44px; + width: 44px; + display: inline-block; + background-color: $cta-color; + color: white; + font-size: 24px; + font-family: Arial, Helvetica, sans-serif; + font-weight: 400; + text-align: center; + padding-top: 8px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + border-width: 2px; + border-color: white; + border-style: solid; + margin-right: 10px; + @include border_box_sizing; + } + + h3 { + font-size: 24px; + margin-bottom: 10px; + } + + p { + font-size: 16px; + line-height: 150%; + } + } + + p { + font-size: 16px; + } + + .cta-row { + padding: 40px 0; + background-color: #262626; + h2 { - font-size:18px; - color:white; - text-decoration:underline; - margin-bottom:8px; + font-size: 30px; + margin-bottom: 10px; } - ul { - width: 100%; - margin:5px 0 0; + + p { + color: $copy-color-on-dark; } - li { - margin-bottom:4px; + } + + .white-bar { + p { + color: $copy-color-on-white; } - .row .column { - padding:0 30px; + + .top-container { + padding-bottom: 20px; + } + } + + .red-bar { + font-size: 30px; + font-weight: bold; + color: white; + background-color: #e03c02; + padding: 40px 0; + text-align: center; + } + + img.app-preview { + width: 340px; + float: left; + margin-left: -15px; // because image has black on the left, which you can't see on back background + margin-right: 20px; + margin-bottom: 30px; + } + h1.jam-track-name { + color: white; + font-size: 30px !important; + padding-top: 0; + margin-left: 35px; + } + + h2.original-artist { + margin-top: 10px; + color: #8d8d8d; + font-size: 24px; + margin-left: 35px; + } + + .preview-text { + color: #fefefe; + } + + .cta-button { + background-color: $cta-color; + + &.processing { + background-color:gray; + } + } + + .cta-text { + color: $cta-color !important; + } + + .privacy-policy { + color: #ffcf00; + } + + .browse-all { + color: #ffb800; + } + + p { + line-height: 150%; + width: 100% !important; + } + + .bottom-banner { + background-color: #e03c02; + } + + .arrow1 { + position:absolute; + left: -371px; + top: -80px; + } + + .arrow2 { + position:absolute; + left:-230px; + top:373px; + } + .testimonials { + background-color:white; + position:absolute; + width:350px; + right:55px; + top:287px; + @include border_box_sizing; + z-index:1; + + .jamtrack-overview-video { + + h3 { + font-size:21px; + width:400px; + } + .video-wrapper { + + .video-container { + width: 400px; + padding-bottom: 53.33%; + + } + } + } + + h3 { + color:$copy-color-on-white; + text-align:center; + margin-bottom:20px; + font-size:24px; + line-height:125%; + } + + h4 { position:absolute; - float:none; + top: 161px; + left: 173px; + font-size:14px; + color:$copy-color-on-white; + strong { + font-weight:bold; + } + white-space:nowrap; } - .row:nth-of-type(2) { - margin-top:30px; + .testimonial { position:relative; - min-height:250px; + width:350px; + margin-bottom:40px; } - - .row .column:nth-of-type(1) { - width: 50%; - left:0; - height:100%; + .testimonial-speech-bubble { + width:350px; } - .row .column:nth-of-type(2) { - width: 50%; - left:50%; + .testimonial-avatar { + width:60px; position:relative; - border-width:0 0 0 1px; - border-color:white; - border-style:solid; + top:5px; + left:92px; + } + .testimonial-youtube { + width:143px; + position:relative; + left:112px; + top:0; } } - .watch-video { - position:relative; - top:40px; - text-align:left; - display:block; - font-size:16px; - left:20%; - width:300px; - } + .preview-and-action-box { + background-color: black; + position: absolute; + right: 55px; + width: 330px; + top: 200px; + @include border_box_sizing; + border-width: $chunkyBorderWidth; + border-style: solid; + border-color: $copy-color-on-dark; + z-index: 1; - .previews { - margin-top:10px; - } - .jamtrack-reasons { - margin: 10px 0 0 20px; - } + .preview-jamtrack-header { + background-color: $cta-color; + color: white; + font-size: 24px; + text-align: center; + padding: 20px 0; + border-width: 0 0 $chunkyBorderWidth; + border-style: solid; + border-color: $copy-color-on-dark; + } - .white-bordered-button { - margin-top: 20px; - } + .preview-area { - .browse-jamtracks-wrapper { - text-align:center; - width:90%; - } + padding: 10px; - .prompt { - margin-top:10px; + border-width: 0 0 $chunkyBorderWidth; + border-style: solid; + border-color: $copy-color-on-dark; + + &.logged-in { + border-width:0; + + .tracks { + height:258px; + } + } + .tracks { + padding:10px 0 20px; + margin-top:10px; + overflow-y: scroll; + height: 160px; + margin-bottom:10px; + } + + .cta-button { + font-size: 24px; + color: white; + background-color: $cta-color; + text-align: center; + padding: 10px; + display: block; + width: 100%; + border: 1px outset buttonface; + font-family: Raleway, Arial, Helvetica, sans-serif; + } + } + + .terms-help { + float:left; + margin-top:2px; + font-size:12px; + } + .register-area { + padding: 10px; + input { + background-color: $copy-color-on-dark; + color: black; + font-size: 16px; + + &[name="terms"] { + width:auto; + line-height:24px; + vertical-align:middle; + } + } + .icheckbox_minimal { + float: left; + top: 6px; + margin-left:64px; + } + .cta-button { + font-size: 24px; + color: white; + background-color: $cta-color; + text-align: center; + padding: 10px; + display: block; + width: 100%; + border: 1px outset buttonface; + font-family: Raleway, Arial, Helvetica, sans-serif; + } + .browse-all { + text-decoration: underline; + text-align: center; + display: block; + + } + .privacy-policy { + text-decoration: underline; + } + + form { + margin: 0 0 10px; + } + .errors { + font-size:12px; + height:20px; + margin:0; + visibility: hidden; + text-align: center; + color: red; + font-weight: bold; + + &.active { + visibility: visible; + } + } + label { + display: inline-block; + height: 36px; + vertical-align: middle; + line-height: 36px; + margin-bottom: 15px; + @include border-box_sizing; + + &.terms-help { + width:205px; + height:28px; + line-height:14px; + float:right; + } + } + input { + width: 206px; + height: 36px; + float: right; + margin-bottom: 15px; + @include border-box_sizing; + } + } + p { + line-height: 150%; + color: white; + font-size: 14px; + } } .jam-track-preview-holder { @@ -87,7 +450,7 @@ body.web.landing_jamtrack.individual_jamtrack { } &[data-track-type="Track"] { - width: 50%; + width: 100%; } } @@ -107,41 +470,10 @@ body.web.landing_jamtrack.individual_jamtrack { background-color:black; } } - - .cta-holder { - - &.instrument-selection { - a { - font-size: 18px; - text-decoration:underline; - } - - .browse-instrument { - margin-top:20px; - text-decoration:underline; - } - } - margin-top:30px; + .price-advisory { + font-size:14px; text-align:center; - .checkout { - display:inline-block; - position:relative; - } - .browse-band { - display:inline-block; - position:relative; - margin-top:20px; - } - .browse-all { - display:inline-block; - position:relative; - margin-top:20px; - } - .value-indicator { - position:absolute; - left: 301px; - top: 19px; - width:80px; - } + color:white; + margin-top:10px; } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/landings/individual_jamtrack_v2.css.scss b/web/app/assets/stylesheets/landings/individual_jamtrack_v2.css.scss new file mode 100644 index 000000000..886614cf5 --- /dev/null +++ b/web/app/assets/stylesheets/landings/individual_jamtrack_v2.css.scss @@ -0,0 +1,147 @@ +body.web.landing_jamtrack.individual_jamtrack_v2 { + + .landing-content { + .header { + text-align:center; + } + p { + width:100%; + } + h1 { + font-size:24px; + } + h2 { + font-size:18px; + color:white; + text-decoration:underline; + margin-bottom:8px; + } + ul { + width: 100%; + margin:5px 0 0; + } + li { + margin-bottom:4px; + } + .row .column { + padding:0 30px; + position:absolute; + float:none; + } + .row:nth-of-type(2) { + margin-top:30px; + position:relative; + min-height:250px; + } + + .row .column:nth-of-type(1) { + width: 50%; + left:0; + height:100%; + } + .row .column:nth-of-type(2) { + width: 50%; + left:50%; + position:relative; + border-width:0 0 0 1px; + border-color:white; + border-style:solid; + } + } + .watch-video { + position:relative; + top:40px; + text-align:left; + display:block; + font-size:16px; + left:20%; + width:300px; + } + + .previews { + margin-top:10px; + } + .jamtrack-reasons { + margin: 10px 0 0 20px; + } + + .white-bordered-button { + margin-top: 20px; + } + + .browse-jamtracks-wrapper { + text-align:center; + width:90%; + } + + .prompt { + margin-top:10px; + } + + .jam-track-preview-holder { + + margin-bottom: 7px; + float:left; + &[data-track-type="Master"] { + width: 100%; + } + + &[data-track-type="Track"] { + width: 50%; + } + } + + .jam-track-preview { + font-size:12px; + + .instrument-part { + width:175px + } + + .instrument-name,.part { + display:none; + } + + .loading-text { + right:-1px; + background-color:black; + } + } + + .cta-holder { + + &.instrument-selection { + a { + font-size: 18px; + text-decoration:underline; + } + + .browse-instrument { + margin-top:20px; + text-decoration:underline; + } + } + margin-top:30px; + text-align:center; + .checkout { + display:inline-block; + position:relative; + } + .browse-band { + display:inline-block; + position:relative; + margin-top:20px; + } + .browse-all { + display:inline-block; + position:relative; + margin-top:20px; + } + .value-indicator { + position:absolute; + left: 301px; + top: 19px; + width:80px; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/landings/landing_page_new.css.scss b/web/app/assets/stylesheets/landings/landing_page_new.css.scss index 85381683d..7e98cecac 100644 --- a/web/app/assets/stylesheets/landings/landing_page_new.css.scss +++ b/web/app/assets/stylesheets/landings/landing_page_new.css.scss @@ -86,12 +86,29 @@ body.web.landing_page { } } + + .white-bar { + z-index:-1; // to let sidebar be interactable + background-color:white; + } + + + + .landing-tag-play-learn-earn { + color:#929292; + font-size: 18px; + position: absolute; + top: 52px; + right: 0; + } + &.landing_jamtrack, &.landing_product { .landing-tag { left:50%; text-align:center; } + p, ul, li { font-size:14px; line-height:125%; diff --git a/web/app/assets/stylesheets/minimal/jamtrack_player.css.scss b/web/app/assets/stylesheets/minimal/jamtrack_player.css.scss new file mode 100644 index 000000000..2ac7625fc --- /dev/null +++ b/web/app/assets/stylesheets/minimal/jamtrack_player.css.scss @@ -0,0 +1,320 @@ +@import "client/common"; + +body.jamtrack-player-popup.popup { + + text-align:center; + + background-color: #242323; + + #minimal-container { + padding-bottom:0; + } + + .media-controls-popup { + padding:15px 15px 3px 15px; + } + + .field { + margin-top:20px; + } + + .icheckbox_minimal { + float:left; + } + + label { + float: left; + margin-left: 5px; + margin-top:2px; + } + + h3 { + text-align:left; + margin-bottom:5px; + } + + .actions { + position:relative; + margin-top:20px; + font-size:11px; + margin-bottom:10px; + + } + + .help-link { + position:absolute; + left:-6px; + top:0; + } + .close-link { + + } + + .header { + padding-bottom:20px; + h3 { + text-align:center; + font-weight:bold; + } + + h4 { + margin-top:15px; + font-size:12px; + font-weight:normal; + + span { + vertical-align:middle; + } + img { + vertical-align:middle; + margin-left:5px; + height:16px; + } + } + + h5 { + font-size:12px; + font-weight:normal; + + span { + vertical-align:middle; + } + img { + vertical-align:middle; + margin-left:5px; + height:16px; + } + } + } + + .extra-controls { + margin-top:20px; + h4 { + text-align:left; + font-size:18px; + a { + font-size:11px; + position:absolute; + right:20px; + } + &.custom-mix-header { + margin-top:20px; + } + } + + .my-mixes { + margin-top:5px; + max-height:170px; + border-width:1px; + border-bottom-color:#676767; + border-top-color:#676767; + border-left-color:#171717; + border-right-color:#171717; + border-style:solid; + overflow:auto; + + @include border_box_sizing; + } + + .mixdown-display { + display:table; + font-size:12px; + color:$ColorTextTypical; + width:100%; + + border-width:1px 0; + border-top-color:#343434; + border-bottom-color:#282828; + border-style:solid; + background-color:#2c2c2c; + @include border_box_sizing; + border-spacing:7px; + text-align: left; + + &.active { + background-color:#44423f; + } + } + + .active-stem-select { + max-width:206px; + } + .stems { + + + height:147px; + overflow:auto; + margin:20px 0; + table { + width:100%; + } + + th { + color:white; + font-size:14px; + line-height:20px; + vertical-align:middle; + } + td { + font-size:12px; + height:24px; + line-height:28px; + vertical-align:middle; + input { + width:auto; + } + img{ + vertical-align:middle; + margin-right:10px; + } + } + td.mute { + padding-right:25px; + } + .mute { + text-align:right; + padding-right:20px; + } + } + + .stems-holder { + height:250px; + overflow:auto; + } + + .stem { + + } + + .mixdown-name { + line-height:125%; + width:210px; + text-align:left; + display: table-cell; + vertical-align: middle; + } + + .mixdown-actions { + display: table-cell; + vertical-align: middle; + margin-left:10px; + width:100px; + white-space:nowrap; + min-width:100px; + } + + .mixdown-stateful { + display:inline-block; + vertical-align:middle; + width:24px; + height:24px; + cursor:pointer; + } + + .mixdown-download { + width:24px; + height:24px; + + cursor:pointer; + + &.not-ready { + visibility:hidden; + } + } + + .mixdown-play { + width:24px; + height:24px; + margin-right:10px; + + cursor:pointer; + } + .mixdown-edit { + margin-left:10px; + width:24px; + height:24px; + + cursor:pointer; + } + + .mixdown-delete { + margin-left:10px; + + width:24px; + height:24px; + cursor:pointer; + } + .create-mix { + margin-top:5px; + border-color:$ColorTextTypical; + border-style: solid; + border-width:1px 0; + padding: 7px 0 20px; + + &.not-active { + padding:7px 0 7px; + } + + p { + line-height:125%; + color:$ColorTextTypical; + text-align:left; + font-size:12px; + } + + .field { + display:block; + height:25px; + margin-top:15px; + } + + ul.error-text { + float:right; + display:block !important; + color: red; + margin-top: 5px; + } + + a.create-mix-btn { + margin-top:15px; + float:right; + margin-right: 2px; + margin-top: 3px; + } + label { + display:inline; + float:left; + } + + select, input { + width:170px; + float:right; + @include border_box_sizing; + background-color:$ColorTextBoxBackground; + } + } + } + + .arrow-down { + float:none; + margin-left:5px; + margin-top:0; + margin-right:0; + border-top: 4px solid #fc0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + display:inline-block; + padding-top:1px; + } + .arrow-up { + float:none; + margin-right:0; + margin-left:5px; + margin-bottom:2px; + border-bottom: 4px solid #fc0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + display:inline-block; + padding-top:1px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/minimal/minimal.css.scss b/web/app/assets/stylesheets/minimal/minimal.css.scss index 0e3be9153..fed70ba94 100644 --- a/web/app/assets/stylesheets/minimal/minimal.css.scss +++ b/web/app/assets/stylesheets/minimal/minimal.css.scss @@ -1,5 +1,7 @@ /** *= require web/Raleway +*= require client/jamServer +*= require dialogs/banner *= require client/ie *= require client/jamkazam *= require client/screen_common @@ -12,4 +14,8 @@ *= require_directory . *= require client/metronomePlaybackModeSelect *= require_directory ../client/react-components -*/ \ No newline at end of file +*/ + +.no-websocket-connection.active { + display:none !important; +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/web/main.css.scss b/web/app/assets/stylesheets/web/main.css.scss index 3522b1398..686b0c2bf 100644 --- a/web/app/assets/stylesheets/web/main.css.scss +++ b/web/app/assets/stylesheets/web/main.css.scss @@ -33,6 +33,11 @@ body.web { } } + .after-black-bar-border { + border-top:1px solid #444; + margin: 0 6%; + box-sizing: border-box; + } #profile.userinfo { margin-top:30px; } diff --git a/web/app/controllers/api_jam_track_mixdowns_controller.rb b/web/app/controllers/api_jam_track_mixdowns_controller.rb index a38c2f734..5f620477c 100644 --- a/web/app/controllers/api_jam_track_mixdowns_controller.rb +++ b/web/app/controllers/api_jam_track_mixdowns_controller.rb @@ -83,7 +83,14 @@ class ApiJamTrackMixdownsController < ApiController @package.last_downloaded_at = now @package.first_downloaded_at = now if @package.first_downloaded_at.nil? @package.save! - redirect_to @package.sign_url(120) + if params[:download] + redirect_to @package.sign_url(120, 'application/octet-stream', "attachment; filename=#{@package.jam_track_mixdown.name}.#{params[:file_type]}") + + else + redirect_to @package.sign_url(120) + end + + else @package.enqueue_if_needed render :json => { :message => "not available, digitally signing Jam Track Mixdown offline." }, :status => 202 diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 14e3a024d..607dc390c 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -3,7 +3,7 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index] before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index] - before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active] + before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active, :download_stem] respond_to :json @@ -30,8 +30,16 @@ class ApiJamTracksController < ApiController def mark_active mixdown_id = params[:mixdown_id] + stem_id = params[:stem_id] + # only one should be active at a time, so we enforce that on the server here. if you specify one, the other will get nulled automatically + if mixdown_id + stem_id = nil + else + mixdown_id = nil + end @jam_track_right.last_mixdown_id = mixdown_id + @jam_track_right.last_stem_id = stem_id @jam_track_right.save if @jam_track_right.errors.any? @@ -96,6 +104,25 @@ class ApiJamTracksController < ApiController render "api_jam_tracks/purchased", :layout => nil end + def download_stem + if @jam_track_right.valid? + + if params[:stem_id] == 'master' + jam_track_track = @jam_track_right.jam_track.master_track + else + jam_track_track = JamTrackTrack.find(params[:stem_id]) + end + + if params[:download] + redirect_to jam_track_track.web_download_sign_url(120, params[:file_type], 'application/octet-stream', "attachment; filename=#{@jam_track_right.jam_track.name + '-' + jam_track_track.display_name}.mp3") + else + redirect_to jam_track_track.web_download_sign_url(120, params[:file_type]) + end + + else + render :json => { :message => "download limit surpassed", :errors=>@jam_track_right.errors }, :status => 403 + end + end def download if @jam_track_right.valid? diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 9ce09c645..47f5e73d3 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -396,8 +396,9 @@ class ApiRecordingsController < ApiController body << "User: " + current_user.admin_url + "\n\n" body << "Recording Landing: #{recording_detail_url(@recording.id)}\n" - AdminMailer.alerts({ - subject:"Video Uploaded by #{current_user.name}", + private_public = @recording.is_public ? 'Public' : 'Private' + AdminMailer.social({ + subject:"#{private_public } Video Uploaded by #{current_user.name}", body:body }).deliver diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 955f157ec..f96373279 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -2,7 +2,7 @@ require 'sanitize' class ApiUsersController < ApiController - before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth] + before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event] before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations, :liking_create, :liking_destroy, # likes :following_create, :following_show, :following_destroy, # followings @@ -683,6 +683,21 @@ ApiUsersController < ApiController render :json => {}, :status => 200 end + def opened_jamtrack_web_player + User.where(id: current_user.id).update_all(first_opened_jamtrack_web_player: Time.now) + render :json => {}, :status => 200 + end + + def user_event + event = UserEvent.new + event.name = params[:name] + event.detail = params[:detail].to_json if params[:detail] + event.user_id = current_user.id if current_user + event.save + + render :json => {}, :status => 200 + end + # creates display-ready session data for sharing def share_session provider = params[:provider] diff --git a/web/app/controllers/application_controller.rb b/web/app/controllers/application_controller.rb index 114666eec..8f422fba7 100644 --- a/web/app/controllers/application_controller.rb +++ b/web/app/controllers/application_controller.rb @@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base end def track_affiliate_visits - if params[:affiliate] && params[:utm_medium] == 'affiliate' && params[:utm_source] == 'affiliate' + if params[:affiliate] visit_cookie = cookies[:affiliate_visitor] AffiliateReferralVisit.track(affiliate_id: params[:affiliate], visited: visit_cookie, remote_ip: request.remote_ip, visited_url: request.fullpath, referral_url: request.referer, current_user: current_user) diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index d1e95e2ad..fa13b018f 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -68,7 +68,65 @@ class LandingsController < ApplicationController render 'watch_overview_tight', layout: 'web' end + def individual_jamtrack + @no_landing_tag = true + @landing_tag_play_learn_earn = true + @show_after_black_bar_border = true + instrument_id = nil + instrument_name = nil + instrument_count = 0 + if params[:instrument] + instrument = params[:instrument].downcase.sub('-', ' ') + instrument = Instrument.find_by_id(instrument) + instrument_id = instrument.id if instrument + instrument_name = instrument.description + query, next_ptr, instrument_count = JamTrack.index({instrument: instrument_id}, current_user) + end + @jam_track = JamTrack.find_by_slug(params[:plan_code]) + @jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track + + if @jam_track.nil? + redirect_to '/client/#jamtrack' + return + end + + band_jam_track_count = @jam_track.band_jam_track_count + jam_track_count = JamTrack.count + @title = individual_jamtrack_title(false, params[:generic], @jam_track) + @description = individual_jamtrack_desc(false, params[:generic], @jam_track) + @page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: false, generic: params[:generic], instrument: instrument_name, instrument_id: instrument_id, instrument_count: instrument_count} + gon.jam_track_plan_code = @jam_track.plan_code if @jam_track + gon.generic = params[:generic] + gon.instrument_id = instrument_id + render 'individual_jamtrack', layout: 'web' + end + + + + def individual_jamtrack_band + @no_landing_tag = true + @landing_tag_play_learn_earn = true + @show_after_black_bar_border = true + @jam_track = JamTrack.find_by_slug(params[:plan_code]) + @jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track + + if @jam_track.nil? + redirect_to '/client/#jamtrack' + return + end + + band_jam_track_count = @jam_track.band_jam_track_count + jam_track_count = JamTrack.count + @title = individual_jamtrack_title(true, params[:generic], @jam_track) + @description = individual_jamtrack_desc(true, params[:generic], @jam_track) + @page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: true, generic: params[:generic]} + gon.jam_track_plan_code = @jam_track.plan_code if @jam_track + gon.generic = params[:generic] + render 'individual_jamtrack', layout: 'web' + end + + def individual_jamtrack_v2 @no_landing_tag = true instrument_id = nil instrument_name = nil @@ -90,11 +148,11 @@ class LandingsController < ApplicationController gon.jam_track_plan_code = @jam_track.plan_code if @jam_track gon.generic = params[:generic] gon.instrument_id = instrument_id - render 'individual_jamtrack', layout: 'web' + render 'individual_jamtrack_v2', layout: 'web' end - def individual_jamtrack_band + def individual_jamtrack_band_v2 @no_landing_tag = true @jam_track = JamTrack.find_by_slug(params[:plan_code]) @jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track @@ -105,7 +163,7 @@ class LandingsController < ApplicationController @page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: true, generic: params[:generic]} gon.jam_track_plan_code = @jam_track.plan_code if @jam_track gon.generic = params[:generic] - render 'individual_jamtrack', layout: 'web' + render 'individual_jamtrack_v2', layout: 'web' end def individual_jamtrack_v1 diff --git a/web/app/controllers/popups_controller.rb b/web/app/controllers/popups_controller.rb index 427bff4f9..b93260d4e 100644 --- a/web/app/controllers/popups_controller.rb +++ b/web/app/controllers/popups_controller.rb @@ -28,4 +28,11 @@ class PopupsController < ApplicationController gon.recording_id = @recording_id render :layout => "minimal" end + + def jamtrack_player + @jamtrack_id = params[:jam_track_id] + @websocket = true + gon.jamtrack_id = @jamtrack_id + render :layout => "minimal" + end end \ No newline at end of file diff --git a/web/app/views/api_feeds/show.rabl b/web/app/views/api_feeds/show.rabl index 99fe1e7c8..7783ae758 100644 --- a/web/app/views/api_feeds/show.rabl +++ b/web/app/views/api_feeds/show.rabl @@ -29,11 +29,11 @@ glue :music_session do end child(:creator => :creator) { - attributes :id, :first_name, :last_name, :photo_url + attributes :id, :first_name, :last_name, :name, :photo_url } child(:unique_user_histories => :participants) { - attributes :first_name, :last_name, :photo_url + attributes :first_name, :last_name, :name, :photo_url node :id do |user| user.user_id @@ -115,7 +115,7 @@ glue :recording do attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url } } @@ -126,7 +126,7 @@ glue :recording do end child(:musician => :musician) { - attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url node do |user| { @@ -142,7 +142,7 @@ glue :recording do attributes :comment, :created_at child(:user => :creator) { - attributes :id, :first_name, :last_name, :photo_url + attributes :id, :first_name, :last_name, :name, :photo_url } } @@ -151,7 +151,7 @@ glue :recording do attributes :id, :is_public, :genre_id, :has_mix?, :user_id child(:user => :creator) { - attributes :id, :first_name, :last_name, :photo_url + attributes :id, :first_name, :last_name, :name, :photo_url } node :share_url do |claimed_recording| diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl index 6bde111a7..1d469cace 100644 --- a/web/app/views/api_jam_tracks/show_for_client.rabl +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -26,8 +26,12 @@ child(:jam_track_tracks => :tracks) { end } -node :last_mixdown_id do |jam_track| - jam_track.right_for_user(current_user).last_mixdown_id +node do |jam_track| + right = jam_track.right_for_user(current_user) + { + last_mixdown_id: right.last_mixdown_id, + last_stem_id: right.last_stem_id + } end node :mixdowns do |jam_track| diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index d5afc2a02..39a71810c 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -126,7 +126,7 @@ else end child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :photo_url } } @@ -134,7 +134,7 @@ else attributes :id, :fully_uploaded, :client_track_id, :client_id, :filename child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :photo_url } } diff --git a/web/app/views/api_music_sessions/show_history.rabl b/web/app/views/api_music_sessions/show_history.rabl index 5d44c43f0..700b00cf8 100644 --- a/web/app/views/api_music_sessions/show_history.rabl +++ b/web/app/views/api_music_sessions/show_history.rabl @@ -219,7 +219,7 @@ else end child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :photo_url } } } diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index dbc488a27..548c2dd02 100644 --- a/web/app/views/api_recordings/show.rabl +++ b/web/app/views/api_recordings/show.rabl @@ -61,7 +61,7 @@ child(:comments => :comments) { attributes :comment, :created_at child(:user => :creator) { - attributes :id, :first_name, :last_name, :photo_url + attributes :id, :first_name, :last_name, :name, :photo_url } } diff --git a/web/app/views/api_recordings/show_recorded_backing_track.rabl b/web/app/views/api_recordings/show_recorded_backing_track.rabl index 6487b9db6..399542c47 100644 --- a/web/app/views/api_recordings/show_recorded_backing_track.rabl +++ b/web/app/views/api_recordings/show_recorded_backing_track.rabl @@ -7,5 +7,5 @@ node :mine do |recorded_backing_track| end child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url } \ No newline at end of file diff --git a/web/app/views/api_recordings/show_recorded_track.rabl b/web/app/views/api_recordings/show_recorded_track.rabl index f5f73cc90..975798e34 100644 --- a/web/app/views/api_recordings/show_recorded_track.rabl +++ b/web/app/views/api_recordings/show_recorded_track.rabl @@ -3,5 +3,5 @@ object @recorded_track attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id, :recording_id child(:user => :user) { - attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url } \ No newline at end of file diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index f8913978a..9c9c8af0a 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -11,7 +11,7 @@ end # give back more info if the user being fetched is yourself if @user == current_user - attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at + attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at, :first_opened_jamtrack_web_player node :geoiplocation do |user| geoiplocation = current_user.geoiplocation diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index b379f0947..a05979179 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -311,6 +311,10 @@ script type="text/template" id="template-help-jamtrack-browse-cta" .jamtrack-browse-cta.big-help p Click to select your first free JamTrack! +script type="text/template" id="template-help-jamtrack-web-play" + .jamtrack-web-play.big-help + p Click here to open your free JamTrack + script type="text/template" id="template-help-master-metronome-notice" .master-metronome-notice p The metronome does not produce any sound in the master mix. @@ -360,3 +364,7 @@ script type="text/template" id="template-help-video-window-not-open" script type="text/template" id="template-help-vid-record-chat-input" .vid-record-chat-input p Any chat inputs in the session will also be included in the video if checked. + +script type="text/template" id="template-help-first-time-jamtrack-web-player" + .first-time-jamtrack-web-player + | Create custom mixes to mute parts, slow down playback, etc. diff --git a/web/app/views/dialogs/_recordingFinishedDialog.html.haml b/web/app/views/dialogs/_recordingFinishedDialog.html.haml index 3814e694d..2fadd425c 100644 --- a/web/app/views/dialogs/_recordingFinishedDialog.html.haml +++ b/web/app/views/dialogs/_recordingFinishedDialog.html.haml @@ -28,7 +28,7 @@ .save-video.field.left{:purpose => "save_video"} %input{:name => "save_video", :type => "checkbox", :checked => "checked"}/ %label{:for => "save_video"} Save Video to Computer - .field.left{:purpose => "upload_to_youtube"} + .upload-to-youtube.field.left{:purpose => "upload_to_youtube"} %span %input{:name => "upload_to_youtube", :type => "checkbox", :checked => "checked"}/ %label{:for => "upload_to_youtube"} Upload Video to YouTube diff --git a/web/app/views/landings/individual_jamtrack.html.slim b/web/app/views/landings/individual_jamtrack.html.slim index 278dec3f3..4ce517c18 100644 --- a/web/app/views/landings/individual_jamtrack.html.slim +++ b/web/app/views/landings/individual_jamtrack.html.slim @@ -1,11 +1,24 @@ -- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack') +- provide(:page_name, 'landing_page full individual_jamtrack') - provide(:description, @description) - provide(:title, @title) -= react_component 'IndividualJamTrackPage', @page_data.to_json += react_component 'JamTrackLandingPage', @page_data.to_json + +- content_for :after_black_bar do + .row.cta-row + h2 GET THIS JAMTRACK FREE NOW! + p And join 20,000+ other musicians who love our JamTracks. + p.cta-text Not sure if JamTracks are for you? Scroll down to learn more. + +- content_for :white_bar do + = react_component 'JamTrackLandingBottomPage', @page_data.to_json + +- content_for :red_bar do + .full-row + | Get your free JamTrack and start playing today! javascript: $(document).on('JAMKAZAM_READY', function(e, data) { - var song = new JK.IndividualJamTrack(data.app); + var song = new JK.IndividualJamTrack(data.app, true); song.initialize(); }) diff --git a/web/app/views/landings/individual_jamtrack_v2.html.slim b/web/app/views/landings/individual_jamtrack_v2.html.slim new file mode 100644 index 000000000..d8bf8d97f --- /dev/null +++ b/web/app/views/landings/individual_jamtrack_v2.html.slim @@ -0,0 +1,11 @@ +- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack_v2') +- provide(:description, @description) +- provide(:title, @title) + += react_component 'IndividualJamTrackPage', @page_data.to_json + +javascript: + $(document).on('JAMKAZAM_READY', function(e, data) { + var song = new JK.IndividualJamTrack(data.app); + song.initialize(); + }) diff --git a/web/app/views/landings/product_jamtracks.html.slim b/web/app/views/landings/product_jamtracks.html.slim index 2b67ebf1d..373a1da9a 100644 --- a/web/app/views/landings/product_jamtracks.html.slim +++ b/web/app/views/landings/product_jamtracks.html.slim @@ -13,7 +13,7 @@ h1 See What You Can With JamTracks .video-wrapper .video-container - iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen="allowfullscreen" + iframe src="//www.youtube.com/embed/07zJC7C2ICA" frameborder="0" allowfullscreen="allowfullscreen" br clear="all" .row .column diff --git a/web/app/views/layouts/minimal.html.erb b/web/app/views/layouts/minimal.html.erb index 5e7e80320..ce624b881 100644 --- a/web/app/views/layouts/minimal.html.erb +++ b/web/app/views/layouts/minimal.html.erb @@ -25,7 +25,11 @@ <%= yield %>
+ <%= render "clients/jamServer" %> <%= render "clients/help" %> + <%= render 'dialogs/banner' %> + <%= render 'dialogs/banners/disconnected' %> + diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index cd839e133..4c09df132 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -39,11 +39,15 @@ <% end %>
- <% unless @welcome_page || @no_landing_tag %> + <% if !@welcome_page && !@no_landing_tag %>

Live music platform &
social network for musicians

+ <% elsif @landing_tag_play_learn_earn %> +
+

WHERE MUSICIANS CONNECT, PLAY, LEARN, & EARN

+
<% end %> <% if @show_cta_free_jamtrack %> @@ -60,10 +64,28 @@
+ + <% if @show_after_black_bar_border %> +
+ <% end %> +
<%= yield(:after_black_bar) %>
+
+
+ <%= yield(:white_bar) %> +
+
+ +
+
+ <%= yield(:red_bar) %> +
+
+ + @@ -81,28 +103,29 @@ <%= render "clients/jam_track_preview" %> <%= render 'dialogs/dialogs' %> +