From 3d43f123b14a5f67354ee9fcdaf605197919a7d1 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 29 Mar 2015 21:51:14 -0500 Subject: [PATCH 01/16] * VRFS-2992 -make mp3 as well as ogg preview --- admin/app/admin/jam_tracks.rb | 2 +- .../_jam_track_track_fields.html.slim | 6 ++- admin/config/initializers/jam_track_tracks.rb | 39 ++++++++++++++----- db/manifest | 3 +- db/up/preview_support_mp3.sql | 3 ++ ruby/lib/jam_ruby/models/jam_track.rb | 19 ++++++++- ruby/lib/jam_ruby/models/jam_track_track.rb | 11 +++--- 7 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 db/up/preview_support_mp3.sql diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 6c90018e6..d51e92f31 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -24,7 +24,7 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do column :original_artist column :name - column :flags do |jam_track| jam_track.duplicate_positions? ? 'DUP POSITIONS' : '' end + column :onboarding_flags do |jam_track| jam_track.onboard_warnings end column :status column :master_track do |jam_track| jam_track.master_track.nil? ? 'None' : (link_to "Download", jam_track.master_track.url_by_sample_rate(44)) end column :licensor diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim index 0ef6078ac..8d7cab1df 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim @@ -8,8 +8,10 @@ = f.input :preview_start_time_raw, :label => 'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string - unless f.object.nil? || f.object[:preview_url].nil? .current_file_holder style='margin-bottom:10px' - a href=f.object.preview_sign_url(3600) style='padding:0 0 0 20px' - | Download Preview + a href=f.object.preview_sign_url(3600, 'ogg') style='padding:0 0 0 20px' + | Download Preview (ogg) + a href=f.object.preview_sign_url(3600, 'mp3') style='padding:0 0 0 20px' + | Download Preview (mp3) // temporarily disable - if f.object.new_record? diff --git a/admin/config/initializers/jam_track_tracks.rb b/admin/config/initializers/jam_track_tracks.rb index b6367cd58..80806db99 100644 --- a/admin/config/initializers/jam_track_tracks.rb +++ b/admin/config/initializers/jam_track_tracks.rb @@ -13,7 +13,6 @@ class JamRuby::JamTrackTrack end - # this is used by active admin/jam-admin def preview_start_time_raw if self.preview_start_time.nil? || self.preview_start_time.nil? @@ -60,6 +59,7 @@ class JamRuby::JamTrackTrack input = File.join(tmp_dir, 'in.ogg') output = File.join(tmp_dir, 'out.ogg') + output_mp3 = File.join(tmp_dir, 'out.mp3') start = self.preview_start_time.to_f / 1000 stop = start + 20 @@ -80,16 +80,37 @@ class JamRuby::JamTrackTrack @@log.debug("fail #{result_code}") @preview_generate_error = "unable to execute cut command #{sox_output}" else - @@log.debug("uploading preview to #{self.preview_filename}") - s3_manager.upload(self.preview_filename, output) + # now create mp3 off of ogg preview - self.skip_uploader = true - # and finally update the JamTrackTrack with the new info - self["preview_url"] = self.preview_filename - self["preview_md5"] = ::Digest::MD5.file(output).hexdigest - self["preview_length"] = File.new(output).size - self.save! + convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\"" + + @@log.debug("converting to mp3 using: " + convert_mp3_cmd) + + convert_output = `#{convert_mp3_cmd}` + + result_code = $?.to_i + + if result_code != 0 + @@log.debug("fail #{result_code}") + @preview_generate_error = "unable to execute mp3 convert command #{convert_output}" + else + @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}") + s3_manager.upload(self.preview_filename('ogg'), output) + @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}") + s3_manager.upload(self.preview_filename('mp3'), output_mp3) + + self.skip_uploader = true + # and finally update the JamTrackTrack with the new info + self["preview_url"] = self.preview_filename('ogg') + self["preview_md5"] = ::Digest::MD5.file(output).hexdigest + self["preview_length"] = File.new(output).size + # and finally update the JamTrackTrack with the new info + self["preview_mp3_url"] = self.preview_filename('mp3') + self["preview_mp3_md5"] = ::Digest::MD5.file(output_mp3).hexdigest + self["preview_mp3_length"] = File.new(output_mp3).size + self.save! + end end end rescue Exception => e diff --git a/db/manifest b/db/manifest index 93be92198..14e582705 100755 --- a/db/manifest +++ b/db/manifest @@ -270,4 +270,5 @@ shopping_cart_anonymous.sql user_reuse_card_and_reedem.sql jam_track_id_to_varchar.sql drop_position_unique_jam_track.sql -recording_client_metadata.sql \ No newline at end of file +recording_client_metadata.sql +preview_support_mp3.sql \ No newline at end of file diff --git a/db/up/preview_support_mp3.sql b/db/up/preview_support_mp3.sql new file mode 100644 index 000000000..089a949be --- /dev/null +++ b/db/up/preview_support_mp3.sql @@ -0,0 +1,3 @@ +ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR; +ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 56341ff26..892285996 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -70,7 +70,6 @@ module JamRuby if count.nil? count = 0 end - puts "count #{count}" counter[track.position] = count + 1 end @@ -84,6 +83,24 @@ module JamRuby duplicate end + def missing_previews? + missing_preview = false + self.jam_track_tracks.each do |track| + unless track.has_preview? + missing_preview = true + break + end + end + missing_preview + end + + def onboard_warnings + warnings = [] + warnings << 'POSITIONS' if duplicate_positions? + warnings << 'PREVIEWS'if missing_previews? + warnings.join(',') + end + class << self # @return array[artist_name(string)] def all_artists diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 78255d07c..36795a432 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -42,20 +42,21 @@ module JamRuby end # create name of the file - def preview_filename - filename("#{File.basename(self["url_44"], ".ogg")}-preview.ogg") + def preview_filename(ext='ogg') + filename("#{File.basename(self["url_44"], ".ogg")}-preview.#{ext}") end def has_preview? - !self["preview_url"].nil? + !self["preview_url"].nil? && !self['preview_mp3_url'].nil? end + # creates a short-lived URL that has access to the object. # 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 preview_sign_url(expiration_time = 120) - s3_manager.sign_url(self[:preview_url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + def preview_sign_url(expiration_time = 120, media_type='ogg') + s3_manager.sign_url(media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url], {:expires => expiration_time, :response_content_type => media_type == 'ogg' ? 'audio/ogg' : 'audio/mpeg', :secure => false}) end From 7058e897c28869003d64d61ad8eadc9be98d4b3d Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 31 Mar 2015 10:28:48 -0500 Subject: [PATCH 02/16] Add no-login-required class to jam track landing page. --- web/app/views/clients/_jamtrack_landing.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/views/clients/_jamtrack_landing.html.slim b/web/app/views/clients/_jamtrack_landing.html.slim index 853ee05fe..06e3efc2f 100644 --- a/web/app/views/clients/_jamtrack_landing.html.slim +++ b/web/app/views/clients/_jamtrack_landing.html.slim @@ -1,4 +1,4 @@ -#jamtrackLanding.screen.secondary layout='screen' layout-id='jamtrackLanding' +#jamtrackLanding.screen.secondary.no-login-required layout='screen' layout-id='jamtrackLanding' .content .content-head .content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19) From f7febfabcfff1d72a6e53877a5c0b24d4c6b5137 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 Mar 2015 13:36:49 -0500 Subject: [PATCH 03/16] * VRFS-2999 - create whole track preview for master track on import * VRFS-3000 - create duration for jam track * VRFS-2996 - create jamtrack preview widget * VRFS-2870 - individual jam track landing page (still needs tests) --- .../views/admin/jam_tracks/_form.html.slim | 1 + .../_jam_track_track_fields.html.slim | 7 +- admin/config/initializers/jam_track_tracks.rb | 29 ++- db/manifest | 3 +- db/up/jam_track_duration.sql | 1 + db/up/preview_support_mp3.sql | 3 +- ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/jam_track_importer.rb | 205 +++++++++++++++++- ruby/lib/jam_ruby/lib/s3_manager.rb | 15 +- .../jam_ruby/lib/s3_public_manager_mixin.rb | 17 ++ ruby/lib/jam_ruby/models/jam_track.rb | 4 +- ruby/lib/jam_ruby/models/jam_track_track.rb | 24 +- web/app/assets/javascripts/application.js | 1 + web/app/assets/javascripts/jam_rest.js | 10 + .../javascripts/jam_track_preview.js.coffee | 125 +++++++++++ web/app/assets/javascripts/utils.js | 15 +- web/app/assets/javascripts/web/web.js | 2 + web/app/assets/stylesheets/client/client.css | 1 + .../assets/stylesheets/client/common.css.scss | 12 + .../client/jamTrackPreview.css.scss | 35 +++ .../landings/individual_song.css.scss | 24 ++ .../landings/landing_page_new.css.scss | 47 ++++ web/app/assets/stylesheets/web/web.css | 5 +- .../controllers/api_jam_tracks_controller.rb | 8 +- web/app/controllers/landings_controller.rb | 7 + web/app/controllers/spikes_controller.rb | 7 + web/app/views/api_jam_tracks/show.rabl | 9 +- .../clients/_jam_track_preview.html.slim | 10 + web/app/views/clients/index.html.erb | 1 + .../views/landings/individual_song.html.slim | 93 ++++++++ web/app/views/layouts/web.html.erb | 1 + .../views/spikes/jam_track_preview.html.slim | 40 ++++ web/config/routes.rb | 5 +- web/lib/tasks/jam_tracks.rake | 52 +++++ 34 files changed, 779 insertions(+), 41 deletions(-) create mode 100644 db/up/jam_track_duration.sql create mode 100644 ruby/lib/jam_ruby/lib/s3_public_manager_mixin.rb create mode 100644 web/app/assets/javascripts/jam_track_preview.js.coffee create mode 100644 web/app/assets/stylesheets/client/jamTrackPreview.css.scss create mode 100644 web/app/assets/stylesheets/landings/individual_song.css.scss create mode 100644 web/app/assets/stylesheets/landings/landing_page_new.css.scss create mode 100644 web/app/views/clients/_jam_track_preview.html.slim create mode 100644 web/app/views/landings/individual_song.html.slim create mode 100644 web/app/views/spikes/jam_track_preview.html.slim diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim index fc0d9502a..51341d812 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.slim +++ b/admin/app/views/admin/jam_tracks/_form.html.slim @@ -13,6 +13,7 @@ = f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 } = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: true = f.input :genre, collection: JamRuby::Genre.all, include_blank: false + = f.input :duration, hint: 'this should rarely need editing because it comes from the import process' = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false = f.input :price, :required => true, :input_html => {type: 'numeric'} = f.input :pro_ascap, :label => 'ASCAP royalties due?' diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim index 8d7cab1df..4439c9550 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.slim @@ -5,12 +5,13 @@ = f.input :instrument, collection: Instrument.all, include_blank: false = f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' } = f.input :position - = f.input :preview_start_time_raw, :label => 'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string + - if !f.object.nil? && f.object.track_type != 'Master' + = f.input :preview_start_time_raw, :label => 'Preview Start Time', :hint => 'MM:SS:MLS', :as => :string - unless f.object.nil? || f.object[:preview_url].nil? .current_file_holder style='margin-bottom:10px' - a href=f.object.preview_sign_url(3600, 'ogg') style='padding:0 0 0 20px' + a href=f.object.preview_public_url('ogg') style='padding:0 0 0 20px' | Download Preview (ogg) - a href=f.object.preview_sign_url(3600, 'mp3') style='padding:0 0 0 20px' + a href=f.object.preview_public_url('mp3') style='padding:0 0 0 20px' | Download Preview (mp3) // temporarily disable diff --git a/admin/config/initializers/jam_track_tracks.rb b/admin/config/initializers/jam_track_tracks.rb index 80806db99..b05126d52 100644 --- a/admin/config/initializers/jam_track_tracks.rb +++ b/admin/config/initializers/jam_track_tracks.rb @@ -80,7 +80,6 @@ class JamRuby::JamTrackTrack @@log.debug("fail #{result_code}") @preview_generate_error = "unable to execute cut command #{sox_output}" else - # now create mp3 off of ogg preview convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\"" @@ -95,21 +94,37 @@ class JamRuby::JamTrackTrack @@log.debug("fail #{result_code}") @preview_generate_error = "unable to execute mp3 convert command #{convert_output}" else + ogg_digest = ::Digest::MD5.file(output) + mp3_digest = ::Digest::MD5.file(output_mp3) + self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest + self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest + @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}") - s3_manager.upload(self.preview_filename('ogg'), output) + s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest) @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}") - s3_manager.upload(self.preview_filename('mp3'), output_mp3) + s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64) self.skip_uploader = true + + original_ogg_preview_url = self["preview_url"] + original_mp3_preview_url = self["preview_mp3_url"] + # and finally update the JamTrackTrack with the new info - self["preview_url"] = self.preview_filename('ogg') - self["preview_md5"] = ::Digest::MD5.file(output).hexdigest + self["preview_url"] = self.preview_filename(ogg_md5, 'ogg') self["preview_length"] = File.new(output).size # and finally update the JamTrackTrack with the new info - self["preview_mp3_url"] = self.preview_filename('mp3') - self["preview_mp3_md5"] = ::Digest::MD5.file(output_mp3).hexdigest + self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3') self["preview_mp3_length"] = File.new(output_mp3).size self.save! + + # if all that worked, now delete old previews, if present + begin + s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"] + s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"] + rescue + puts "UNABLE TO CLEANUP OLD PREVIEW URL" + end + end end end diff --git a/db/manifest b/db/manifest index 14e582705..1d16c74df 100755 --- a/db/manifest +++ b/db/manifest @@ -271,4 +271,5 @@ user_reuse_card_and_reedem.sql jam_track_id_to_varchar.sql drop_position_unique_jam_track.sql recording_client_metadata.sql -preview_support_mp3.sql \ No newline at end of file +preview_support_mp3.sql +jam_track_duration.sql \ No newline at end of file diff --git a/db/up/jam_track_duration.sql b/db/up/jam_track_duration.sql new file mode 100644 index 000000000..a4dbee409 --- /dev/null +++ b/db/up/jam_track_duration.sql @@ -0,0 +1 @@ +ALTER TABLE jam_tracks ADD COLUMN duration INTEGER; \ No newline at end of file diff --git a/db/up/preview_support_mp3.sql b/db/up/preview_support_mp3.sql index 089a949be..90dd1c642 100644 --- a/db/up/preview_support_mp3.sql +++ b/db/up/preview_support_mp3.sql @@ -1,3 +1,4 @@ ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_url VARCHAR; ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_md5 VARCHAR; -ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT; \ No newline at end of file +ALTER TABLE jam_track_tracks ADD COLUMN preview_mp3_length BIGINT; +UPDATE jam_track_tracks SET preview_url = NULL where track_type = 'Master'; \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index d1bef15e0..6d61d32a4 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -30,6 +30,7 @@ require "jam_ruby/errors/jam_argument_error" require "jam_ruby/errors/conflict_error" require "jam_ruby/lib/app_config" require "jam_ruby/lib/s3_manager_mixin" +require "jam_ruby/lib/s3_public_manager_mixin" require "jam_ruby/lib/module_overrides" require "jam_ruby/lib/s3_util" require "jam_ruby/lib/s3_manager" diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 8874a66fe..887ebb1e0 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -19,6 +19,10 @@ module JamRuby @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end + def public_jamkazam_s3_manager + @public_s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_public, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + end + def finish(reason, detail) self.reason = reason self.detail = detail @@ -93,6 +97,7 @@ module JamRuby self.name = metadata["name"] || name if jam_track.new_record? + jam_track.id = "#{JamTrack.count + 1}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that jam_track.status = 'Staging' jam_track.metalocation = metalocation jam_track.original_artist = metadata["original_artist"] || original_artist @@ -228,7 +233,7 @@ module JamRuby part = potential_instrument_original if !part {instrument: instrument, - part: part} + part: part} end @@ -259,7 +264,7 @@ module JamRuby if stem_location bits = filename_no_ext[stem_location..-1].split('-') - bits.collect! {|bit| bit.strip} + bits.collect! { |bit| bit.strip } possible_instrument = nil possible_part = nil @@ -424,6 +429,15 @@ module JamRuby track["url_48"] = ogg_48000_s3_path track["md5_48"] = 'md5' track["length_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) @@ -456,15 +470,26 @@ module JamRuby jamkazam_s3_manager.upload(ogg_48000_s3_path, ogg_48000) + ogg_44100_digest = ::Digest::MD5.file(ogg_44100) # and finally update the JamTrackTrack with the new info track["url_44"] = ogg_44100_s3_path - track["md5_44"] = ::Digest::MD5.file(ogg_44100).hexdigest + track["md5_44"] = ogg_44100_digest.hexdigest track["length_44"] = File.new(ogg_44100).size track["url_48"] = ogg_48000_s3_path track["md5_48"] = ::Digest::MD5.file(ogg_48000).hexdigest track["length_48"] = File.new(ogg_48000).size + synchronize_duration(jam_track, ogg_44100) + + # convert entire master ogg file to mp3, and push both to public destination + preview_succeeded = synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_44100_digest) if track.track_type == 'Master' + + if !preview_succeeded + return false + end + + end track.save! @@ -478,6 +503,77 @@ module JamRuby return true end + def synchronize_duration(jam_track, ogg_44100) + duration_command = "soxi -D \"#{ogg_44100}\"" + output = `#{duration_command}` + + result_code = $?.to_i + + if result_code == 0 + duration = output.to_f.round + jam_track.duration = duration + else + @@log.warn("unable to determine duration for jam_track #{jam_track.name}. output #{output}") + end + true + end + + def synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_digest) + + begin + mp3_44100 = File.join(tmp_dir, 'output-preview-44100.mp3') + convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{ogg_44100}\" -ab 192k \"#{mp3_44100}\"" + @@log.debug("converting to mp3 using: " + convert_mp3_cmd) + + convert_output = `#{convert_mp3_cmd}` + + mp3_digest = ::Digest::MD5.file(mp3_44100) + + track["preview_md5"] = ogg_md5 = ogg_digest.hexdigest + track["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest + + # upload 44100 ogg and mp3 to public location as well + @@log.debug("uploading ogg preview to #{track.preview_filename('ogg')}") + public_jamkazam_s3_manager.upload(track.preview_filename(ogg_digest.hexdigest, 'ogg'), ogg_44100, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest) + @@log.debug("uploading mp3 preview to #{track.preview_filename('mp3')}") + public_jamkazam_s3_manager.upload(track.preview_filename(mp3_digest.hexdigest, 'mp3'), mp3_44100, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest) + + + track.skip_uploader = true + + original_ogg_preview_url = track["preview_url"] + original_mp3_preview_url = track["preview_mp3_url"] + + # and finally update the JamTrackTrack with the new info + track["preview_url"] = track.preview_filename(ogg_md5, 'ogg') + track["preview_length"] = File.new(ogg_44100).size + # and finally update the JamTrackTrack with the new info + track["preview_mp3_url"] = track.preview_filename(mp3_md5, 'mp3') + track["preview_mp3_length"] = File.new(mp3_44100).size + track["preview_start_time"] = 0 + + if !track.save + finish("save_master_preview", track.errors.to_s) + return false + end + + # if all that worked, now delete old previews, if present + begin + public_jamkazam_s3_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != track["preview_url"] + public_jamkazam_s3_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"] + rescue + puts "UNABLE TO CLEANUP OLD PREVIEW URL" + end + rescue Exception => e + finish("sync_master_preview_exception", e.to_s) + return false + end + + + return true + + end + def fetch_all_files(s3_path) JamTrackImporter::s3_manager.list_files(s3_path) end @@ -533,6 +629,10 @@ module JamRuby @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end + def private_s3_manager + @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + end + def dry_run s3_manager.list_directories('audio').each do |original_artist| @@ -554,6 +654,99 @@ module JamRuby end + def synchronize_jamtrack_master_preview(jam_track) + importer = JamTrackImporter.new + importer.name = jam_track.name + + master_track = jam_track.master_track + + if master_track + Dir.mktmpdir do |tmp_dir| + ogg_44100 = File.join(tmp_dir, 'input.ogg') + private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100) + ogg_44100_digest = ::Digest::MD5.file(ogg_44100) + if importer.synchronize_master_preview(master_track, tmp_dir, ogg_44100, ogg_44100_digest) + importer.finish("success", nil) + end + end + else + importer.finish('no_master_track', nil) + end + + importer + end + + def synchronize_jamtrack_master_previews + importers = [] + + JamTrack.all.each do |jam_track| + importers << synchronize_jamtrack_master_preview(jam_track) + end + + @@log.info("SUMMARY") + @@log.info("-------") + importers.each do |importer| + if importer + if importer.reason == "success" || importer.reason == "jam_track_exists" + @@log.info("#{importer.name} #{importer.reason}") + else + @@log.error("#{importer.name} failed to import.") + @@log.error("#{importer.name} reason=#{importer.reason}") + @@log.error("#{importer.name} detail=#{importer.detail}") + end + else + @@log.error("NULL IMPORTER") + end + + end + end + + def synchronize_duration(jam_track) + importer = JamTrackImporter.new + importer.name = jam_track.name + + master_track = jam_track.master_track + if master_track + Dir.mktmpdir do |tmp_dir| + ogg_44100 = File.join(tmp_dir, 'input.ogg') + private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100) + + if importer.synchronize_duration(jam_track, ogg_44100) + jam_track.save! + importer.finish("success", nil) + end + end + else + importer.finish('no_duration', nil) + end + + importer + end + + def synchronize_durations + importers = [] + + JamTrack.all.each do |jam_track| + importers << synchronize_duration(jam_track) + end + + @@log.info("SUMMARY") + @@log.info("-------") + importers.each do |importer| + if importer + if importer.reason == "success" || importer.reason == "jam_track_exists" + @@log.info("#{importer.name} #{importer.reason}") + else + @@log.error("#{importer.name} failed to import.") + @@log.error("#{importer.name} reason=#{importer.reason}") + @@log.error("#{importer.name} detail=#{importer.detail}") + end + else + @@log.error("NULL IMPORTER") + end + + end + end def synchronize_all(options) importers = [] @@ -607,14 +800,14 @@ module JamRuby def load_metalocation(metalocation) begin - data = s3_manager.read_all(metalocation) + data = s3_manager.read_all(metalocation) return YAML.load(data) rescue AWS::S3::Errors::NoSuchKey return nil end end - def create_from_metalocation(meta, metalocation, options = {skip_audio_upload:false}) + def create_from_metalocation(meta, metalocation, options = {skip_audio_upload: false}) jam_track = JamTrack.new sync_from_metadata(jam_track, meta, metalocation, options) end @@ -628,7 +821,7 @@ module JamRuby JamTrack.transaction do #begin - jam_track_importer.synchronize(jam_track, meta, metalocation, options) + jam_track_importer.synchronize(jam_track, meta, metalocation, options) #rescue Exception => e # jam_track_importer.finish("unhandled_exception", e.to_s) #end diff --git a/ruby/lib/jam_ruby/lib/s3_manager.rb b/ruby/lib/jam_ruby/lib/s3_manager.rb index 8f8eead79..cf86fdc9b 100644 --- a/ruby/lib/jam_ruby/lib/s3_manager.rb +++ b/ruby/lib/jam_ruby/lib/s3_manager.rb @@ -44,6 +44,10 @@ module JamRuby s3_bucket.objects[key].url_for(operation, options).to_s end + def public_url(key, options = @@def_opts) + s3_bucket.objects[key].public_url(options).to_s + end + def presigned_post(key, options = @@def_opts) s3_bucket.objects[key].presigned_post(options) end @@ -72,8 +76,15 @@ module JamRuby s3_bucket.objects[filename].delete end - def upload(key, filename) - s3_bucket.objects[key].write(:file => filename) + def upload(key, filename, options={}) + options[:file] = filename + s3_bucket.objects[key].write(options) + end + + def cached_upload(key, filename, options={}) + options[:file] = filename + options.merge({expires: 5.years.from_now}) + s3_bucket.objects[key].write(filename, options) end def delete_folder(folder) diff --git a/ruby/lib/jam_ruby/lib/s3_public_manager_mixin.rb b/ruby/lib/jam_ruby/lib/s3_public_manager_mixin.rb new file mode 100644 index 000000000..6eba1990a --- /dev/null +++ b/ruby/lib/jam_ruby/lib/s3_public_manager_mixin.rb @@ -0,0 +1,17 @@ +module JamRuby + module S3PublicManagerMixin + extend ActiveSupport::Concern + include AppConfig + + included do + end + + module ClassMethods + + end + + def s3_public_manager() + @s3_public_manager ||= S3Manager.new(app_config.aws_bucket_public, app_config.aws_access_key_id, app_config.aws_secret_access_key) + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 892285996..5426b08b7 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -37,6 +37,7 @@ module JamRuby validates :public_performance_royalty, inclusion: {in: [nil, true, false]} validates :reproduction_royalty, inclusion: {in: [nil, true, false]} validates :public_performance_royalty, inclusion: {in: [nil, true, false]} + validates :duration, numericality: {only_integer: true}, :allow_nil => true validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,3}$/ validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,3}$/ @@ -44,7 +45,7 @@ module JamRuby belongs_to :genre, class_name: "JamRuby::Genre" belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id' - has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC, part ASC, instrument_id ASC' + has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'track_type ASC, position ASC, part ASC, instrument_id ASC' has_many :jam_track_tap_ins, :class_name => "JamRuby::JamTrackTapIn", order: 'offset_time ASC' has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # ' @@ -98,6 +99,7 @@ module JamRuby warnings = [] warnings << 'POSITIONS' if duplicate_positions? warnings << 'PREVIEWS'if missing_previews? + warnings << 'DURATION' if duration.nil? warnings.join(',') end diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 36795a432..1bc2fa6b3 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -3,6 +3,7 @@ module JamRuby # describes an audio track (like the drums, or guitar) that comprises a JamTrack class JamTrackTrack < ActiveRecord::Base include JamRuby::S3ManagerMixin + include JamRuby::S3PublicManagerMixin # there should only be one Master per JamTrack, but there can be N Track per JamTrack TRACK_TYPE = %w{Track Master} @@ -41,9 +42,11 @@ module JamRuby "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}" end - # create name of the file - def preview_filename(ext='ogg') - filename("#{File.basename(self["url_44"], ".ogg")}-preview.#{ext}") + # create name of the preview file. + # md5-'ed because we cache forever + def preview_filename(md5, ext='ogg') + original_name = "#{File.basename(self["url_44"], ".ogg")}-preview-#{md5}.#{ext}" + "jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}" end def has_preview? @@ -51,15 +54,16 @@ module JamRuby end - # creates a short-lived URL that has access to the object. - # 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 preview_sign_url(expiration_time = 120, media_type='ogg') - s3_manager.sign_url(media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url], {:expires => expiration_time, :response_content_type => media_type == 'ogg' ? 'audio/ogg' : 'audio/mpeg', :secure => false}) + # generates a URL that points to a public version of the preview + def preview_public_url(media_type='ogg') + url = media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url] + if url + s3_public_manager.public_url(url,{ :secure => false}) + else + nil + end end - def manually_uploaded_filename(mounted_as) if track_type == 'Master' filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg") diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 73a57daad..bc065cf22 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -36,6 +36,7 @@ //= require jquery.custom-protocol //= require jquery.exists //= require jquery.payment +//= require howler.core.js //= require jstz //= require class //= require AAC_underscore diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 77d05cf7b..0181bd8af 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1434,6 +1434,15 @@ }); } + function getJamTrack(options) { + return $.ajax({ + type: "GET", + url: '/api/jamtracks/' + options['plan_code'] + '?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function getJamtracks(options) { return $.ajax({ type: "GET", @@ -1742,6 +1751,7 @@ this.createDiagnostic = createDiagnostic; this.getLatencyTester = getLatencyTester; this.updateAudioLatency = updateAudioLatency; + this.getJamTrack = getJamTrack; this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; this.getPaymentHistory = getPaymentHistory; diff --git a/web/app/assets/javascripts/jam_track_preview.js.coffee b/web/app/assets/javascripts/jam_track_preview.js.coffee new file mode 100644 index 000000000..07988cf4a --- /dev/null +++ b/web/app/assets/javascripts/jam_track_preview.js.coffee @@ -0,0 +1,125 @@ +$ = jQuery +context = window +context.JK ||= {}; + + +context.JK.JamTrackPreview = {} +context.JK.JamTrackPreview = class JamTrackPreview + constructor: (app, $root, jamTrack, jamTrackTrack, options) -> + @EVENTS = context.JK.EVENTS + @rest = context.JK.Rest() + @logger = context.JK.logger + @options = options || {master_shows_duration: false} + @app = app + @jamTrack = jamTrack + @jamTrackTrack = jamTrackTrack + @root = $root + @playButton = null + @stopButton = null + @instrumentIcon = null + @instrumentName = null + @part = null + + template = $('#template-jam-track-preview') + throw "no jam track preview template" if not template.exists() + + @root.html($(template.html())) + @playButton = @root.find('.play-button') + @stopButton = @root.find('.stop-button') + @instrumentIcon = @root.find('.instrument-icon') + @instrumentName = @root.find('.instrument-name') + @part = @root.find('.part') + + @playButton.on('click', @play) + @stopButton.on('click', @stop) + + @root.attr('data-track-type', @jamTrackTrack.track_type) + instrumentId = null + instrumentDescription = '?' + if @jamTrackTrack.track_type == 'Track' + if @jamTrackTrack.instrument + instrumentId = @jamTrackTrack.instrument.id + instrumentDescription = @jamTrackTrack.instrument.description + else + instrumentId = 'other' + instrumentDescription= 'Master Mix' + + instrument_src = context.JK.getInstrumentIcon24(instrumentId) + + @instrumentIcon.attr('data-instrument-id', instrumentId).attr('src', instrument_src) + @instrumentName.text(instrumentDescription) + #context.JK.bindInstrumentHover(@root) + + part = '' + + if @jamTrackTrack.track_type == 'Track' + part = "#{@jamTrackTrack.part}" if @jamTrackTrack.part? && @jamTrackTrack.part != instrumentDescription + + else + if @options.master_shows_duration + duration = 'entire song' + if @jamTrack.duration + duration = "0:00 - #{context.JK.prettyPrintSeconds(@jamTrack.duration)}" + part = duration + else + part = @jamTrack.name + ' by ' + @jamTrack.original_artist + + @part.text("(#{part})") if part != '' + + if @jamTrackTrack.preview_mp3_url? + + urls = [@jamTrackTrack.preview_mp3_url] + if @jamTrackTrack.preview_ogg_url? + urls.push(@jamTrackTrack.preview_ogg_url) + + @no_audio = false + @sound = new Howl({ + src: urls, + autoplay: false, + loop: false, + volume: 1.0, + onend: @onHowlerEnd}) + else + @no_audio = true + + if @no_audio + @playButton.addClass('disabled') + @stopButton.addClass('disabled') + + onHowlerEnd: () => + @logger.debug("on end $(this)", $(this)) + @stopButton.addClass('hidden') + @playButton.removeClass('hidden') + + play: (e) => + if e? + e.stopPropagation() + + if @no_audio + context.JK.prodBubble(@playButton, 'There is no preview available for this track.', {}, {duration:2000}) + else + logger.debug("play issued for jam track preview") + @sound.play() + @playButton.addClass('hidden') + @stopButton.removeClass('hidden') + + return false + + stop: (e) => + if e? + e.stopPropagation() + + if @no_audio + context.JK.helpBubble(@playButton, 'There is no preview available for this track.', {}, {duration:2000}) + else + logger.debug("stop issued for jam track preview") + @sound.stop() + @stopButton.addClass('hidden') + @playButton.removeClass('hidden') + + return false + + + + + diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 0f1c12a44..33ba2dd4e 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -128,12 +128,17 @@ } } else { - var $template = $('#template-help-' + templateName) - if($template.length == 0) { - var helpText = templateName; + try { + var $template = $('#template-help-' + templateName) + if ($template.length == 0) { + var helpText = templateName; + } + else { + var helpText = context._.template($template.html(), data, { variable: 'data' }); + } } - else { - var helpText = context._.template($template.html(), data, { variable: 'data' }); + catch(e) { + var helpText = templateName; } holder = $('
'); diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index 466533efd..eca70f736 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -20,6 +20,7 @@ //= require jquery.icheck //= require jquery.bt //= require jquery.exists +//= require howler.core.js //= require AAA_Log //= require AAC_underscore //= require alert @@ -52,6 +53,7 @@ //= require recording_utils //= require helpBubbleHelper //= require facebook_rest +//= require jam_track_preview //= require landing/init //= require landing/signup //= require web/downloads diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 4f94cb2b4..28e2f4ed5 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -71,4 +71,5 @@ *= require icheck/minimal/minimal *= require users/syncViewer *= require ./downloadJamTrack + *= require ./jamTrackPreview */ \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss index 69f430b7f..dbaaa80cf 100644 --- a/web/app/assets/stylesheets/client/common.css.scss +++ b/web/app/assets/stylesheets/client/common.css.scss @@ -318,3 +318,15 @@ $fair: #cc9900; } } +.white-bordered-button { + font-size:20px; + font-weight:bold; + background-color:white; + color:$ColorScreenPrimary; + border:3px solid $ColorScreenPrimary; + padding:18px; + -webkit-border-radius:8px; + -moz-border-radius:8px; + border-radius:8px; +} + diff --git a/web/app/assets/stylesheets/client/jamTrackPreview.css.scss b/web/app/assets/stylesheets/client/jamTrackPreview.css.scss new file mode 100644 index 000000000..28213d8b4 --- /dev/null +++ b/web/app/assets/stylesheets/client/jamTrackPreview.css.scss @@ -0,0 +1,35 @@ +@import "client/common"; + +.jam-track-preview { + + display:inline-block; + line-height:24px; + vertical-align: middle; + @include border_box_sizing; + color:$ColorTextTypical; + font-size:14px; + + .actions { + display:inline; + vertical-align: middle; + } + + img.instrument-icon { + display:inline; + vertical-align: middle; + margin-left:10px; + } + + .instrument-name { + display:inline; + vertical-align: middle; + margin-left:10px; + + } + + .part { + display:inline; + vertical-align: middle; + margin-left:4px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/landings/individual_song.css.scss b/web/app/assets/stylesheets/landings/individual_song.css.scss new file mode 100644 index 000000000..f35531d54 --- /dev/null +++ b/web/app/assets/stylesheets/landings/individual_song.css.scss @@ -0,0 +1,24 @@ +body.web.landing_jamtrack.individual { + + .jamtrack-reasons { + margin: 20px 0 0 20px; + } + + .white-bordered-button { + margin-top: 20px; + } + + .jam-track-preview-holder { + + margin-bottom: 15px; + float: left; + + &[data-track-type="Master"] { + width: 100%; + } + + &[data-track-type="Track"] { + width: 50%; + } + } +} \ 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 new file mode 100644 index 000000000..0d541f984 --- /dev/null +++ b/web/app/assets/stylesheets/landings/landing_page_new.css.scss @@ -0,0 +1,47 @@ +@import "client/common.css.scss"; + +body.web.landing_page { + + .two_by_two { + + h1 { + margin:0 0 10px; + } + .row { + @include border_box_sizing; + + + .column { + width:50%; + float:left; + @include border_box_sizing; + } + } + } + + &.landing_jamtrack { + + p, ul, li { + font-size:14px; + line-height:125%; + color:$ColorTextTypical; + } + p { + + } + ul { + + } + .video-container { + margin-top:0; + } + &.individual { + .jamtrack-reasons { + margin:20px 0 0 20px; + } + .white-bordered-button { + margin-top:20px; + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/web/web.css b/web/app/assets/stylesheets/web/web.css index 93d52b7c2..ac4463b15 100644 --- a/web/app/assets/stylesheets/web/web.css +++ b/web/app/assets/stylesheets/web/web.css @@ -2,6 +2,7 @@ *= require client/jamServer *= require client/ie *= require client/jamkazam +*= require jquery.bt *= require easydropdown *= require easydropdown_jk *= require client/screen_common @@ -14,6 +15,7 @@ *= require client/help *= require client/listenBroadcast *= require client/flash +*= require client/jamTrackPreview *= require web/main *= require web/footer *= require web/recordings @@ -24,6 +26,7 @@ *= require web/downloads *= require users/signinCommon *= require dialogs/dialog -*= require landings/landing_page +*= require client/help +*= require_directory ../landings *= require icheck/minimal/minimal */ \ No newline at end of file diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index f1b4264fa..e40598fd5 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -1,12 +1,16 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen - before_filter :api_signed_in_user, :except => [:index] - before_filter :api_any_user, :only => [:index] + before_filter :api_signed_in_user, :except => [:index, :show] + before_filter :api_any_user, :only => [:index, :show] before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right] respond_to :json + def show + @jam_track = JamTrack.find_by_plan_code!(params[:plan_code]) + end + def index data = JamTrack.index(params, any_user) @jam_tracks, @next = data[0], data[1] diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index 1268f730b..7ecd72208 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -65,5 +65,12 @@ class LandingsController < ApplicationController def watch_overview_tight render 'watch_overview_tight', layout: 'web' end + + def individual_song + gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil + + + render 'individual_song', layout: 'web' + end end diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index ab6da676d..6807ef6bd 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -53,6 +53,13 @@ class SpikesController < ApplicationController render :layout => 'web' end + def jam_track_preview + gon.jamTrackPlanCode = params[:plan_code] + + + render :layout => 'web' + end + def site_validate render :layout => 'web' end diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl index e4226b553..e616306e2 100644 --- a/web/app/views/api_jam_tracks/show.rabl +++ b/web/app/views/api_jam_tracks/show.rabl @@ -1,6 +1,6 @@ object @jam_track -attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version +attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version, :duration node :genres do |item| [item.genre.description] # XXX: need to return single genre; not array @@ -16,6 +16,13 @@ end child(:jam_track_tracks => :tracks) { attributes :id, :part, :instrument, :track_type + + node do |track| + { + preview_mp3_url: track.preview_public_url('mp3'), + preview_ogg_url: track.preview_public_url('ogg') + } + end } child(:licensor => :licensor) { diff --git a/web/app/views/clients/_jam_track_preview.html.slim b/web/app/views/clients/_jam_track_preview.html.slim new file mode 100644 index 000000000..f10de55c7 --- /dev/null +++ b/web/app/views/clients/_jam_track_preview.html.slim @@ -0,0 +1,10 @@ +script type="text/template" id='template-jam-track-preview' + .jam-track-preview + .actions + a.play-button href="#" + | Play + a.stop-button.hidden href="#" + | Stop + img.instrument-icon hoveraction="instrument" data-instrument-id="" width="24" height="24" + .instrument-name + .part \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 92b2111c5..3b28d1209 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -70,6 +70,7 @@ <%= render "listenBroadcast" %> <%= render "sync_viewer_templates" %> <%= render "download_jamtrack_templates" %> +<%= render "jam_track_preview" %> <%= render "help" %> <%= render 'dialogs/dialogs' %>
diff --git a/web/app/views/landings/individual_song.html.slim b/web/app/views/landings/individual_song.html.slim new file mode 100644 index 000000000..8f09b3fc4 --- /dev/null +++ b/web/app/views/landings/individual_song.html.slim @@ -0,0 +1,93 @@ +- provide(:page_name, 'landing_page full landing_jamtrack individual') + +.two_by_two + .row + .column + h1 + | Check Out Our  + strong.jamtrack_name + |  JamTrack + p Click the play buttons below to hear the master mix and each fully isolated track. All are included in each single JamTrack. + .previews + .column + h1 See What You Can Do With JamTracks + .video-wrapper + .video-container + iframe src="//www.youtube.com/embed/gAJAIHMyois" frameborder="0" allowfullscreen + br clear="all" + .row + .column + h1 + | Get Your First JamTrack Free Now! + p Click the GET A JAMTRACK FREE button below. Browse to find the one you want. Click Add to cart, and we'll apply a credit during checkout to make this first one free! We're confident you'll be back for more. + .browse-jamtracks-wrapper + a.white-bordered-button href="/client#/jamtrack" GET A JAMTRACK FREE! + .column + h1 Why Are JamTracks Different & Better? + p + | JamTracks are the best way to play with your favorite music.  + | Unlike traditional backing tracks, JamTracks are complete multitrack recordings,  + | with fully isolated tracks for each part. Used with the free JamKazam app/service, you can: + ul.jamtrack-reasons + li - Solo just the individual track you want to play to hear and learn it + li - Mute just the track you want to play, and play along with the rest + li - Make audio recordings and share them via Facebook or URL + li - Make video recordings and share them via YouTube or URL + li - And even go online to play JamTracks with others in real time! + br clear="all" + br clear="all" + +javascript: + (function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.IndividualSong = function (app) { + + var rest = context.JK.Rest(); + var logger = context.JK.logger; + var $page = null; + var $jamtrack_name = null; + var $previews = null; + + function fetchJamTrack() { + rest.getJamTrack({plan_code: gon.jam_track_plan_code}) + .done(function (jam_track) { + logger.debug("jam_track", jam_track) + + $jamtrack_name.text(jam_track.name); + + context._.each(jam_track.tracks, function (track) { + + var $element = $('
') + + $previews.append($element); + + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: true}) + }) + + $previews.append('
') + }) + .fail(function () { + app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) + }) + + } + function initialize() { + + $page = $('body') + $jamtrack_name = $page.find('.jamtrack_name') + $previews = $page.find('.previews') + + fetchJamTrack(); + } + + this.initialize = initialize; + } + })(window, jQuery); + + $(document).on('JAMKAZAM_READY', function(e, data) { + var song = new JK.IndividualSong(data.app); + song.initialize(); + }) diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 74c968812..ab11e217c 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -85,6 +85,7 @@ <%= render "clients/help" %> <%= render "clients/listenBroadcast" %> <%= render "clients/flash" %> + <%= render "clients/jam_track_preview" %> <%= render 'dialogs/dialogs' %> diff --git a/web/app/views/spikes/jam_track_preview.html.slim b/web/app/views/spikes/jam_track_preview.html.slim new file mode 100644 index 000000000..3ea46c7d3 --- /dev/null +++ b/web/app/views/spikes/jam_track_preview.html.slim @@ -0,0 +1,40 @@ + +- provide(:title, 'Jam Track Preview') + +.content-wrapper + h2 Jam Track Preview + + #players + + +javascript: + var initialized = false; + $(document).on('JAMKAZAM_READY', function(e, data) { + + var rest = JK.Rest(); + + if(gon.jamTrackPlanCode) { + rest.getJamTrack({plan_code: gon.jamTrackPlanCode}) + .done(function(jamTrack) { + var $players = $('#players') + + _.each(jamTrack.tracks, function(track) { + + var $element = $('
') + + $players.append($element); + + new JK.JamTrackPreview(data.app, $element, jamTrack, track, {master_shows_duration: true}) + }) + }) + .fail(function() { + alert("couldn't fetch jam track") + }) + + } + else { + alert("You need to add ?jam_track_plan_code=jamtracks-acdc-backinblack for this to work (or any jamtrack 'plancode')") + } + + + }) diff --git a/web/config/routes.rb b/web/config/routes.rb index 727d7c0fc..74ad6cd38 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -30,6 +30,7 @@ SampleApp::Application.routes.draw do match '/landing/kick2', to: 'landings#watch_overview_kick2', via: :get, as: 'landing_kick2' match '/landing/kick3', to: 'landings#watch_overview_kick3', via: :get, as: 'landing_kick3' match '/landing/kick4', to: 'landings#watch_overview_kick4', via: :get, as: 'landing_kick4' + match '/landing/jamtracks/:plan_code', to: 'landings#individual_song', via: :get, as: 'individual_song' # oauth match '/auth/:provider/callback', :to => 'sessions#oauth_callback' @@ -94,7 +95,8 @@ SampleApp::Application.routes.draw do match '/launch_app', to: 'spikes#launch_app' match '/websocket', to: 'spikes#websocket' match '/test_subscription', to: 'spikes#subscription' - match '/widgets/download_jam_track', to: 'spikes#download_jam_track' + match '/widgets/download_jam_track', to: 'spikes #download_jam_track' + match '/widgets/jam_track_preview', to: 'spikes#jam_track_preview' match '/site_validate', to: 'spikes#site_validate' match '/recording_source', to: 'spikes#recording_source' @@ -204,6 +206,7 @@ SampleApp::Application.routes.draw do match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list' # Jamtracks + match '/jamtracks/:plan_code' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show' match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list' match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased' match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake index 58b4dcf7b..a59549236 100644 --- a/web/lib/tasks/jam_tracks.rake +++ b/web/lib/tasks/jam_tracks.rake @@ -24,4 +24,56 @@ namespace :jam_tracks do JamTrackImporter.synchronize_all(skip_audio_upload:true) end + + + task sync_master_preview_all: :environment do |task, args| + importer = JamTrackImporter.synchronize_jamtrack_master_previews + end + + # syncs just one master track for a give JamTrack + task sync_master_preview: :environment do |task, args| + plan_code = ENV['PLAN_CODE'] + if !plan_code + puts "PLAN_CODE must be set to something like jamtrack-acdc-backinblack" + exit(1) + end + + jam_track = JamTrack.find_by_plan_code!(plan_code) + + importer = JamTrackImporter.synchronize_jamtrack_master_preview(jam_track) + + if importer.reason.nil? || importer.reason == "success" || importer.reason == "jam_track_exists" + puts("#{importer.name} #{importer.reason}") + else + puts("#{importer.name} failed to import.") + puts("#{importer.name} reason=#{importer.reason}") + puts("#{importer.name} detail=#{importer.detail}") + end + end + + + task sync_duration_all: :environment do |task, args| + importer = JamTrackImporter.synchronize_durations + end + + # syncs just one master track for a give JamTrack + task sync_duration: :environment do |task, args| + plan_code = ENV['PLAN_CODE'] + if !plan_code + puts "PLAN_CODE must be set to something like jamtrack-acdc-backinblack" + exit(1) + end + + jam_track = JamTrack.find_by_plan_code!(plan_code) + + importer = JamTrackImporter.synchronize_duration(jam_track) + + if importer.reason.nil? || importer.reason == "success" || importer.reason == "jam_track_exists" + puts("#{importer.name} #{importer.reason}") + else + puts("#{importer.name} failed to import.") + puts("#{importer.name} reason=#{importer.reason}") + puts("#{importer.name} detail=#{importer.detail}") + end + end end From 6f64b4e5c7a2ab69e792d0335cb7548fa4245cc6 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 Mar 2015 14:04:05 -0500 Subject: [PATCH 04/16] * add missing howler --- web/vendor/assets/javascripts/howler.core.js | 1651 ++++++++++++++++++ 1 file changed, 1651 insertions(+) create mode 100644 web/vendor/assets/javascripts/howler.core.js diff --git a/web/vendor/assets/javascripts/howler.core.js b/web/vendor/assets/javascripts/howler.core.js new file mode 100644 index 000000000..33b8bfbc3 --- /dev/null +++ b/web/vendor/assets/javascripts/howler.core.js @@ -0,0 +1,1651 @@ +/*! + * howler.js v2.0.0-beta + * howlerjs.com + * + * (c) 2013-2015, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +(function() { + + 'use strict'; + + // Setup our audio context. + var ctx = null; + var usingWebAudio = true; + var noAudio = false; + setupAudioContext(); + + // Create a master gain node. + if (usingWebAudio) { + var masterGain = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain(); + masterGain.gain.value = 1; + masterGain.connect(ctx.destination); + } + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Create the global controller. All contained methods and properties apply + * to all sounds that are currently playing or will be in the future. + */ + var HowlerGlobal = function() { + this.init(); + }; + HowlerGlobal.prototype = { + /** + * Initialize the global Howler object. + * @return {Howler} + */ + init: function() { + var self = this || Howler; + + // Internal properties. + self._codecs = {}; + self._howls = []; + self._muted = false; + self._volume = 1; + + // Set to false to disable the auto iOS enabler. + self.iOSAutoEnable = true; + + // No audio is available on this system if this is set to true. + self.noAudio = noAudio; + + // This will be true if the Web Audio API is available. + self.usingWebAudio = usingWebAudio; + + // Expose the AudioContext when using Web Audio. + self.ctx = ctx; + + // Check for supported codecs. + if (!noAudio) { + self._setupCodecs(); + } + + return self; + }, + + /** + * Get/set the global volume for all sounds. + * @param {Float} vol Volume from 0.0 to 1.0. + * @return {Howler/Float} Returns self or current volume. + */ + volume: function(vol) { + var self = this || Howler; + vol = parseFloat(vol); + + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + self._volume = vol; + + // When using Web Audio, we just need to adjust the master gain. + if (usingWebAudio) { + masterGain.gain.value = vol; + } + + // Loop through and change volume for all HTML5 audio nodes. + for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000; + var duration = ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek; + + // Create a timer to fire at the end of playback or the start of a new loop. + var ended = function() { + // Should this sound loop? + var loop = !!(sound._loop || self._sprite[sprite][2]); + + // Fire the ended event. + self._emit('end', sound._id); + + // Restart the playback for HTML5 Audio loop. + if (!self._webAudio && loop) { + self.stop(sound._id).play(sound._id); + } + + // Restart this timer if on a Web Audio loop. + if (self._webAudio && loop) { + self._emit('play', sound._id); + sound._seek = sound._start || 0; + sound._playStart = ctx.currentTime; + self._endTimers[sound._id] = setTimeout(ended, ((sound._stop - sound._start) * 1000) / Math.abs(self._rate)); + } + + // Mark the node as paused. + if (self._webAudio && !loop) { + sound._paused = true; + sound._ended = true; + sound._seek = sound._start || 0; + self._clearTimer(sound._id); + + // Clean up the buffer source. + sound._node.bufferSource = null; + } + + // When using a sprite, end the track. + if (!self._webAudio && !loop) { + self.stop(sound._id); + } + }; + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + + // Update the parameters of the sound + sound._paused = false; + sound._ended = false; + sound._sprite = sprite; + sound._seek = seek; + sound._start = self._sprite[sprite][0] / 1000; + sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; + sound._loop = !!(sound._loop || self._sprite[sprite][2]); + + // Begin the actual playback. + var node = sound._node; + if (self._webAudio) { + // Fire this when the sound is ready to play to begin Web Audio playback. + var playWebAudio = function() { + self._refreshBuffer(sound); + + // Setup the playback params. + var vol = (sound._muted || self._muted) ? 0 : sound._volume * Howler.volume(); + node.gain.setValueAtTime(vol, ctx.currentTime); + sound._playStart = ctx.currentTime; + + // Play the sound using the supported method. + if (typeof node.bufferSource.start === 'undefined') { + node.bufferSource.noteGrainOn(0, seek, duration); + } else { + node.bufferSource.start(0, seek, duration); + } + + // Start a new timer if none is present. + if (!self._endTimers[sound._id]) { + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + } + + if (!args[1]) { + setTimeout(function() { + self._emit('play', sound._id); + }, 0); + } + }; + + if (self._loaded) { + playWebAudio(); + } else { + // Wait for the audio to load and then begin playback. + self.once('load', playWebAudio); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } else { + // Fire this when the sound is ready to play to begin HTML5 Audio playback. + var playHtml5 = function() { + node.currentTime = seek; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.volume = sound._volume * Howler.volume(); + node.playbackRate = self._rate; + setTimeout(function() { + node.play(); + if (!args[1]) { + self._emit('play', sound._id); + } + }, 0); + }; + + // Play immediately if ready, or wait for the 'canplaythrough'e vent. + if (node.readyState === 4 || !node.readyState && navigator.isCocoonJS) { + playHtml5(); + } else { + var listener = function() { + // Setup the new end timer. + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + + // Begin playback. + playHtml5(); + + // Clear this listener. + node.removeEventListener('canplaythrough', listener, false); + }; + node.addEventListener('canplaythrough', listener, false); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } + + return sound._id; + }, + + /** + * Pause playback and save current position. + * @param {Number} id The sound ID (empty to pause all in group). + * @return {Howl} + */ + pause: function(id) { + var self = this; + + // Wait for the sound to begin playing before pausing it. + if (!self._loaded) { + self.once('play', function() { + self.pause(id); + }); + + return self; + } + + // If no id is passed, get all ID's to be paused. + var ids = self._getSoundIds(id); + + for (var i=0; i Returns the group's volume value. + * volume(id) -> Returns the sound id's current volume. + * volume(vol) -> Sets the volume of all sounds in this Howl group. + * volume(vol, id) -> Sets the volume of passed sound id. + * @return {Howl/Number} Returns self or current volume. + */ + volume: function() { + var self = this; + var args = arguments; + var vol, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the value of the groups' volume. + return self._volume; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new volume. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + vol = parseFloat(args[0]); + } + } else if (args.length === 2) { + vol = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the volume or return the current volume. + var sound; + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + // Wait for the sound to begin playing before changing the volume. + if (!self._loaded) { + self.once('play', function() { + self.volume.apply(self, args); + }); + + return self; + } + + // Set the group volume. + if (typeof id === 'undefined') { + self._volume = vol; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i 0 ? Math.ceil((end - ctx.currentTime) * 1000) : 0); + }.bind(self, ids[i], sound), len); + } else { + var diff = Math.abs(from - to); + var dir = from > to ? 'out' : 'in'; + var steps = diff / 0.01; + var stepLen = len / steps; + + (function() { + var vol = from; + var interval = setInterval(function(id) { + // Update the volume amount. + vol += (dir === 'in' ? 0.01 : -0.01); + + // Make sure the volume is in the right bounds. + vol = Math.max(0, vol); + vol = Math.min(1, vol); + + // Round to within 2 decimal points. + vol = Math.round(vol * 100) / 100; + + // Change the volume. + self.volume(vol, id); + + // When the fade is complete, stop it and fire event. + if (vol === to) { + clearInterval(interval); + self._emit('faded', id); + } + }.bind(self, ids[i]), stepLen); + })(); + } + } + } + + return self; + }, + + /** + * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments. + * loop() -> Returns the group's loop value. + * loop(id) -> Returns the sound id's loop value. + * loop(loop) -> Sets the loop value for all sounds in this Howl group. + * loop(loop, id) -> Sets the loop value of passed sound id. + * @return {Howl/Boolean} Returns self or current loop value. + */ + loop: function() { + var self = this; + var args = arguments; + var loop, id, sound; + + // Determine the values for loop and id. + if (args.length === 0) { + // Return the grou's loop value. + return self._loop; + } else if (args.length === 1) { + if (typeof args[0] === 'boolean') { + loop = args[0]; + self._loop = loop; + } else { + // Return this sound's loop value. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._loop : false; + } + } else if (args.length === 2) { + loop = args[0]; + id = parseInt(args[1], 10); + } + + // If no id is passed, get all ID's to be looped. + var ids = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current seek position. + * seek(id) -> Returns the sound id's current seek position. + * seek(seek) -> Sets the seek position of the first sound node. + * seek(seek, id) -> Sets the seek position of passed sound id. + * @return {Howl/Number} Returns self or the current seek position. + */ + seek: function() { + var self = this; + var args = arguments; + var seek, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current position of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new seek position. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + id = self._sounds[0]._id; + seek = parseFloat(args[0]); + } + } else if (args.length === 2) { + seek = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // If there is no ID, bail out. + if (typeof id === 'undefined') { + return self; + } + + // Wait for the sound to load before seeking it. + if (!self._loaded) { + self.once('load', function() { + self.seek.apply(self, args); + }); + + return self; + } + + // Get the sound. + var sound = self._soundById(id); + + if (sound) { + if (seek >= 0) { + // Pause the sound and update position for restarting playback. + var playing = self.playing(id); + if (playing) { + self.pause(id, true); + } + + // Move the position of the track and cancel timer. + sound._seek = seek; + self._clearTimer(id); + + // Restart the playback if the sound was playing. + if (playing) { + self.play(id, true); + } + } else { + if (self._webAudio) { + return (sound._seek + self.playing(id) ? ctx.currentTime - sound._playStart : 0); + } else { + return sound._node.currentTime; + } + } + } + + return self; + }, + + /** + * Check if a specific sound is currently playing or not. + * @param {Number} id The sound id to check. If none is passed, first sound is used. + * @return {Boolean} True if playing and false if not. + */ + playing: function(id) { + var self = this; + var sound = self._soundById(id) || self._sounds[0]; + + return sound ? !sound._paused : false; + }, + + /** + * Get the duration of this sound. + * @return {Number} Audio duration. + */ + duration: function() { + return this._duration; + }, + + /** + * Unload and destroy the current Howl object. + * This will immediately stop all sound instances attached to this group. + */ + unload: function() { + var self = this; + + // Stop playing any active sounds. + var sounds = self._sounds; + for (var i=0; i= 0) { + Howler._howls.splice(index, 1); + } + } + + // Delete this sound from the cache. + if (cache) { + delete cache[self._src]; + } + + // Clear out `self`. + self = null; + + return null; + }, + + /** + * Listen to a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to call. + * @param {Number} id (optional) Only listen to events for this sound. + * @return {Howl} + */ + on: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + + if (typeof fn === 'function') { + events.push({id: id, fn: fn}); + } + + return self; + }, + + /** + * Remove a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to remove. Leave empty to remove all. + * @param {Number} id (optional) Only remove events for this sound. + * @return {Howl} + */ + off: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + + if (fn) { + // Loop through event store and remove the passed function. + for (var i=0; i=0; i--) { + if (cnt <= limit) { + return; + } + + if (self._sounds[i]._ended) { + // Disconnect the audio source when using Web Audio. + if (self._webAudio && self._sounds[i]._node) { + self._sounds[i]._node.disconnect(0); + } + + // Remove sounds until we have the pool size. + self._sounds.splice(i, 1); + cnt--; + } + } + }, + + /** + * Get all ID's from the sounds pool. + * @param {Number} id Only return one ID if one is passed. + * @return {Array} Array of IDs. + */ + _getSoundIds: function(id) { + var self = this; + + if (typeof id === 'undefined') { + var ids = []; + for (var i=0; i> (-2 * bc & 6)) : 0 + ) { + buffer = chars.indexOf(buffer); + } + + return output; + }; + + // Decode the base64 data URI without XHR, since some browsers don't support it. + var data = atob(url.split(',')[1]); + var dataView = new Uint8Array(data.length); + for (var i=0; i Date: Tue, 31 Mar 2015 15:17:43 -0500 Subject: [PATCH 05/16] * VRFS-2870 - tests add for individual jam track song page --- ruby/lib/jam_ruby/models/jam_track_track.rb | 5 +- .../javascripts/jam_track_preview.js.coffee | 2 +- .../landings/individual_song.css.scss | 7 +- .../landings/landing_page_new.css.scss | 11 +- .../views/landings/individual_song.html.slim | 4 +- web/spec/features/individual_jamtrack_spec.rb | 103 ++++++++++++++++++ 6 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 web/spec/features/individual_jamtrack_spec.rb diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 1bc2fa6b3..41514b2f8 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -53,7 +53,6 @@ module JamRuby !self["preview_url"].nil? && !self['preview_mp3_url'].nil? end - # generates a URL that points to a public version of the preview def preview_public_url(media_type='ogg') url = media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url] @@ -72,6 +71,10 @@ module JamRuby end end + def master? + track_type == 'Master' + end + def url_by_sample_rate(sample_rate=48) field_name = (sample_rate==48) ? "url_48" : "url_44" self[field_name] diff --git a/web/app/assets/javascripts/jam_track_preview.js.coffee b/web/app/assets/javascripts/jam_track_preview.js.coffee index 07988cf4a..926a8d824 100644 --- a/web/app/assets/javascripts/jam_track_preview.js.coffee +++ b/web/app/assets/javascripts/jam_track_preview.js.coffee @@ -33,7 +33,7 @@ context.JK.JamTrackPreview = class JamTrackPreview @playButton.on('click', @play) @stopButton.on('click', @stop) - @root.attr('data-track-type', @jamTrackTrack.track_type) + @root.attr('data-track-type', @jamTrackTrack.track_type).attr('data-id', @jamTrackTrack.id) instrumentId = null instrumentDescription = '?' if @jamTrackTrack.track_type == 'Track' diff --git a/web/app/assets/stylesheets/landings/individual_song.css.scss b/web/app/assets/stylesheets/landings/individual_song.css.scss index f35531d54..4aebaa6c3 100644 --- a/web/app/assets/stylesheets/landings/individual_song.css.scss +++ b/web/app/assets/stylesheets/landings/individual_song.css.scss @@ -1,7 +1,10 @@ body.web.landing_jamtrack.individual { + .previews { + margin-top:10px; + } .jamtrack-reasons { - margin: 20px 0 0 20px; + margin: 10px 0 0 20px; } .white-bordered-button { @@ -10,7 +13,7 @@ body.web.landing_jamtrack.individual { .jam-track-preview-holder { - margin-bottom: 15px; + margin-bottom: 7px; float: left; &[data-track-type="Master"] { 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 0d541f984..7a9b6067c 100644 --- a/web/app/assets/stylesheets/landings/landing_page_new.css.scss +++ b/web/app/assets/stylesheets/landings/landing_page_new.css.scss @@ -21,6 +21,9 @@ body.web.landing_page { &.landing_jamtrack { + .landing-tag { + left:50%; + } p, ul, li { font-size:14px; line-height:125%; @@ -35,13 +38,5 @@ body.web.landing_page { .video-container { margin-top:0; } - &.individual { - .jamtrack-reasons { - margin:20px 0 0 20px; - } - .white-bordered-button { - margin-top:20px; - } - } } } \ No newline at end of file diff --git a/web/app/views/landings/individual_song.html.slim b/web/app/views/landings/individual_song.html.slim index 8f09b3fc4..12f7b4b3b 100644 --- a/web/app/views/landings/individual_song.html.slim +++ b/web/app/views/landings/individual_song.html.slim @@ -50,6 +50,7 @@ javascript: var $page = null; var $jamtrack_name = null; var $previews = null; + var $jamTracksButton = null; function fetchJamTrack() { rest.getJamTrack({plan_code: gon.jam_track_plan_code}) @@ -57,6 +58,7 @@ javascript: logger.debug("jam_track", jam_track) $jamtrack_name.text(jam_track.name); + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') context._.each(jam_track.tracks, function (track) { @@ -79,7 +81,7 @@ javascript: $page = $('body') $jamtrack_name = $page.find('.jamtrack_name') $previews = $page.find('.previews') - + $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') fetchJamTrack(); } diff --git a/web/spec/features/individual_jamtrack_spec.rb b/web/spec/features/individual_jamtrack_spec.rb new file mode 100644 index 000000000..938591040 --- /dev/null +++ b/web/spec/features/individual_jamtrack_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe "Home Screen", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + ShoppingCart.delete_all + JamTrackRight.delete_all + JamTrack.delete_all + JamTrackTrack.delete_all + JamTrackLicensor.delete_all + end + + let(:user) { FactoryGirl.create(:user) } + let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } + let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } + + let(:billing_info) { + { + first_name: 'Seth', + last_name: 'Call', + address1: '10704 Buckthorn Drive', + city: 'Austin', + state: 'Texas', + country: 'US', + zip: '78759', + number: '4111111111111111', + month: '08', + year: '2017', + verification_value: '012' + } + } + + def create_account(user, billing_info) + @recurlyClient.create_account(user, billing_info) + @created_accounts << user + end + + + before(:all) do + + @recurlyClient = RecurlyClient.new + @created_accounts = [] + + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') + + # make sure plans are there + @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) + @recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow) + end + + + after(:each) do + @created_accounts.each do |user| + if user.recurly_code + begin + @account = Recurly::Account.find(user.recurly_code) + if @account.present? + @account.destroy + end + rescue + end + end + end + end + + describe "AC/DC Back in Black" do + + it "logged out" do + visit "/landing/jamtracks/acdc-backinblack" + + find('h1', text: 'Check Out Our Back in Black JamTrack') + jamtrack_acdc_backinblack.jam_track_tracks.each do |track| + if track.master? + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:'Master Mix') + else + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) + end + end + find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + end + + it "logged in" do + fast_signin(user, "/landing/jamtracks/acdc-backinblack") + + find('h1', text: 'Check Out Our Back in Black JamTrack') + jamtrack_acdc_backinblack.jam_track_tracks.each do |track| + if track.master? + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:'Master Mix') + else + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) + end + end + find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + end + end +end From b1b2d3ad2425bd47370c240063adad3e351eb50a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 Mar 2015 16:17:17 -0500 Subject: [PATCH 06/16] * VRFS-2869 - band landing page --- ruby/lib/jam_ruby/models/jam_track.rb | 4 + web/app/assets/javascripts/jam_rest.js | 10 ++ ....css.scss => individual_jamtrack.css.scss} | 7 +- .../individual_jamtrack_band.css.scss | 32 ++++++ .../landings/landing_page_new.css.scss | 1 + .../controllers/api_jam_tracks_controller.rb | 8 +- web/app/controllers/landings_controller.rb | 9 +- web/app/views/api_jam_tracks/show.rabl | 2 +- .../api_jam_tracks/show_with_artist_info.rabl | 7 ++ ...tml.slim => individual_jamtrack.html.slim} | 8 +- .../individual_jamtrack_band.html.slim | 106 ++++++++++++++++++ web/config/routes.rb | 4 +- .../features/individual_jamtrack_band_spec.rb | 103 +++++++++++++++++ web/spec/features/individual_jamtrack_spec.rb | 2 +- 14 files changed, 291 insertions(+), 12 deletions(-) rename web/app/assets/stylesheets/landings/{individual_song.css.scss => individual_jamtrack.css.scss} (73%) create mode 100644 web/app/assets/stylesheets/landings/individual_jamtrack_band.css.scss create mode 100644 web/app/views/api_jam_tracks/show_with_artist_info.rabl rename web/app/views/landings/{individual_song.html.slim => individual_jamtrack.html.slim} (95%) create mode 100644 web/app/views/landings/individual_jamtrack_band.html.slim create mode 100644 web/spec/features/individual_jamtrack_band_spec.rb diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 5426b08b7..a4d293276 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -103,6 +103,10 @@ module JamRuby warnings.join(',') end + def band_jam_track_count + JamTrack.where(original_artist: original_artist).count + end + class << self # @return array[artist_name(string)] def all_artists diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 0181bd8af..43dff7227 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1443,6 +1443,15 @@ }); } + function getJamTrackWithArtistInfo(options) { + return $.ajax({ + type: "GET", + url: '/api/jamtracks/band/' + options['plan_code'] + '?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function getJamtracks(options) { return $.ajax({ type: "GET", @@ -1752,6 +1761,7 @@ this.getLatencyTester = getLatencyTester; this.updateAudioLatency = updateAudioLatency; this.getJamTrack = getJamTrack; + this.getJamTrackWithArtistInfo = getJamTrackWithArtistInfo; this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; this.getPaymentHistory = getPaymentHistory; diff --git a/web/app/assets/stylesheets/landings/individual_song.css.scss b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss similarity index 73% rename from web/app/assets/stylesheets/landings/individual_song.css.scss rename to web/app/assets/stylesheets/landings/individual_jamtrack.css.scss index 4aebaa6c3..6111876ee 100644 --- a/web/app/assets/stylesheets/landings/individual_song.css.scss +++ b/web/app/assets/stylesheets/landings/individual_jamtrack.css.scss @@ -1,4 +1,4 @@ -body.web.landing_jamtrack.individual { +body.web.landing_jamtrack.individual_jamtrack { .previews { margin-top:10px; @@ -11,6 +11,11 @@ body.web.landing_jamtrack.individual { margin-top: 20px; } + .browse-jamtracks-wrapper { + text-align:center; + width:90%; + } + .jam-track-preview-holder { margin-bottom: 7px; diff --git a/web/app/assets/stylesheets/landings/individual_jamtrack_band.css.scss b/web/app/assets/stylesheets/landings/individual_jamtrack_band.css.scss new file mode 100644 index 000000000..db25181b1 --- /dev/null +++ b/web/app/assets/stylesheets/landings/individual_jamtrack_band.css.scss @@ -0,0 +1,32 @@ +body.web.landing_jamtrack.individual_jamtrack_band { + + .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%; + } + + .jam-track-preview-holder { + + margin-bottom: 7px; + float: left; + + &[data-track-type="Master"] { + width: 100%; + } + + &[data-track-type="Track"] { + width: 50%; + } + } +} \ 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 7a9b6067c..d8938ec15 100644 --- a/web/app/assets/stylesheets/landings/landing_page_new.css.scss +++ b/web/app/assets/stylesheets/landings/landing_page_new.css.scss @@ -23,6 +23,7 @@ body.web.landing_page { .landing-tag { left:50%; + text-align:center; } p, ul, li { font-size:14px; diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index e40598fd5..fd8019462 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -1,8 +1,8 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen - before_filter :api_signed_in_user, :except => [:index, :show] - before_filter :api_any_user, :only => [:index, :show] + before_filter :api_signed_in_user, :except => [:index, :show, :show_with_artist_info] + before_filter :api_any_user, :only => [:index, :show, :show_with_artist_info] before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right] respond_to :json @@ -11,6 +11,10 @@ class ApiJamTracksController < ApiController @jam_track = JamTrack.find_by_plan_code!(params[:plan_code]) end + def show_with_artist_info + @jam_track = JamTrack.find_by_plan_code!(params[:plan_code]) + end + def index data = JamTrack.index(params, any_user) @jam_tracks, @next = data[0], data[1] diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index 7ecd72208..a8ba721be 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -66,11 +66,16 @@ class LandingsController < ApplicationController render 'watch_overview_tight', layout: 'web' end - def individual_song + def individual_jamtrack gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil + render 'individual_jamtrack', layout: 'web' + end - render 'individual_song', layout: 'web' + def individual_jamtrack_band + gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil + + render 'individual_jamtrack_band', layout: 'web' end end diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl index e616306e2..65d887ea8 100644 --- a/web/app/views/api_jam_tracks/show.rabl +++ b/web/app/views/api_jam_tracks/show.rabl @@ -1,6 +1,6 @@ object @jam_track -attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version, :duration +attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version node :genres do |item| [item.genre.description] # XXX: need to return single genre; not array diff --git a/web/app/views/api_jam_tracks/show_with_artist_info.rabl b/web/app/views/api_jam_tracks/show_with_artist_info.rabl new file mode 100644 index 000000000..76dc3b68b --- /dev/null +++ b/web/app/views/api_jam_tracks/show_with_artist_info.rabl @@ -0,0 +1,7 @@ +object @jam_track + +attributes :band_jam_track_count + +node do |jam_track| + partial "api_jam_tracks/show", object: @jam_track +end diff --git a/web/app/views/landings/individual_song.html.slim b/web/app/views/landings/individual_jamtrack.html.slim similarity index 95% rename from web/app/views/landings/individual_song.html.slim rename to web/app/views/landings/individual_jamtrack.html.slim index 12f7b4b3b..39bba98e6 100644 --- a/web/app/views/landings/individual_song.html.slim +++ b/web/app/views/landings/individual_jamtrack.html.slim @@ -1,4 +1,4 @@ -- provide(:page_name, 'landing_page full landing_jamtrack individual') +- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack') .two_by_two .row @@ -43,7 +43,7 @@ javascript: "use strict"; context.JK = context.JK || {}; - context.JK.IndividualSong = function (app) { + context.JK.IndividualJamTrack = function (app) { var rest = context.JK.Rest(); var logger = context.JK.logger; @@ -66,7 +66,7 @@ javascript: $previews.append($element); - new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: true}) + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) }) $previews.append('
') @@ -90,6 +90,6 @@ javascript: })(window, jQuery); $(document).on('JAMKAZAM_READY', function(e, data) { - var song = new JK.IndividualSong(data.app); + var song = new JK.IndividualJamTrack(data.app); song.initialize(); }) diff --git a/web/app/views/landings/individual_jamtrack_band.html.slim b/web/app/views/landings/individual_jamtrack_band.html.slim new file mode 100644 index 000000000..986e5c21a --- /dev/null +++ b/web/app/views/landings/individual_jamtrack_band.html.slim @@ -0,0 +1,106 @@ +- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack_band') + +.two_by_two + .row + .column + h1 + | We Have  + span.jamtrack_band_info + |   + span.jamtrack_noun JamTracks + span.check-it-out , Check One Out! + p Click the play buttons below to hear the master mix and each fully isolated track. All are included in each single JamTrack. + .previews + .column + h1 See What You Can Do With JamTracks + .video-wrapper + .video-container + iframe src="//www.youtube.com/embed/gAJAIHMyois" frameborder="0" allowfullscreen + br clear="all" + .row + .column + h1 + | Get Your First JamTrack Free Now! + p Click the GET A JAMTRACK FREE button below. Browse to find the one you want. Click Add to cart, and we'll apply a credit during checkout to make this first one free! We're confident you'll be back for more. + .browse-jamtracks-wrapper + a.white-bordered-button href="/client#/jamtrack" GET A JAMTRACK FREE! + .column + h1 Why Are JamTracks Different & Better? + p + | JamTracks are the best way to play with your favorite music.  + | Unlike traditional backing tracks, JamTracks are complete multitrack recordings,  + | with fully isolated tracks for each part. Used with the free JamKazam app/service, you can: + ul.jamtrack-reasons + li - Solo just the individual track you want to play to hear and learn it + li - Mute just the track you want to play, and play along with the rest + li - Make audio recordings and share them via Facebook or URL + li - Make video recordings and share them via YouTube or URL + li - And even go online to play JamTracks with others in real time! + br clear="all" + br clear="all" + +javascript: + (function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.IndividualJamTrackBand = function (app) { + + var rest = context.JK.Rest(); + var logger = context.JK.logger; + var $page = null; + var $jamTrackBandInfo = null; + var $jamTrackNoun = null; + var $previews = null; + var $jamTracksButton = null; + var $checkItOut = null; + + function fetchJamTrack() { + rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code}) + .done(function (jam_track) { + logger.debug("jam_track", jam_track) + + $jamTrackBandInfo.text(jam_track.band_jam_track_count + ' ' + jam_track.original_artist); + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + + if(jam_track.band_jam_track_count == 1) { + $jamTrackNoun.text('JamTrack') + $checkItOut.text(', Check It Out!') + } + context._.each(jam_track.tracks, function (track) { + + var $element = $('
') + + $previews.append($element); + + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) + }) + + $previews.append('
') + }) + .fail(function () { + app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) + }) + + } + function initialize() { + + $page = $('body') + $jamTrackBandInfo = $page.find('.jamtrack_band_info') + $previews = $page.find('.previews') + $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') + $jamTrackNoun = $page.find('.jamtrack_noun') + $checkItOut = $page.find('.check-it-out') + + fetchJamTrack(); + } + + this.initialize = initialize; + } + })(window, jQuery); + + $(document).on('JAMKAZAM_READY', function(e, data) { + var song = new JK.IndividualJamTrackBand(data.app); + song.initialize(); + }) diff --git a/web/config/routes.rb b/web/config/routes.rb index 74ad6cd38..15eeeb0ac 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -30,7 +30,8 @@ SampleApp::Application.routes.draw do match '/landing/kick2', to: 'landings#watch_overview_kick2', via: :get, as: 'landing_kick2' match '/landing/kick3', to: 'landings#watch_overview_kick3', via: :get, as: 'landing_kick3' match '/landing/kick4', to: 'landings#watch_overview_kick4', via: :get, as: 'landing_kick4' - match '/landing/jamtracks/:plan_code', to: 'landings#individual_song', via: :get, as: 'individual_song' + match '/landing/jamtracks/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack' + match '/landing/jamtracks/band/:plan_code', to: 'landings#individual_jamtrack_band', via: :get, as: 'individual_jamtrack_band' # oauth match '/auth/:provider/callback', :to => 'sessions#oauth_callback' @@ -207,6 +208,7 @@ SampleApp::Application.routes.draw do # Jamtracks match '/jamtracks/:plan_code' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show' + match '/jamtracks/band/:plan_code' => 'api_jam_tracks#show_with_artist_info', :via => :get, :as => 'api_jam_tracks_show_with_artist_info' match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list' match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased' match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' diff --git a/web/spec/features/individual_jamtrack_band_spec.rb b/web/spec/features/individual_jamtrack_band_spec.rb new file mode 100644 index 000000000..934762fbf --- /dev/null +++ b/web/spec/features/individual_jamtrack_band_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe "Individual JamTrack Band", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + ShoppingCart.delete_all + JamTrackRight.delete_all + JamTrack.delete_all + JamTrackTrack.delete_all + JamTrackLicensor.delete_all + end + + let(:user) { FactoryGirl.create(:user) } + let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } + let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } + + let(:billing_info) { + { + first_name: 'Seth', + last_name: 'Call', + address1: '10704 Buckthorn Drive', + city: 'Austin', + state: 'Texas', + country: 'US', + zip: '78759', + number: '4111111111111111', + month: '08', + year: '2017', + verification_value: '012' + } + } + + def create_account(user, billing_info) + @recurlyClient.create_account(user, billing_info) + @created_accounts << user + end + + + before(:all) do + + @recurlyClient = RecurlyClient.new + @created_accounts = [] + + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') + + # make sure plans are there + @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) + @recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow) + end + + + after(:each) do + @created_accounts.each do |user| + if user.recurly_code + begin + @account = Recurly::Account.find(user.recurly_code) + if @account.present? + @account.destroy + end + rescue + end + end + end + end + + describe "AC/DC Back in Black" do + + it "logged out" do + visit "/landing/jamtracks/band/acdc-backinblack" + + find('h1', text: "We Have 1 #{@jamtrack_acdc_backinblack.original_artist} JamTrack, Check It Out!") + jamtrack_acdc_backinblack.jam_track_tracks.each do |track| + if track.master? + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:'Master Mix') + else + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) + end + end + find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + end + + it "logged in" do + fast_signin(user, "/landing/jamtracks/band/acdc-backinblack") + + find('h1', text: "We Have 1 #{@jamtrack_acdc_backinblack.original_artist} JamTrack, Check It Out!") + jamtrack_acdc_backinblack.jam_track_tracks.each do |track| + if track.master? + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:'Master Mix') + else + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) + end + end + find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + end + end +end diff --git a/web/spec/features/individual_jamtrack_spec.rb b/web/spec/features/individual_jamtrack_spec.rb index 938591040..490fe9757 100644 --- a/web/spec/features/individual_jamtrack_spec.rb +++ b/web/spec/features/individual_jamtrack_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Home Screen", :js => true, :type => :feature, :capybara_feature => true do +describe "Individual JamTrack", :js => true, :type => :feature, :capybara_feature => true do subject { page } From 84ac9f049c29bab548972349fd436446a917e87d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 Mar 2015 16:25:10 -0500 Subject: [PATCH 07/16] * VRFS-2871 - generic jamtrack landing page --- web/app/controllers/landings_controller.rb | 2 +- .../landings/individual_jamtrack.html.slim | 18 +++++++++++++++--- web/spec/features/individual_jamtrack_spec.rb | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index a8ba721be..59714c31c 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -68,7 +68,7 @@ class LandingsController < ApplicationController def individual_jamtrack gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil - + gon.generic = params[:generic] render 'individual_jamtrack', layout: 'web' end diff --git a/web/app/views/landings/individual_jamtrack.html.slim b/web/app/views/landings/individual_jamtrack.html.slim index 39bba98e6..bcfdb1abb 100644 --- a/web/app/views/landings/individual_jamtrack.html.slim +++ b/web/app/views/landings/individual_jamtrack.html.slim @@ -3,10 +3,12 @@ .two_by_two .row .column - h1 + h1.hidden.individualized | Check Out Our  strong.jamtrack_name |  JamTrack + h1.hidden.generic + | We Have 100+ Amazing JamTracks, Check One Out! p Click the play buttons below to hear the master mix and each fully isolated track. All are included in each single JamTrack. .previews .column @@ -51,14 +53,22 @@ javascript: var $jamtrack_name = null; var $previews = null; var $jamTracksButton = null; + var $genericHeader = null; + var $individualizedHeader = null; function fetchJamTrack() { rest.getJamTrack({plan_code: gon.jam_track_plan_code}) .done(function (jam_track) { logger.debug("jam_track", jam_track) - $jamtrack_name.text(jam_track.name); - $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + if(gon.generic) { + $genericHeader.removeClass('hidden'); + } + else { + $individualizedHeader.removeClass('hidden') + $jamtrack_name.text(jam_track.name); + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + } context._.each(jam_track.tracks, function (track) { @@ -82,6 +92,8 @@ javascript: $jamtrack_name = $page.find('.jamtrack_name') $previews = $page.find('.previews') $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') + $genericHeader = $page.find('h1.generic') + $individualizedHeader = $page.find('h1.individualized') fetchJamTrack(); } diff --git a/web/spec/features/individual_jamtrack_spec.rb b/web/spec/features/individual_jamtrack_spec.rb index 490fe9757..1a516edc1 100644 --- a/web/spec/features/individual_jamtrack_spec.rb +++ b/web/spec/features/individual_jamtrack_spec.rb @@ -99,5 +99,21 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur end find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") end + + it "generic version" do + visit "/landing/jamtracks/acdc-backinblack?generic=true" + + find('h1', text: 'We Have 100+ Amazing JamTracks, Check One Out!') + jamtrack_acdc_backinblack.jam_track_tracks.each do |track| + if track.master? + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="other"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:'Master Mix') + else + find('.jam-track-preview-holder[data-id="' + track.id + '"] img.instrument-icon[data-instrument-id="' + track.instrument.id + '"]') + find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description) + end + end + find('a.white-bordered-button')['href'].should eq("/client#/jamtrack") + end end end From 546205b18669b4c6c2a4b77b516297e0e1b03879 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 31 Mar 2015 20:35:38 -0400 Subject: [PATCH 08/16] VRFS-2701 wip editing presence and samples --- .../assets/javascripts/accounts_profile.js | 56 +++--- .../javascripts/accounts_profile_samples.js | 44 +---- web/app/assets/javascripts/profile_utils.js | 24 +-- .../client/accountProfileSamples.css.scss | 3 +- web/app/assets/stylesheets/client/client.css | 1 - .../views/clients/_account_profile.html.erb | 171 +++++++++--------- 6 files changed, 137 insertions(+), 162 deletions(-) diff --git a/web/app/assets/javascripts/accounts_profile.js b/web/app/assets/javascripts/accounts_profile.js index b7189774a..1572d55a3 100644 --- a/web/app/assets/javascripts/accounts_profile.js +++ b/web/app/assets/javascripts/accounts_profile.js @@ -18,11 +18,19 @@ var nilOptionStr = ''; var nilOptionText = 'n/a'; var $screen = $('#account-profile-basics'); + var $avatar = $screen.find('#avatar'); + var $country = $screen.find('#country'); + var $region = $screen.find('#region'); + var $city = $screen.find('#city'); + var $firstName = $screen.find('#first-name'); + var $lastName = $screen.find('#last-name'); + var $gender = $screen.find('#gender'); + var $biography = $screen.find('#biography'); + var $subscribe = $screen.find('#subscribe'); + var $btnCancel = $screen.find('#account-edit-profile-cancel'); var $btnSubmit = $screen.find('#account-edit-profile-submit'); - var $biography = null; - function beforeShow(data) { userId = data.id; } @@ -39,27 +47,33 @@ } function populateAccountProfile(userDetail) { - var template = context.JK.fillTemplate($('#template-account-profile-basics').html(), { - country: userDetail.country, - region: userDetail.state, - city: userDetail.city, - first_name: userDetail.first_name, - last_name: userDetail.last_name, - photoUrl: context.JK.resolveAvatarUrl(userDetail.photo_url), - birth_date : userDetail.birth_date, - gender: userDetail.gender, - biography: userDetail.biography ? userDetail.biography : '', - subscribe_email: userDetail.subscribe_email ? "checked=checked" : "" - }); + // var template = context.JK.fillTemplate($('#template-account-profile-basics').html(), { + // country: userDetail.country, + // region: userDetail.state, + // city: userDetail.city, + // first_name: userDetail.first_name, + // last_name: userDetail.last_name, + // photoUrl: context.JK.resolveAvatarUrl(userDetail.photo_url), + // birth_date : userDetail.birth_date, + // gender: userDetail.gender, + // biography: userDetail.biography ? userDetail.biography : '', + // subscribe_email: userDetail.subscribe_email ? "checked=checked" : "" + // }); + + $avatar.attr('src', context.JK.resolveAvatarUrl(userDetail.photo_url)); + $country.val(userDetail.country); + $region.val(userDetail.state); + $city.val(userDetail.city); + $firstName.val(userDetail.first_name); + $lastName.val(userDetail.last_name); + $gender.val(userDetail.gender); + $biography.val(userDetail.biography); + + if (userDetail.subscribe_email) { + $subscribe.attr('checked', 'checked'); + } var content_root = $('#account-profile-content-scroller'); - content_root.html(template); - - $biography = $screen.find('#biography'); - - // now use javascript to fix up values too hard to do with templating - // set gender - $('select[name=gender]', content_root).val(userDetail.gender) // set birth_date if(userDetail.birth_date) { diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js index 8e96446b4..97a83ed51 100644 --- a/web/app/assets/javascripts/accounts_profile_samples.js +++ b/web/app/assets/javascripts/accounts_profile_samples.js @@ -137,24 +137,6 @@ return false; }); - $btnAddSoundCloudRecording.click(function(evt) { - var url = $soundCloudRecordingUrl.val(); - if (url.length > 0) { - if (extractSoundCloudUrlParts(url)) { - // add to list - } - } - }); - - $btnAddYouTubeVideo.click(function(evt) { - var url = $youTubeVideoUrl.val(); - if (url.length) { - if (extractYouTubeUrlParts(url)) { - // add to list - } - } - }); - $btnCancel.click(function(evt) { evt.stopPropagation(); navigateTo('/client#/profile/' + context.JK.currentUserId); @@ -176,22 +158,8 @@ } function validate() { - // website - if ($.trim($website.val()).length > 0) { - - } - - // SoundCloud - if ($.trim($soundCloudUsername.val()).length > 0) { - - } - - // ReverbNation - if ($.trim($reverbNationUsername.val())) { - - } - - return true; + var errors = $screen.find('.site_validator.error'); + return !(errors && errors.length > 0); } function navigateTo(targetLocation) { @@ -199,7 +167,7 @@ } function addOnlinePresence(presenceArray, username, type) { - if ($.trim($soundCloudUsername.val()).length > 0) { + if ($.trim(username).length > 0) { presenceArray.push({ service_type: type, username: username @@ -228,9 +196,9 @@ // extract performance samples var ps = []; var performanceSampleTypes = profileUtils.SAMPLE_TYPES; - addPerformanceSamples(ps, $jamkazamSampleList, performanceSampleTypes.JAMKAZAM); - addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD); - addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE); + addPerformanceSamples(ps, $jamkazamSampleList, performanceSampleTypes.JAMKAZAM.description); + addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description); + addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description); // api.updateUser({ // website: $website.val(), diff --git a/web/app/assets/javascripts/profile_utils.js b/web/app/assets/javascripts/profile_utils.js index edab5c367..79c12c2fc 100644 --- a/web/app/assets/javascripts/profile_utils.js +++ b/web/app/assets/javascripts/profile_utils.js @@ -18,14 +18,14 @@ var COWRITING_GENRE_TYPE = 'cowriting'; // performance sample types - var SAMPLE_TYPES = { + profileUtils.SAMPLE_TYPES = { JAMKAZAM: {description: "jamkazam"}, SOUNDCLOUD: {description: "soundcloud"}, YOUTUBE: {description: "youtube"} }; // online presence types - var ONLINE_PRESENCE_TYPES = { + profileUtils.ONLINE_PRESENCE_TYPES = { SOUNDCLOUD: {description: "soundcloud"}, REVERBNATION: {description: "reverbnation"}, BANDCAMP: {description: "bandcamp"}, @@ -176,7 +176,7 @@ profileUtils.jamkazamSamples = function(samples) { var matches = $.grep(samples, function(s) { - return s.service_type === SAMPLE_TYPES.JAMKAZAM.description; + return s.service_type === profileUtils.SAMPLE_TYPES.JAMKAZAM.description; }); return matches; @@ -184,7 +184,7 @@ profileUtils.soundCloudSamples = function(samples) { var matches = $.grep(samples, function(s) { - return s.service_type === SAMPLE_TYPES.SOUNDCLOUD.description; + return s.service_type === profileUtils.SAMPLE_TYPES.SOUNDCLOUD.description; }); return matches; @@ -192,7 +192,7 @@ profileUtils.youTubeSamples = function(samples) { var matches = $.grep(samples, function(s) { - return s.service_type === SAMPLE_TYPES.YOUTUBE.description; + return s.service_type === profileUtils.SAMPLE_TYPES.YOUTUBE.description; }); return matches; @@ -200,7 +200,7 @@ profileUtils.soundCloudPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.SOUNDCLOUD.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.SOUNDCLOUD.description; }); return matches; @@ -208,7 +208,7 @@ profileUtils.reverbNationPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.REVERBNATION.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.REVERBNATION.description; }); return matches; @@ -216,7 +216,7 @@ profileUtils.bandCampPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.BANDCAMP.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.BANDCAMP.description; }); return matches; @@ -224,7 +224,7 @@ profileUtils.fandalismPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.FANDALISM.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.FANDALISM.description; }); return matches; @@ -232,7 +232,7 @@ profileUtils.youTubePresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.YOUTUBE.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.YOUTUBE.description; }); return matches; @@ -240,7 +240,7 @@ profileUtils.facebookPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.FACEBOOK.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.FACEBOOK.description; }); return matches; @@ -248,7 +248,7 @@ profileUtils.twitterPresences = function(presences) { var matches = $.grep(presences, function(p) { - return p.service_type === ONLINE_PRESENCE_TYPES.TWITTER.description; + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.TWITTER.description; }); return matches; diff --git a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss index e5f1e310b..dfe7bc128 100644 --- a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss +++ b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss @@ -32,7 +32,8 @@ } .sample-list { - + height: inherit; + overflow: auto; } } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 28049797e..c48ea6bef 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -59,7 +59,6 @@ *= require ./checkout_signin *= require ./checkout_payment *= require ./checkout_order - *= require ./genreSelector *= require ./sessionList *= require ./searchResults *= require ./clientUpdate diff --git a/web/app/views/clients/_account_profile.html.erb b/web/app/views/clients/_account_profile.html.erb index 08312237b..7d6da9224 100644 --- a/web/app/views/clients/_account_profile.html.erb +++ b/web/app/views/clients/_account_profile.html.erb @@ -1,105 +1,98 @@
- +
- +
<%= image_tag "content/icon_account.png", {:width => 27, :height => 20} %>
- +

my account

<%= render "screen_navigation" %>
- -
-
- - From 9d4943d81400e183ee34081227c0e4dd26133b48 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 31 Mar 2015 20:42:57 -0400 Subject: [PATCH 09/16] VRFS-2701 fixed HTML --- web/app/assets/javascripts/accounts_profile.js | 12 ------------ web/app/views/clients/_account_profile.html.erb | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/web/app/assets/javascripts/accounts_profile.js b/web/app/assets/javascripts/accounts_profile.js index 1572d55a3..c791fb567 100644 --- a/web/app/assets/javascripts/accounts_profile.js +++ b/web/app/assets/javascripts/accounts_profile.js @@ -47,18 +47,6 @@ } function populateAccountProfile(userDetail) { - // var template = context.JK.fillTemplate($('#template-account-profile-basics').html(), { - // country: userDetail.country, - // region: userDetail.state, - // city: userDetail.city, - // first_name: userDetail.first_name, - // last_name: userDetail.last_name, - // photoUrl: context.JK.resolveAvatarUrl(userDetail.photo_url), - // birth_date : userDetail.birth_date, - // gender: userDetail.gender, - // biography: userDetail.biography ? userDetail.biography : '', - // subscribe_email: userDetail.subscribe_email ? "checked=checked" : "" - // }); $avatar.attr('src', context.JK.resolveAvatarUrl(userDetail.photo_url)); $country.val(userDetail.country); diff --git a/web/app/views/clients/_account_profile.html.erb b/web/app/views/clients/_account_profile.html.erb index 7d6da9224..a0f3cde6f 100644 --- a/web/app/views/clients/_account_profile.html.erb +++ b/web/app/views/clients/_account_profile.html.erb @@ -89,7 +89,7 @@
-
+
From 4c2d4958fedebeb7e3098f9243ecfd53639c7391 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 31 Mar 2015 20:25:23 -0500 Subject: [PATCH 10/16] VRFS-2894 Update email / user info in recurly upon finalize. Test to verify. --- ruby/lib/jam_ruby/models/user.rb | 5 ++++ ruby/lib/jam_ruby/recurly_client.rb | 5 ++++ ruby/spec/jam_ruby/models/user_spec.rb | 34 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index d41a68884..8cc54d7b4 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -868,6 +868,11 @@ module JamRuby user.email = user.update_email user.update_email_token = nil user.save + begin + RecurlyClient.new.update_account(user) + rescue Recurly::Error + @@log.debug("No recurly account found; continuing") + end return user end diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index 6b12332a0..516aa7235 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -22,6 +22,11 @@ module JamRuby account end + def has_account?(current_user) + account = get_account(current_user) + !!account + end + def delete_account(current_user) account = get_account(current_user) if (account) diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 6d51c9b6e..1d6b0f40f 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'jam_ruby/recurly_client' RESET_PASSWORD_URL = "/reset_token" @@ -9,6 +10,7 @@ describe User do @user = User.new(first_name: "Example", last_name: "User", email: "user@example.com", password: "foobar", password_confirmation: "foobar", city: "Apex", state: "NC", country: "US", terms_of_service: true, musician: true) @user.musician_instruments << FactoryGirl.build(:musician_instrument, user: @user) + @recurly = RecurlyClient.new end subject { @user } @@ -434,6 +436,8 @@ describe User do describe "finalize email update" do before do + @recurly.has_account?(@user).should == false + @user.begin_update_email("somenewemail@blah.com", "foobar", "http://www.jamkazam.com/confirm_email_update?token=") UserMailer.deliveries.clear end @@ -464,6 +468,36 @@ describe User do end end + describe "finalize email updates recurly" do + before do + + @user.begin_update_email("somenewemail@blah.com", "foobar", "http://www.jamkazam.com/confirm_email_update?token=") + UserMailer.deliveries.clear + billing_info = { + first_name: @user.first_name, + last_name: @user.last_name, + address1: 'Test Address 1', + address2: 'Test Address 2', + city: @user.city, + state: @user.state, + country: @user.country, + zip: '12345', + number: '4111-1111-1111-1111', + month: '08', + year: '2017', + verification_value: '111' + } + @recurly.find_or_create_account(@user, billing_info) + end + + it "should update recurly" do + @recurly.has_account?(@user).should == true + @recurly.get_account(@user).email.should_not == "somenewemail@blah.com" + @finalized = User.finalize_update_email(@user.update_email_token) + @recurly.get_account(@user).email.should == "somenewemail@blah.com" + end + end + describe "user_authorizations" do it "can create" do From 80a48c60c62d16166205650acdd509636f4e8e18 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 31 Mar 2015 21:08:09 -0500 Subject: [PATCH 11/16] * VRFS-2879 - adding 3 product pages (jamblaster, platform, jamtracks) --- web/app/assets/javascripts/session.js | 6 + .../javascripts/web/individual_jamtrack.js | 62 +++++++++ .../web/individual_jamtrack_band.js | 60 +++++++++ web/app/assets/javascripts/web/web.js | 2 + .../stylesheets/client/jamkazam.css.scss | 2 +- .../landings/landing_page_new.css.scss | 15 ++- .../landings/landing_product.css.scss | 53 ++++++++ web/app/controllers/landings_controller.rb | 20 +++ .../landings/individual_jamtrack.html.slim | 69 +--------- .../individual_jamtrack_band.html.slim | 70 +--------- .../landings/product_jamblaster.html.slim | 37 ++++++ .../landings/product_jamtracks.html.slim | 42 ++++++ .../views/landings/product_platform.html.slim | 40 ++++++ web/config/application.rb | 2 + web/config/routes.rb | 5 + .../features/individual_jamtrack_band_spec.rb | 9 +- web/spec/features/individual_jamtrack_spec.rb | 12 +- web/spec/features/products_spec.rb | 122 ++++++++++++++++++ 18 files changed, 489 insertions(+), 139 deletions(-) create mode 100644 web/app/assets/javascripts/web/individual_jamtrack.js create mode 100644 web/app/assets/javascripts/web/individual_jamtrack_band.js create mode 100644 web/app/assets/stylesheets/landings/landing_product.css.scss create mode 100644 web/app/views/landings/product_jamblaster.html.slim create mode 100644 web/app/views/landings/product_jamtracks.html.slim create mode 100644 web/app/views/landings/product_platform.html.slim create mode 100644 web/spec/features/products_spec.rb diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index db44a05fa..d3669b68d 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -2740,6 +2740,12 @@ logger.debug("Unstable clocks: ", names, unstable) context.JK.Banner.showAlert("Couldn't open metronome", context._.template($('#template-help-metronome-unstable').html(), {names: names}, { variable: 'data' })); } else { + var data = { + value: 1, + session_size: sessionModel.participants().length, + user_id: context.JK.currentUserId, + user_name: context.JK.currentUserName } + context.stats.write('web.metronome.open', {}) var bpm = 120; logger.debug("opening the metronome with bpm: " + bpm + ", sound:" + metroSound) rest.openMetronome({id: sessionModel.id()}) diff --git a/web/app/assets/javascripts/web/individual_jamtrack.js b/web/app/assets/javascripts/web/individual_jamtrack.js new file mode 100644 index 000000000..bbfd05896 --- /dev/null +++ b/web/app/assets/javascripts/web/individual_jamtrack.js @@ -0,0 +1,62 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.IndividualJamTrack = function (app) { + + var rest = context.JK.Rest(); + var logger = context.JK.logger; + var $page = null; + var $jamtrack_name = null; + var $previews = null; + var $jamTracksButton = null; + var $genericHeader = null; + var $individualizedHeader = null; + + function fetchJamTrack() { + rest.getJamTrack({plan_code: gon.jam_track_plan_code}) + .done(function (jam_track) { + logger.debug("jam_track", jam_track) + + if(!gon.just_previews) { + if (gon.generic) { + $genericHeader.removeClass('hidden'); + } + else { + $individualizedHeader.removeClass('hidden') + $jamtrack_name.text(jam_track.name); + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + } + } + + context._.each(jam_track.tracks, function (track) { + + var $element = $('
') + + $previews.append($element); + + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) + }) + + $previews.append('
') + }) + .fail(function () { + app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) + }) + + } + function initialize() { + + $page = $('body') + $jamtrack_name = $page.find('.jamtrack_name') + $previews = $page.find('.previews') + $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') + $genericHeader = $page.find('h1.generic') + $individualizedHeader = $page.find('h1.individualized') + fetchJamTrack(); + } + + this.initialize = initialize; + } +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/web/individual_jamtrack_band.js b/web/app/assets/javascripts/web/individual_jamtrack_band.js new file mode 100644 index 000000000..0de60a2e7 --- /dev/null +++ b/web/app/assets/javascripts/web/individual_jamtrack_band.js @@ -0,0 +1,60 @@ + +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.IndividualJamTrackBand = function (app) { + + var rest = context.JK.Rest(); + var logger = context.JK.logger; + var $page = null; + var $jamTrackBandInfo = null; + var $jamTrackNoun = null; + var $previews = null; + var $jamTracksButton = null; + var $checkItOut = null; + + function fetchJamTrack() { + rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code}) + .done(function (jam_track) { + logger.debug("jam_track", jam_track) + + $jamTrackBandInfo.text(jam_track.band_jam_track_count + ' ' + jam_track.original_artist); + $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') + + if(jam_track.band_jam_track_count == 1) { + $jamTrackNoun.text('JamTrack') + $checkItOut.text(', Check It Out!') + } + context._.each(jam_track.tracks, function (track) { + + var $element = $('
') + + $previews.append($element); + + new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) + }) + + $previews.append('
') + }) + .fail(function () { + app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) + }) + + } + function initialize() { + + $page = $('body') + $jamTrackBandInfo = $page.find('.jamtrack_band_info') + $previews = $page.find('.previews') + $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') + $jamTrackNoun = $page.find('.jamtrack_noun') + $checkItOut = $page.find('.check-it-out') + + fetchJamTrack(); + } + + this.initialize = initialize; + } +})(window, jQuery); diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index eca70f736..a36ad12e2 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -62,6 +62,8 @@ //= require web/session_info //= require web/recordings //= require web/welcome +//= require web/individual_jamtrack +//= require web/individual_jamtrack_band //= require fakeJamClient //= require fakeJamClientMessages //= require fakeJamClientRecordings diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index d1e2b4192..b898d2958 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -134,7 +134,7 @@ input[type="button"] { } .hidden { - display:none; + display:none !important; } .small { 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 d8938ec15..2fd40ea5f 100644 --- a/web/app/assets/stylesheets/landings/landing_page_new.css.scss +++ b/web/app/assets/stylesheets/landings/landing_page_new.css.scss @@ -5,11 +5,17 @@ body.web.landing_page { .two_by_two { h1 { - margin:0 0 10px; + margin:0 0 5px; + padding:7px 0; + display:inline-block; } .row { @include border_box_sizing; + &:nth-of-type(1) { + padding:20px 0 0 0; + } + .column { width:50%; @@ -19,7 +25,7 @@ body.web.landing_page { } } - &.landing_jamtrack { + &.landing_jamtrack, &.landing_product { .landing-tag { left:50%; @@ -34,7 +40,10 @@ body.web.landing_page { } ul { - + list-style-type: disc; + } + li { + margin-left:20px; } .video-container { margin-top:0; diff --git a/web/app/assets/stylesheets/landings/landing_product.css.scss b/web/app/assets/stylesheets/landings/landing_product.css.scss new file mode 100644 index 000000000..99739aa57 --- /dev/null +++ b/web/app/assets/stylesheets/landings/landing_product.css.scss @@ -0,0 +1,53 @@ +@import "client/common.css.scss"; + +body.web.landing_product { + + h1.product-headline { + color:white; + background-color:$ColorScreenPrimary; + margin-bottom:20px; + padding:7px; + border-radius:4px; + } + + .product-description { + margin-bottom:20px; + } + + .white-bordered-button { + margin-top: 20px; + } + + .cta-big-button { + text-align:center; + width:90%; + } + + .linked-video-holder { + text-align:center; + width:90%; + margin-top:20px; + } + + .previews { + margin-top:10px; + } + + .jamtrack-reasons { + margin-top:20px; + } + + .jam-track-preview-holder { + + margin-bottom: 7px; + float: left; + + &[data-track-type="Master"] { + width: 100%; + } + + &[data-track-type="Track"] { + width: 50%; + } + } +} \ No newline at end of file diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index 59714c31c..e2df670cc 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -77,5 +77,25 @@ class LandingsController < ApplicationController render 'individual_jamtrack_band', layout: 'web' end + + def product_jamblaster + render 'product_jamblaster', layout: 'web' + end + + def product_platform + render 'product_platform', layout: 'web' + end + + def product_jamtracks + gon.generic = true + gon.just_previews = true + jam_track = JamTrack.select('plan_code').where(plan_code: Rails.application.config.nominated_jam_track).first + unless jam_track + jam_track = JamTrack.first + end + + gon.jam_track_plan_code = jam_track.plan_code + render 'product_jamtracks', layout: 'web' + end end diff --git a/web/app/views/landings/individual_jamtrack.html.slim b/web/app/views/landings/individual_jamtrack.html.slim index bcfdb1abb..ad52b67f3 100644 --- a/web/app/views/landings/individual_jamtrack.html.slim +++ b/web/app/views/landings/individual_jamtrack.html.slim @@ -31,75 +31,16 @@ | Unlike traditional backing tracks, JamTracks are complete multitrack recordings,  | with fully isolated tracks for each part. Used with the free JamKazam app/service, you can: ul.jamtrack-reasons - li - Solo just the individual track you want to play to hear and learn it - li - Mute just the track you want to play, and play along with the rest - li - Make audio recordings and share them via Facebook or URL - li - Make video recordings and share them via YouTube or URL - li - And even go online to play JamTracks with others in real time! + li Solo just the individual track you want to play to hear and learn it + li Mute just the track you want to play, and play along with the rest + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube or URL + li And even go online to play JamTracks with others in real time! br clear="all" br clear="all" javascript: - (function (context, $) { - "use strict"; - - context.JK = context.JK || {}; - context.JK.IndividualJamTrack = function (app) { - - var rest = context.JK.Rest(); - var logger = context.JK.logger; - var $page = null; - var $jamtrack_name = null; - var $previews = null; - var $jamTracksButton = null; - var $genericHeader = null; - var $individualizedHeader = null; - - function fetchJamTrack() { - rest.getJamTrack({plan_code: gon.jam_track_plan_code}) - .done(function (jam_track) { - logger.debug("jam_track", jam_track) - - if(gon.generic) { - $genericHeader.removeClass('hidden'); - } - else { - $individualizedHeader.removeClass('hidden') - $jamtrack_name.text(jam_track.name); - $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') - } - - context._.each(jam_track.tracks, function (track) { - - var $element = $('
') - - $previews.append($element); - - new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) - }) - - $previews.append('
') - }) - .fail(function () { - app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) - }) - - } - function initialize() { - - $page = $('body') - $jamtrack_name = $page.find('.jamtrack_name') - $previews = $page.find('.previews') - $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') - $genericHeader = $page.find('h1.generic') - $individualizedHeader = $page.find('h1.individualized') - fetchJamTrack(); - } - - this.initialize = initialize; - } - })(window, jQuery); $(document).on('JAMKAZAM_READY', function(e, data) { var song = new JK.IndividualJamTrack(data.app); diff --git a/web/app/views/landings/individual_jamtrack_band.html.slim b/web/app/views/landings/individual_jamtrack_band.html.slim index 986e5c21a..dad0f43c3 100644 --- a/web/app/views/landings/individual_jamtrack_band.html.slim +++ b/web/app/views/landings/individual_jamtrack_band.html.slim @@ -31,75 +31,15 @@ | Unlike traditional backing tracks, JamTracks are complete multitrack recordings,  | with fully isolated tracks for each part. Used with the free JamKazam app/service, you can: ul.jamtrack-reasons - li - Solo just the individual track you want to play to hear and learn it - li - Mute just the track you want to play, and play along with the rest - li - Make audio recordings and share them via Facebook or URL - li - Make video recordings and share them via YouTube or URL - li - And even go online to play JamTracks with others in real time! + li Solo just the individual track you want to play to hear and learn it + li Mute just the track you want to play, and play along with the rest + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube or URL + li And even go online to play JamTracks with others in real time! br clear="all" br clear="all" javascript: - (function (context, $) { - - "use strict"; - - context.JK = context.JK || {}; - context.JK.IndividualJamTrackBand = function (app) { - - var rest = context.JK.Rest(); - var logger = context.JK.logger; - var $page = null; - var $jamTrackBandInfo = null; - var $jamTrackNoun = null; - var $previews = null; - var $jamTracksButton = null; - var $checkItOut = null; - - function fetchJamTrack() { - rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code}) - .done(function (jam_track) { - logger.debug("jam_track", jam_track) - - $jamTrackBandInfo.text(jam_track.band_jam_track_count + ' ' + jam_track.original_artist); - $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack') - - if(jam_track.band_jam_track_count == 1) { - $jamTrackNoun.text('JamTrack') - $checkItOut.text(', Check It Out!') - } - context._.each(jam_track.tracks, function (track) { - - var $element = $('
') - - $previews.append($element); - - new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false}) - }) - - $previews.append('
') - }) - .fail(function () { - app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) - }) - - } - function initialize() { - - $page = $('body') - $jamTrackBandInfo = $page.find('.jamtrack_band_info') - $previews = $page.find('.previews') - $jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') - $jamTrackNoun = $page.find('.jamtrack_noun') - $checkItOut = $page.find('.check-it-out') - - fetchJamTrack(); - } - - this.initialize = initialize; - } - })(window, jQuery); - $(document).on('JAMKAZAM_READY', function(e, data) { var song = new JK.IndividualJamTrackBand(data.app); song.initialize(); diff --git a/web/app/views/landings/product_jamblaster.html.slim b/web/app/views/landings/product_jamblaster.html.slim new file mode 100644 index 000000000..c31e76678 --- /dev/null +++ b/web/app/views/landings/product_jamblaster.html.slim @@ -0,0 +1,37 @@ +- provide(:page_name, 'landing_page full landing_product product_jamblaster') + +.two_by_two + .row + .column + h1.product-headline + | The JamBlaster by JamKazam + p.product-description + | The JamBlaster is a device designed from the ground up to meet the unique requirements of real-time, online, distributed music performance. This device vastly extends the range/distance over which musicians can play together across the Internet. + ul + li Radically reduces audio processing latency compared to today's industry standard computers and audio interfaces. + li Delivers plug-and-play ease of use, with no worries about hardware and software incompatibilities, driver problems, and arcane configurations. + li Combines both a computer and an audio interface into a single elegant device. + li Works with computers (even old crappy ones), tablets or smartphones. + li Works with your favorite recording software applications like Garage Band, Reaper, Pro Tools, etc. + .column + h1 See What You Can Do With The JamBlaster + .video-wrapper + .video-container + iframe src="//www.youtube.com/embed/2Zk7-04IAx4" frameborder="0" allowfullscreen + br clear="all" + .row + .column + h1 + | Want a JamBlaster? Need One? + p If you are a registered member of the JamKazam community, and if you "know" you will buy a JamBlaster for $199 as soon as they become available, then click the button below to add yourself to our wait list. When we get enough "virtual orders", we'll reach back out to all signups to take real orders. + + .cta-big-button + a.white-bordered-button href="#" SIGN UP TO BUY A JAMBLASTER + .column + h1 Want To Know More About Latency? + p + | How is it possible that someone hundreds of miles away could feel like they are 20 feet away from you? Check out this video on latency to understand more: + .linked-video-holder + a href="https://www.youtube.com/watch?v=mB3_KMse-J4" rel="external" Watch Video About Latency - It's Pretty Fascinating + br clear="all" + br clear="all" \ No newline at end of file diff --git a/web/app/views/landings/product_jamtracks.html.slim b/web/app/views/landings/product_jamtracks.html.slim new file mode 100644 index 000000000..a20b16584 --- /dev/null +++ b/web/app/views/landings/product_jamtracks.html.slim @@ -0,0 +1,42 @@ +- provide(:page_name, 'landing_page full landing_product product_jamtracks') + +.two_by_two + .row + .column + h1.product-headline + | JamTracks by JamKazam + p.product-description + | We have 100+ amazing JamTracks. Click the play buttons to hear the master mix and fully isolated track. All are included in each JamTrack. + .previews + .column + h1 See What You Can With JamTracks + .video-wrapper + .video-container + iframe src="//www.youtube.com/embed/2Zk7-04IAx4" frameborder="0" allowfullscreen + br clear="all" + .row + .column + h1 + | Get Your First JamTrack Free Now! + p Click the GET A JAMTRACK FREE button below. Browse to find the one you want, click the Add to cart, and we'll apply a credit during checkout to make the first one free! We're confident you'll be back for more. + + .cta-big-button + a.white-bordered-button href="/client#/jamtrack" GET A JAMTRACK FREE! + .column + h1 Why are JamTracks Better than Backing Tracks? + p + | JamTracks are the best way to play with your favorite music. Unlike traditional backing tracks, JamTracks are complete multitrack recordings, with fully isolated tracks for each part. Used with the free JamKazam app/service, you can: + ul.jamtrack-reasons + li Solo just the individual track you want to play to hear and learn it + li Mute just the track you want to play, and play along with the rest + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube or URL + li And even go online to play JamTracks with others in real time! + br clear="all" + br clear="all" + +javascript: + $(document).on('JAMKAZAM_READY', function (e, data) { + var song = new JK.IndividualJamTrack(data.app); + song.initialize(); + }) \ No newline at end of file diff --git a/web/app/views/landings/product_platform.html.slim b/web/app/views/landings/product_platform.html.slim new file mode 100644 index 000000000..16c2f351c --- /dev/null +++ b/web/app/views/landings/product_platform.html.slim @@ -0,0 +1,40 @@ +- provide(:page_name, 'landing_page full landing_product product_platform') + +.two_by_two + .row + .column + h1.product-headline + | The JamKazam Platform + p.product-description + | JamKazam is an innovative live music platform and social network, enabling musicians to play music together in real time from different locations over the internet as if they are sitting in the same room. The core platform is free to use and delivers immense value: + ul + li Play music from home with your friends and bandmates without packing and transporting gear, and without needing a rehearsal space + li Connect with new musician friends from our community of thousands of musicians to play more often, explore new styles, learn from others + li Find musicians to join your band, or find a band to join, either virtual on JamKazam, or real world to meet and play in person + li Schedule sessions or jump into ad hoc jams + li Make and share recordings or session performances via Facebook or URL + li Live broadcast sessions to family, friends, and fans + li List your band for hire to play gigs at clubs and events + li List yourself for hire to play studio sessions or lay down recorded tracks remotely + .column + h1 See What You Can Do With JamKazam + .video-wrapper + .video-container + iframe src="//www.youtube.com/embed/ylYcvTY9CVo" frameborder="0" allowfullscreen + br clear="all" + .row + .column + h1 + | Sign Up for JamKazam Now, It's Free! + p Yep, seriously. Sign up and start playing music online in real time with your friends - or make new ones from our community of thousands of musicians. It's free to play with others as much as you want. + + .cta-big-button + a.white-bordered-button href="/signup" SIGN UP NOW FOR YOUR FREE ACCOUNT + .column + h1 Does This Really Work? + p + | Feeling skeptical about whether this can actually work? That's natural. We'd encourage you to watch a video of endorsements and kudos from just a few of the musicians who use JamKazam. + .linked-video-holder + a href="https://www.youtube.com/watch?v=_7qj5RXyHCo" rel="external" Check Out Endorsements Of Real Users + br clear="all" + br clear="all" diff --git a/web/config/application.rb b/web/config/application.rb index aac520b7d..6ded2fb75 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -318,5 +318,7 @@ if defined?(Bundler) config.metronome_available = true config.backing_tracks_available = true config.one_free_jamtrack_per_user = true + + config.nominated_jam_track = 'jamtrack-pearljam-alive' end end diff --git a/web/config/routes.rb b/web/config/routes.rb index 15eeeb0ac..aa0b96d70 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -33,6 +33,11 @@ SampleApp::Application.routes.draw do match '/landing/jamtracks/:plan_code', to: 'landings#individual_jamtrack', via: :get, as: 'individual_jamtrack' match '/landing/jamtracks/band/:plan_code', to: 'landings#individual_jamtrack_band', via: :get, as: 'individual_jamtrack_band' + # product pages + match '/products/jamblaster', to: 'landings#product_jamblaster', via: :get, as: 'product_jamblaster' + match '/products/platform', to: 'landings#product_platform', via: :get, as: 'product_platform' + match '/products/jamtracks', to: 'landings#product_jamtracks', via: :get, as: 'product_jamtracks' + # oauth match '/auth/:provider/callback', :to => 'sessions#oauth_callback' match '/auth/failure', :to => 'sessions#failure' diff --git a/web/spec/features/individual_jamtrack_band_spec.rb b/web/spec/features/individual_jamtrack_band_spec.rb index 934762fbf..5db9ac971 100644 --- a/web/spec/features/individual_jamtrack_band_spec.rb +++ b/web/spec/features/individual_jamtrack_band_spec.rb @@ -14,7 +14,6 @@ describe "Individual JamTrack Band", :js => true, :type => :feature, :capybara_f let(:user) { FactoryGirl.create(:user) } let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } - let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } let(:billing_info) { { @@ -44,11 +43,9 @@ describe "Individual JamTrack Band", :js => true, :type => :feature, :capybara_f @created_accounts = [] @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') - @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') # make sure plans are there @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) - @recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow) end @@ -82,6 +79,9 @@ describe "Individual JamTrack Band", :js => true, :type => :feature, :capybara_f end end find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + + find('a.white-bordered-button').trigger(:click) + find('h1', text: 'jamtracks') end it "logged in" do @@ -98,6 +98,9 @@ describe "Individual JamTrack Band", :js => true, :type => :feature, :capybara_f end end find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + + find('a.white-bordered-button').trigger(:click) + find('h1', text: 'jamtracks') end end end diff --git a/web/spec/features/individual_jamtrack_spec.rb b/web/spec/features/individual_jamtrack_spec.rb index 1a516edc1..7aa9db507 100644 --- a/web/spec/features/individual_jamtrack_spec.rb +++ b/web/spec/features/individual_jamtrack_spec.rb @@ -14,7 +14,6 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur let(:user) { FactoryGirl.create(:user) } let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } - let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } let(:billing_info) { { @@ -44,11 +43,9 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur @created_accounts = [] @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') - @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') # make sure plans are there @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) - @recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow) end @@ -82,6 +79,9 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur end end find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + + find('a.white-bordered-button').trigger(:click) + find('h1', text: 'jamtracks') end it "logged in" do @@ -98,6 +98,9 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur end end find('a.white-bordered-button')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack") + + find('a.white-bordered-button').trigger(:click) + find('h1', text: 'jamtracks') end it "generic version" do @@ -114,6 +117,9 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur end end find('a.white-bordered-button')['href'].should eq("/client#/jamtrack") + + find('a.white-bordered-button').trigger(:click) + find('h1', text: 'jamtracks') end end end diff --git a/web/spec/features/products_spec.rb b/web/spec/features/products_spec.rb new file mode 100644 index 000000000..634d768f3 --- /dev/null +++ b/web/spec/features/products_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe "Product Pages", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + ShoppingCart.delete_all + JamTrackRight.delete_all + JamTrack.delete_all + JamTrackTrack.delete_all + JamTrackLicensor.delete_all + end + + let(:user) { FactoryGirl.create(:user) } + let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } + + let(:billing_info) { + { + first_name: 'Seth', + last_name: 'Call', + address1: '10704 Buckthorn Drive', + city: 'Austin', + state: 'Texas', + country: 'US', + zip: '78759', + number: '4111111111111111', + month: '08', + year: '2017', + verification_value: '012' + } + } + + def create_account(user, billing_info) + @recurlyClient.create_account(user, billing_info) + @created_accounts << user + end + + + before(:all) do + + @recurlyClient = RecurlyClient.new + @created_accounts = [] + + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + + # make sure plans are there + @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) + end + + + after(:each) do + @created_accounts.each do |user| + if user.recurly_code + begin + @account = Recurly::Account.find(user.recurly_code) + if @account.present? + @account.destroy + end + rescue + end + end + end + end + + describe "JamBlaster" do + it "logged out" do + visit "/products/jamblaster" + + find('h1', text: 'The JamBlaster by JamKazam') + find('a.white-bordered-button')['href'].should eq("#") # nowhere to go yet + end + + it "logged in" do + fast_signin(user, "/products/jamblaster") + + find('h1', text: 'The JamBlaster by JamKazam') + find('a.white-bordered-button')['href'].should eq("#") # nowhere to go yet + end + end + + describe "Platform" do + it "logged out" do + visit "/products/platform" + + find('h1', text: 'The JamKazam Platform') + find('a.white-bordered-button').trigger(:click) + + find('h2', text: 'Create your free JamKazam account') + end + + it "logged in" do + fast_signin(user, "/products/platform") + + find('h1', text: 'The JamKazam Platform') + find('a.white-bordered-button').trigger(:click) + + # clicking /signup just redirects you to the client + find('h2', text: 'create session') + end + end + + describe "JamTracks" do + it "logged out" do + visit "/products/jamtracks" + + find('h1', text: 'JamTracks by JamKazam') + find('a.white-bordered-button').trigger(:click) + + find('h1', text: 'jamtracks') + end + + it "logged in" do + fast_signin(user, "/products/jamtracks") + + find('h1', text: 'JamTracks by JamKazam') + find('a.white-bordered-button').trigger(:click) + + find('h1', text: 'jamtracks') + end + end +end From 03f94480e76c8ac90bd403ee25b0e0b2aedafcfa Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 31 Mar 2015 22:32:41 -0400 Subject: [PATCH 12/16] VRFS-2701 wip adding recording sources --- .../javascripts/accounts_profile_samples.js | 28 ++++----- .../javascripts/site_validator.js.coffee | 3 + .../client/accountProfileSamples.css.scss | 6 +- .../clients/_account_profile_samples.html.erb | 58 +++++++++++-------- 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js index 97a83ed51..e884d3549 100644 --- a/web/app/assets/javascripts/accounts_profile_samples.js +++ b/web/app/assets/javascripts/accounts_profile_samples.js @@ -25,18 +25,12 @@ var $twitterUsername = $screen.find('#twitter-username'); // performance samples - var $soundCloudRecordingUrl = $screen.find('#soundcloud-recording'); - var $youTubeVideoUrl = $screen.find('#youtube-video'); - var $jamkazamSampleList = $screen.find('.samples.jamkazam'); var $soundCloudSampleList = $screen.find('.samples.soundcloud'); var $youTubeSampleList = $screen.find('.samples.youtube'); // buttons var $btnAddJkRecording = $screen.find('#btn-add-jk-recording'); - var $btnAddSoundCloudRecording = $screen.find('#btn-add-soundcloud-recording'); - var $btnAddYouTubeVideo = $screen.find('#btn-add-youtube-video'); - var $btnCancel = $screen.find('#account-edit-profile-cancel'); var $btnBack = $screen.find('#account-edit-profile-back'); var $btnSubmit = $screen.find('#account-edit-profile-submit'); @@ -102,7 +96,7 @@ // JamKazam recordings var samples = profileUtils.jamkazamSamples(user.performance_samples); - if (samples) { + if (samples && samples.length > 0) { $.each(samples, function(index, val) { }); @@ -110,7 +104,7 @@ // SoundCloud recordings samples = profileUtils.soundCloudSamples(user.performance_samples); - if (samples) { + if (samples && samples.length > 0) { $.each(samples, function(index, val) { }); @@ -118,9 +112,9 @@ // YouTube videos samples = profileUtils.youTubeSamples(user.performance_samples); - if (samples) { + if (samples && samples.length > 0) { $.each(samples, function(index, val) { - + }); } } @@ -200,13 +194,13 @@ addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description); addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description); - // api.updateUser({ - // website: $website.val(), - // online_presences: op, - // performance_samples: ps - // }) - // .done(postUpdateProfileSuccess) - // .fail(postUpdateProfileFailure); + api.updateUser({ + website: $website.val(), + online_presences: op, + performance_samples: ps + }) + .done(postUpdateProfileSuccess) + .fail(postUpdateProfileFailure); } function postUpdateProfileSuccess(response) { diff --git a/web/app/assets/javascripts/site_validator.js.coffee b/web/app/assets/javascripts/site_validator.js.coffee index fc54465dd..a17bbd565 100644 --- a/web/app/assets/javascripts/site_validator.js.coffee +++ b/web/app/assets/javascripts/site_validator.js.coffee @@ -174,3 +174,6 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit src_data['url'] == url 0 < vals.length + recordingSources: () => + @recording_sources + diff --git a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss index dfe7bc128..11b3bb704 100644 --- a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss +++ b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss @@ -31,8 +31,10 @@ } } - .sample-list { - height: inherit; + div.sample-list { + height: 250px; + width: 250px; + border: 2px solid #ccc; overflow: auto; } } diff --git a/web/app/views/clients/_account_profile_samples.html.erb b/web/app/views/clients/_account_profile_samples.html.erb index 79e63f5ae..35c61d4ee 100644 --- a/web/app/views/clients/_account_profile_samples.html.erb +++ b/web/app/views/clients/_account_profile_samples.html.erb @@ -74,21 +74,18 @@
+
-
+

- - - - - -
Test RecordingX
+  
+
@@ -98,14 +95,10 @@
- - - - - -
Test RecordingX
+  
+
@@ -115,22 +108,18 @@
- - - - - -
Test RecordingX
+  
+


- CANCEL   - BACK   + CANCEL  + BACK  SAVE & FINISH
@@ -152,6 +141,12 @@ } initialized = true; + var $screen = $('#account-profile-samples'); + var $btnAddSoundCloudRecording = $screen.find('#btn-add-soundcloud-recording'); + var $btnAddYouTubeVideo = $screen.find('#btn-add-youtube-video'); + var $soundCloudSampleList = $screen.find('.samples.soundcloud'); + var $youTubeSampleList = $screen.find('.samples.youtube'); + setTimeout(function() { window.urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback); urlValidator.init(); @@ -177,13 +172,30 @@ window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback); twitterValidator.init(); - window.soundCloudRecordingValidator = new JK.SiteValidator('rec_soundcloud', userNameSuccessCallback, userNameFailCallback); + window.soundCloudRecordingValidator = new JK.SiteValidator('rec_soundcloud', siteSuccessCallback, siteFailCallback); soundCloudRecordingValidator.init(); - window.youTubeRecordingValidator = new JK.SiteValidator('rec_youtube', userNameSuccessCallback, userNameFailCallback); + window.youTubeRecordingValidator = new JK.SiteValidator('rec_youtube', siteSuccessCallback, siteFailCallback); youTubeRecordingValidator.init(); }, 1); + $btnAddSoundCloudRecording.click(function(evt) { + evt.preventDefault(); + console.log("IN ADD"); + if (!$('#rec_soundcloud_validator').hasClass('error')) { + console.log("NO ERROR"); + var recordingSources = [];//window.soundCloudRecordingValidator.recordingSources(); + console.log("recordingSources=%o", recordingSources); + if (recordingSources && recordingSources.length > 0) { + var $sampleList = $soundCloudSampleList.find('.sample-list'); + var addedRecording = recordingSources[recordingSources.length-1]; + $sampleList.append('
'); + $sampleList.append(addedRecording.url); + $sampleList.append('
'); + } + } + }); + function userNameSuccessCallback($inputDiv) { $inputDiv.removeClass('error'); $inputDiv.find('.error-text').remove(); From c276d97e4220b6ad83ac8f7489c1073c94b69154 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 1 Apr 2015 09:04:52 -0500 Subject: [PATCH 13/16] * pass in data with metronome open stat --- web/app/assets/javascripts/session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index d3669b68d..2b6ea1e80 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -2745,7 +2745,7 @@ session_size: sessionModel.participants().length, user_id: context.JK.currentUserId, user_name: context.JK.currentUserName } - context.stats.write('web.metronome.open', {}) + context.stats.write('web.metronome.open', data) var bpm = 120; logger.debug("opening the metronome with bpm: " + bpm + ", sound:" + metroSound) rest.openMetronome({id: sessionModel.id()}) From d3cc9fa6af3dfed852d318366ce0126639a440ef Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 1 Apr 2015 20:35:23 -0500 Subject: [PATCH 14/16] * download master command --- ruby/lib/jam_ruby/jam_track_importer.rb | 39 +++++++++++++++++++++++++ web/lib/tasks/jam_tracks.rake | 4 +++ 2 files changed, 43 insertions(+) diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 887ebb1e0..0fde164b4 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -748,6 +748,45 @@ module JamRuby end end + def download_master(jam_track) + importer = JamTrackImporter.new + importer.name = jam_track.name + + Dir.mkdir('tmp') unless Dir.exists?('tmp') + Dir.mkdir('tmp/jam_track_masters') unless Dir.exists?('tmp/jam_track_masters') + + master_track = jam_track.master_track + if master_track + ogg_44100 = File.join('tmp/jam_track_masters', "#{jam_track.original_artist} - #{jam_track.name}.ogg") + private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100) + end + end + + def download_masters + importers = [] + + JamTrack.all.each do |jam_track| + importers << download_master(jam_track) + 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 synchronize_all(options) importers = [] diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake index a59549236..dfdb0c9d8 100644 --- a/web/lib/tasks/jam_tracks.rake +++ b/web/lib/tasks/jam_tracks.rake @@ -76,4 +76,8 @@ namespace :jam_tracks do puts("#{importer.name} detail=#{importer.detail}") end end + + task download_masters: :environment do |task, arg| + JamTrackImporter.download_masters + end end From c0e541b49da704c167bd69552f91c30e2a496e5e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 1 Apr 2015 20:38:54 -0500 Subject: [PATCH 15/16] * importer --- ruby/lib/jam_ruby/jam_track_importer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 0fde164b4..58138dbf9 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -760,6 +760,7 @@ module JamRuby ogg_44100 = File.join('tmp/jam_track_masters', "#{jam_track.original_artist} - #{jam_track.name}.ogg") private_s3_manager.download(master_track.url_by_sample_rate(44), ogg_44100) end + importer end def download_masters From 269519a394a6385a19eeda6567898029756cad4b Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 1 Apr 2015 23:09:16 -0400 Subject: [PATCH 16/16] VRFS-2701 wip adding external recordings --- .../javascripts/site_validator.js.coffee | 14 ++++++-- .../clients/_account_profile_samples.html.erb | 33 ++++++++----------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/web/app/assets/javascripts/site_validator.js.coffee b/web/app/assets/javascripts/site_validator.js.coffee index a17bbd565..0c450cd72 100644 --- a/web/app/assets/javascripts/site_validator.js.coffee +++ b/web/app/assets/javascripts/site_validator.js.coffee @@ -83,7 +83,7 @@ context.JK.SiteValidator = class SiteValidator @fail_callback(@input_div) @deferred_status_check = null - @logger.debug("site_status = "+@site_status) + @logger.debug("site_status = " + @site_status) processSiteCheckFail: (response) => @logger.error("site check error") @@ -130,10 +130,12 @@ context.JK.SiteValidator = class SiteValidator context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator constructor: (site_type, success_callback, fail_callback) -> - super(site_type, success_callback, fail_callback) + super(site_type) @recording_sources = [] @is_rec_src = true @add_btn = @input_div.find('a.add-recording-source') + @site_success_callback = success_callback + @site_fail_callback = fail_callback init: (sources) => super() @@ -145,11 +147,17 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit processSiteCheckSucceed: (response) => super(response) @add_btn.removeClass('disabled') - @recording_sources.push({ url: response.data, recording_id: response.recording_id }) + + if @site_status + @recording_sources.push({ url: response.data, recording_id: response.recording_id }) + if @site_success_callback + @site_success_callback(@input_div) processSiteCheckFail: (response) => super(response) @add_btn.removeClass('disabled') + if @site_fail_callback + @site_fail_callback(@input_div) didBlur: () => # do nothing, validate on add only diff --git a/web/app/views/clients/_account_profile_samples.html.erb b/web/app/views/clients/_account_profile_samples.html.erb index 35c61d4ee..e295830e0 100644 --- a/web/app/views/clients/_account_profile_samples.html.erb +++ b/web/app/views/clients/_account_profile_samples.html.erb @@ -172,30 +172,13 @@ window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback); twitterValidator.init(); - window.soundCloudRecordingValidator = new JK.SiteValidator('rec_soundcloud', siteSuccessCallback, siteFailCallback); + window.soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', siteSuccessCallback, siteFailCallback); soundCloudRecordingValidator.init(); - window.youTubeRecordingValidator = new JK.SiteValidator('rec_youtube', siteSuccessCallback, siteFailCallback); + window.youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', siteSuccessCallback, siteFailCallback); youTubeRecordingValidator.init(); }, 1); - $btnAddSoundCloudRecording.click(function(evt) { - evt.preventDefault(); - console.log("IN ADD"); - if (!$('#rec_soundcloud_validator').hasClass('error')) { - console.log("NO ERROR"); - var recordingSources = [];//window.soundCloudRecordingValidator.recordingSources(); - console.log("recordingSources=%o", recordingSources); - if (recordingSources && recordingSources.length > 0) { - var $sampleList = $soundCloudSampleList.find('.sample-list'); - var addedRecording = recordingSources[recordingSources.length-1]; - $sampleList.append('
'); - $sampleList.append(addedRecording.url); - $sampleList.append('
'); - } - } - }); - function userNameSuccessCallback($inputDiv) { $inputDiv.removeClass('error'); $inputDiv.find('.error-text').remove(); @@ -210,6 +193,18 @@ function siteSuccessCallback($inputDiv) { $inputDiv.removeClass('error'); $inputDiv.find('.error-text').remove(); + + var recordingSources = window.soundCloudRecordingValidator.recordingSources(); + if (recordingSources && recordingSources.length > 0) { + console.log('recordingSources=%o', recordingSources); + var $sampleList = $soundCloudSampleList.find('.sample-list'); + var addedRecording = recordingSources[recordingSources.length-1]; + $sampleList.append('
'); + $sampleList.append(addedRecording.url); + $sampleList.append('
'); + } + + $inputDiv.find('input').val(''); } function siteFailCallback($inputDiv) {