VRFS-3459 merging develop
|
|
@ -0,0 +1,36 @@
|
|||
ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Stats' do
|
||||
|
||||
menu :label => 'Daily Stats', :parent => 'Affiliates'
|
||||
|
||||
config.sort_order = 'referral_user_count DESC'
|
||||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.filters = true
|
||||
config.per_page = 50
|
||||
config.paginate = true
|
||||
|
||||
filter :affiliate_partner
|
||||
filter :day
|
||||
filter :signups
|
||||
filter :visits
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
scope("Active", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') }
|
||||
|
||||
index do
|
||||
|
||||
# default_actions # use this for all view/edit/delete links
|
||||
|
||||
column 'Day' do |oo| oo.day end
|
||||
column 'Partner' do |oo| link_to(oo.affiliate_partner, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
|
||||
column 'Signups' do |oo| oo.signups end
|
||||
column 'Visits' do |oo| oo.visits end
|
||||
|
||||
end
|
||||
|
||||
|
||||
controller do
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -7,10 +7,10 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
|
|||
config.filters = false
|
||||
|
||||
index do
|
||||
column 'User' do |oo| link_to(oo.name, "http://www.jamkazam.com/client#/profile/#{oo.id}", {:title => oo.name}) end
|
||||
column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end
|
||||
column 'Email' do |oo| oo.email end
|
||||
column 'Created' do |oo| oo.created_at end
|
||||
column 'Partner' do |oo| oo.affiliate_referral.partner_name end
|
||||
column 'Partner' do |oo| oo.affiliate_referral.display_name end
|
||||
end
|
||||
|
||||
controller do
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
|
|||
config.batch_actions = false
|
||||
# config.clear_action_items!
|
||||
config.filters = false
|
||||
config.per_page = 50
|
||||
config.paginate = true
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL') }
|
||||
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
|
||||
scope("Unpaid") { |partner| partner.unpaid }
|
||||
|
||||
index do
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ ActiveAdmin.register JamRuby::User, :as => 'User Progression' do
|
|||
config.filters = false
|
||||
|
||||
index do
|
||||
column :email do |user| link_to(truncate(user.email, {:length => 12}), resource_path(user), {:title => "#{user.first_name} #{user.last_name} (#{user.email})"}) end
|
||||
column :email do |user| link_to(truncate(user.email, {:length => 12}), resource_path(user), {:title => "#{user.name} (#{user.email})"}) end
|
||||
column :updated_at do |uu| uu.updated_at.strftime(PROGRESSION_DATE) end
|
||||
column :created_at do |uu| uu.created_at.strftime(PROGRESSION_DATE) end
|
||||
column :city
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<%- headers = ['email', 'name', 'unsubscribe_token'] -%>
|
||||
<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.first_name, user.unsubscribe_token]) %><%- end -%>
|
||||
<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.anonymous? ? '-' : user.first_name, user.unsubscribe_token]) %><%- end -%>
|
||||
|
|
@ -154,5 +154,8 @@ module JamAdmin
|
|||
config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep"))
|
||||
|
||||
config.email_dump_code = 'rcAUyC3TZCbgGx4YQpznBRbNnQMXW5iKTzf9NSBfzMLsnw9dRQ'
|
||||
|
||||
config.admin_port = ENV['ADMIN_PORT'] || 3333
|
||||
config.admin_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.admin_port == 80 || config.admin_port == 443) ? '' : ':' + config.admin_port.to_s}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -306,4 +306,5 @@ jam_track_slug.sql
|
|||
mixdown.sql
|
||||
aac_master.sql
|
||||
video_recording.sql
|
||||
web_playable_jamtracks.sql
|
||||
jam_track_lang_idx.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
ALTER TABLE users ADD COLUMN first_opened_jamtrack_web_player TIMESTAMP;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN last_stem_id VARCHAR(64) REFERENCES jam_track_tracks(id) ON DELETE SET NULL;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN url_mp3_48 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN md5_mp3_48 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN length_mp3_48 BIGINT;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN url_aac_48 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN md5_aac_48 VARCHAR;
|
||||
ALTER TABLE jam_track_tracks ADD COLUMN length_aac_48 BIGINT;
|
||||
|
||||
CREATE TABLE user_events (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
detail JSON,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -106,6 +106,7 @@ require "jam_ruby/models/max_mind_release"
|
|||
require "jam_ruby/models/genre_player"
|
||||
require "jam_ruby/models/genre"
|
||||
require "jam_ruby/models/user"
|
||||
require "jam_ruby/models/user_event"
|
||||
require "jam_ruby/models/anonymous_user"
|
||||
require "jam_ruby/models/signup_hint"
|
||||
require "jam_ruby/models/machine_fingerprint"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<% provide(:title, 'Confirm Email') %>
|
||||
|
||||
<p>Welcome to JamKazam, <%= @user.first_name %>!</p>
|
||||
<p>Welcome to JamKazam<%= @user.anonymous? ? '!' : ", #{@user.first_name}!" %></p>
|
||||
|
||||
<p>To confirm this email address, please go to the <a style="color: #ffcc00;" href="<%= @signup_confirm_url %>">signup confirmation page</a>.</p>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
Welcome to JamKazam, <%= @user.first_name %>!
|
||||
Welcome to JamKazam<%= @user.anonymous? ? '!' : ", #{@user.first_name}!" %>
|
||||
|
||||
To confirm this email address, please go to the signup confirmation page at: <%= @signup_confirm_url %>.
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<% provide(:title, 'New Musicians You Should Check Out') %>
|
||||
Hi <%= @user.first_name %>,
|
||||
<% if !@user.anonymous? %>
|
||||
<p>Hi <%= @user.first_name %>,</p>
|
||||
<% end %>
|
||||
|
||||
<p>The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
New Musicians You Should Check Out
|
||||
|
||||
<% if !@user.anonymous? %>
|
||||
Hi <%= @user.first_name %>,
|
||||
|
||||
<% end %>
|
||||
The following new musicians have joined JamKazam within the last week, and have Internet connections with low enough latency to you that you can have a good online session together. We'd suggest that you look through the new musicians listed below to see if any match your musical interests, and if so, click through to their profile page on the JamKazam website to send them a message or a request to connect as a JamKazam friend:
|
||||
|
||||
<% @new_musicians.each do |user| %>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<% provide(:title, @title) %>
|
||||
|
||||
<p>Hello <%= @user.first_name %> --
|
||||
</p>
|
||||
<% if !@user.anonymous? %>
|
||||
<p>Hi <%= @user.first_name %>,</p>
|
||||
<% end %>
|
||||
|
||||
<p>The following new sessions have been posted within the last 24 hours, and you have good or acceptable latency to the organizer of each session below. If a session looks interesting, click the Details link to see the session page. You can RSVP to a session from the session page, and you'll be notified if/when the session organizer approves your RSVP.</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<% provide(:title, @title) %>
|
||||
|
||||
Hello <%= @user.first_name %> --
|
||||
|
||||
<% if !@user.anonymous? %>
|
||||
Hi <%= @user.first_name %>,
|
||||
<% end %>
|
||||
The following new sessions have been posted within the last 24 hours, and you have good or acceptable latency to the organizer of each session below. If a session looks interesting, click the Details link to see the session page. You can RSVP to a session from the session page, and you'll be notified if/when the session organizer approves your RSVP.
|
||||
|
||||
GENRE | NAME | DESCRIPTION | LATENCY
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
<% provide(:title, 'JamKazam Session Reminder') %>
|
||||
|
||||
|
||||
<div>
|
||||
<% if !@user.anonymous? %>
|
||||
<p>
|
||||
Hi <%= @user.first_name %>,
|
||||
</div>
|
||||
</p>
|
||||
<% end %>
|
||||
<br/>
|
||||
<div>
|
||||
<p>
|
||||
<span>This is a reminder that your JamKazam session</span>
|
||||
<a href='<%=@session_url%>'><%= @session_name %></a>
|
||||
<span>is scheduled for tomorrow. We hope you have fun!</span>
|
||||
</div>
|
||||
</p>
|
||||
<br/>
|
||||
<div>
|
||||
<p>
|
||||
Best Regards,
|
||||
<br/>
|
||||
Team JamKazam
|
||||
</div>
|
||||
</p>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<% if !@user.anonymous? %>
|
||||
Hi <%= @user.first_name %>,
|
||||
|
||||
<% end %>
|
||||
This is a reminder that your JamKazam session <%=@session_name%> is scheduled for tomorrow. We hope you have fun!
|
||||
|
||||
Best Regards,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
<% provide(:title, 'Your JamKazam session starts in 1 hour!') %>
|
||||
|
||||
<div>
|
||||
<% if !@user.anonymous? %>
|
||||
<p>
|
||||
Hi <%= @user.first_name %>,
|
||||
</div>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<br/>
|
||||
<div>
|
||||
<p>
|
||||
<span>This is a reminder that your JamKazam session</span>
|
||||
<a href='<%=@session_url%>'><%= @session_name %></a>
|
||||
<span>starts in 1 hour. We hope you have fun!</span>
|
||||
</div>
|
||||
</p>
|
||||
<br/>
|
||||
<div>
|
||||
<p>
|
||||
Best Regards,
|
||||
<br/>
|
||||
Team JamKazam
|
||||
</div>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<% if !@user.anonymous? %>
|
||||
Hi <%= @user.first_name %>,
|
||||
<% end %>
|
||||
|
||||
This is a reminder that your JamKazam session
|
||||
<%=@session_name%>
|
||||
|
|
|
|||
|
|
@ -981,6 +981,44 @@ module JamRuby
|
|||
sorted_tracks
|
||||
end
|
||||
|
||||
# this will put original_audio_s3_path on each jam_track_track
|
||||
def associate_tracks_with_original_stems(jam_track, s3_path)
|
||||
attempt_to_match_existing_tracks = true
|
||||
|
||||
# find all wav files in the JamTracks s3 bucket
|
||||
wav_files = fetch_important_files(s3_path)
|
||||
|
||||
tracks = []
|
||||
|
||||
wav_files.each do |wav_file|
|
||||
|
||||
if attempt_to_match_existing_tracks
|
||||
# try to find a matching track from the JamTrack based on the name of the 44.1 path
|
||||
basename = File.basename(wav_file)
|
||||
ogg_44100_filename = File.basename(basename, ".wav") + "-44100.ogg"
|
||||
|
||||
found_track = nil
|
||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||
|
||||
if jam_track_track["url_44"] && jam_track_track["url_44"].end_with?(ogg_44100_filename)
|
||||
# found a match!
|
||||
found_track = jam_track_track
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found_track
|
||||
@@log.debug("found a existing track to reuse")
|
||||
found_track.original_audio_s3_path = wav_file
|
||||
tracks << found_track
|
||||
next
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tracks
|
||||
end
|
||||
|
||||
def synchronize_audio(jam_track, metadata, s3_path, skip_audio_upload)
|
||||
|
||||
attempt_to_match_existing_tracks = true
|
||||
|
|
@ -1104,6 +1142,7 @@ module JamRuby
|
|||
begin
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
|
||||
jam_track.jam_track_tracks.each do |track|
|
||||
|
||||
basename = File.basename(track.original_audio_s3_path)
|
||||
|
|
@ -1204,6 +1243,87 @@ module JamRuby
|
|||
return true
|
||||
end
|
||||
|
||||
def generate_mp3_aac_stem(jam_track, tmp_dir, skip_audio_upload)
|
||||
jam_track.jam_track_tracks.each do |track|
|
||||
|
||||
if track.original_audio_s3_path.nil?
|
||||
|
||||
@@log.error("jam_track #{jam_track.name} has empty stem. stem: #{track.id}")
|
||||
next
|
||||
end
|
||||
|
||||
basename = File.basename(track.original_audio_s3_path)
|
||||
s3_dirname = File.dirname(track.original_audio_s3_path)
|
||||
|
||||
# make a 44100 version, and a 48000 version
|
||||
mp3_48000_filename = File.basename(basename, ".wav") + "-48000.mp3"
|
||||
aac_48000_filename = File.basename(basename, ".wav") + "-48000.aac"
|
||||
|
||||
mp3_48000_s3_path = track.filename(mp3_48000_filename)
|
||||
aac_48000_s3_path = track.filename(aac_48000_filename)
|
||||
|
||||
track.skip_uploader = true
|
||||
|
||||
if skip_audio_upload
|
||||
track["url_mp3_48"] = mp3_48000_filename
|
||||
track["md5_mp3_48"] = 'md5'
|
||||
track["length_mp3_48"] = 1
|
||||
|
||||
track["url_aac_48"] = aac_48000_filename
|
||||
track["md5_aac_48"] = 'md5'
|
||||
track["length_aac_48"] = 1
|
||||
|
||||
# we can't fake the preview as easily because we don't know the MD5 of the current item
|
||||
#track["preview_md5"] = 'md5'
|
||||
#track["preview_mp3_md5"] = 'md5'
|
||||
#track["preview_url"] = track.preview_filename('md5', 'ogg')
|
||||
#track["preview_length"] = 1
|
||||
#track["preview_mp3_url"] = track.preview_filename('md5', 'mp3')
|
||||
#track["preview_mp3_length"] = 1
|
||||
#track["preview_start_time"] = 0
|
||||
else
|
||||
wav_file = File.join(tmp_dir, basename)
|
||||
|
||||
# the wave file might already be on the system...
|
||||
|
||||
# don't bother with the same track twice
|
||||
|
||||
next if track["url_mp3_48"] && track["url_aac_48"]
|
||||
|
||||
# bring the original wav file down from S3 to local file system
|
||||
JamTrackImporter::song_storage_manager.download(track.original_audio_s3_path, wav_file) unless File.exists?(wav_file)
|
||||
|
||||
mp3_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.mp3")
|
||||
aac_48000 = File.join(tmp_dir, File.basename(basename, ".wav") + "-48000.aac")
|
||||
|
||||
`ffmpeg -i "#{wav_file}" -ar 48000 -ab 192k "#{mp3_48000}"`
|
||||
|
||||
`ffmpeg -i "#{wav_file}" -c:a libfdk_aac -b:a 192k "#{aac_48000}"`
|
||||
|
||||
# upload the new ogg files to s3
|
||||
@@log.debug("uploading mp3 48000 to #{mp3_48000_s3_path}")
|
||||
|
||||
jamkazam_s3_manager.upload(mp3_48000_s3_path, mp3_48000)
|
||||
|
||||
@@log.debug("uploading aac 48000 to #{aac_48000_s3_path}")
|
||||
|
||||
jamkazam_s3_manager.upload(aac_48000_s3_path, aac_48000)
|
||||
|
||||
mp3_48000_digest = ::Digest::MD5.file(mp3_48000)
|
||||
# and finally update the JamTrackTrack with the new info
|
||||
track["url_mp3_48"] = mp3_48000_s3_path
|
||||
track["md5_mp3_48"] = mp3_48000_digest.hexdigest
|
||||
track["length_mp3_48"] = File.new(mp3_48000).size
|
||||
|
||||
track["url_aac_48"] = aac_48000_s3_path
|
||||
track["md5_aac_48"] = ::Digest::MD5.file(aac_48000).hexdigest
|
||||
track["length_aac_48"] = File.new(aac_48000).size
|
||||
track.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def synchronize_duration(jam_track, ogg_44100)
|
||||
duration_command = "soxi -D \"#{ogg_44100}\""
|
||||
output = `#{duration_command}`
|
||||
|
|
@ -1527,6 +1647,11 @@ module JamRuby
|
|||
id
|
||||
end
|
||||
|
||||
def is_default_storage?
|
||||
assert_storage_set
|
||||
@storage_format == 'default'
|
||||
end
|
||||
|
||||
def is_tency_storage?
|
||||
assert_storage_set
|
||||
@storage_format == 'Tency'
|
||||
|
|
@ -1929,6 +2054,20 @@ module JamRuby
|
|||
importer
|
||||
end
|
||||
|
||||
def generate_mp3_aac_stem(jam_track)
|
||||
importer = JamTrackImporter.new
|
||||
importer.name = jam_track.name
|
||||
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
audio_path = jam_track.metalocation[0...-"/meta.yml".length]
|
||||
importer.associate_tracks_with_original_stems(jam_track, audio_path)
|
||||
importer.generate_mp3_aac_stem(jam_track, tmp_dir, false)
|
||||
end
|
||||
|
||||
importer
|
||||
end
|
||||
|
||||
def download_masters
|
||||
importers = []
|
||||
|
||||
|
|
@ -1958,6 +2097,67 @@ module JamRuby
|
|||
filename.tr('/&@:,$=+?;\^`><{}[]#%~|', '')
|
||||
end
|
||||
|
||||
def generate_mp3_aac_stems(format)
|
||||
importers = []
|
||||
|
||||
jam_tracks = []
|
||||
|
||||
tency = JamTrackLicensor.find_by_name('Tency Music')
|
||||
|
||||
@@log.info("processing storage #{@storage_format}")
|
||||
if is_tency_storage?
|
||||
tency = JamTrackLicensor.find_by_name!('Tency Music')
|
||||
jam_tracks = JamTrack.where(licensor_id: tency.id)
|
||||
elsif is_default_storage?
|
||||
# XXX IF WE ADD ANOTHER STORAGE, UPDATE THE WHERE TO EXCLUDE IT AS WELL
|
||||
jam_tracks = JamTrack.where('licensor_id is null OR licensor_id != ?', tency.id )
|
||||
else
|
||||
raise 'unknown storage format!'
|
||||
end
|
||||
|
||||
jam_tracks.each do |jam_track|
|
||||
|
||||
if ENV['NODE_COUNT']
|
||||
node_count = ENV['NODE_COUNT'].to_i
|
||||
node_number = ENV['NODE_NUMBER'].to_i
|
||||
raise "NO NODE_COUNT" if node_count == 0
|
||||
|
||||
jam_track_id = jam_track.id.to_i
|
||||
jam_track_id = jam_track_id + node_number
|
||||
if jam_track_id == 0
|
||||
@@log.warn("skipping #{jam_track_id} because non-numeric ID")
|
||||
next
|
||||
elsif jam_track_id % node_count == 0
|
||||
@@log.warn("starting JamTrack #{jam_track.id} (#{jam_track_id})")
|
||||
importers << generate_mp3_aac_stem(jam_track)
|
||||
else
|
||||
@@log.warn("skipping #{jam_track_id}")
|
||||
next
|
||||
end
|
||||
else
|
||||
importers << generate_mp3_aac_stem(jam_track)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@log.info("SUMMARY")
|
||||
@@log.info("-------")
|
||||
importers.each do |importer|
|
||||
if importer
|
||||
if importer.reason == "success"
|
||||
@@log.info("#{importer.name} #{importer.reason}")
|
||||
else
|
||||
@@log.error("#{importer.name} failed to download.")
|
||||
@@log.error("#{importer.name} reason=#{importer.reason}")
|
||||
@@log.error("#{importer.name} detail=#{importer.detail}")
|
||||
end
|
||||
else
|
||||
@@log.error("NULL IMPORTER")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def generate_slugs
|
||||
JamTrack.all.each do |jam_track|
|
||||
jam_track.generate_slug
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ class JamRuby::AffiliatePartner < ActiveRecord::Base
|
|||
record.entity_type ||= ENTITY_TYPES.first
|
||||
end
|
||||
|
||||
def display_name
|
||||
partner_name || (partner_user ? partner_user.name : 'abandoned')
|
||||
end
|
||||
|
||||
def admin_url
|
||||
APP_CONFIG.admin_root_url + "/admin/affiliates/" + id
|
||||
end
|
||||
|
||||
# used by admin
|
||||
def self.create_with_params(params={})
|
||||
raise 'not supported'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module JamRuby
|
|||
validates :jam_track, presence: true
|
||||
validates :settings, presence: true
|
||||
|
||||
validates_uniqueness_of :name, scope: :user_id
|
||||
validates_uniqueness_of :name, scope: [:user_id, :jam_track_id]
|
||||
|
||||
validate :verify_settings
|
||||
validate :verify_max_mixdowns
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ module JamRuby
|
|||
@@log = Logging.logger[JamTrackMixdownPackage]
|
||||
|
||||
# these are used as extensions for the files stored in s3
|
||||
FILE_TYPE_MP3 = 'mp3'
|
||||
FILE_TYPE_OGG = 'ogg'
|
||||
FILE_TYPE_AAC = 'aac'
|
||||
FILE_TYPES = [FILE_TYPE_OGG, FILE_TYPE_AAC]
|
||||
FILE_TYPES = [FILE_TYPE_MP3, FILE_TYPE_OGG, FILE_TYPE_AAC]
|
||||
|
||||
SAMPLE_RATE_44 = 44
|
||||
SAMPLE_RATE_48 = 48
|
||||
|
|
@ -135,8 +136,11 @@ module JamRuby
|
|||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self['url'], {:expires => expiration_time, :secure => true})
|
||||
def sign_url(expiration_time = 120, content_type = nil, response_content_disposition = nil)
|
||||
options = {:expires => expiration_time, :secure => true}
|
||||
options[:response_content_type] = content_type if content_type
|
||||
options[:response_content_disposition] = response_content_disposition if response_content_disposition
|
||||
s3_manager.sign_url(self['url'], options)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module JamRuby
|
|||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right
|
||||
belongs_to :last_stem, class_name: 'JamRuby::JamTrackTrack', foreign_key: 'last_stem_id', inverse_of: :jam_track_right
|
||||
|
||||
validates :user, presence: true
|
||||
validates :jam_track, presence: true
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ module JamRuby
|
|||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
|
||||
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy
|
||||
has_one :jam_track_right, class_name: 'JamRuby::JamTrackRight', foreign_key: 'last_stem_id', inverse_of: :last_stem
|
||||
|
||||
# create storage directory that will house this jam_track, as well as
|
||||
def store_dir
|
||||
|
|
@ -79,6 +80,18 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def display_name
|
||||
if track_type == 'Master'
|
||||
'Master Mix'
|
||||
else
|
||||
display_part = ''
|
||||
if part
|
||||
display_part = "-(#{part})"
|
||||
end
|
||||
"#{instrument.description}#{display_part}"
|
||||
end
|
||||
end
|
||||
|
||||
def manually_uploaded_filename(mounted_as)
|
||||
if track_type == 'Master'
|
||||
filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
|
||||
|
|
@ -102,6 +115,18 @@ module JamRuby
|
|||
def sign_url(expiration_time = 120, sample_rate=48)
|
||||
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
|
||||
end
|
||||
|
||||
def web_download_sign_url(expiration_time = 120, type='mp3', content_type = nil, response_content_disposition = nil)
|
||||
options = {:expires => expiration_time, :secure => true}
|
||||
options[:response_content_type] = content_type if content_type
|
||||
options[:response_content_disposition] = response_content_disposition if response_content_disposition
|
||||
|
||||
url_field = self['url_' + type + '_48']
|
||||
url_field = self['url_48'] if type == 'ogg' # ogg has different column format in database
|
||||
|
||||
|
||||
s3_manager.sign_url(url_field, options)
|
||||
end
|
||||
|
||||
def can_download?(user)
|
||||
# I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ module JamRuby
|
|||
.first
|
||||
end
|
||||
|
||||
def name
|
||||
user.name
|
||||
end
|
||||
|
||||
def music_session
|
||||
@msh ||= JamRuby::MusicSession.find_by_music_session_id(self.music_session_id)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -176,11 +176,13 @@ module JamRuby
|
|||
has_one :musician_search, :class_name => 'JamRuby::MusicianSearch'
|
||||
has_one :band_search, :class_name => 'JamRuby::BandSearch'
|
||||
|
||||
before_save :default_anonymous_names
|
||||
before_save :create_remember_token, :if => :should_validate_password?
|
||||
before_save :stringify_avatar_info , :if => :updating_avatar
|
||||
|
||||
validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :first_name, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, length: {maximum: 50}, no_profanity: true
|
||||
validates :biography, length: {maximum: 4000}, no_profanity: true
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email
|
||||
|
|
@ -295,8 +297,16 @@ module JamRuby
|
|||
online?
|
||||
end
|
||||
|
||||
def anonymous?
|
||||
first_name == 'Anonymous' && last_name == 'Anonymous'
|
||||
end
|
||||
|
||||
def name
|
||||
"#{first_name} #{last_name}"
|
||||
if anonymous?
|
||||
'Anonymous'
|
||||
else
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def location
|
||||
|
|
@ -575,9 +585,7 @@ module JamRuby
|
|||
def to_s
|
||||
return email unless email.nil?
|
||||
|
||||
if !first_name.nil? && !last_name.nil?
|
||||
return first_name + ' ' + last_name
|
||||
end
|
||||
return name unless name.nil?
|
||||
|
||||
id
|
||||
end
|
||||
|
|
@ -1680,14 +1688,17 @@ module JamRuby
|
|||
else
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
private
|
||||
def create_remember_token
|
||||
self.remember_token = SecureRandom.urlsafe_base64
|
||||
end
|
||||
|
||||
def default_anonymous_names
|
||||
self.first_name = 'Anonymous' if self.first_name.nil?
|
||||
self.last_name = 'Anonymous' if self.last_name.nil?
|
||||
end
|
||||
|
||||
def stringify_avatar_info
|
||||
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
|
||||
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
module JamRuby
|
||||
class UserEvent < ActiveRecord::Base
|
||||
|
||||
belongs_to :user, class_name: 'JamRuby::User'
|
||||
|
||||
validates :name, presence: true
|
||||
end
|
||||
end
|
||||
|
|
@ -352,9 +352,15 @@ module JamRuby
|
|||
# otherwise we need to convert from lastly created file to correct
|
||||
output = File.join(tmp_dir, "output.#{@mixdown_package.file_type}")
|
||||
|
||||
raise 'unknown file_type' if @mixdown_package.file_type != JamTrackMixdownPackage::FILE_TYPE_AAC
|
||||
if @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_AAC
|
||||
cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
|
||||
elsif @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_MP3
|
||||
cmd("ffmpeg -i \"#{@speed_mix_file}\" -ab 192k \"#{output}\"", 'convert_mp3')
|
||||
else
|
||||
raise 'unknown file_type'
|
||||
end
|
||||
|
||||
|
||||
cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
|
||||
output
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -101,12 +101,12 @@ describe User do
|
|||
|
||||
describe "when first name is not present" do
|
||||
before { @user.first_name = " " }
|
||||
it { should_not be_valid }
|
||||
it { should be_valid }
|
||||
end
|
||||
|
||||
describe "when last name is not present" do
|
||||
before { @user.last_name = " " }
|
||||
it { should_not be_valid }
|
||||
it { should be_valid }
|
||||
end
|
||||
|
||||
describe "when email is not present" do
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 557 B |
|
After Width: | Height: | Size: 984 B |
|
After Width: | Height: | Size: 701 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 976 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 754 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -226,7 +226,7 @@
|
|||
|
||||
app.clientId = payload.client_id;
|
||||
|
||||
if (isClientMode()) {
|
||||
if (isClientMode() && context.jamClient) {
|
||||
// tell the backend that we have logged in
|
||||
context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
|
||||
$.cookie('client_id', payload.client_id);
|
||||
|
|
@ -321,7 +321,7 @@
|
|||
function socketClosed(in_error) {
|
||||
|
||||
// tell the backend that we have logged out
|
||||
context.jamClient.OnLoggedOut();
|
||||
if (context.jamClient) context.jamClient.OnLoggedOut();
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -593,7 +593,10 @@
|
|||
clientType = context.JK.clientType();
|
||||
}
|
||||
if(!mode) {
|
||||
mode = context.jamClient.getOperatingMode ? context.jamClient.getOperatingMode() : 'client';
|
||||
mode = 'client'
|
||||
if (context.jamClient && context.jamClient.getOperatingMode) {
|
||||
mode = context.jamClient.getOperatingMode()
|
||||
}
|
||||
}
|
||||
|
||||
connectDeferred = new $.Deferred();
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
//= require web/tracking
|
||||
//= require webcam_viewer
|
||||
//= require react-components
|
||||
//= require playbackControls
|
||||
//= require_directory .
|
||||
//= require_directory ./dialog
|
||||
//= require_directory ./wizard
|
||||
|
|
|
|||
|
|
@ -15,12 +15,21 @@
|
|||
// remove all display errors
|
||||
$('#recording-finished-dialog form .error-text').remove()
|
||||
$('#recording-finished-dialog form .error').removeClass("error")
|
||||
console.log("save video?", recording)
|
||||
if(recording.video) {
|
||||
$dialog.find('.save-video').show()
|
||||
if(recording.owner.id == context.JK.currentUserId) {
|
||||
// only the owner of the video gets to see video options
|
||||
$dialog.find('.save-video').show()
|
||||
$dialog.find('.upload-to-youtube').show()
|
||||
}
|
||||
else {
|
||||
$dialog.find('.save-video').hide()
|
||||
$dialog.find('.upload-to-youtube').hide()
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
$dialog.find('.save-video').hide()
|
||||
$dialog.find('.upload-to-youtube').hide()
|
||||
}
|
||||
removeGoogleLoginErrors()
|
||||
}
|
||||
|
|
@ -209,6 +218,8 @@
|
|||
var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
|
||||
|
||||
var recording_id = recording.id
|
||||
var recording_video = recording.video
|
||||
|
||||
rest.claimRecording({
|
||||
id: recording.id,
|
||||
name: name,
|
||||
|
|
@ -222,7 +233,7 @@
|
|||
$dialog.data('result', {keep:true});
|
||||
app.layout.closeDialog('recordingFinished');
|
||||
context.JK.GA.trackMakeRecording();
|
||||
if(save_video && upload_to_youtube) {
|
||||
if(recording_video && save_video && upload_to_youtube) {
|
||||
// you have to have elected to save video to have upload to youtube have
|
||||
context.VideoUploaderActions.showUploader(recording_id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@
|
|||
|
||||
context.stats.write = context.stats.writePoint;
|
||||
}
|
||||
|
||||
function initializeStun(app) {
|
||||
stun = new context.JK.Stun(app);
|
||||
context.JK.StunInstance = stun;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@
|
|||
context.JK.PLAYBACK_MONITOR_MODE = {
|
||||
MEDIA_FILE: 'MEDIA_FILE',
|
||||
JAMTRACK: 'JAMTRACK',
|
||||
METRONOME: 'METRONOME'
|
||||
METRONOME: 'METRONOME',
|
||||
BROWSER_MEDIA: 'BROWSER_MEDIA'
|
||||
}
|
||||
context.JK.ALERT_NAMES = {
|
||||
NO_EVENT : 0,
|
||||
|
|
|
|||
|
|
@ -131,4 +131,8 @@
|
|||
return context.JK.prodBubble($element, 'jamtrack-browse-cta', {}, bigHelpOptions({positions:['top'], offsetParent: $offsetParent}))
|
||||
}
|
||||
|
||||
helpBubble.jamtrackWebPlay = function($element, $offsetParent) {
|
||||
return context.JK.prodBubble($element, 'jamtrack-web-play', {}, bigHelpOptions({positions:['bottom'], offsetParent: $offsetParent}))
|
||||
}
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -540,6 +540,11 @@
|
|||
processData: false
|
||||
});
|
||||
}
|
||||
if(detail) {
|
||||
detail.done(function(user) {
|
||||
window.UserActions.loaded(user)
|
||||
})
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
|
|
@ -1134,6 +1139,29 @@
|
|||
});
|
||||
}
|
||||
|
||||
function userOpenedJamTrackWebPlayer(options) {
|
||||
var id = getId(options);
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/users/progression/opened_jamtrack_web_player",
|
||||
processData: false
|
||||
});
|
||||
}
|
||||
|
||||
function postUserEvent(options) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/users/event/record",
|
||||
data: JSON.stringify(options),
|
||||
processData: false
|
||||
});
|
||||
}
|
||||
|
||||
function signout() {
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
|
|
@ -1144,11 +1172,12 @@
|
|||
}
|
||||
|
||||
function updateUser(options) {
|
||||
options = options || {};
|
||||
var id = getId(options);
|
||||
|
||||
delete options['id'];
|
||||
|
||||
return $.ajax({
|
||||
var deferred = $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
|
|
@ -1156,6 +1185,13 @@
|
|||
data: JSON.stringify(options),
|
||||
processData: false
|
||||
});
|
||||
|
||||
deferred.done(function(user) {
|
||||
window.UserActions.loaded(user)
|
||||
})
|
||||
|
||||
return deferred;
|
||||
|
||||
}
|
||||
|
||||
function startRecording(options) {
|
||||
|
|
@ -2058,6 +2094,8 @@
|
|||
this.userDownloadedClient = userDownloadedClient;
|
||||
this.userCertifiedGear = userCertifiedGear;
|
||||
this.userSocialPromoted = userSocialPromoted;
|
||||
this.userOpenedJamTrackWebPlayer = userOpenedJamTrackWebPlayer;
|
||||
this.postUserEvent = postUserEvent;
|
||||
this.createJoinRequest = createJoinRequest;
|
||||
this.updateJoinRequest = updateJoinRequest;
|
||||
this.updateUser = updateUser;
|
||||
|
|
|
|||
|
|
@ -9,17 +9,39 @@
|
|||
//= require jquery.icheck
|
||||
//= require jquery.easydropdown
|
||||
//= require jquery.metronomePlaybackMode
|
||||
//= require jquery.cookie
|
||||
//= require influxdb-latest
|
||||
//= require howler.core.js
|
||||
//= require classnames
|
||||
//= require reflux
|
||||
//= require AAC_underscore
|
||||
//= require AAA_Log
|
||||
//= require globals
|
||||
//= require AAB_message_factory
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require layout
|
||||
//= require jamkazam
|
||||
//= require utils
|
||||
//= require playbackControls
|
||||
//= require subscription_utils
|
||||
//= require webcam_viewer
|
||||
//= require JamServer
|
||||
//= require react
|
||||
//= require react_ujs
|
||||
//= require react-init
|
||||
//= require react-components
|
||||
//= require react-components
|
||||
//= require playbackControls
|
||||
|
||||
function initializeInfluxDB() {
|
||||
window.stats = new InfluxDB({
|
||||
"host" : gon.global.influxdb_host,
|
||||
"port" : gon.global.influxdb_port,
|
||||
"username" : gon.global.influxdb_username,
|
||||
"password" : gon.global.influxdb_password,
|
||||
"database" : gon.global.influxdb_database
|
||||
});
|
||||
|
||||
window.stats.write = window.stats.writePoint;
|
||||
}
|
||||
|
||||
initializeInfluxDB()
|
||||
|
|
@ -2,8 +2,25 @@
|
|||
* Playback widget (play, pause , etc)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
(function (context, $) {
|
||||
|
||||
|
||||
// this check ensures we attempt to listen if this component is created in a popup
|
||||
var reactContext = window.opener ? window.opener : window
|
||||
// make sure this is actually us opening the window, not someone else (by checking for MixerStore)
|
||||
if (window.opener) {
|
||||
try {
|
||||
m = window.opener.MixerStore
|
||||
}
|
||||
catch (e) {
|
||||
reactContext = window
|
||||
}
|
||||
}
|
||||
|
||||
var BrowserMediaStore = reactContext.BrowserMediaStore
|
||||
|
||||
"use strict";
|
||||
|
||||
var PlaybackMode = {
|
||||
|
|
@ -75,7 +92,6 @@
|
|||
}
|
||||
else {
|
||||
$self.triggerHandler('play', {playbackMode: playbackMode, playbackMonitorMode: playbackMonitorMode});
|
||||
|
||||
}
|
||||
|
||||
if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.JAMTRACK) {
|
||||
|
|
@ -241,6 +257,9 @@
|
|||
else if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.METRONOME) {
|
||||
$parentElement.addClass('metronome-mode');
|
||||
}
|
||||
else if (playbackMonitorMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA) {
|
||||
$parentElement.addClass('mediafile-mode');
|
||||
}
|
||||
else {
|
||||
throw "unknown playbackMonitorMode: " + playbackMonitorMode;
|
||||
}
|
||||
|
|
@ -294,14 +313,19 @@
|
|||
var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs();
|
||||
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
|
||||
var durationMs = duration.media_len;
|
||||
var isPlaying = context.jamClient.isSessionTrackPlaying();
|
||||
}
|
||||
else if(playbackMonitorMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA) {
|
||||
var positionMs = BrowserMediaStore.onGetPlayPosition() || 0
|
||||
var durationMs = BrowserMediaStore.onGetPlayDuration() || 0;
|
||||
var isPlaying = BrowserMediaStore.playing;
|
||||
}
|
||||
else {
|
||||
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
|
||||
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
|
||||
var isPlaying = context.jamClient.isSessionTrackPlaying();
|
||||
}
|
||||
|
||||
var isPlaying = context.jamClient.isSessionTrackPlaying();
|
||||
|
||||
executeMonitor(positionMs, durationMs, isPlaying)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
//= require_directory ./react-components/helpers
|
||||
//= require_directory ./react-components/actions
|
||||
//= require ./react-components/stores/AppStore
|
||||
//= require ./react-components/stores/BrowserMediaStore
|
||||
//= require ./react-components/stores/RecordingStore
|
||||
//= require ./react-components/stores/VideoStore
|
||||
//= require ./react-components/stores/SessionStore
|
||||
|
|
@ -11,11 +12,13 @@
|
|||
//= require ./react-components/stores/JamTrackStore
|
||||
//= require ./react-components/stores/SessionNotificationStore
|
||||
//= require ./react-components/stores/MediaPlaybackStore
|
||||
//= require ./react-components/stores/BrowserMediaPlaybackStore
|
||||
//= require ./react-components/stores/SessionMyTracksStore
|
||||
//= require ./react-components/stores/SessionOtherTracksStore
|
||||
//= require ./react-components/stores/SessionMediaTracksStore
|
||||
//= require ./react-components/stores/PlatformStore
|
||||
//= require ./react-components/stores/VideoUploaderStore
|
||||
//= require ./react-components/stores/JamTrackPlayerStore
|
||||
//= require_directory ./react-components/stores
|
||||
//= require_directory ./react-components/mixins
|
||||
//= require_directory ./react-components
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
context = window
|
||||
PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE
|
||||
EVENTS = context.JK.EVENTS
|
||||
logger = context.JK.logger
|
||||
|
||||
mixins = []
|
||||
|
||||
# this check ensures we attempt to listen if this component is created in a popup
|
||||
reactContext = if window.opener? then window.opener else window
|
||||
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
|
||||
if window.opener?
|
||||
try
|
||||
m = window.opener.MixerStore
|
||||
catch e
|
||||
reactContext = window
|
||||
|
||||
# temporarily..
|
||||
# reactContext = window
|
||||
|
||||
MediaPlaybackActions = reactContext.MediaPlaybackActions
|
||||
BrowserMediaStore = reactContext.BrowserMediaStore
|
||||
BrowserMediaPlaybackStore = reactContext.BrowserMediaPlaybackStore
|
||||
BrowserMediaPlaybackActions = reactContext.BrowserMediaPlaybackActions
|
||||
|
||||
mixins.push(Reflux.listenTo(BrowserMediaPlaybackStore,"onMediaStateChanged"))
|
||||
|
||||
|
||||
@BrowserMediaControls = React.createClass({
|
||||
|
||||
mixins: mixins
|
||||
tempos : [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, 208 ]
|
||||
|
||||
onMediaStateChanged: (changes) ->
|
||||
if changes.playbackStateChanged
|
||||
if @state.controls?
|
||||
if changes.playbackState == 'play_start'
|
||||
@state.controls.onPlayStartEvent()
|
||||
else if changes.playbackState == 'play_stop'
|
||||
@state.controls.onPlayStopEvent()
|
||||
else if changes.playbackState == 'play_pause'
|
||||
@state.controls.onPlayPauseEvent();
|
||||
if changes.positionUpdateChanged
|
||||
if @state.controls?
|
||||
@state.controls.executeMonitor(changes.positionMs, changes.durationMs, changes.isPlaying)
|
||||
if changes.currentTimeChanged
|
||||
@setState({time: changes.time})
|
||||
|
||||
monitorControls: (controls) ->
|
||||
|
||||
controls.startMonitor(PLAYBACK_MONITOR_MODE.BROWSER_MEDIA)
|
||||
|
||||
render: () ->
|
||||
|
||||
|
||||
tempo_options = []
|
||||
for tempo in @tempos
|
||||
tempo_options.push(`<option key={tempo} value={tempo}>{tempo}</option>`)
|
||||
|
||||
`<div className="media-controls has-mix">
|
||||
|
||||
<div className="jam-track-get-ready">
|
||||
<div className="spinner-small"></div>
|
||||
<span>Get Ready!</span>
|
||||
</div>
|
||||
|
||||
<div className="play-buttons">
|
||||
<a className="play-button" href="#">
|
||||
<img src="/assets/content/icon_playbutton.png" width="20" height="20" className="playbutton" />
|
||||
<img src="/assets/content/icon_pausebutton.png" width="20" height="20" className="pausebutton" />
|
||||
</a>
|
||||
<a className="stop-button" href="#">
|
||||
<img src="/assets/content/icon_stopbutton.png" width="20" height="20" className="stopbutton" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="metronome-playback-options">
|
||||
<span id="metronome-playback-select"></span>
|
||||
</div>
|
||||
|
||||
<div className="metronome-options">
|
||||
<div className="metronome-selects">
|
||||
<div class="metronome-field">
|
||||
<select className="metronome-select metro-sound" title="Metronome Sound" name="metronome_sound">
|
||||
<option value="Beep">Knock</option>
|
||||
<option value="Click">Tap</option>
|
||||
<option value="Snare">Snare</option>
|
||||
<option value="Kick">Kick</option>
|
||||
</select>
|
||||
<label htmlFor="metronome_sound">Sound:</label>
|
||||
</div>
|
||||
<div class="metronome-field">
|
||||
<select className="metronome-select metro-tempo" title="Metronome Tempo" name="metronome_tempo">
|
||||
{tempo_options}
|
||||
</select>
|
||||
<label htmlFor="metronome_tempo">Tempo:</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="recording-time start-time">{this.state.time}</div>
|
||||
<div className="recording-playback">
|
||||
<div className="recording-slider"><img src="/assets/content/slider_playcontrols.png" height="16" width="5" /></div>
|
||||
</div>
|
||||
<div className="recording-time duration-time">0:00</div>
|
||||
|
||||
<div className="recording-current">0:00</div>
|
||||
|
||||
<div className="playback-mode-buttons icheckbuttons">
|
||||
<input type="radio" name="playback-mode" defaultChecked="checked" value="preview-to-all" className="preview-to-all" /><label htmlFor="playback-mode-preview-all" className="radio-text">Preview to All</label>
|
||||
<input type="radio" name="playback-mode" value="preview-to-me" className="preview-to-me" /><label htmlFor="playback-mode-preview-me" className="radio-text">Preview Only to Me</label>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
getInitialState: () ->
|
||||
{controls: null, time: '0:00'}
|
||||
|
||||
componentDidUpdate: (prevProps, prevState) ->
|
||||
|
||||
componentDidMount: () ->
|
||||
$root = jQuery(this.getDOMNode())
|
||||
controls = context.JK.PlaybackControls($root, {mediaActions: BrowserMediaPlaybackActions})
|
||||
controls.setDisabled(@props.disabled)
|
||||
|
||||
@monitorControls(controls)
|
||||
|
||||
@setState({controls:controls})
|
||||
|
||||
componentWillUpdate: (nextProps) ->
|
||||
|
||||
@state.controls.setDisabled(nextProps.disabled) if @state.controls?
|
||||
})
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
context = window
|
||||
MIX_MODES = context.JK.MIX_MODES
|
||||
rest = context.JK.Rest()
|
||||
|
||||
|
||||
@JamTrackLandingScreen = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
|
||||
mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
|
||||
|
||||
getInitialState: () ->
|
||||
{user: null}
|
||||
{user: null, purchasedJamTracks: []}
|
||||
|
||||
onUserChanged: (userState) ->
|
||||
@onUser(userState.user) if userState.user
|
||||
|
||||
render: () ->
|
||||
|
||||
|
|
@ -22,10 +26,31 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
else
|
||||
howTo = `<div className="no-free-jamtrack">
|
||||
<span>
|
||||
To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or <a href="/client#/account/jamtracks">visit the JamTracks section of your account.</a>
|
||||
</span>
|
||||
The fastest way to start playing with your JamTracks is to open them below and use our <a href="https://jamkazam.desk.com/customer/en/portal/articles/2138903-using-custom-mixes-to-slow-tempo-change-pitch" onClick={this.customMixHelpClicked}>custom mix feature</a> to play them back in your browser. To access the full set of JamTrack features, <a href="/downloads" rel="external" onClick={this.downloadsClicked}>install our free app</a>. To learn more about all you can do with JamTracks, check out our <a href="https://jamkazam.desk.com/customer/en/portal/articles/2124663-playing-with-jamtracks" onClick={this.jamTrackHelpClicked}>JamTracks help docs</a>.
|
||||
</span>
|
||||
</div>`
|
||||
|
||||
playJamTracks = []
|
||||
|
||||
for jamTrack in @state.purchasedJamTracks
|
||||
playJamTracks.push `<tr className="play-jamtrack"><td><a onClick={this.onPlayJamTrack.bind(this, jamTrack)}>{jamTrack.name}</a> by {jamTrack.original_artist}</td></tr>`
|
||||
|
||||
if @state.purchasedJamTracks.length < 5
|
||||
# fill out the table with empty rows
|
||||
for x in [@state.purchasedJamTracks.length...(6 - @state.purchasedJamTracks.length )] by 1
|
||||
playJamTracks.push `<tr><td> </td></tr>`
|
||||
|
||||
playableJamTracks =
|
||||
`<div className="purchased-jam-tracks">
|
||||
<table className="jamtable" cellspacing="0" cellpadding="0" border="0">
|
||||
<thead>
|
||||
<tr><th className="jamtrack-name" align="left">JAMTRACKS</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{playJamTracks}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`
|
||||
|
||||
`<div className="content-body-scroller">
|
||||
<div className="list-columns">
|
||||
|
|
@ -33,6 +58,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
<h2>my jamtracks</h2>
|
||||
<div className="howto">
|
||||
{howTo}
|
||||
{playableJamTracks}
|
||||
</div>
|
||||
<h2 className="browse-jamtracks">search jamtracks</h2>
|
||||
<div className="search-area">
|
||||
|
|
@ -60,15 +86,20 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each part of the master mix. Used with the free JamKazam app & Internet service, you can:
|
||||
</div>
|
||||
<ul>
|
||||
<li>Solo just the part you want to play in order to hear and learn it</li>
|
||||
<li>Mute just the part you want to play and play along with the rest</li>
|
||||
<li>Choose your favorites from our catalog of 3,700+ songs</li>
|
||||
<li>Listen to just the single part you want to play to learn it</li>
|
||||
<li>Mute the part you want to play, and play along with the rest of the band</li>
|
||||
<li>Slow down playback to practice without changing the pitch</li>
|
||||
<li>Change the song key by raising or lowering pitch in half steps</li>
|
||||
<li>Make audio recordings and share them via Facebook or URL</li>
|
||||
<li>Make video recordings and share them via YouTube</li>
|
||||
<li>And even go online to play with others live & in sync</li>
|
||||
<li>Save your custom mixes for easy access, and export them to use anywhere</li>
|
||||
<li>Apply VST & AU audio plugin effects to your live performance</li>
|
||||
<li>Use MIDI with VST & AU instruments for keys, electronic drums, more</li>
|
||||
<li>Make audio recordings and share via Facebook or URL</li>
|
||||
<li>Make video recordings and share via YouTube</li>
|
||||
<li>Play online live and in sync with others from different locations</li>
|
||||
<li>JamTracks work in standard browser, with more features in Win/Mac apps</li>
|
||||
</ul>
|
||||
<a className="video-thumbnail" href="https://www.youtube.com/watch?v=askHvcCoNfw" rel="external">
|
||||
<a className="video-thumbnail" href="https://www.youtube.com/watch?v=07zJC7C2ICA" rel="external">
|
||||
<img className="play" src="/assets/content/icon_youtube_play.png" />
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -96,19 +127,50 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
instrument = $root.find('select.instrument-list').val()
|
||||
context.JamTrackActions.requestFilter(genre, instrument)
|
||||
|
||||
processUrl: () ->
|
||||
|
||||
if $.QueryString['redeemed_flow']?
|
||||
@redeemedFlow = true
|
||||
|
||||
if @redeemedFlow
|
||||
|
||||
if window.history.replaceState #ie9 proofing
|
||||
window.history.replaceState({}, "", "/client#/jamtrack")
|
||||
|
||||
preparePlayJamTrackProd: () ->
|
||||
setTimeout((() =>
|
||||
$element = $(this.getDOMNode()).find('.purchased-jam-tracks .play-jamtrack a').first()
|
||||
$offsetParent = $element.closest('.screen')
|
||||
|
||||
context.JK.HelpBubbleHelper.jamtrackWebPlay($element, $offsetParent)
|
||||
), 1500)
|
||||
|
||||
afterShow: (data) ->
|
||||
|
||||
if context.JK.currentUserId
|
||||
@app.user().done(@onUser)
|
||||
else
|
||||
@processUrl()
|
||||
|
||||
if !context.JK.currentUserId
|
||||
@onUser({free_jamtrack: context.JK.currentUserFreeJamTrack})
|
||||
|
||||
|
||||
|
||||
beforeShow: () ->
|
||||
@setState({user: null})
|
||||
|
||||
onUser:(user) ->
|
||||
@setState({user: user})
|
||||
|
||||
rest.getPurchasedJamTracks({page:1, per_page:20})
|
||||
.done((purchasedJamTracks) =>
|
||||
if @redeemedFlow
|
||||
@preparePlayJamTrackProd()
|
||||
@redeemedFlow = false
|
||||
@setState({purchasedJamTracks: purchasedJamTracks.jamtracks})
|
||||
)
|
||||
.fail((jqXHR, textStatus, errorMessage) =>
|
||||
@app.ajaxError(jqXHR, textStatus, errorMessage);
|
||||
)
|
||||
|
||||
# Get artist names and build links
|
||||
#@rest.getJamTrackArtists({group_artist: true, per_page:100})
|
||||
#.done(this.buildArtistLinks)
|
||||
|
|
@ -118,6 +180,24 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
# artist_name
|
||||
#@bindArtistLinks()
|
||||
|
||||
customMixHelpClicked: (e) ->
|
||||
e.preventDefault()
|
||||
context.JK.popExternalLink($(e.target).attr('href'))
|
||||
|
||||
downloadsClicked:(e) ->
|
||||
e.preventDefault()
|
||||
context.JK.popExternalLink($(e.target).attr('href'))
|
||||
|
||||
jamTrackHelpClicked: (e) ->
|
||||
e.preventDefault()
|
||||
context.JK.popExternalLink($(e.target).attr('href'))
|
||||
|
||||
onPlayJamTrack: (jamTrack, e) ->
|
||||
e.preventDefault()
|
||||
|
||||
# popup window
|
||||
JamTrackPlayerActions.open(jamTrack)
|
||||
|
||||
|
||||
onAppInit: (@app) ->
|
||||
@rest = context.JK.Rest()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,750 @@
|
|||
context = window
|
||||
logger = context.JK.logger
|
||||
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
||||
rest = context.JK.Rest()
|
||||
|
||||
mixins = []
|
||||
|
||||
|
||||
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
|
||||
# this check ensures we attempt to listen if this component is created in a popup
|
||||
reactContext = if window.opener? then window.opener else window
|
||||
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
|
||||
if window.opener?
|
||||
try
|
||||
m = window.opener.MixerStore
|
||||
catch e
|
||||
reactContext = window
|
||||
|
||||
# temporarily..
|
||||
# reactContext = window
|
||||
|
||||
AppActions = reactContext.AppActions
|
||||
JamTrackPlayerActions = reactContext.JamTrackPlayerActions
|
||||
JamTrackPlayerStore = reactContext.JamTrackPlayerStore
|
||||
|
||||
mixins.push(Reflux.listenTo(JamTrackPlayerStore, 'onJamTrackPlayerStoreChanged'))
|
||||
|
||||
|
||||
@PopupJamTrackPlayer = React.createClass({
|
||||
|
||||
mixins: mixins
|
||||
|
||||
|
||||
onJamTrackPlayerStoreChanged: (changes) ->
|
||||
#logger.debug("PopupMediaControls: jamtrack changed", changes)
|
||||
@setState({jamTrackState: changes})
|
||||
|
||||
showMetronome: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
SessionActions.showNativeMetronomeGui()
|
||||
|
||||
getInitialState: () ->
|
||||
state = {}
|
||||
state.jamTrackState = JamTrackPlayerStore.getState()
|
||||
state.showCustomMixes = true
|
||||
return state
|
||||
|
||||
close: () ->
|
||||
window.close()
|
||||
|
||||
help: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
AppActions.openExternalUrl($(e.target).attr('href'))
|
||||
|
||||
render: () ->
|
||||
|
||||
closeLinkText = null
|
||||
header = null
|
||||
extraControls = null
|
||||
|
||||
jamTrack = @state.jamTrackState?.jamTrack
|
||||
mediaType = "JamTrack"
|
||||
mediaName = jamTrack?.name
|
||||
closeLinkText = 'CLOSE JAMTRACK'
|
||||
helpLink = 'https://jamkazam.desk.com/customer/portal/articles/2138903-using-custom-mixes-to-slow-tempo-change-pitch'
|
||||
|
||||
selectedMixdown = jamTrack?.activeMixdown
|
||||
selectedStem = jamTrack?.activeStem
|
||||
|
||||
if selectedMixdown?
|
||||
jamTrackTypeHeader = 'Custom Mix'
|
||||
|
||||
disabled = true
|
||||
if selectedMixdown.client_state?
|
||||
switch selectedMixdown.client_state
|
||||
when 'cant_open'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'keying_timeout'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'download_fail'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'keying'
|
||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
when 'downloading'
|
||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
when 'ready'
|
||||
customMixName = `<h5>{selectedMixdown.name}</h5>`
|
||||
disabled = false
|
||||
else
|
||||
if selectedMixdown.myPackage
|
||||
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
else
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
|
||||
else if selectedStem?
|
||||
if selectedStem.instrument
|
||||
instrumentId = selectedStem.instrument.id
|
||||
instrumentDescription = selectedStem.instrument.description
|
||||
part = "(#{selectedStem.part})" if selectedStem.part? && selectedStem.part != instrumentDescription
|
||||
part = '' unless part
|
||||
|
||||
jamTrackTypeHeader = 'Track'
|
||||
|
||||
trackName = "#{instrumentDescription} #{part}"
|
||||
|
||||
disabled = true
|
||||
if selectedStem.client_state?
|
||||
switch selectedStem.client_state
|
||||
when 'downloading'
|
||||
customMixName = `<h5>Loading {trackName}... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
when 'ready'
|
||||
customMixName = `<h5>{trackName}</h5>`
|
||||
disabled = false
|
||||
else
|
||||
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
|
||||
else
|
||||
if jamTrack?.client_state == 'downloading'
|
||||
downloader = `<img src="/assets/shared/spinner.gif" />`
|
||||
|
||||
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
|
||||
|
||||
header = `
|
||||
<div className="header">
|
||||
<h3>{mediaType}: {mediaName}</h3>
|
||||
<h4>{jamTrackTypeHeader}</h4>
|
||||
{customMixName}
|
||||
</div>`
|
||||
|
||||
myMixes = null
|
||||
if @state.showMyMixes
|
||||
myMixdowns = []
|
||||
|
||||
boundPlayClick = this.jamTrackPlay.bind(this, jamTrack);
|
||||
boundDownloadClick = this.jamTrackDownload.bind(this, jamTrack);
|
||||
|
||||
active = jamTrack.last_mixdown_id == null && jamTrack.last_stem_id == null
|
||||
|
||||
myMixdowns.push `
|
||||
<div key="full-track" className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
|
||||
<div className="mixdown-name">
|
||||
Full JamTrack
|
||||
</div>
|
||||
<div className="mixdown-actions">
|
||||
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>
|
||||
<img src="/assets/content/icon_download@2X.png" className="mixdown-download" onClick={boundDownloadClick}/>
|
||||
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />
|
||||
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete" onClick={false} />
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
for mixdown in jamTrack.mixdowns
|
||||
boundPlayClick = this.mixdownPlay.bind(this, mixdown);
|
||||
boundEditClick = this.mixdownEdit.bind(this, mixdown);
|
||||
boundSaveClick = this.mixdownSave.bind(this, mixdown);
|
||||
boundDeleteClick = this.mixdownDelete.bind(this, mixdown);
|
||||
boundErrorClick = this.mixdownError.bind(this, mixdown);
|
||||
boundEditKeydown = this.onEditKeydown.bind(this, mixdown);
|
||||
|
||||
boundDownloadNotReadyClick = this.downloadNotReady.bind(this, mixdown)
|
||||
boundDownloadReadyClick = this.downloadMixdownReady.bind(this, mixdown)
|
||||
|
||||
mixdown_package = mixdown.myPackage
|
||||
|
||||
active = mixdown.id == jamTrack.last_mixdown_id
|
||||
|
||||
editing = mixdown.id == @state.editingMixdownId
|
||||
|
||||
# if there is a package, check it's state; otherwise let the user enqueue it
|
||||
if mixdown_package
|
||||
switch mixdown_package.signing_state
|
||||
when 'QUIET_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'QUIET'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'QUEUED'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'QUEUED_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'SIGNING'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'SIGNING_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'SIGNED'
|
||||
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>`
|
||||
when 'ERROR'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
else
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
|
||||
if editing
|
||||
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name} onKeyDown={boundEditKeydown} />`
|
||||
editIcon = `<img src="/assets/content/icon-save@2X.png" className="mixdown-edit" onClick={boundSaveClick}/>`
|
||||
else
|
||||
mixdownName = mixdown.name
|
||||
editIcon = `<img src="/assets/content/icon-edit@2X.png" className="mixdown-edit" onClick={boundEditClick}/>`
|
||||
|
||||
# create hidden objects to deal with alginment issues using a table
|
||||
if !editIcon
|
||||
editIcon = `<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />`
|
||||
|
||||
download = `<img src="/assets/content/icon_download@2X.png" className="mixdown-download" onClick={boundDownloadReadyClick} />`
|
||||
|
||||
myMixdowns.push `
|
||||
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
|
||||
<div className="mixdown-name">
|
||||
{mixdownName}
|
||||
</div>
|
||||
<div className="mixdown-actions">
|
||||
{action}
|
||||
{download}
|
||||
{editIcon}
|
||||
|
||||
<img src ="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick} />
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
active = jamTrack.last_stem_id?
|
||||
trackOptions = []
|
||||
for track in jamTrack.tracks
|
||||
if track.track_type == 'Track'
|
||||
|
||||
|
||||
if track.instrument
|
||||
instrumentId = track.instrument.id
|
||||
instrumentDescription = track.instrument.description
|
||||
if track.part? && track.part != instrumentDescription
|
||||
part = "(#{track.part})"
|
||||
else
|
||||
part = ''
|
||||
trackOptions.push(`<option key={track.id} value={track.id}>{instrumentDescription} {part}</option>`)
|
||||
|
||||
boundStemActivateClick = this.activateStem.bind(this)
|
||||
boundStemPlayClick = this.downloadStem.bind(this)
|
||||
boundStemChange = this.stemChanged.bind(this)
|
||||
|
||||
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundStemActivateClick}/>`
|
||||
download = `<img src="/assets/content/icon_download@2X.png" className="mixdown-download" onClick={boundStemPlayClick} />`
|
||||
|
||||
myMixdowns.push `
|
||||
<div key={track.id} className={classNames({'stem-track' : true, 'mixdown-display': true, 'active' : active})}>
|
||||
<div className="mixdown-name">
|
||||
<select className="active-stem-select" name="active-stem-select" onChange={boundStemChange} defaultValue={jamTrack.last_stem_id}>
|
||||
<option key="null" value="">or select a track</option>
|
||||
{trackOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mixdown-actions">
|
||||
{action}
|
||||
{download}
|
||||
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />
|
||||
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete" onClick={false} />
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
myMixes = `<div key="my-mixes" className="my-mixes">{myMixdowns}</div>`
|
||||
|
||||
mixControls = null
|
||||
|
||||
if @state.showCustomMixes
|
||||
|
||||
tracks = []
|
||||
if jamTrack?
|
||||
for track in jamTrack.tracks
|
||||
if track.track_type == 'Track'
|
||||
if track.instrument
|
||||
instrumentId = track.instrument.id
|
||||
instrumentDescription = track.instrument.description
|
||||
if track.part? && track.part != instrumentDescription
|
||||
part = "(#{track.part})"
|
||||
else
|
||||
part = ''
|
||||
|
||||
|
||||
tracks.push(`
|
||||
<tr className="stem">
|
||||
<td><img src={context.JK.getInstrumentIcon24(instrumentId)} className="instrument-icon" /> {instrumentDescription} {part}</td>
|
||||
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id} /></td>
|
||||
</tr>`)
|
||||
|
||||
stems = `<div key="stems" className="stems">
|
||||
<table>
|
||||
<thead>
|
||||
<col align="left" /><col align="right"/>
|
||||
<tr><th>TRACKS</th><th className="mute">mute</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tracks}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`
|
||||
|
||||
|
||||
nameClassData = {field: true}
|
||||
if @state.createMixdownErrors?
|
||||
|
||||
errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
|
||||
|
||||
createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown})
|
||||
|
||||
mixControls = `
|
||||
<div key="create-mix" className="create-mix">
|
||||
<p>Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.</p>
|
||||
{stems}
|
||||
<div className="field">
|
||||
<label>TEMPO</label>
|
||||
<select name="mix-speed">
|
||||
<option value="">No change</option>
|
||||
<option value="separator-1">------------</option>
|
||||
<option value="-5">Slower by 5%</option>
|
||||
<option value="-10">Slower by 10%</option>
|
||||
<option value="-15">Slower by 15%</option>
|
||||
<option value="-20">Slower by 20%</option>
|
||||
<option value="-25">Slower by 25%</option>
|
||||
<option value="-30">Slower by 30%</option>
|
||||
<option value="-35">Slower by 35%</option>
|
||||
<option value="-40">Slower by 40%</option>
|
||||
<option value="-45">Slower by 45%</option>
|
||||
<option value="-50">Slower by 50%</option>
|
||||
<option value="-60">Slower by 60%</option>
|
||||
<option value="-70">Slower by 70%</option>
|
||||
<option value="-80">Slower by 80%</option>
|
||||
<option value="separator-2">------------</option>
|
||||
<option value="5">Faster by 5%</option>
|
||||
<option value="10">Faster by 10%</option>
|
||||
<option value="15">Faster by 15%</option>
|
||||
<option value="20">Faster by 20%</option>
|
||||
<option value="30">Faster by 30%</option>
|
||||
<option value="40">Faster by 40%</option>
|
||||
<option value="50">Faster by 50%</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>PITCH</label>
|
||||
<select name="mix-pitch">
|
||||
<option value="">No change</option>
|
||||
<option value="separator-1">---------------</option>
|
||||
<option value="-1">Down 1 Semitone</option>
|
||||
<option value="-2">Down 2 Semitones</option>
|
||||
<option value="-3">Down 3 Semitones</option>
|
||||
<option value="-4">Down 4 Semitones</option>
|
||||
<option value="-5">Down 5 Semitones</option>
|
||||
<option value="-6">Down 6 Semitones</option>
|
||||
<option value="-7">Down 7 Semitones</option>
|
||||
<option value="-8">Down 8 Semitones</option>
|
||||
<option value="-9">Down 9 Semitones</option>
|
||||
<option value="-10">Down 10 Semitones</option>
|
||||
<option value="-11">Down 11 Semitones</option>
|
||||
<option value="-12">Down 12 Semitones</option>
|
||||
<option value="separator-2">---------------</option>
|
||||
<option value="1">Up 1 Semitone</option>
|
||||
<option value="2">Up 2 Semitones</option>
|
||||
<option value="3">Up 3 Semitones</option>
|
||||
<option value="4">Up 4 Semitones</option>
|
||||
<option value="5">Up 5 Semitones</option>
|
||||
<option value="6">Up 6 Semitones</option>
|
||||
<option value="7">Up 7 Semitones</option>
|
||||
<option value="8">Up 8 Semitones</option>
|
||||
<option value="9">Up 9 Semitones</option>
|
||||
<option value="10">Up 10 Semitones</option>
|
||||
<option value="11">Up 11 Semitones</option>
|
||||
<option value="12">Up 12 Semitones</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className={classNames(nameClassData)}>
|
||||
<label>MIX NAME</label>
|
||||
<input type="text" name="mix-name"/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<a className={createMixClasses} onClick={this.createMix}>CREATE MIX</a>
|
||||
{errorHtml}
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
|
||||
</div>`
|
||||
|
||||
|
||||
if @state.showMyMixes
|
||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>hide my mixes <div className="details-arrow arrow-up" /></a>`
|
||||
else
|
||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>show my mixes <div className="details-arrow arrow-down" /></a>`
|
||||
|
||||
if @state.showCustomMixes
|
||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>hide mix controls <div className="details-arrow arrow-up" /></a>`
|
||||
else
|
||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>show mix controls <div className="details-arrow arrow-down" /></a>`
|
||||
|
||||
extraControls = `
|
||||
<div className="extra-controls">
|
||||
<h4>My Mixes {showMyMixesText}</h4>
|
||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
||||
{myMixes}
|
||||
</ReactCSSTransitionGroup>
|
||||
<h4 className="custom-mix-header">Create Custom Mix {showMixControlsText}</h4>
|
||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={false}>
|
||||
{mixControls}
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>`
|
||||
|
||||
|
||||
|
||||
if helpLink?
|
||||
helpButton = `<a className="help-link button-grey" href={helpLink} onClick={this.help}>HELP</a>`
|
||||
|
||||
`<div className="media-controls-popup">
|
||||
{header}
|
||||
<BrowserMediaControls disabled={this.disableLoading}/>
|
||||
{extraControls}
|
||||
<div className="actions">
|
||||
{helpButton}
|
||||
<a className="close-link button-orange" onClick={this.close}>{closeLinkText}</a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
windowUnloaded: () ->
|
||||
JamTrackPlayerActions.windowUnloaded() unless window.DontAutoCloseMedia
|
||||
|
||||
toggleMyMixes: (e) ->
|
||||
e.preventDefault()
|
||||
@setState({showMyMixes: !@state.showMyMixes})
|
||||
|
||||
toggleCustomMixes: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
|
||||
@setState({showCustomMixes: !@state.showCustomMixes})
|
||||
|
||||
downloadNotReady: (mixdown, e) ->
|
||||
alert("This mix is not yet ready to download.")
|
||||
e.preventDefault()
|
||||
|
||||
downloadMixdownReady: (mixdown, e) ->
|
||||
e.preventDefault()
|
||||
|
||||
if mixdown.myPackage?.signing_state == 'SIGNED'
|
||||
iframe = document.createElement("iframe")
|
||||
iframe.src = window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{mixdown.id}/download.mp3?file_type=mp3&sample_rate=48&download=1"
|
||||
iframe.style.display = "none"
|
||||
document.body.appendChild(iframe);
|
||||
else
|
||||
alert("The mix is not yet ready to download")
|
||||
|
||||
activateStem: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$select = $(this.getDOMNode()).find('.active-stem-select')
|
||||
|
||||
selectedTrackId = $select.val()
|
||||
|
||||
if !selectedTrackId? || selectedTrackId == ''
|
||||
alert("You must pick a track from the dropdown in order to play it.")
|
||||
else
|
||||
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if @disableLoading
|
||||
alert('Certain actions are disabled while a track is being loaded.')
|
||||
return
|
||||
|
||||
# make this package the active one
|
||||
JamTrackPlayerActions.openStem(selectedTrackId)
|
||||
|
||||
downloadStem: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$select = $(this.getDOMNode()).find('.active-stem-select')
|
||||
|
||||
selectedTrackId = $select.val()
|
||||
|
||||
if !selectedTrackId? || selectedTrackId == ''
|
||||
alert("You must select a track in order to download.")
|
||||
else
|
||||
e.preventDefault()
|
||||
|
||||
iframe = document.createElement("iframe")
|
||||
iframe.src = window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{@state.jamTrackState.jamTrack.id}/stems/#{selectedTrackId}/download.mp3?file_type=mp3&download=1"
|
||||
iframe.style.display = "none"
|
||||
document.body.appendChild(iframe);
|
||||
stemChanged: () ->
|
||||
|
||||
mixdownPlay: (mixdown, e) ->
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if @disableLoading
|
||||
alert('Certain actions are disabled while a track is being loaded.')
|
||||
return
|
||||
|
||||
# make this package the active one
|
||||
JamTrackPlayerActions.openMixdown(mixdown)
|
||||
|
||||
jamTrackPlay: (jamtrack, e) ->
|
||||
e.preventDefault()
|
||||
# user wants to select the full track
|
||||
|
||||
if @disableLoading
|
||||
alert('Certain actions are disabled while a track is being loaded.')
|
||||
return
|
||||
|
||||
JamTrackPlayerActions.activateNoMixdown(jamtrack)
|
||||
|
||||
jamTrackDownload: (jamTrack, e) ->
|
||||
e.preventDefault()
|
||||
|
||||
iframe = document.createElement("iframe")
|
||||
iframe.src = window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{jamTrack.id}/stems/master/download.mp3?file_type=mp3&download=1"
|
||||
iframe.style.display = "none"
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
|
||||
onEditKeydown: (mixdown, e) ->
|
||||
logger.debug("on edit keydown", e)
|
||||
if e.keyCode == 13 # enter
|
||||
@mixdownSave(mixdown, e)
|
||||
else if e.keyCode == 27 # esc
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
mixdownEdit: (mixdown) ->
|
||||
@setState({editingMixdownId: mixdown.id})
|
||||
|
||||
mixdownSave: (mixdown, e) ->
|
||||
e.preventDefault()
|
||||
$input = $(this.getDOMNode()).find('input.edit-name')
|
||||
newValue = $input.val()
|
||||
logger.debug("editing mixdown name to be: " + newValue)
|
||||
JamTrackPlayerActions.editMixdown({id: mixdown.id, name: newValue})
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
mixdownDelete: (mixdown) ->
|
||||
if @state.editingMixdownId?
|
||||
@setState({editingMixdownId:null})
|
||||
return
|
||||
|
||||
if confirm("Delete this custom mix?")
|
||||
JamTrackPlayerActions.deleteMixdown(mixdown)
|
||||
|
||||
|
||||
mixdownError: (mixdown) ->
|
||||
|
||||
myPackage = mixdown.myPackage
|
||||
|
||||
if myPackage?
|
||||
switch myPackage.signing_state
|
||||
when 'QUIET_TIMEOUT'
|
||||
action = 'Custom mix never got created. Retry?'
|
||||
when 'QUEUED_TIMEOUT'
|
||||
action = 'Custom mix was never built. Retry?'
|
||||
when 'SIGNING_TIMEOUT'
|
||||
action = 'Custom mix took took long to build. Retry?'
|
||||
when 'ERROR'
|
||||
action = 'Custom mix failed to build. Retry?'
|
||||
else
|
||||
action = 'Custom mix never got created. Retry?'
|
||||
|
||||
return unless action?
|
||||
|
||||
if confirm(action)
|
||||
JamTrackPlayerActions.enqueueMixdown(mixdown, @enqueueDone)
|
||||
|
||||
enqueueDone: (enqueued) ->
|
||||
@promptEstimate(enqueued)
|
||||
|
||||
promptEstimate: (enqueued) ->
|
||||
time = enqueued.queue_time
|
||||
|
||||
if time == 0
|
||||
alert("Your custom mix will take about 1 minute to be created.")
|
||||
else
|
||||
guess = Math.ceil(time / 60.0)
|
||||
if guess == 1
|
||||
msg = '1 minute'
|
||||
else
|
||||
msg = "#{guess} minutes"
|
||||
alert("Your custom mix will take about #{msg} to be created.")
|
||||
|
||||
|
||||
createMix: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
return if @state.creatingMix
|
||||
|
||||
$root = $(@getDOMNode())
|
||||
|
||||
name = $root.find('input[name="mix-name"]').val()
|
||||
speed = $root.find('select[name="mix-speed"]').val()
|
||||
pitch = $root.find('select[name="mix-pitch"]').val()
|
||||
|
||||
if name == null || name == ''
|
||||
@setState({createMixdownErrors: {errors: {'Mix Name': ["can't be blank"]}}})
|
||||
return
|
||||
|
||||
# sanitize junk out of speed/pitch
|
||||
if speed == '' || speed.indexOf('separator') > -1
|
||||
speed = undefined
|
||||
else
|
||||
speed = parseInt(speed)
|
||||
if pitch == '' || pitch.indexOf('separator') > -1
|
||||
pitch = undefined
|
||||
else
|
||||
pitch = parseInt(pitch)
|
||||
|
||||
|
||||
# get mute state of all tracks
|
||||
$mutes = $(@getDOMNode()).find('.stems .stem .stem-mute')
|
||||
|
||||
tracks = []
|
||||
$mutes.each((i, mute) =>
|
||||
$mute = $(mute)
|
||||
stemId = $mute.attr('data-stem-id')
|
||||
muted = $mute.is(':checked')
|
||||
|
||||
tracks.push({id: stemId, mute: muted})
|
||||
)
|
||||
|
||||
mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, tracks:tracks}}
|
||||
|
||||
JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)
|
||||
|
||||
@setState({creatingMixdown: true, createMixdownErrors: null})
|
||||
|
||||
createMixdownDone: (created) ->
|
||||
logger.debug("created (within PopupMediaControls)", created)
|
||||
@setState({creatingMixdown: false, showCustomMixes: true, showMyMixes: true})
|
||||
|
||||
@promptEstimate(created)
|
||||
|
||||
createMixdownFail: (jqXHR) ->
|
||||
logger.debug("create mixdown fail (within PopupMediaControls)", jqXHR.status)
|
||||
@setState({creatingMixdown: false})
|
||||
if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
logger.warn("failed to create mixdown", response, jqXHR.responseText)
|
||||
|
||||
@setState({createMixdownErrors: response})
|
||||
|
||||
fetchJamTrackInfo: () ->
|
||||
rest.getJamTrack({plan_code: gon.jamtrack_id})
|
||||
.done((response) =>
|
||||
JamTrackPlayerActions.open(response, false);
|
||||
@fetchUserInfo()
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
alert("Unable to fetch JamTrack information. Try logging in.")
|
||||
)
|
||||
|
||||
fetchUserInfo: () ->
|
||||
rest.getUserDetail()
|
||||
.done((response) =>
|
||||
|
||||
rest.postUserEvent({name: 'jamtrack_web_player_open'})
|
||||
context.stats.write('web.jamtrack_web_player.open', {
|
||||
value: 1,
|
||||
user_id: context.JK.currentUserId,
|
||||
user_name: context.JK.currentUserName
|
||||
})
|
||||
|
||||
if !response.first_opened_jamtrack_web_player
|
||||
setTimeout((() =>
|
||||
logger.debug("first time user")
|
||||
rest.userOpenedJamTrackWebPlayer()
|
||||
$root = $(@getDOMNode())
|
||||
#context.JK.prodBubble($root.find('.create-mix-btn'), 'first-time-jamtrack-web-player', {}, {positions:['left'], offsetParent: $root})
|
||||
), 1500)
|
||||
)
|
||||
|
||||
|
||||
|
||||
componentDidMount: () ->
|
||||
|
||||
$(window).unload(@windowUnloaded)
|
||||
|
||||
@root = jQuery(this.getDOMNode())
|
||||
|
||||
$loop = @root.find('input[name="loop"]')
|
||||
context.JK.checkbox($loop)
|
||||
|
||||
$loop.on('ifChecked', () =>
|
||||
# it doesn't matter if you do personal or master, because backend just syncs both
|
||||
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, true)
|
||||
)
|
||||
$loop.on('ifUnchecked', () =>
|
||||
# it doesn't matter if you do personal or master, because backend just syncs both
|
||||
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, false)
|
||||
)
|
||||
|
||||
@resizeWindow()
|
||||
|
||||
# this is necessary due to whatever the client's rendering behavior is.
|
||||
setTimeout(@resizeWindow, 300)
|
||||
|
||||
@fetchJamTrackInfo()
|
||||
|
||||
componentDidUpdate: () ->
|
||||
@resizeWindow()
|
||||
setTimeout(@resizeWindow, 1000)
|
||||
|
||||
resizeWindow: () =>
|
||||
$container = $('#minimal-container')
|
||||
width = $container.width()
|
||||
height = $container.height()
|
||||
|
||||
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
|
||||
#mysteryTopMargin = 20
|
||||
mysteryTopMargin = 0
|
||||
# deal with chrome in real browsers
|
||||
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
|
||||
|
||||
# handle native client chrome that the above outer-inner doesn't catch
|
||||
#if navigator.userAgent.indexOf('JamKazam') > -1
|
||||
|
||||
#offset += 25
|
||||
|
||||
window.resizeTo(450, height + offset)
|
||||
|
||||
computeDisableLoading: (state) ->
|
||||
@disableLoading = false
|
||||
|
||||
return unless nextState?
|
||||
|
||||
selectedMixdown = state?.jamTrackState?.jamTrack?.activeMixdown
|
||||
|
||||
mixdownDownloading = false
|
||||
if selectedMixdown?
|
||||
switch selectedMixdown.client_state
|
||||
when 'downloading'
|
||||
mixdownDownloading = true
|
||||
|
||||
selectedStem = state?.jamTrackState?.jamTrack?.activeStem
|
||||
|
||||
stemDownloading = false
|
||||
if selectedStem?
|
||||
switch selectedStem.client_state
|
||||
when 'downloading'
|
||||
stemDownloading = true
|
||||
|
||||
@disableLoading = state?.jamTrackState?.jamTrack?.client_state == 'downloading' || mixdownDownloading || stemDownloading
|
||||
|
||||
componentWillMount: () ->
|
||||
@computeDisableLoading(@state)
|
||||
|
||||
componentWillUpdate: (nextProps, nextState) ->
|
||||
@computeDisableLoading(nextState)
|
||||
|
||||
|
||||
})
|
||||
|
|
@ -252,7 +252,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
|||
if !selectedMixdown?
|
||||
mixControls = `
|
||||
<div key="create-mix" className="create-mix">
|
||||
<p>Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.</p>
|
||||
<p>Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.</p>
|
||||
<div className="field">
|
||||
<label>Change Tempo:</label>
|
||||
<select name="mix-speed">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
context = window
|
||||
|
||||
@BrowserMediaActions = Reflux.createActions({
|
||||
load: {}
|
||||
play: {}
|
||||
stop: {}
|
||||
pause: {}
|
||||
seek: {}
|
||||
setVolume: {}
|
||||
getPlayPosition: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
context = window
|
||||
|
||||
@BrowserMediaPlaybackActions = Reflux.createActions({
|
||||
playbackStateChange: {}
|
||||
positionUpdate:{}
|
||||
mediaStartPlay: {}
|
||||
mediaStopPlay: {}
|
||||
mediaPausePlay: {}
|
||||
mediaChangePosition: {}
|
||||
currentTimeChanged: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
context = window
|
||||
|
||||
@JamTrackPlayerActions = Reflux.createActions({
|
||||
open: {}
|
||||
createMixdown: {}
|
||||
editMixdown: {}
|
||||
deleteMixdown: {}
|
||||
openMixdown: {}
|
||||
activateNoMixdown: {}
|
||||
closeMixdown: {}
|
||||
enqueueMixdown: {}
|
||||
downloadMixdown: {}
|
||||
refreshMixdown: {}
|
||||
openStem: {}
|
||||
|
||||
windowUnloaded: {}
|
||||
})
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
context = window
|
||||
|
||||
@UserActions = Reflux.createActions({
|
||||
loaded: {}
|
||||
})
|
||||
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
context = window
|
||||
|
||||
@JamTrackLandingBottomPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
`<div className="top-container">
|
||||
<div className="row awesome">
|
||||
<h2 className="awesome">What Makes JamTracks Awesome?</h2>
|
||||
|
||||
<p>JamTracks by JamKazam deliver an unparalleled combination of multitrack pro audio and whiz bang technology -
|
||||
all with an eye toward the things that really matter to musicians who love to play. Below are the top 10 great
|
||||
things about JamTracks.
|
||||
</p>
|
||||
|
||||
<div className="testimonials">
|
||||
<h3>Leading Musicians & Teachers Love JamTracks</h3>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Andy Crowley - Speech Bubble.png" className="testimonial-speech-bubble" />
|
||||
<img src="/assets/landing/Andy Crowley - Avatar.png" className="testimonial-avatar" />
|
||||
<a rel="external" href="https://www.youtube.com/user/andycrowley"><img src="/assets/landing/Andy Crowley - YouTube.png" className="testimonial-youtube" /></a>
|
||||
<h4><strong>Andy Crowley</strong> of AndyGuitar</h4>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Ryan Jones - Speech Bubble.png" className="testimonial-speech-bubble" />
|
||||
<img src="/assets/landing/Ryan Jones - Avatar.png" className="testimonial-avatar" />
|
||||
<a rel="external" href="https://www.youtube.com/user/gotitans999"><img src="/assets/landing/Ryan Jones - PianoKeyz - YouTube.png" className="testimonial-youtube" /></a>
|
||||
<h4><strong>Ryan Jones</strong> of PianoKeyz</h4>
|
||||
</div>
|
||||
<div className="testimonial">
|
||||
<img src="/assets/landing/Carl Brown - Speech Bubble.png" className="testimonial-speech-bubble" />
|
||||
<img src="/assets/landing/Carl Brown - Avatar.png" className="testimonial-avatar" />
|
||||
<a rel="external" href="https://www.youtube.com/channel/UCvnfBBzEizi1T5unOXNCxdQ"><img src="/assets/landing/Carl Brown - YouTube.png" className="testimonial-youtube" /></a>
|
||||
<h4><strong>Carl Brown</strong> of GuitarLessions365</h4>
|
||||
</div>
|
||||
<div className="jamtrack-overview-video">
|
||||
<h3>Watch A JamTracks Overview Video</h3>
|
||||
<div className="video-wrapper left">
|
||||
<div className="video-container">
|
||||
<iframe src="//www.youtube.com/embed/07zJC7C2ICA" frameborder="0" allowfullscreen="allowfullscreen" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">1</div>Huge, High Quality Multi-Track Catalog</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 1.png" className="awesome-image" width="405" height="212"/>
|
||||
JamKazam offers a catalog of 3,700+ songs. Each song is reviewed for quality, and every recording
|
||||
is a complete multi-track, with fully isolated tracks for each part of the music - e.g. lead vocal,
|
||||
backing vocals, lead guitar, rhythm guitar, keys, bass, drums, etc. This gives you complete creative control
|
||||
over every aspect of the music and how you want to use it for learning, practice, recording, and other creative endeavors.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">2</div>Solo, Mute, Pan or Set Level on Any Part</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 2.png" className="awesome-image left" width="150" height="191" />
|
||||
When learning to play a part, it's incredibly valuable to be able to hear just one part in isolation.
|
||||
Once you've learned your part, you can turn around and mute just that one part, and then play along with the rest of the band.
|
||||
Or if you prefer, you can turn that part down low but keep it around as a subtle hint. Or pan the recorded track into
|
||||
your left ear while your live performance is panned into your right ear. Whatever you like! You are in control.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">3</div>Make Custom Mixes</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 3.png" className="awesome-image" width="224" height="276" />
|
||||
When you've customized the JamTrack mix, you can easily save your custom mixes to use them again later without
|
||||
having to recreate them. Your custom mixes are saved to the JamKazam cloud, so you can access them from almost
|
||||
any Internet-connected device. If you want to use your mixes outside the JamKazam app, you can also export custom mixes
|
||||
as a simple .mp3, .wav, or .ogg audio file to use anywhere.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">4</div>Slow Down For Practice</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 4.png" className="awesome-image left" width="220" height="203" />
|
||||
You can easily slow down playback of your JamTrack by a specific % without changing pitch, so that the song still
|
||||
sounds "right", just slower. This is great for building your technique on tougher sections while gradually increasing tempo.
|
||||
You can also make JamTracks play faster if you want to hit the jets.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">5</div>Change Pitch/Key</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 5.png" className="awesome-image" width="228" height="200" />
|
||||
If you're a singer and you need to bring the song down into your vocal range, or if you're an instrumentalist
|
||||
and want to change the piece to a different key, the JamKazam app lets you change the pitch of any JamTrack up or down
|
||||
by a specified number of semitones (half steps).
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">6</div>Apply VST & AU Audio Plug-In Effects</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 6.png" className="awesome-image left" width="350" height="240" />
|
||||
The free JamKazam app lets you easily apply VST & AU plugin effects to your live performance, mixed together
|
||||
seamlessly with JamTrack playback. For example, guitarists can apply popular amp sims like AmpliTube to get
|
||||
just the right guitar tone to match the song, and vocalists can apply effects like reverb, pitch correction, etc.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">7</div>Use MIDI Instruments</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 7.png" className="awesome-image" width="320" height="257" />
|
||||
The free JamKazam app also lets you use MIDI instruments, and mix and record this instrumental audio with JamTracks.
|
||||
For example, keys players can use MIDI keyboard controllers with VST & AU plugins to generate traditional piano sounds,
|
||||
Rhodes electric piano, Hammond organ, and other classic keys tones. And drummers who use electronic kits can use their favorite
|
||||
plugins to power their percussive audio.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">8</div>Make & Share Recordings</h3>
|
||||
<p>
|
||||
<div className="video-wrapper left">
|
||||
<div className="video-container">
|
||||
<iframe src="//www.youtube.com/embed/ysptXwFYDhQ" frameborder="0" allowfullscreen="allowfullscreen" />
|
||||
</div>
|
||||
<div className="cta-text">watch this sample video made by one of our users</div>
|
||||
</div>
|
||||
<p>Use the JamKazam app to make either audio-only or video + audio recordings. The app captures video from built-in
|
||||
or external webcams and combines this video with the mixed audio from the JamTrack and your live performance
|
||||
into a single integrated video, and will even upload the video to YouTube for you!</p>
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">9</div>Play Live In Sync With Others Over the Internet</h3>
|
||||
<p>
|
||||
<div className="video-wrapper">
|
||||
<div className="video-container">
|
||||
<iframe src="//www.youtube.com/embed/MASQJnlUBAM" frameborder="0" allowfullscreen="allowfullscreen" />
|
||||
</div>
|
||||
<div className="cta-text">watch this sample video made by three of our users</div>
|
||||
</div>
|
||||
<p>Perhaps the most mind-blowing thing you can do with JamKazam is that you can play live in sync with others
|
||||
from different locations over the Internet using the free JamKazam app and Internet service. And you can play
|
||||
with your JamTracks in these sessions, with others playing different parts.</p>
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row awesome-thing">
|
||||
<div className="awesome-item">
|
||||
<h3> <div className="awesome-number">10</div>JamTracks Work With All Your Stuff</h3>
|
||||
<p><img src="/assets/landing/Top 10 Image - Number 10.png" className="awesome-image" width="160" height="326" />
|
||||
You can use your JamTracks with any device that can run a standard web browser for playback of the JamTracks.
|
||||
If you want to mix your live performance with the JamTrack for recordings, and to access other advanced
|
||||
features, you'll need to use a JamKazam app. Our app is currently available for Mac and Windows computers,
|
||||
and we will have iOS and Android apps coming very soon as well. So you can access your JamTracks on just about any device.
|
||||
<div className="clearall" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>`
|
||||
})
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
context = window
|
||||
rest = context.JK.Rest()
|
||||
|
||||
@JamTrackLandingPage = React.createClass({
|
||||
|
||||
render: () ->
|
||||
|
||||
hasFree = context.JK.currentUserFreeJamTrack
|
||||
|
||||
loggedIn = context.JK.currentUserId?
|
||||
|
||||
if this.state.done
|
||||
ctaButtonText = 'sending you in...'
|
||||
else if this.state.processing
|
||||
ctaButtonText = 'hold on...'
|
||||
else
|
||||
if hasFree
|
||||
ctaButtonText = 'GET IT FREE!'
|
||||
else
|
||||
ctaButtonText = 'Add To Cart'
|
||||
|
||||
if loggedIn
|
||||
loggedInCtaButton = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>`
|
||||
if !hasFree
|
||||
loggedInPriceAdvisory = `<div className="price-advisory">$1.99</div>`
|
||||
else
|
||||
if !hasFree
|
||||
loggedOutPriceAdvisory = `<div className="price-advisory">$1.99</div>`
|
||||
|
||||
if this.state.loginErrors?
|
||||
for key, value of this.state.loginErrors
|
||||
break
|
||||
|
||||
errorText = context.JK.getFullFirstError(key, this.state.loginErrors, {email: 'Email', password: 'Password', 'terms_of_service' : 'The terms of service'})
|
||||
|
||||
register = `<div className="register-area">
|
||||
<p>Register for a free account to get this JamTrack free. We will not share your email. See our <a className="privacy-policy" onClick={this.privacyPolicy}>privacy policy</a>.</p>
|
||||
<div className={classNames({'errors': true, 'active': this.state.loginErrors})}>
|
||||
{errorText}
|
||||
</div>
|
||||
<form className="jamtrack-signup-form">
|
||||
<label>Email: </label><input type="text" name="email" />
|
||||
<label>Password: </label><input type="password" name="password" />
|
||||
<div className="clearall"/>
|
||||
<input className="terms-checkbox" type="checkbox" name="terms" /><label className="terms-help">I have read and agree to the JamKazam <a href="/corp/terms" onClick={this.termsClicked}>terms of service</a></label>
|
||||
<div className="clearall"/>
|
||||
<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>
|
||||
{loggedOutPriceAdvisory}
|
||||
</form>
|
||||
|
||||
<a className="browse-all" href="/client?search=#/jamtrack/search">or browse our catalog of 3,700+ songs</a>
|
||||
</div>`
|
||||
|
||||
|
||||
`<div className="top-container">
|
||||
<div className="full-row name-and-artist">
|
||||
<div>
|
||||
<img className="app-preview" width="340" height="178" src="/assets/landing/JK_FBAd_Guitar_with_Keys.png" alt="screenshot of app"/>
|
||||
<h1 className="jam-track-name">{this.props.jam_track.name.toUpperCase()}</h1>
|
||||
<h2 className="original-artist">by {this.props.jam_track.original_artist.toUpperCase()}</h2>
|
||||
<div className="clearall"/>
|
||||
</div>
|
||||
<div className="preview-and-action-box">
|
||||
<img src="/assets/landing/jamtrack_landing_arrow_1.png" className="arrow1" />
|
||||
<img src="/assets/landing/jamtrack_landing_arrow_2.png" className="arrow2" />
|
||||
<div className="preview-jamtrack-header">
|
||||
Preview JamTrack
|
||||
</div>
|
||||
<div className={classNames({'preview-area': true, 'logged-in' : loggedIn})}>
|
||||
<p>Click the play buttons below to preview the master mix and 20-second samples of all the isolated tracks.</p>
|
||||
<div className="tracks previews">
|
||||
|
||||
</div>
|
||||
{loggedInCtaButton}
|
||||
{loggedInPriceAdvisory}
|
||||
</div>
|
||||
|
||||
{register}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row summary-text">
|
||||
<p className="top-summary">
|
||||
JamTracks by JamKazam are the best way to play along with your favorite songs. Far better and different than traditional
|
||||
backing tracks, our JamTracks are complete multi-track professional recordings, with fully isolated tracks for each part of the music.
|
||||
And our free app and Internet service are packed with features that give you unmatched creative freedom to learn, practice, record, play with others, and share your performances.
|
||||
</p>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
getInitialState: () ->
|
||||
{loginErrors: null, processing:false}
|
||||
|
||||
privacyPolicy: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
context.JK.popExternalLink('/corp/privacy')
|
||||
|
||||
termsClicked: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
context.JK.popExternalLink('/corp/terms')
|
||||
|
||||
componentDidMount:() ->
|
||||
$root = $(this.getDOMNode())
|
||||
$checkbox = $root.find('.terms-checkbox')
|
||||
console.log("$checkbox", $checkbox)
|
||||
context.JK.checkbox($checkbox)
|
||||
|
||||
# add item to cart, create the user if necessary, and then place the order to get the free JamTrack.
|
||||
ctaClick: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
return if @state.processing
|
||||
|
||||
@setState({loginErrors: null})
|
||||
|
||||
loggedIn = context.JK.currentUserId?
|
||||
|
||||
isFree = context.JK.currentUserFreeJamTrack
|
||||
|
||||
|
||||
rest.addJamtrackToShoppingCart({id: @props.jam_track.id}).done((response) =>
|
||||
if isFree
|
||||
if loggedIn
|
||||
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
|
||||
@setState({done: true})
|
||||
context.location = '/client?redeemed_flow=1#/jamtrack'
|
||||
else
|
||||
@createUser()
|
||||
else
|
||||
if loggedIn
|
||||
@setState({done: true})
|
||||
context.location = '/client#/shoppingCart'
|
||||
else
|
||||
@createUser(true)
|
||||
|
||||
).fail((jqXHR, textStatus, errorMessage) =>
|
||||
if jqXHR.status == 422
|
||||
errors = JSON.parse(jqXHR.responseText)
|
||||
cart_errors = errors?.errors?.cart_id
|
||||
if cart_errors?.length == 1 && cart_errors[0] == 'has already been taken'
|
||||
if loggedIn
|
||||
@setState({done: true})
|
||||
context.location = '/client#/shoppingCart'
|
||||
else
|
||||
@createUser(true)
|
||||
else
|
||||
context.JK.app.ajaxError(jqXHR, textStatus, errorMessage)
|
||||
@setState({processing:false})
|
||||
)
|
||||
|
||||
createUser: (redirectToShoppingCart) ->
|
||||
$form = $('.jamtrack-signup-form')
|
||||
email = $form.find('input[name="email"]').val()
|
||||
password = $form.find('input[name="password"]').val()
|
||||
terms = $form.find('input[name="terms"]').is(':checked')
|
||||
|
||||
rest.signup({email: email, password: password, first_name: null, last_name: null, terms:terms})
|
||||
.done((response) =>
|
||||
|
||||
if redirectToShoppingCart
|
||||
@setState({done: true})
|
||||
context.location = '/client#/shoppingCart'
|
||||
return
|
||||
|
||||
rest.placeOrder()
|
||||
.done((response) =>
|
||||
this.setState({done: true})
|
||||
context.JK.Tracking.redeemCompleteTrack()
|
||||
window.location = '/client?redeemed_flow=1#/jamtrack'
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
logger.error("unable to place an older after creating the user")
|
||||
window.reload()
|
||||
)
|
||||
|
||||
).fail((jqXHR) =>
|
||||
@setState({processing:false})
|
||||
if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
if response.errors
|
||||
@setState({loginErrors: response.errors})
|
||||
else
|
||||
context.JK.app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
|
||||
else
|
||||
context.JK.app.notifyServerError(jqXHR, "Unable to Sign Up")
|
||||
)
|
||||
|
||||
|
||||
@setState({processing:true})
|
||||
|
||||
})
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
PLAYBACK_MONITOR_MODE = context.JK.PLAYBACK_MONITOR_MODE
|
||||
RecordingActions = @RecordingActions
|
||||
BrowserMediaActions = @BrowserMediaActions
|
||||
|
||||
@BrowserMediaPlaybackStore = Reflux.createStore(
|
||||
{
|
||||
listenables: @BrowserMediaPlaybackActions
|
||||
|
||||
playbackStateChanged: false
|
||||
positionUpdateChanged: false
|
||||
currentTimeChanged: false
|
||||
playbackState: null
|
||||
positionMs: 0
|
||||
durationMs: 0
|
||||
isRecording: false
|
||||
sessionHelper: null
|
||||
|
||||
init: () ->
|
||||
this.listenTo(context.BrowserMediaStore, this.onBrowserMediaChange);
|
||||
|
||||
onBrowserMediaChange: (changes) ->
|
||||
@onPositionUpdate(PLAYBACK_MONITOR_MODE.BROWSER_MEDIA)
|
||||
|
||||
onCurrentTimeChanged: (time) ->
|
||||
@time = time
|
||||
@currentTimeChanged = true
|
||||
@issueChange()
|
||||
|
||||
onMediaStartPlay: (data) ->
|
||||
BrowserMediaActions.play()
|
||||
|
||||
onMediaStopPlay: (data) ->
|
||||
|
||||
if !data.endReached
|
||||
BrowserMediaActions.stop()
|
||||
|
||||
onMediaPausePlay: (data) ->
|
||||
# if a JamTrack is open, and the user hits 'pause' or 'stop', we need to automatically stop the recording
|
||||
if !data.endReached
|
||||
BrowserMediaActions.pause()
|
||||
|
||||
onMediaChangePosition: (data) ->
|
||||
seek = data.positionMs;
|
||||
|
||||
BrowserMediaActions.seek(seek);
|
||||
|
||||
issueChange: () ->
|
||||
|
||||
@state =
|
||||
playbackState: @playbackState
|
||||
playbackStateChanged: @playbackStateChanged
|
||||
positionUpdateChanged: @positionUpdateChanged
|
||||
currentTimeChanged: @currentTimeChanged
|
||||
positionMs: @positionMs
|
||||
durationMs: @durationMs
|
||||
isPlaying: @isPlaying
|
||||
time: @time
|
||||
|
||||
this.trigger(@state)
|
||||
@playbackStateChanged = false
|
||||
@positionUpdateChanged = false
|
||||
@currentTimeChanged = false
|
||||
|
||||
onPlaybackStateChange: (text) ->
|
||||
@playbackState = text
|
||||
@playbackStateChanged = true
|
||||
|
||||
@issueChange()
|
||||
|
||||
onPositionUpdate: (playbackMode) ->
|
||||
if playbackMode == PLAYBACK_MONITOR_MODE.BROWSER_MEDIA
|
||||
@positionMs = BrowserMediaStore.onGetPlayPosition() || 0
|
||||
@durationMs = BrowserMediaStore.onGetPlayDuration() || 0;
|
||||
@isPlaying = BrowserMediaStore.playing;
|
||||
else
|
||||
raise 'Only BROWSER_MEDIA is supported'
|
||||
@positionUpdateChanged = true
|
||||
@issueChange()
|
||||
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
rest = context.JK.Rest()
|
||||
EVENTS = context.JK.EVENTS
|
||||
|
||||
BrowserMediaActions = @BrowserMediaActions
|
||||
|
||||
@BrowserMediaStore = Reflux.createStore(
|
||||
{
|
||||
listenables: BrowserMediaActions
|
||||
audio: null
|
||||
loaded: false
|
||||
loading: false
|
||||
playing: false
|
||||
paused: false
|
||||
id: null
|
||||
media_type: null
|
||||
|
||||
playbackState:(position, time) ->
|
||||
state = {}
|
||||
if @playing
|
||||
state.playbackState = 'play_start'
|
||||
else if @paused
|
||||
state.playbackState = 'play_pause'
|
||||
else
|
||||
state.playbackState = 'play_stop'
|
||||
|
||||
state.playbackStateChanged = !@state? || @state.isPlaying != @playing || @state.paused != @paused
|
||||
state.positionUpdateChanged = !@state? || @state.positionMs != position
|
||||
state.currentTimeChanged = !@state? || @state.time != time
|
||||
|
||||
state
|
||||
|
||||
changed: () ->
|
||||
position = @onGetPlayPosition()
|
||||
time = context.JK.prettyPrintSeconds(parseInt(position / 1000))
|
||||
playbackState = @playbackState(position, time)
|
||||
|
||||
# XXX: how to deal with duration? no mention in Howler API
|
||||
|
||||
target = {id: @id, isPlaying: @playing, loaded: @loaded, paused: @paused, positionMs: position, time: time, durationMs: @onGetPlayDuration()}
|
||||
$.extend(true, target, playbackState)
|
||||
@state = target
|
||||
@trigger(@state)
|
||||
|
||||
onLoad: (id, urls, media_type) ->
|
||||
if @audio
|
||||
@audio.stop()
|
||||
@audio.unload()
|
||||
@loaded = false
|
||||
@loading = false
|
||||
@playing = false
|
||||
@paused = false
|
||||
|
||||
@id = id
|
||||
@media_type = media_type
|
||||
@loading = true
|
||||
@playing = false
|
||||
@paused = false
|
||||
|
||||
console.log("URLS", urls)
|
||||
@audio = new Howl({
|
||||
src: urls,
|
||||
autoplay: false,
|
||||
loop: false,
|
||||
volume: 1,
|
||||
preload: true,
|
||||
onend: @onAudioEnded,
|
||||
onload: @onAudioLoaded,
|
||||
onpause: @onAudioPause,
|
||||
onplay: @onAudioPlay
|
||||
})
|
||||
|
||||
@changed()
|
||||
|
||||
onPlay: () ->
|
||||
if @audio
|
||||
@playing = true
|
||||
@paused = false
|
||||
@audio.play()
|
||||
|
||||
try
|
||||
if !@audio.recorded_play
|
||||
rest.postUserEvent({name: @media_type + '_play'}) if @media_type?
|
||||
context.stats.write('web.' + @media_type + '.play', {
|
||||
value: 1,
|
||||
user_id: context.JK.currentUserId,
|
||||
user_name: context.JK.currentUserName
|
||||
}) if @media_type
|
||||
|
||||
@audio.recorded_play = true
|
||||
catch e
|
||||
logger.warn("BrowserMediaStore: unable to post user event")
|
||||
|
||||
onPause: () ->
|
||||
if @audio
|
||||
@playing = false
|
||||
@paused = true
|
||||
@audio.pause()
|
||||
|
||||
onStop: () ->
|
||||
if @audio
|
||||
@playing = false
|
||||
@paused = false
|
||||
@audio.pause()
|
||||
@audio.seek(0)
|
||||
|
||||
onSeek: (pos) ->
|
||||
if @audio
|
||||
console.log("seek time", pos)
|
||||
@audio.seek(pos / 1000)
|
||||
|
||||
onGetPlayPosition: () ->
|
||||
if @audio
|
||||
try
|
||||
position = @audio.seek()
|
||||
if position == @audio
|
||||
return 0
|
||||
position * 1000
|
||||
catch e
|
||||
return 0
|
||||
else
|
||||
0
|
||||
|
||||
onGetPlayDuration: () ->
|
||||
if @audio
|
||||
# XXX : how to determine duration?
|
||||
try
|
||||
duration = @audio.duration()
|
||||
return Math.round(duration) * 1000
|
||||
catch e
|
||||
return 0
|
||||
else
|
||||
0
|
||||
|
||||
onAudioEnded: () ->
|
||||
logger.debug("onAudioEnded", this, arguments)
|
||||
@playing = false
|
||||
@changed()
|
||||
|
||||
onAudioLoaded: () ->
|
||||
logger.debug("onAudioLoaded")
|
||||
@loaded = true
|
||||
@changed()
|
||||
|
||||
onAudioPause: () ->
|
||||
logger.debug("onAudioPause")
|
||||
|
||||
@changed()
|
||||
|
||||
onAudioPlay: () ->
|
||||
logger.debug("onAudioPlay")
|
||||
@playing = true
|
||||
@paused = false
|
||||
@changed()
|
||||
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,454 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
rest = context.JK.Rest()
|
||||
EVENTS = context.JK.EVENTS
|
||||
|
||||
JamTrackPlayerActions = @JamTrackPlayerActions
|
||||
BrowserMediaStore = @BrowserMediaStore
|
||||
BrowserMediaActions = @BrowserMediaActions
|
||||
|
||||
@JamTrackPlayerStore = Reflux.createStore(
|
||||
{
|
||||
listenables: JamTrackPlayerActions
|
||||
jamTrack: null
|
||||
previous: null
|
||||
requestedSearch: null
|
||||
requestedFilter: null
|
||||
subscriptions: {}
|
||||
enqueuedMixdowns: {}
|
||||
childWindow: null
|
||||
watchedMixdowns: {}
|
||||
|
||||
init: ->
|
||||
# Register with the app store to get @app
|
||||
this.listenTo(context.AppStore, this.onAppInit)
|
||||
this.listenTo(context.BrowserMediaStore, this.onBrowserMediaChanged)
|
||||
|
||||
onAppInit: (app) ->
|
||||
@app = app
|
||||
|
||||
onBrowserMediaChanged: (browserMediaState) ->
|
||||
@browserMediaState = browserMediaState
|
||||
|
||||
@changed()
|
||||
|
||||
getState: () ->
|
||||
@state
|
||||
|
||||
onOpen: (jamTrack, popup=true) ->
|
||||
|
||||
|
||||
if jamTrack.id == @jamTrack?.id && @childWindow?
|
||||
logger.info("JamTrackPlayerStore: request to open already-opened JamTrack; ignoring")
|
||||
return
|
||||
|
||||
@enqueuedMixdowns = {}
|
||||
@jamTrack = jamTrack
|
||||
|
||||
sampleRate = 48
|
||||
@sampleRate = if sampleRate == 48 then 48 else 44
|
||||
|
||||
|
||||
if popup
|
||||
if @childWindow?
|
||||
@childWindow.close()
|
||||
|
||||
logger.debug("opening JamTrackPlayer window")
|
||||
@childWindow = window.open("/popups/jamtrack-player/" + @jamTrack.id, 'Media Controls', 'scrollbars=yes,toolbar=no,status=no,height=580,width=450')
|
||||
|
||||
@changed()
|
||||
|
||||
onWindowUnloaded: () ->
|
||||
BrowserMediaActions.stop()
|
||||
@childWindow = null
|
||||
|
||||
pickMyPackage: () ->
|
||||
|
||||
return unless @jamTrack?
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
|
||||
myPackage = null
|
||||
for mixdown_package in mixdown.packages
|
||||
if mixdown_package.file_type == 'mp3' && mixdown_package.encrypt_type == null && mixdown_package.sample_rate == @sampleRate
|
||||
myPackage = mixdown_package
|
||||
break
|
||||
|
||||
mixdown.myPackage = myPackage
|
||||
|
||||
subscriptionKey: (mixdown_package) ->
|
||||
"mixdown-#{mixdown_package.id}"
|
||||
|
||||
subscribe: (mixdown_package) ->
|
||||
key = @subscriptionKey(mixdown_package)
|
||||
|
||||
if !@watchedMixdowns[key]?
|
||||
# we need to register
|
||||
context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onMixdownSubscriptionEvent)
|
||||
@watchedMixdowns[key] = {type:'mixdown', id: mixdown_package.id}
|
||||
|
||||
unsubscribe: (mixdown_package) ->
|
||||
key = @subscriptionKey(mixdown_package)
|
||||
if @watchedMixdowns[key]?
|
||||
context.JK.SubscriptionUtils.unsubscribe('mixdown', mixdown_package.id)
|
||||
delete @watchedMixdowns[key]
|
||||
|
||||
manageWatchedMixdowns: () ->
|
||||
|
||||
if @jamTrack?
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
if mixdown.myPackage
|
||||
if mixdown.myPackage.signing_state == 'SIGNED'
|
||||
@unsubscribe(mixdown.myPackage)
|
||||
else
|
||||
@subscribe(mixdown.myPackage)
|
||||
|
||||
else
|
||||
for key, subscription of @watchedMixdowns
|
||||
logger.debug("unsubscribing bulk", key, subscription)
|
||||
context.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id)
|
||||
|
||||
# we cleared them all out; clear out storage
|
||||
@watchedMixdowns = {}
|
||||
|
||||
onMixdownSubscriptionEvent: (e, data) ->
|
||||
logger.debug("JamTrackStore: subscription notification received: type:" + data.type, data)
|
||||
|
||||
return unless @jamTrack?
|
||||
|
||||
mixdown_package_id = data.id
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
for mixdown_package in mixdown.packages
|
||||
if mixdown_package.id == mixdown_package_id
|
||||
mixdown_package.signing_state = data.body.signing_state
|
||||
mixdown_package.packaging_steps = data.body.packaging_steps
|
||||
mixdown_package.current_packaging_step = data.body.current_packaging_step
|
||||
logger.debug("updated package with subscription notification event")
|
||||
|
||||
if mixdown_package.signing_state == 'SIGNING_TIMEOUT' || mixdown_package.signing_state == 'QUEUED_TIMEOUT' || mixdown_package.signing_state == 'QUIET_TIMEOUT' || mixdown_package.signing_state == 'ERROR'
|
||||
@reportError(mixdown)
|
||||
|
||||
@changed()
|
||||
break
|
||||
|
||||
# this drives the state engine required to get a Mixdown from 'available on the server' to
|
||||
manageMixdownSynchronization: () ->
|
||||
|
||||
|
||||
if @jamTrack
|
||||
@jamTrack.activeMixdown = null
|
||||
@jamTrack.activeStem = null
|
||||
|
||||
# let's see if we have a mixdown active?
|
||||
|
||||
#if !@jamTrack?.last_mixdown_id?
|
||||
# logger.debug("JamTrackStore: no mixdown active")
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
if mixdown.id == @jamTrack.last_mixdown_id
|
||||
@jamTrack.activeMixdown = mixdown
|
||||
#logger.debug("JamTrackStore: mixdown active:", mixdown)
|
||||
break
|
||||
|
||||
for stem in @jamTrack.tracks
|
||||
if stem.id == @jamTrack.last_stem_id
|
||||
@jamTrack.activeStem = stem
|
||||
break
|
||||
|
||||
# let's check and see if we've asked the BrowserMediaStore to load this particular file or not
|
||||
|
||||
if @jamTrack?.activeStem
|
||||
|
||||
if @browserMediaState?.id != @jamTrack.activeStem.id
|
||||
BrowserMediaActions.load(@jamTrack.activeStem.id, [window.location.protocol + '//' + window.location.host + "/api/jamtracks/#{@jamTrack.id}/stems/#{@jamTrack.activeStem.id}/download.mp3?file_type=mp3"], 'jamtrack_web_player')
|
||||
@jamTrack.activeStem.client_state = 'downloading'
|
||||
else
|
||||
if @browserMediaState.loaded
|
||||
@jamTrack.activeStem.client_state = 'ready'
|
||||
else
|
||||
@jamTrack.activeStem.client_state = 'downloading'
|
||||
|
||||
|
||||
else if @jamTrack?.activeMixdown
|
||||
|
||||
# if we don't have this on the server yet, don't engage the rest of this logic...
|
||||
return if @jamTrack.activeMixdown?.myPackage?.signing_state != 'SIGNED'
|
||||
|
||||
activePackage = @jamTrack.activeMixdown.myPackage
|
||||
|
||||
if activePackage?
|
||||
if @browserMediaState?.id != activePackage.id
|
||||
BrowserMediaActions.load(activePackage.id, [window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{@jamTrack.activeMixdown.id}/download.mp3?file_type=mp3&sample_rate=48"], 'jamtrack_web_player')
|
||||
@jamTrack.activeMixdown.client_state = 'downloading'
|
||||
else
|
||||
if @browserMediaState.loaded
|
||||
@jamTrack.activeMixdown.client_state = 'ready'
|
||||
else
|
||||
@jamTrack.activeMixdown.client_state = 'downloading'
|
||||
|
||||
else if @jamTrack?
|
||||
|
||||
masterTrack = null
|
||||
for jamTrackTrack in @jamTrack.tracks
|
||||
if jamTrackTrack.track_type == 'Master'
|
||||
masterTrack = jamTrackTrack
|
||||
break
|
||||
|
||||
if @browserMediaState?.id != @jamTrack.id
|
||||
BrowserMediaActions.load(@jamTrack.id, [masterTrack.preview_mp3_url], 'jamtrack_web_player')
|
||||
@jamTrack.client_state = 'downloading'
|
||||
else
|
||||
if @browserMediaState.loaded
|
||||
@jamTrack.client_state = 'ready'
|
||||
else
|
||||
@jamTrack.client_state = 'downloading'
|
||||
else
|
||||
logger.error("unknown condition when processing manageMixdownSynchronization")
|
||||
|
||||
|
||||
changed: () ->
|
||||
|
||||
@pickMyPackage()
|
||||
@manageWatchedMixdowns()
|
||||
@manageMixdownSynchronization()
|
||||
|
||||
@state = {
|
||||
jamTrack: @jamTrack,
|
||||
opened: @previous == null && @jamTrack != null,
|
||||
closed: @previous != null && @jamTrack == null,
|
||||
fullTrackActivated: @previousMixdown != null && @jamTrack?.activeMixdown == null}
|
||||
@previous = @jamTrack
|
||||
@previousMixdown = @jamTrack?.activeMixdown
|
||||
this.trigger(@state)
|
||||
|
||||
onCreateMixdown: (mixdown, done, fail) ->
|
||||
|
||||
#volumeSettings = context.jamClient.GetJamTrackSettings();
|
||||
|
||||
#track_settings = []
|
||||
|
||||
#for track in volumeSettings.tracks
|
||||
# track_settings.push({id: track.id, pan: track.pan, vol: track.vol_l, mute: track.mute})
|
||||
|
||||
#mixdown.settings.tracks = track_settings
|
||||
|
||||
logger.debug("creating mixdown", mixdown)
|
||||
|
||||
rest.createMixdown(mixdown)
|
||||
.done((created) =>
|
||||
|
||||
@addMixdown(created)
|
||||
|
||||
logger.debug("created mixdown", created)
|
||||
|
||||
@onEnqueueMixdown({id: created.id}, done, fail)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
fail(jqxhr)
|
||||
)
|
||||
|
||||
|
||||
onEditMixdown: (mixdown) ->
|
||||
logger.debug("editing mixdown", mixdown)
|
||||
|
||||
rest.editMixdown(mixdown)
|
||||
.done((updatedMixdown) =>
|
||||
logger.debug("edited mixdown")
|
||||
@updateMixdown(updatedMixdown)
|
||||
).fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Custom Mix', text: 'The server was unable to edit this mix.'})
|
||||
)
|
||||
|
||||
onDeleteMixdown: (mixdown) ->
|
||||
logger.debug("deleting mixdown", mixdown)
|
||||
|
||||
rest.deleteMixdown(mixdown)
|
||||
.done(() =>
|
||||
logger.debug("deleted mixdown")
|
||||
|
||||
@deleteMixdown(mixdown)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Deleted Custom Mix', text: 'The server was unable to delete this mix.'})
|
||||
)
|
||||
|
||||
stopPlaying: () ->
|
||||
alert("stop playing")
|
||||
|
||||
onOpenMixdown: (mixdown) ->
|
||||
logger.debug("opening mixdown", mixdown)
|
||||
|
||||
# check if it's already available in the backend or not
|
||||
rest.markMixdownActive({id: @jamTrack.id, mixdown_id: mixdown.id})
|
||||
.done((edited) =>
|
||||
logger.debug("marked mixdown as active")
|
||||
@jamTrack = edited
|
||||
|
||||
BrowserMediaActions.stop()
|
||||
|
||||
@changed()
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
|
||||
)
|
||||
|
||||
onOpenStem: (stem_id) ->
|
||||
logger.debug("opening stem", stem_id)
|
||||
|
||||
# check if it's already available in the backend or not
|
||||
rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null, stem_id: stem_id})
|
||||
.done((edited) =>
|
||||
logger.debug("marked stem as active")
|
||||
@jamTrack = edited
|
||||
|
||||
BrowserMediaActions.stop()
|
||||
|
||||
@changed()
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
|
||||
)
|
||||
|
||||
onActivateNoMixdown: (jamTrack) ->
|
||||
logger.debug("activating no mixdown")
|
||||
|
||||
rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null})
|
||||
.done((edited) =>
|
||||
logger.debug("marked JamTrack as active")
|
||||
|
||||
@jamTrack = edited
|
||||
@changed()
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
|
||||
)
|
||||
|
||||
|
||||
onCloseMixdown: (mixdown) ->
|
||||
logger.debug("closing mixdown", mixdown)
|
||||
|
||||
onEnqueueMixdown: (mixdown, done, fail) ->
|
||||
logger.debug("enqueuing mixdown", mixdown)
|
||||
|
||||
package_settings = {file_type: 'mp3', encrypt_type: null, sample_rate: @sampleRate}
|
||||
package_settings.id = mixdown.id
|
||||
|
||||
rest.enqueueMixdown(package_settings)
|
||||
.done((enqueued) =>
|
||||
|
||||
@enqueuedMixdowns[mixdown.id] = {}
|
||||
|
||||
logger.debug("enqueued mixdown package", package_settings)
|
||||
@addOrUpdatePackage(enqueued)
|
||||
done(enqueued) if done
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Create Custom Mix', text: 'Click the error icon to retry.'})
|
||||
fail(jqxhr) if fail?
|
||||
)
|
||||
|
||||
onDownloadMixdown: (mixdown) ->
|
||||
logger.debug("download mixdown", mixdown)
|
||||
|
||||
onRefreshMixdown: (mixdown) ->
|
||||
logger.debug("refresh mixdown", mixdown)
|
||||
|
||||
addMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("adding mixdown to JamTrackStore", mixdown)
|
||||
@jamTrack.mixdowns.splice(0, 0, mixdown)
|
||||
@changed()
|
||||
else
|
||||
logger.warn("no jamtrack to add mixdown to in JamTrackStore", mixdown)
|
||||
|
||||
deleteMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("deleting mixdown from JamTrackStore", mixdown)
|
||||
index = null
|
||||
for matchMixdown, i in @jamTrack.mixdowns
|
||||
if mixdown.id == matchMixdown.id
|
||||
index = i
|
||||
if index?
|
||||
@jamTrack.mixdowns.splice(index, 1)
|
||||
|
||||
if @jamTrack.activeMixdown?.id == mixdown.id
|
||||
@onActivateNoMixdown(@jamTrack)
|
||||
|
||||
@changed()
|
||||
else
|
||||
logger.warn("unable to find mixdown to delete in JamTrackStore", mixdown)
|
||||
else
|
||||
logger.warn("no jamtrack to delete mixdown for in JamTrackStore", mixdown)
|
||||
|
||||
updateMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("editing mixdown from JamTrackStore", mixdown)
|
||||
index = null
|
||||
for matchMixdown, i in @jamTrack.mixdowns
|
||||
if mixdown.id == matchMixdown.id
|
||||
index = i
|
||||
if index?
|
||||
@jamTrack.mixdowns[index] = mixdown
|
||||
|
||||
@changed()
|
||||
else
|
||||
logger.warn("unable to find mixdown to edit in JamTrackStore", mixdown)
|
||||
else
|
||||
logger.warn("no jamtrack to edit mixdown for in JamTrackStore", mixdown)
|
||||
|
||||
addOrUpdatePackage: (mixdown_package) ->
|
||||
if @jamTrack?
|
||||
added = false
|
||||
index = null
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
existing = false
|
||||
if mixdown_package.jam_track_mixdown_id == mixdown.id
|
||||
for possiblePackage, i in mixdown.packages
|
||||
if possiblePackage.id == mixdown_package.id
|
||||
existing = true
|
||||
index = i
|
||||
break
|
||||
|
||||
if existing
|
||||
mixdown.packages[index] = mixdown_package
|
||||
logger.debug("replacing mixdown package in JamTrackStore", mixdown_package)
|
||||
else
|
||||
mixdown.packages.splice(0, 0, mixdown_package)
|
||||
logger.debug("adding mixdown package in JamTrackStore")
|
||||
|
||||
added = true
|
||||
@changed()
|
||||
break
|
||||
|
||||
if !added
|
||||
logger.debug("couldn't find the mixdown associated with package in JamTrackStore", mixdown_package)
|
||||
else
|
||||
logger.warn("no mixdown to add package to in JamTrackStore", mixdown_package)
|
||||
|
||||
|
||||
|
||||
reportError: (mixdown) ->
|
||||
|
||||
enqueued = @enqueuedMixdowns[mixdown?.id]
|
||||
|
||||
# don't double-report
|
||||
if !enqueued? || enqueued.marked
|
||||
return
|
||||
|
||||
enqueued.marked = true
|
||||
data = {
|
||||
value: 1,
|
||||
user_id: context.JK.currentUserId,
|
||||
user_name: context.JK.currentUserName,
|
||||
result: "signing state: #{mixdown.myPackage?.signing_state}, client state: #{mixdown.client_state}",
|
||||
mixdown: mixdown.id,
|
||||
package: mixdown.myPackage?.id
|
||||
detail: mixdown.myPackage?.error_reason
|
||||
}
|
||||
rest.createAlert("Mixdown Sync failed for #{context.JK.currentUserName}", data)
|
||||
|
||||
context.stats.write('web.mixdown.error', data)
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
|
||||
@UserStore = Reflux.createStore(
|
||||
{
|
||||
user: null
|
||||
|
||||
listenables: @UserActions
|
||||
|
||||
onLoaded:(user) ->
|
||||
@user = user
|
||||
@changed()
|
||||
|
||||
changed:() ->
|
||||
@trigger({user: @user})
|
||||
}
|
||||
)
|
||||
|
|
@ -35,6 +35,9 @@
|
|||
}
|
||||
|
||||
function afterShow(data) {
|
||||
|
||||
context.JK.Tracking.redeemCompleteTrack()
|
||||
|
||||
$noPurchasesPrompt.addClass('hidden')
|
||||
$purchasedJamTracks.empty()
|
||||
$thanksPanel.addClass("hidden")
|
||||
|
|
@ -68,6 +71,7 @@
|
|||
rest.placeOrder()
|
||||
.done(function(purchaseResponse) {
|
||||
context.JK.currentUserFreeJamTrack = false // make sure the user sees no more free notices without having to do a full page refresh
|
||||
rest.updateUser()
|
||||
|
||||
checkoutUtils.setLastPurchase(purchaseResponse)
|
||||
jamTrackUtils.checkShoppingCart()
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
function getItemHtml(item) {
|
||||
var replacements = {
|
||||
id: item.id,
|
||||
name: item.first_name + " " + item.last_name,
|
||||
name: item.name,
|
||||
image: item.photo_url,
|
||||
subtext: item.location
|
||||
};
|
||||
|
|
|
|||
|
|
@ -81,7 +81,13 @@
|
|||
}
|
||||
|
||||
function updateHeader() {
|
||||
$('#user').html(userMe.name);
|
||||
if (userMe.first_name =='Anonymous' && userMe.last_name == 'Anonymous') {
|
||||
$('#user').html(userMe.email);
|
||||
}
|
||||
else {
|
||||
$('#user').html(userMe.name);
|
||||
}
|
||||
|
||||
showAvatar();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@
|
|||
// * Unix
|
||||
context.JK.GetOSAsString = function() {
|
||||
if(!os) {
|
||||
if(context.jamClient.IsNativeClient()) {
|
||||
if(context.jamClient && context.jamClient.IsNativeClient()) {
|
||||
os = context.jamClient.GetOSAsString();
|
||||
}
|
||||
else {
|
||||
|
|
@ -868,6 +868,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
context.JK.getFullFirstError = function(fieldName, errors, field_mapper) {
|
||||
|
||||
if (errors == null) return null;
|
||||
|
||||
if (errors[fieldName] && errors[fieldName].length > 0) {
|
||||
var displayField = fieldName
|
||||
if (field_mapper) {
|
||||
displayField = field_mapper[fieldName]
|
||||
if (!displayField) {
|
||||
// in case mapper has no data for this field
|
||||
displayField = fieldName;
|
||||
}
|
||||
}
|
||||
return displayField + ' ' + errors[fieldName][0]
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns a ul with an li per field name.
|
||||
* @param fieldName the name of the field
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.IndividualJamTrack = function (app) {
|
||||
context.JK.IndividualJamTrack = function (app, dontPoke) {
|
||||
|
||||
var rest = context.JK.Rest();
|
||||
var logger = context.JK.logger;
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: true, color:'black', master_adds_line_break: false, preload_master:true})
|
||||
|
||||
if(track.track_type =='Master') {
|
||||
if(track.track_type =='Master' && !dontPoke) {
|
||||
context.JK.HelpBubbleHelper.rotateJamTrackLandingBubbles($element.find('.jam-track-preview'), $page.find('.one_by_two .watch-video'), $page.find('.checkout'));
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ class Tracking
|
|||
@logger.debug("existing new recorded")
|
||||
context.JK.GA.virtualPageView('/client#/redeemSignup/new-user')
|
||||
|
||||
redeemCompleteTrack: () ->
|
||||
context.JK.GA.virtualPageView('/client#/redeemed-successful')
|
||||
|
||||
|
||||
context.JK.Tracking = new Tracking()
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,17 @@ body.jam, body.web, .dialog{
|
|||
font-size:20px;
|
||||
color: #ed3618;
|
||||
p {color:#ed3618}
|
||||
|
||||
&.jamtrack-web-play {
|
||||
p {
|
||||
font-size:20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {
|
||||
|
||||
font-size:12px;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,31 @@
|
|||
display:inline-block;
|
||||
}
|
||||
|
||||
.purchased-jam-tracks {
|
||||
height:167px;
|
||||
overflow:auto;
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.jamtable {
|
||||
|
||||
a {
|
||||
text-decoration: underline !important;
|
||||
color:#fc0 !important;
|
||||
}
|
||||
th {
|
||||
font-size:14px;
|
||||
padding:3px 10px;
|
||||
}
|
||||
td {
|
||||
padding:4px 15px;
|
||||
font-size:14px;
|
||||
}
|
||||
tbody {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.search-area {
|
||||
.easydropdown-wrapper{
|
||||
max-width:350px;
|
||||
|
|
|
|||
|
|
@ -1,81 +1,444 @@
|
|||
body.web.landing_jamtrack.individual_jamtrack {
|
||||
@import "client/common";
|
||||
|
||||
.landing-content {
|
||||
.header {
|
||||
text-align:center;
|
||||
body.web.individual_jamtrack {
|
||||
|
||||
$copy-color-on-dark: #b9b9b9;
|
||||
$copy-color-on-white: #575757;
|
||||
$cta-color: #e03d04;
|
||||
$chunkyBorderWidth: 6px;
|
||||
|
||||
.full-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 663px;
|
||||
}
|
||||
|
||||
.name-and-artist {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.summary-text {
|
||||
|
||||
p {
|
||||
color: $copy-color-on-dark;
|
||||
clear: both;
|
||||
font-size: 20px;
|
||||
line-height: 175%;
|
||||
}
|
||||
}
|
||||
|
||||
.row.awesome {
|
||||
h2 {
|
||||
color: $copy-color-on-white;
|
||||
font-size: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
p {
|
||||
width:100%;
|
||||
color: $copy-color-on-white;
|
||||
font-size: 18px;
|
||||
line-height: 175%;
|
||||
}
|
||||
h1 {
|
||||
font-size:24px;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.awesome-thing {
|
||||
color: $copy-color-on-white;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.awesome-item {
|
||||
padding: 24px;
|
||||
background-color: #dedede;
|
||||
border-radius: 12px;
|
||||
@include border_box_sizing;
|
||||
width: 663px;
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
margin-bottom:80px;
|
||||
|
||||
.cta-text {
|
||||
margin-left: 10px;
|
||||
text-align:center;
|
||||
margin-top:20px;
|
||||
}
|
||||
&.left {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
|
||||
.cta-text {
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
width: 420px;
|
||||
padding-bottom: 53.33%;
|
||||
|
||||
}
|
||||
.awesome-image {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
|
||||
&.left {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.awesome-number {
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
display: inline-block;
|
||||
background-color: $cta-color;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
border-width: 2px;
|
||||
border-color: white;
|
||||
border-style: solid;
|
||||
margin-right: 10px;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.cta-row {
|
||||
padding: 40px 0;
|
||||
background-color: #262626;
|
||||
|
||||
h2 {
|
||||
font-size:18px;
|
||||
color:white;
|
||||
text-decoration:underline;
|
||||
margin-bottom:8px;
|
||||
font-size: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
ul {
|
||||
width: 100%;
|
||||
margin:5px 0 0;
|
||||
|
||||
p {
|
||||
color: $copy-color-on-dark;
|
||||
}
|
||||
li {
|
||||
margin-bottom:4px;
|
||||
}
|
||||
|
||||
.white-bar {
|
||||
p {
|
||||
color: $copy-color-on-white;
|
||||
}
|
||||
.row .column {
|
||||
padding:0 30px;
|
||||
|
||||
.top-container {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.red-bar {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: #e03c02;
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img.app-preview {
|
||||
width: 340px;
|
||||
float: left;
|
||||
margin-left: -15px; // because image has black on the left, which you can't see on back background
|
||||
margin-right: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
h1.jam-track-name {
|
||||
color: white;
|
||||
font-size: 30px !important;
|
||||
padding-top: 0;
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
h2.original-artist {
|
||||
margin-top: 10px;
|
||||
color: #8d8d8d;
|
||||
font-size: 24px;
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.preview-text {
|
||||
color: #fefefe;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
background-color: $cta-color;
|
||||
|
||||
&.processing {
|
||||
background-color:gray;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-text {
|
||||
color: $cta-color !important;
|
||||
}
|
||||
|
||||
.privacy-policy {
|
||||
color: #ffcf00;
|
||||
}
|
||||
|
||||
.browse-all {
|
||||
color: #ffb800;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 150%;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.bottom-banner {
|
||||
background-color: #e03c02;
|
||||
}
|
||||
|
||||
.arrow1 {
|
||||
position:absolute;
|
||||
left: -371px;
|
||||
top: -80px;
|
||||
}
|
||||
|
||||
.arrow2 {
|
||||
position:absolute;
|
||||
left:-230px;
|
||||
top:373px;
|
||||
}
|
||||
.testimonials {
|
||||
background-color:white;
|
||||
position:absolute;
|
||||
width:350px;
|
||||
right:55px;
|
||||
top:287px;
|
||||
@include border_box_sizing;
|
||||
z-index:1;
|
||||
|
||||
.jamtrack-overview-video {
|
||||
|
||||
h3 {
|
||||
font-size:21px;
|
||||
width:400px;
|
||||
}
|
||||
.video-wrapper {
|
||||
|
||||
.video-container {
|
||||
width: 400px;
|
||||
padding-bottom: 53.33%;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color:$copy-color-on-white;
|
||||
text-align:center;
|
||||
margin-bottom:20px;
|
||||
font-size:24px;
|
||||
line-height:125%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
position:absolute;
|
||||
float:none;
|
||||
top: 161px;
|
||||
left: 173px;
|
||||
font-size:14px;
|
||||
color:$copy-color-on-white;
|
||||
strong {
|
||||
font-weight:bold;
|
||||
}
|
||||
white-space:nowrap;
|
||||
}
|
||||
.row:nth-of-type(2) {
|
||||
margin-top:30px;
|
||||
.testimonial {
|
||||
position:relative;
|
||||
min-height:250px;
|
||||
width:350px;
|
||||
margin-bottom:40px;
|
||||
}
|
||||
|
||||
.row .column:nth-of-type(1) {
|
||||
width: 50%;
|
||||
left:0;
|
||||
height:100%;
|
||||
.testimonial-speech-bubble {
|
||||
width:350px;
|
||||
}
|
||||
.row .column:nth-of-type(2) {
|
||||
width: 50%;
|
||||
left:50%;
|
||||
.testimonial-avatar {
|
||||
width:60px;
|
||||
position:relative;
|
||||
border-width:0 0 0 1px;
|
||||
border-color:white;
|
||||
border-style:solid;
|
||||
top:5px;
|
||||
left:92px;
|
||||
}
|
||||
.testimonial-youtube {
|
||||
width:143px;
|
||||
position:relative;
|
||||
left:112px;
|
||||
top:0;
|
||||
}
|
||||
}
|
||||
.watch-video {
|
||||
position:relative;
|
||||
top:40px;
|
||||
text-align:left;
|
||||
display:block;
|
||||
font-size:16px;
|
||||
left:20%;
|
||||
width:300px;
|
||||
}
|
||||
.preview-and-action-box {
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
right: 55px;
|
||||
width: 330px;
|
||||
top: 200px;
|
||||
@include border_box_sizing;
|
||||
border-width: $chunkyBorderWidth;
|
||||
border-style: solid;
|
||||
border-color: $copy-color-on-dark;
|
||||
z-index: 1;
|
||||
|
||||
.previews {
|
||||
margin-top:10px;
|
||||
}
|
||||
.jamtrack-reasons {
|
||||
margin: 10px 0 0 20px;
|
||||
}
|
||||
.preview-jamtrack-header {
|
||||
background-color: $cta-color;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
border-width: 0 0 $chunkyBorderWidth;
|
||||
border-style: solid;
|
||||
border-color: $copy-color-on-dark;
|
||||
}
|
||||
|
||||
.white-bordered-button {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.preview-area {
|
||||
|
||||
.browse-jamtracks-wrapper {
|
||||
text-align:center;
|
||||
width:90%;
|
||||
}
|
||||
padding: 10px;
|
||||
|
||||
.prompt {
|
||||
margin-top:10px;
|
||||
border-width: 0 0 $chunkyBorderWidth;
|
||||
border-style: solid;
|
||||
border-color: $copy-color-on-dark;
|
||||
|
||||
&.logged-in {
|
||||
border-width:0;
|
||||
|
||||
.tracks {
|
||||
height:258px;
|
||||
}
|
||||
}
|
||||
.tracks {
|
||||
padding:10px 0 20px;
|
||||
margin-top:10px;
|
||||
overflow-y: scroll;
|
||||
height: 160px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
background-color: $cta-color;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px outset buttonface;
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.terms-help {
|
||||
float:left;
|
||||
margin-top:2px;
|
||||
font-size:12px;
|
||||
}
|
||||
.register-area {
|
||||
padding: 10px;
|
||||
input {
|
||||
background-color: $copy-color-on-dark;
|
||||
color: black;
|
||||
font-size: 16px;
|
||||
|
||||
&[name="terms"] {
|
||||
width:auto;
|
||||
line-height:24px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
}
|
||||
.icheckbox_minimal {
|
||||
float: left;
|
||||
top: 6px;
|
||||
margin-left:64px;
|
||||
}
|
||||
.cta-button {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
background-color: $cta-color;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px outset buttonface;
|
||||
font-family: Raleway, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.browse-all {
|
||||
text-decoration: underline;
|
||||
text-align: center;
|
||||
display: block;
|
||||
|
||||
}
|
||||
.privacy-policy {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.errors {
|
||||
font-size:12px;
|
||||
height:20px;
|
||||
margin:0;
|
||||
visibility: hidden;
|
||||
text-align: center;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
|
||||
&.active {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
line-height: 36px;
|
||||
margin-bottom: 15px;
|
||||
@include border-box_sizing;
|
||||
|
||||
&.terms-help {
|
||||
width:205px;
|
||||
height:28px;
|
||||
line-height:14px;
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
input {
|
||||
width: 206px;
|
||||
height: 36px;
|
||||
float: right;
|
||||
margin-bottom: 15px;
|
||||
@include border-box_sizing;
|
||||
}
|
||||
}
|
||||
p {
|
||||
line-height: 150%;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.jam-track-preview-holder {
|
||||
|
|
@ -87,7 +450,7 @@ body.web.landing_jamtrack.individual_jamtrack {
|
|||
}
|
||||
|
||||
&[data-track-type="Track"] {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,41 +470,10 @@ body.web.landing_jamtrack.individual_jamtrack {
|
|||
background-color:black;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-holder {
|
||||
|
||||
&.instrument-selection {
|
||||
a {
|
||||
font-size: 18px;
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
.browse-instrument {
|
||||
margin-top:20px;
|
||||
text-decoration:underline;
|
||||
}
|
||||
}
|
||||
margin-top:30px;
|
||||
.price-advisory {
|
||||
font-size:14px;
|
||||
text-align:center;
|
||||
.checkout {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
}
|
||||
.browse-band {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
}
|
||||
.browse-all {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
}
|
||||
.value-indicator {
|
||||
position:absolute;
|
||||
left: 301px;
|
||||
top: 19px;
|
||||
width:80px;
|
||||
}
|
||||
color:white;
|
||||
margin-top:10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
body.web.landing_jamtrack.individual_jamtrack_v2 {
|
||||
|
||||
.landing-content {
|
||||
.header {
|
||||
text-align:center;
|
||||
}
|
||||
p {
|
||||
width:100%;
|
||||
}
|
||||
h1 {
|
||||
font-size:24px;
|
||||
}
|
||||
h2 {
|
||||
font-size:18px;
|
||||
color:white;
|
||||
text-decoration:underline;
|
||||
margin-bottom:8px;
|
||||
}
|
||||
ul {
|
||||
width: 100%;
|
||||
margin:5px 0 0;
|
||||
}
|
||||
li {
|
||||
margin-bottom:4px;
|
||||
}
|
||||
.row .column {
|
||||
padding:0 30px;
|
||||
position:absolute;
|
||||
float:none;
|
||||
}
|
||||
.row:nth-of-type(2) {
|
||||
margin-top:30px;
|
||||
position:relative;
|
||||
min-height:250px;
|
||||
}
|
||||
|
||||
.row .column:nth-of-type(1) {
|
||||
width: 50%;
|
||||
left:0;
|
||||
height:100%;
|
||||
}
|
||||
.row .column:nth-of-type(2) {
|
||||
width: 50%;
|
||||
left:50%;
|
||||
position:relative;
|
||||
border-width:0 0 0 1px;
|
||||
border-color:white;
|
||||
border-style:solid;
|
||||
}
|
||||
}
|
||||
.watch-video {
|
||||
position:relative;
|
||||
top:40px;
|
||||
text-align:left;
|
||||
display:block;
|
||||
font-size:16px;
|
||||
left:20%;
|
||||
width:300px;
|
||||
}
|
||||
|
||||
.previews {
|
||||
margin-top:10px;
|
||||
}
|
||||
.jamtrack-reasons {
|
||||
margin: 10px 0 0 20px;
|
||||
}
|
||||
|
||||
.white-bordered-button {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.browse-jamtracks-wrapper {
|
||||
text-align:center;
|
||||
width:90%;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.jam-track-preview-holder {
|
||||
|
||||
margin-bottom: 7px;
|
||||
float:left;
|
||||
&[data-track-type="Master"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[data-track-type="Track"] {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.jam-track-preview {
|
||||
font-size:12px;
|
||||
|
||||
.instrument-part {
|
||||
width:175px
|
||||
}
|
||||
|
||||
.instrument-name,.part {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
right:-1px;
|
||||
background-color:black;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-holder {
|
||||
|
||||
&.instrument-selection {
|
||||
a {
|
||||
font-size: 18px;
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
.browse-instrument {
|
||||
margin-top:20px;
|
||||
text-decoration:underline;
|
||||
}
|
||||
}
|
||||
margin-top:30px;
|
||||
text-align:center;
|
||||
.checkout {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
}
|
||||
.browse-band {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
}
|
||||
.browse-all {
|
||||
display:inline-block;
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
}
|
||||
.value-indicator {
|
||||
position:absolute;
|
||||
left: 301px;
|
||||
top: 19px;
|
||||
width:80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,12 +86,29 @@ body.web.landing_page {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.white-bar {
|
||||
z-index:-1; // to let sidebar be interactable
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.landing-tag-play-learn-earn {
|
||||
color:#929292;
|
||||
font-size: 18px;
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.landing_jamtrack, &.landing_product {
|
||||
|
||||
.landing-tag {
|
||||
left:50%;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
p, ul, li {
|
||||
font-size:14px;
|
||||
line-height:125%;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
@import "client/common";
|
||||
|
||||
body.jamtrack-player-popup.popup {
|
||||
|
||||
text-align:center;
|
||||
|
||||
background-color: #242323;
|
||||
|
||||
#minimal-container {
|
||||
padding-bottom:0;
|
||||
}
|
||||
|
||||
.media-controls-popup {
|
||||
padding:15px 15px 3px 15px;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.icheckbox_minimal {
|
||||
float:left;
|
||||
}
|
||||
|
||||
label {
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
margin-top:2px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align:left;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
font-size:11px;
|
||||
margin-bottom:10px;
|
||||
|
||||
}
|
||||
|
||||
.help-link {
|
||||
position:absolute;
|
||||
left:-6px;
|
||||
top:0;
|
||||
}
|
||||
.close-link {
|
||||
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-bottom:20px;
|
||||
h3 {
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top:15px;
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
|
||||
span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
img {
|
||||
vertical-align:middle;
|
||||
margin-left:5px;
|
||||
height:16px;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
|
||||
span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
img {
|
||||
vertical-align:middle;
|
||||
margin-left:5px;
|
||||
height:16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extra-controls {
|
||||
margin-top:20px;
|
||||
h4 {
|
||||
text-align:left;
|
||||
font-size:18px;
|
||||
a {
|
||||
font-size:11px;
|
||||
position:absolute;
|
||||
right:20px;
|
||||
}
|
||||
&.custom-mix-header {
|
||||
margin-top:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.my-mixes {
|
||||
margin-top:5px;
|
||||
max-height:170px;
|
||||
border-width:1px;
|
||||
border-bottom-color:#676767;
|
||||
border-top-color:#676767;
|
||||
border-left-color:#171717;
|
||||
border-right-color:#171717;
|
||||
border-style:solid;
|
||||
overflow:auto;
|
||||
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.mixdown-display {
|
||||
display:table;
|
||||
font-size:12px;
|
||||
color:$ColorTextTypical;
|
||||
width:100%;
|
||||
|
||||
border-width:1px 0;
|
||||
border-top-color:#343434;
|
||||
border-bottom-color:#282828;
|
||||
border-style:solid;
|
||||
background-color:#2c2c2c;
|
||||
@include border_box_sizing;
|
||||
border-spacing:7px;
|
||||
text-align: left;
|
||||
|
||||
&.active {
|
||||
background-color:#44423f;
|
||||
}
|
||||
}
|
||||
|
||||
.active-stem-select {
|
||||
max-width:206px;
|
||||
}
|
||||
.stems {
|
||||
|
||||
|
||||
height:147px;
|
||||
overflow:auto;
|
||||
margin:20px 0;
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
th {
|
||||
color:white;
|
||||
font-size:14px;
|
||||
line-height:20px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
td {
|
||||
font-size:12px;
|
||||
height:24px;
|
||||
line-height:28px;
|
||||
vertical-align:middle;
|
||||
input {
|
||||
width:auto;
|
||||
}
|
||||
img{
|
||||
vertical-align:middle;
|
||||
margin-right:10px;
|
||||
}
|
||||
}
|
||||
td.mute {
|
||||
padding-right:25px;
|
||||
}
|
||||
.mute {
|
||||
text-align:right;
|
||||
padding-right:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.stems-holder {
|
||||
height:250px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
.stem {
|
||||
|
||||
}
|
||||
|
||||
.mixdown-name {
|
||||
line-height:125%;
|
||||
width:210px;
|
||||
text-align:left;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mixdown-actions {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
margin-left:10px;
|
||||
width:100px;
|
||||
white-space:nowrap;
|
||||
min-width:100px;
|
||||
}
|
||||
|
||||
.mixdown-stateful {
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
width:24px;
|
||||
height:24px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.mixdown-download {
|
||||
width:24px;
|
||||
height:24px;
|
||||
|
||||
cursor:pointer;
|
||||
|
||||
&.not-ready {
|
||||
visibility:hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.mixdown-play {
|
||||
width:24px;
|
||||
height:24px;
|
||||
margin-right:10px;
|
||||
|
||||
cursor:pointer;
|
||||
}
|
||||
.mixdown-edit {
|
||||
margin-left:10px;
|
||||
width:24px;
|
||||
height:24px;
|
||||
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.mixdown-delete {
|
||||
margin-left:10px;
|
||||
|
||||
width:24px;
|
||||
height:24px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.create-mix {
|
||||
margin-top:5px;
|
||||
border-color:$ColorTextTypical;
|
||||
border-style: solid;
|
||||
border-width:1px 0;
|
||||
padding: 7px 0 20px;
|
||||
|
||||
&.not-active {
|
||||
padding:7px 0 7px;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height:125%;
|
||||
color:$ColorTextTypical;
|
||||
text-align:left;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display:block;
|
||||
height:25px;
|
||||
margin-top:15px;
|
||||
}
|
||||
|
||||
ul.error-text {
|
||||
float:right;
|
||||
display:block !important;
|
||||
color: red;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
a.create-mix-btn {
|
||||
margin-top:15px;
|
||||
float:right;
|
||||
margin-right: 2px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
label {
|
||||
display:inline;
|
||||
float:left;
|
||||
}
|
||||
|
||||
select, input {
|
||||
width:170px;
|
||||
float:right;
|
||||
@include border_box_sizing;
|
||||
background-color:$ColorTextBoxBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
float:none;
|
||||
margin-left:5px;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
border-top: 4px solid #fc0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
display:inline-block;
|
||||
padding-top:1px;
|
||||
}
|
||||
.arrow-up {
|
||||
float:none;
|
||||
margin-right:0;
|
||||
margin-left:5px;
|
||||
margin-bottom:2px;
|
||||
border-bottom: 4px solid #fc0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
display:inline-block;
|
||||
padding-top:1px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
/**
|
||||
*= require web/Raleway
|
||||
*= require client/jamServer
|
||||
*= require dialogs/banner
|
||||
*= require client/ie
|
||||
*= require client/jamkazam
|
||||
*= require client/screen_common
|
||||
|
|
@ -12,4 +14,8 @@
|
|||
*= require_directory .
|
||||
*= require client/metronomePlaybackModeSelect
|
||||
*= require_directory ../client/react-components
|
||||
*/
|
||||
*/
|
||||
|
||||
.no-websocket-connection.active {
|
||||
display:none !important;
|
||||
}
|
||||
|
|
@ -33,6 +33,11 @@ body.web {
|
|||
}
|
||||
}
|
||||
|
||||
.after-black-bar-border {
|
||||
border-top:1px solid #444;
|
||||
margin: 0 6%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#profile.userinfo {
|
||||
margin-top:30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,14 @@ class ApiJamTrackMixdownsController < ApiController
|
|||
@package.last_downloaded_at = now
|
||||
@package.first_downloaded_at = now if @package.first_downloaded_at.nil?
|
||||
@package.save!
|
||||
redirect_to @package.sign_url(120)
|
||||
if params[:download]
|
||||
redirect_to @package.sign_url(120, 'application/octet-stream', "attachment; filename=#{@package.jam_track_mixdown.name}.#{params[:file_type]}")
|
||||
|
||||
else
|
||||
redirect_to @package.sign_url(120)
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
@package.enqueue_if_needed
|
||||
render :json => { :message => "not available, digitally signing Jam Track Mixdown offline." }, :status => 202
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class ApiJamTracksController < ApiController
|
|||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active]
|
||||
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active, :download_stem]
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
@ -30,8 +30,16 @@ class ApiJamTracksController < ApiController
|
|||
|
||||
def mark_active
|
||||
mixdown_id = params[:mixdown_id]
|
||||
stem_id = params[:stem_id]
|
||||
|
||||
# only one should be active at a time, so we enforce that on the server here. if you specify one, the other will get nulled automatically
|
||||
if mixdown_id
|
||||
stem_id = nil
|
||||
else
|
||||
mixdown_id = nil
|
||||
end
|
||||
@jam_track_right.last_mixdown_id = mixdown_id
|
||||
@jam_track_right.last_stem_id = stem_id
|
||||
@jam_track_right.save
|
||||
|
||||
if @jam_track_right.errors.any?
|
||||
|
|
@ -96,6 +104,25 @@ class ApiJamTracksController < ApiController
|
|||
render "api_jam_tracks/purchased", :layout => nil
|
||||
end
|
||||
|
||||
def download_stem
|
||||
if @jam_track_right.valid?
|
||||
|
||||
if params[:stem_id] == 'master'
|
||||
jam_track_track = @jam_track_right.jam_track.master_track
|
||||
else
|
||||
jam_track_track = JamTrackTrack.find(params[:stem_id])
|
||||
end
|
||||
|
||||
if params[:download]
|
||||
redirect_to jam_track_track.web_download_sign_url(120, params[:file_type], 'application/octet-stream', "attachment; filename=#{@jam_track_right.jam_track.name + '-' + jam_track_track.display_name}.mp3")
|
||||
else
|
||||
redirect_to jam_track_track.web_download_sign_url(120, params[:file_type])
|
||||
end
|
||||
|
||||
else
|
||||
render :json => { :message => "download limit surpassed", :errors=>@jam_track_right.errors }, :status => 403
|
||||
end
|
||||
end
|
||||
|
||||
def download
|
||||
if @jam_track_right.valid?
|
||||
|
|
|
|||
|
|
@ -396,8 +396,9 @@ class ApiRecordingsController < ApiController
|
|||
body << "User: " + current_user.admin_url + "\n\n"
|
||||
body << "Recording Landing: #{recording_detail_url(@recording.id)}\n"
|
||||
|
||||
AdminMailer.alerts({
|
||||
subject:"Video Uploaded by #{current_user.name}",
|
||||
private_public = @recording.is_public ? 'Public' : 'Private'
|
||||
AdminMailer.social({
|
||||
subject:"#{private_public } Video Uploaded by #{current_user.name}",
|
||||
body:body
|
||||
}).deliver
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ require 'sanitize'
|
|||
class
|
||||
ApiUsersController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth]
|
||||
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event]
|
||||
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations,
|
||||
:liking_create, :liking_destroy, # likes
|
||||
:following_create, :following_show, :following_destroy, # followings
|
||||
|
|
@ -683,6 +683,21 @@ ApiUsersController < ApiController
|
|||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
def opened_jamtrack_web_player
|
||||
User.where(id: current_user.id).update_all(first_opened_jamtrack_web_player: Time.now)
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
def user_event
|
||||
event = UserEvent.new
|
||||
event.name = params[:name]
|
||||
event.detail = params[:detail].to_json if params[:detail]
|
||||
event.user_id = current_user.id if current_user
|
||||
event.save
|
||||
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
# creates display-ready session data for sharing
|
||||
def share_session
|
||||
provider = params[:provider]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def track_affiliate_visits
|
||||
if params[:affiliate] && params[:utm_medium] == 'affiliate' && params[:utm_source] == 'affiliate'
|
||||
if params[:affiliate]
|
||||
visit_cookie = cookies[:affiliate_visitor]
|
||||
AffiliateReferralVisit.track(affiliate_id: params[:affiliate], visited: visit_cookie, remote_ip: request.remote_ip, visited_url: request.fullpath, referral_url: request.referer, current_user: current_user)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,65 @@ class LandingsController < ApplicationController
|
|||
render 'watch_overview_tight', layout: 'web'
|
||||
end
|
||||
|
||||
|
||||
def individual_jamtrack
|
||||
@no_landing_tag = true
|
||||
@landing_tag_play_learn_earn = true
|
||||
@show_after_black_bar_border = true
|
||||
instrument_id = nil
|
||||
instrument_name = nil
|
||||
instrument_count = 0
|
||||
if params[:instrument]
|
||||
instrument = params[:instrument].downcase.sub('-', ' ')
|
||||
instrument = Instrument.find_by_id(instrument)
|
||||
instrument_id = instrument.id if instrument
|
||||
instrument_name = instrument.description
|
||||
query, next_ptr, instrument_count = JamTrack.index({instrument: instrument_id}, current_user)
|
||||
end
|
||||
@jam_track = JamTrack.find_by_slug(params[:plan_code])
|
||||
@jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track
|
||||
|
||||
if @jam_track.nil?
|
||||
redirect_to '/client/#jamtrack'
|
||||
return
|
||||
end
|
||||
|
||||
band_jam_track_count = @jam_track.band_jam_track_count
|
||||
jam_track_count = JamTrack.count
|
||||
@title = individual_jamtrack_title(false, params[:generic], @jam_track)
|
||||
@description = individual_jamtrack_desc(false, params[:generic], @jam_track)
|
||||
@page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: false, generic: params[:generic], instrument: instrument_name, instrument_id: instrument_id, instrument_count: instrument_count}
|
||||
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
|
||||
gon.generic = params[:generic]
|
||||
gon.instrument_id = instrument_id
|
||||
render 'individual_jamtrack', layout: 'web'
|
||||
end
|
||||
|
||||
|
||||
|
||||
def individual_jamtrack_band
|
||||
@no_landing_tag = true
|
||||
@landing_tag_play_learn_earn = true
|
||||
@show_after_black_bar_border = true
|
||||
@jam_track = JamTrack.find_by_slug(params[:plan_code])
|
||||
@jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track
|
||||
|
||||
if @jam_track.nil?
|
||||
redirect_to '/client/#jamtrack'
|
||||
return
|
||||
end
|
||||
|
||||
band_jam_track_count = @jam_track.band_jam_track_count
|
||||
jam_track_count = JamTrack.count
|
||||
@title = individual_jamtrack_title(true, params[:generic], @jam_track)
|
||||
@description = individual_jamtrack_desc(true, params[:generic], @jam_track)
|
||||
@page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: true, generic: params[:generic]}
|
||||
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
|
||||
gon.generic = params[:generic]
|
||||
render 'individual_jamtrack', layout: 'web'
|
||||
end
|
||||
|
||||
def individual_jamtrack_v2
|
||||
@no_landing_tag = true
|
||||
instrument_id = nil
|
||||
instrument_name = nil
|
||||
|
|
@ -90,11 +148,11 @@ class LandingsController < ApplicationController
|
|||
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
|
||||
gon.generic = params[:generic]
|
||||
gon.instrument_id = instrument_id
|
||||
render 'individual_jamtrack', layout: 'web'
|
||||
render 'individual_jamtrack_v2', layout: 'web'
|
||||
end
|
||||
|
||||
|
||||
def individual_jamtrack_band
|
||||
def individual_jamtrack_band_v2
|
||||
@no_landing_tag = true
|
||||
@jam_track = JamTrack.find_by_slug(params[:plan_code])
|
||||
@jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) unless @jam_track
|
||||
|
|
@ -105,7 +163,7 @@ class LandingsController < ApplicationController
|
|||
@page_data = {jam_track: @jam_track, all_track_count: jam_track_count, band_track_count: band_jam_track_count, band: true, generic: params[:generic]}
|
||||
gon.jam_track_plan_code = @jam_track.plan_code if @jam_track
|
||||
gon.generic = params[:generic]
|
||||
render 'individual_jamtrack', layout: 'web'
|
||||
render 'individual_jamtrack_v2', layout: 'web'
|
||||
end
|
||||
|
||||
def individual_jamtrack_v1
|
||||
|
|
|
|||
|
|
@ -28,4 +28,11 @@ class PopupsController < ApplicationController
|
|||
gon.recording_id = @recording_id
|
||||
render :layout => "minimal"
|
||||
end
|
||||
|
||||
def jamtrack_player
|
||||
@jamtrack_id = params[:jam_track_id]
|
||||
@websocket = true
|
||||
gon.jamtrack_id = @jamtrack_id
|
||||
render :layout => "minimal"
|
||||
end
|
||||
end
|
||||