merging feature/musician_profile_enhancements
This commit is contained in:
commit
2ea1259060
|
|
@ -74,6 +74,7 @@ gem 'sanitize'
|
|||
gem 'slim'
|
||||
gem 'influxdb', '0.1.8'
|
||||
gem 'influxdb-rails', '0.1.10'
|
||||
gem 'recurly'
|
||||
|
||||
group :libv8 do
|
||||
gem 'libv8', "~> 3.11.8"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
require 'jam_ruby/recurly_client'
|
||||
ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
||||
|
||||
menu :label => 'Purchased JamTracks', :parent => 'JamTracks'
|
||||
|
||||
config.sort_order = 'updated_at DESC'
|
||||
config.batch_actions = false
|
||||
|
||||
#form :partial => 'form'
|
||||
|
||||
index do
|
||||
default_actions
|
||||
|
||||
column "Order" do |right|
|
||||
link_to("Place", order_admin_jam_track_right_path(right)) + " | " +
|
||||
link_to("Refund", refund_admin_jam_track_right_path(right))
|
||||
end
|
||||
|
||||
column "Last Name" do |right|
|
||||
right.user.last_name
|
||||
end
|
||||
column "First Name" do |right|
|
||||
right.user.first_name
|
||||
end
|
||||
column "Jam Track" do |right|
|
||||
link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track))
|
||||
# right.jam_track
|
||||
end
|
||||
column "Plan Code" do |right|
|
||||
|
||||
right.jam_track.plan_code
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.inputs 'New Jam Track Right' do
|
||||
f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
|
||||
f.input :user, :required=>true, collection: User.all, include_blank: false
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
|
||||
member_action :order, :method => :get do
|
||||
right = JamTrackRight.where("id=?",params[:id]).first
|
||||
user = right.user
|
||||
jam_track = right.jam_track
|
||||
client = RecurlyClient.new
|
||||
billing_info = {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
address1: 'Test Address 1',
|
||||
address2: 'Test Address 2',
|
||||
city: user.city,
|
||||
state: user.state,
|
||||
country: user.country,
|
||||
zip: '12345',
|
||||
number: '4111-1111-1111-1111',
|
||||
month: '08',
|
||||
year: '2017',
|
||||
verification_value: '111'
|
||||
}
|
||||
|
||||
begin
|
||||
client.find_or_create_account(user, billing_info)
|
||||
client.place_order(user, jam_track)
|
||||
rescue RecurlyClientError=>x
|
||||
redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}"
|
||||
else
|
||||
redirect_to admin_jam_track_rights_path, notice: "Placed order of #{jam_track} for #{user.to_s}."
|
||||
end
|
||||
end
|
||||
|
||||
member_action :refund, :method => :get do
|
||||
right = JamTrackRight.where("id=?",params[:id]).first
|
||||
client = RecurlyClient.new
|
||||
|
||||
begin
|
||||
client.refund_user_subscription(right.user, right.jam_track)
|
||||
rescue RecurlyClientError=>x
|
||||
redirect_to admin_jam_track_rights_path, notice: "Could not issue refund on #{right.jam_track} for #{right.user.to_s}: #{x.errors.inspect}"
|
||||
else
|
||||
redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -223,7 +223,6 @@ FactoryGirl.define do
|
|||
factory :jam_track, :class => JamRuby::JamTrack do
|
||||
sequence(:name) { |n| "jam-track-#{n}" }
|
||||
sequence(:description) { |n| "description-#{n}" }
|
||||
bpm 100.1
|
||||
time_signature '4/4'
|
||||
status 'Production'
|
||||
recording_type 'Cover'
|
||||
|
|
|
|||
|
|
@ -247,6 +247,12 @@ user_model_about_changes.sql
|
|||
performance_samples.sql
|
||||
user_presences.sql
|
||||
discard_scores_optimized.sql
|
||||
backing_tracks.sql
|
||||
metronome.sql
|
||||
recorded_backing_tracks.sql
|
||||
recorded_backing_tracks_add_filename.sql
|
||||
user_syncs_include_backing_tracks.sql
|
||||
remove_bpm_from_jamtracks.sql
|
||||
alter_type_columns.sql
|
||||
user_presences_rename.sql
|
||||
add_genre_type.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE active_music_sessions ADD COLUMN backing_track_path VARCHAR(1024);
|
||||
ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id VARCHAR(64);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE active_music_sessions ADD COLUMN metronome_active BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE active_music_sessions ADD COLUMN metronome_initiator_id VARCHAR(64);
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
CREATE UNLOGGED TABLE backing_tracks (
|
||||
id VARCHAR(64) NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
filename VARCHAR(1024) NOT NULL,
|
||||
|
||||
connection_id VARCHAR(64) NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
|
||||
client_track_id VARCHAR(64) NOT NULL,
|
||||
client_resource_id VARCHAR(100),
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE recorded_backing_tracks (
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
|
||||
backing_track_id VARCHAR(64),
|
||||
recording_id VARCHAR(64) NOT NULL,
|
||||
|
||||
client_track_id VARCHAR(64) NOT NULL,
|
||||
is_part_uploading BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
next_part_to_upload INTEGER NOT NULL DEFAULT 0,
|
||||
upload_id CHARACTER VARYING(1024),
|
||||
part_failures INTEGER NOT NULL DEFAULT 0,
|
||||
discard BOOLEAN,
|
||||
download_count INTEGER NOT NULL DEFAULT 0,
|
||||
md5 CHARACTER VARYING(100),
|
||||
length BIGINT,
|
||||
client_id VARCHAR(64) NOT NULL,
|
||||
file_offset BIGINT,
|
||||
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
fully_uploaded BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
upload_failures INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE recorded_backing_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE recorded_backing_tracks ADD COLUMN filename VARCHAR NOT NULL;
|
||||
ALTER TABLE recorded_backing_tracks ADD COLUMN last_downloaded_at TIMESTAMP WITHOUT TIME ZONE;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE jam_tracks DROP COLUMN bpm;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
DROP VIEW user_syncs;
|
||||
|
||||
CREATE VIEW user_syncs AS
|
||||
SELECT DISTINCT b.id AS recorded_track_id,
|
||||
CAST(NULL as BIGINT) AS mix_id,
|
||||
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||
b.id AS unified_id,
|
||||
a.user_id AS user_id,
|
||||
b.fully_uploaded,
|
||||
recordings.created_at AS created_at,
|
||||
recordings.id AS recording_id
|
||||
FROM recorded_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE INNER JOIN recorded_tracks b ON a.recording_id = b.recording_id
|
||||
UNION ALL
|
||||
SELECT CAST(NULL AS BIGINT) AS recorded_track_id,
|
||||
CAST(NULL as BIGINT) AS mix_id,
|
||||
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||
a.id AS recorded_backing_track_id,
|
||||
a.id AS unified_id,
|
||||
a.user_id AS user_id,
|
||||
a.fully_uploaded,
|
||||
recordings.created_at AS created_at,
|
||||
recordings.id AS recording_id
|
||||
FROM recorded_backing_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE
|
||||
UNION ALL
|
||||
SELECT CAST(NULL as BIGINT) AS recorded_track_id,
|
||||
mixes.id AS mix_id,
|
||||
CAST(NULL as BIGINT) AS quick_mix_id,
|
||||
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||
mixes.id AS unified_id,
|
||||
claimed_recordings.user_id AS user_id,
|
||||
NULL as fully_uploaded,
|
||||
recordings.created_at AS created_at,
|
||||
recordings.id AS recording_id
|
||||
FROM mixes INNER JOIN recordings ON mixes.recording_id = recordings.id INNER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id WHERE claimed_recordings.discarded = FALSE AND deleted = FALSE
|
||||
UNION ALL
|
||||
SELECT CAST(NULL as BIGINT) AS recorded_track_id,
|
||||
CAST(NULL as BIGINT) AS mix_id,
|
||||
quick_mixes.id AS quick_mix_id,
|
||||
CAST(NULL as BIGINT) AS recorded_backing_track_id,
|
||||
quick_mixes.id AS unified_id,
|
||||
quick_mixes.user_id,
|
||||
quick_mixes.fully_uploaded,
|
||||
recordings.created_at AS created_at,
|
||||
recordings.id AS recording_id
|
||||
FROM quick_mixes INNER JOIN recordings ON quick_mixes.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE;
|
||||
|
|
@ -430,7 +430,7 @@ def assert_all_tracks_seen(users=[])
|
|||
users.each do |user|
|
||||
in_client(user) do
|
||||
users.reject {|u| u==user}.each do |other|
|
||||
find('div.track-label', text: other.name)
|
||||
find('div.track-label > span', text: other.name)
|
||||
#puts user.name + " is able to see " + other.name + "\'s track"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ gem 'iso-639'
|
|||
gem 'rubyzip'
|
||||
gem 'sanitize'
|
||||
gem 'influxdb', '0.1.8'
|
||||
gem 'recurly'
|
||||
|
||||
group :test do
|
||||
gem 'simplecov', '~> 0.7.1'
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ require "jam_ruby/resque/scheduled/stats_maker"
|
|||
require "jam_ruby/resque/google_analytics_event"
|
||||
require "jam_ruby/resque/batch_email_job"
|
||||
require "jam_ruby/mq_router"
|
||||
require "jam_ruby/recurly_client"
|
||||
require "jam_ruby/base_manager"
|
||||
require "jam_ruby/connection_manager"
|
||||
require "jam_ruby/version"
|
||||
|
|
@ -87,6 +88,7 @@ require "jam_ruby/lib/stats.rb"
|
|||
require "jam_ruby/amqp/amqp_connection_manager"
|
||||
require "jam_ruby/database"
|
||||
require "jam_ruby/message_factory"
|
||||
require "jam_ruby/models/backing_track"
|
||||
require "jam_ruby/models/feedback"
|
||||
require "jam_ruby/models/feedback_observer"
|
||||
#require "jam_ruby/models/max_mind_geo"
|
||||
|
|
@ -132,8 +134,11 @@ require "jam_ruby/models/search"
|
|||
require "jam_ruby/models/recording"
|
||||
require "jam_ruby/models/recording_comment"
|
||||
require "jam_ruby/models/recording_liker"
|
||||
require "jam_ruby/models/recorded_backing_track"
|
||||
require "jam_ruby/models/recorded_backing_track_observer"
|
||||
require "jam_ruby/models/recorded_track"
|
||||
require "jam_ruby/models/recorded_track_observer"
|
||||
require "jam_ruby/models/recorded_video"
|
||||
require "jam_ruby/models/quick_mix"
|
||||
require "jam_ruby/models/quick_mix_observer"
|
||||
require "jam_ruby/models/share_token"
|
||||
|
|
@ -197,7 +202,6 @@ require "jam_ruby/models/score_history"
|
|||
require "jam_ruby/models/jam_company"
|
||||
require "jam_ruby/models/user_sync"
|
||||
require "jam_ruby/models/video_source"
|
||||
require "jam_ruby/models/recorded_video"
|
||||
require "jam_ruby/models/text_message"
|
||||
require "jam_ruby/jam_tracks_manager"
|
||||
require "jam_ruby/models/performance_sample"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class JamTrackTrackUploader < CarrierWave::Uploader::Base
|
|||
|
||||
# Add a white list of extensions which are allowed to be uploaded.
|
||||
def extension_white_list
|
||||
%w(ogg)
|
||||
%w(ogg wav)
|
||||
end
|
||||
|
||||
def store_dir
|
||||
|
|
|
|||
|
|
@ -311,14 +311,19 @@ SQL
|
|||
end
|
||||
end
|
||||
else
|
||||
# there are still people in the session
|
||||
|
||||
conn.exec("UPDATE active_music_sessions set backing_track_initiator_id = NULL, backing_track_path = NULL where backing_track_initiator_id = $1 and id = $2",
|
||||
[user_id, previous_music_session_id])
|
||||
|
||||
conn.exec("UPDATE active_music_sessions set metronome_initiator_id = NULL, metronome_active = FALSE where metronome_initiator_id = $1 and id = $2",
|
||||
[user_id, previous_music_session_id])
|
||||
|
||||
#ensure that there is no active claimed recording if the owner of that recording left the session
|
||||
conn.exec("UPDATE active_music_sessions set claimed_recording_id = NULL, claimed_recording_initiator_id = NULL where claimed_recording_initiator_id = $1 and id = $2",
|
||||
[user_id, previous_music_session_id])
|
||||
[user_id, previous_music_session_id])
|
||||
|
||||
conn.exec("UPDATE active_music_sessions set jam_track_id = NULL, jam_track_initiator_id = NULL where jam_track_initiator_id = $1 and id = $2",
|
||||
[user_id, previous_music_session_id])
|
||||
[user_id, previous_music_session_id])
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ module ValidationMessages
|
|||
MUST_BE_KNOWN_TIMEZONE = "not valid"
|
||||
JAM_TRACK_ALREADY_OPEN = 'another jam track already open'
|
||||
RECORDING_ALREADY_IN_PROGRESS = "recording being made"
|
||||
METRONOME_ALREADY_OPEN = 'another metronome already open'
|
||||
BACKING_TRACK_ALREADY_OPEN = 'another audio file already open'
|
||||
|
||||
# notification
|
||||
DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
# initialize actionmailer
|
||||
ActionMailer::Base.raise_delivery_errors = true
|
||||
ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__)
|
||||
ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__)
|
||||
|
||||
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||
case JamRuby::Environment
|
||||
when 'production'
|
||||
Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6"
|
||||
Recurly.subdomain = 'jamkazam'
|
||||
when 'development'
|
||||
Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6"
|
||||
Recurly.subdomain = 'jamkazam-development'
|
||||
else
|
||||
Recurly.api_key = "4631527f203b41848523125b3ae51341"
|
||||
Recurly.subdomain = 'jamkazam-test'
|
||||
end
|
||||
|
||||
Recurly.default_currency = 'USD'
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ module JamRuby
|
|||
Dir.mktmpdir do |tmp_dir|
|
||||
jam_file_opts=""
|
||||
jam_track.jam_track_tracks.each do |jam_track_track|
|
||||
|
||||
next if jam_track_track.track_type != "Track" # master mixes do not go into the JKZ
|
||||
|
||||
# use the jam_track_track ID as the filename.ogg/.wav, because it's important metadata
|
||||
nm = jam_track_track.id + File.extname(jam_track_track.filename)
|
||||
track_filename = File.join(tmp_dir, nm)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module JamRuby
|
|||
|
||||
self.table_name = 'active_music_sessions'
|
||||
|
||||
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording
|
||||
attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome
|
||||
|
||||
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
|
||||
belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"
|
||||
|
|
@ -15,6 +15,9 @@ module JamRuby
|
|||
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions
|
||||
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id"
|
||||
|
||||
belongs_to :backing_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "backing_track_initiator_id"
|
||||
belongs_to :metronome_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "metronome_initiator_id"
|
||||
|
||||
has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id'
|
||||
has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id'
|
||||
belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id
|
||||
|
|
@ -27,6 +30,8 @@ module JamRuby
|
|||
validate :creator_is_musician
|
||||
validate :validate_opening_recording, :if => :opening_recording
|
||||
validate :validate_opening_jam_track, :if => :opening_jam_track
|
||||
validate :validate_opening_backing_track, :if => :opening_backing_track
|
||||
validate :validate_opening_metronome, :if => :opening_metronome
|
||||
|
||||
after_create :started_session
|
||||
|
||||
|
|
@ -73,22 +78,52 @@ module JamRuby
|
|||
if is_jam_track_open?
|
||||
errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
||||
end
|
||||
|
||||
if is_backing_track_open?
|
||||
errors.add(:claimed_recording, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
|
||||
end
|
||||
|
||||
if is_metronome_open?
|
||||
errors.add(:claimed_recording, ValidationMessages::METRONOME_ALREADY_OPEN)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_opening_jam_track
|
||||
validate_other_audio(:jam_track)
|
||||
end
|
||||
|
||||
def validate_opening_backing_track
|
||||
validate_other_audio(:backing_track)
|
||||
end
|
||||
|
||||
def validate_opening_metronome
|
||||
validate_other_audio(:metronome)
|
||||
end
|
||||
|
||||
def validate_other_audio(error_key)
|
||||
# validate that there is no metronome already open in this session
|
||||
if metronome_active_was
|
||||
errors.add(error_key, ValidationMessages::METRONOME_ALREADY_OPEN)
|
||||
end
|
||||
|
||||
# validate that there is no backing track already open in this session
|
||||
if backing_track_path_was.present?
|
||||
errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
|
||||
end
|
||||
|
||||
# validate that there is no jam track already open in this session
|
||||
unless jam_track_id_was.nil?
|
||||
errors.add(:jam_track, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
||||
if jam_track_id_was.present?
|
||||
errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
|
||||
end
|
||||
|
||||
# validate that there is no recording being made
|
||||
if is_recording?
|
||||
errors.add(:jam_track, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS)
|
||||
errors.add(error_key, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS)
|
||||
end
|
||||
|
||||
# validate that there is no recording being played back to the session
|
||||
if is_playing_recording?
|
||||
errors.add(:jam_track, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
|
||||
errors.add(error_key, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -593,6 +628,14 @@ module JamRuby
|
|||
!self.jam_track.nil?
|
||||
end
|
||||
|
||||
def is_backing_track_open?
|
||||
self.backing_track_path.present?
|
||||
end
|
||||
|
||||
def is_metronome_open?
|
||||
self.metronome_active.present?
|
||||
end
|
||||
|
||||
# is this music session currently recording?
|
||||
def is_recording?
|
||||
recordings.where(:duration => nil).count > 0
|
||||
|
|
@ -742,6 +785,35 @@ module JamRuby
|
|||
self.save
|
||||
end
|
||||
|
||||
# @param backing_track_path is a relative path:
|
||||
def open_backing_track(user, backing_track_path)
|
||||
self.backing_track_path = backing_track_path
|
||||
self.backing_track_initiator = user
|
||||
self.opening_backing_track = true
|
||||
self.save
|
||||
self.opening_backing_track = false
|
||||
end
|
||||
|
||||
def close_backing_track
|
||||
self.backing_track_path = nil
|
||||
self.backing_track_initiator = nil
|
||||
self.save
|
||||
end
|
||||
|
||||
def open_metronome(user)
|
||||
self.metronome_active = true
|
||||
self.metronome_initiator = user
|
||||
self.opening_metronome = true
|
||||
self.save
|
||||
self.opening_metronome = false
|
||||
end
|
||||
|
||||
def close_metronome
|
||||
self.metronome_active = false
|
||||
self.metronome_initiator = nil
|
||||
self.save
|
||||
end
|
||||
|
||||
def self.sync(session_history)
|
||||
music_session = MusicSession.find_by_id(session_history.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
module JamRuby
|
||||
class BackingTrack < ActiveRecord::Base
|
||||
|
||||
self.table_name = "backing_tracks"
|
||||
self.primary_key = 'id'
|
||||
|
||||
default_scope order('created_at ASC')
|
||||
|
||||
belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks, :foreign_key => 'connection_id'
|
||||
validates :connection, presence: true
|
||||
validates :client_track_id, presence: true
|
||||
validates :filename, presence: true
|
||||
|
||||
def user
|
||||
self.connection.user
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -18,6 +18,7 @@ module JamRuby
|
|||
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", foreign_key: :music_session_id
|
||||
has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id
|
||||
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||
has_many :backing_tracks, :class_name => "JamRuby::BackingTrack", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||
has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
|
||||
|
||||
validates :as_musician, :inclusion => {:in => [true, false, nil]}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ module JamRuby
|
|||
|
||||
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
|
||||
validates :description, length: {maximum: 1000}
|
||||
validates_format_of :bpm, with: /^\d+\.*\d{0,1}$/
|
||||
validates :time_signature, inclusion: {in: [nil] + TIME_SIGNATURES}
|
||||
validates :status, inclusion: {in: [nil] + STATUS}
|
||||
validates :recording_type, inclusion: {in: [nil] + RECORDING_TYPE}
|
||||
|
|
|
|||
|
|
@ -138,11 +138,17 @@ module JamRuby
|
|||
|
||||
manifest = { "files" => [], "timeline" => [] }
|
||||
mix_params = []
|
||||
|
||||
recording.recorded_tracks.each do |recorded_track|
|
||||
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
|
||||
mix_params << { "level" => 100, "balance" => 0 }
|
||||
end
|
||||
|
||||
recording.recorded_backing_tracks.each do |recorded_backing_track|
|
||||
manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
|
||||
mix_params << { "level" => 100, "balance" => 0 }
|
||||
end
|
||||
|
||||
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
||||
manifest["output"] = { "codec" => "vorbis" }
|
||||
manifest["recording_id"] = self.recording.id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,196 @@
|
|||
module JamRuby
|
||||
# BackingTrack analog to JamRuby::RecordedTrack
|
||||
class RecordedBackingTrack < ActiveRecord::Base
|
||||
|
||||
include JamRuby::S3ManagerMixin
|
||||
|
||||
attr_accessor :marking_complete
|
||||
attr_writer :current_user
|
||||
|
||||
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_backing_tracks
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_backing_tracks
|
||||
validates :filename, :presence => true
|
||||
|
||||
validates :client_id, :presence => true # not a connection relation on purpose
|
||||
validates :backing_track_id, :presence => true # not a track relation on purpose
|
||||
validates :client_track_id, :presence => true
|
||||
validates :md5, :presence => true, :if => :upload_starting?
|
||||
validates :length, length: {minimum: 1, maximum: 1024 * 1024 * 256 }, if: :upload_starting? # 256 megs max. is this reasonable? surely...
|
||||
validates :user, presence: true
|
||||
validates :download_count, presence: true
|
||||
|
||||
before_destroy :delete_s3_files
|
||||
validate :validate_fully_uploaded
|
||||
validate :validate_part_complete
|
||||
validate :validate_too_many_upload_failures
|
||||
validate :verify_download_count
|
||||
|
||||
def self.create_from_backing_track(backing_track, recording)
|
||||
recorded_backing_track = self.new
|
||||
recorded_backing_track.recording = recording
|
||||
recorded_backing_track.client_id = backing_track.connection.client_id
|
||||
recorded_backing_track.backing_track_id = backing_track.id
|
||||
recorded_backing_track.client_track_id = "R" + backing_track.client_track_id # Matches behavior in RecordingManager.cpp#getWavComment
|
||||
recorded_backing_track.user = backing_track.connection.user
|
||||
recorded_backing_track.filename = backing_track.filename
|
||||
recorded_backing_track.next_part_to_upload = 0
|
||||
recorded_backing_track.file_offset = 0
|
||||
recorded_backing_track[:url] = construct_filename(recording.created_at, recording.id, backing_track.client_track_id)
|
||||
recorded_backing_track.save
|
||||
recorded_backing_track
|
||||
end
|
||||
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false})
|
||||
end
|
||||
|
||||
def can_download?(some_user)
|
||||
claimed_recording = recording.claimed_recordings.find{|claimed_recording| claimed_recording.user == some_user }
|
||||
|
||||
if claimed_recording
|
||||
!claimed_recording.discarded
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def too_many_upload_failures?
|
||||
upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||
end
|
||||
|
||||
def too_many_downloads?
|
||||
(self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
|
||||
end
|
||||
|
||||
def upload_starting?
|
||||
next_part_to_upload_was == 0 && next_part_to_upload == 1
|
||||
end
|
||||
|
||||
def validate_too_many_upload_failures
|
||||
if upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||
errors.add(:upload_failures, ValidationMessages::UPLOAD_FAILURES_EXCEEDED)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_fully_uploaded
|
||||
if marking_complete && fully_uploaded && fully_uploaded_was
|
||||
errors.add(:fully_uploaded, ValidationMessages::ALREADY_UPLOADED)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_part_complete
|
||||
|
||||
# if we see a transition from is_part_uploading from true to false, we validate
|
||||
if is_part_uploading_was && !is_part_uploading
|
||||
if next_part_to_upload_was + 1 != next_part_to_upload
|
||||
errors.add(:next_part_to_upload, ValidationMessages::INVALID_PART_NUMBER_SPECIFIED)
|
||||
end
|
||||
|
||||
if file_offset > length
|
||||
errors.add(:file_offset, ValidationMessages::FILE_OFFSET_EXCEEDS_LENGTH)
|
||||
end
|
||||
elsif next_part_to_upload_was + 1 == next_part_to_upload
|
||||
# this makes sure we are only catching 'upload_part_complete' transitions, and not upload_start
|
||||
if next_part_to_upload_was != 0
|
||||
# we see that the part number was ticked--but was is_part_upload set to true before this transition?
|
||||
if !is_part_uploading_was && !is_part_uploading
|
||||
errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_STARTED)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verify_download_count
|
||||
if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
|
||||
errors.add(:download_count, "must be less than or equal to 100")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def upload_start(length, md5)
|
||||
#self.upload_id set by the observer
|
||||
self.next_part_to_upload = 1
|
||||
self.length = length
|
||||
self.md5 = md5
|
||||
save
|
||||
end
|
||||
|
||||
# if for some reason the server thinks the client can't carry on with the upload,
|
||||
# this resets everything to the initial state
|
||||
def reset_upload
|
||||
self.upload_failures = self.upload_failures + 1
|
||||
self.part_failures = 0
|
||||
self.file_offset = 0
|
||||
self.next_part_to_upload = 0
|
||||
self.upload_id = nil
|
||||
self.md5 = nil
|
||||
self.length = 0
|
||||
self.fully_uploaded = false
|
||||
self.is_part_uploading = false
|
||||
save :validate => false # skip validation because we need this to always work
|
||||
end
|
||||
|
||||
def upload_next_part(length, md5)
|
||||
self.marking_complete = true
|
||||
if next_part_to_upload == 0
|
||||
upload_start(length, md5)
|
||||
end
|
||||
self.is_part_uploading = true
|
||||
save
|
||||
end
|
||||
|
||||
def upload_sign(content_md5)
|
||||
s3_manager.upload_sign(self[:url], content_md5, next_part_to_upload, upload_id)
|
||||
end
|
||||
|
||||
def upload_part_complete(part, offset)
|
||||
# validated by :validate_part_complete
|
||||
self.marking_complete = true
|
||||
self.is_part_uploading = false
|
||||
self.next_part_to_upload = self.next_part_to_upload + 1
|
||||
self.file_offset = offset.to_i
|
||||
self.part_failures = 0
|
||||
save
|
||||
end
|
||||
|
||||
def upload_complete
|
||||
# validate from happening twice by :validate_fully_uploaded
|
||||
self.fully_uploaded = true
|
||||
self.marking_complete = true
|
||||
save
|
||||
end
|
||||
|
||||
def increment_part_failures(part_failure_before_error)
|
||||
self.part_failures = part_failure_before_error + 1
|
||||
RecordedBackingTrack.update_all("part_failures = #{self.part_failures}", "id = '#{self.id}'")
|
||||
end
|
||||
|
||||
def stored_filename
|
||||
# construct a path from s3
|
||||
RecordedBacknigTrack.construct_filename(recording.created_at, self.recording.id, self.client_track_id)
|
||||
end
|
||||
|
||||
def update_download_count(count=1)
|
||||
self.download_count = self.download_count + count
|
||||
self.last_downloaded_at = Time.now
|
||||
end
|
||||
|
||||
def delete_s3_files
|
||||
s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url])
|
||||
end
|
||||
|
||||
def mark_silent
|
||||
destroy
|
||||
|
||||
# check if we have all the files we need, now that the recorded_backing_track is out of the way
|
||||
recording.preconditions_for_mix?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.construct_filename(created_at, recording_id, client_track_id)
|
||||
raise "unknown ID" unless client_track_id
|
||||
"recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
module JamRuby
|
||||
class RecordedBackingTrackObserver < ActiveRecord::Observer
|
||||
|
||||
# if you change the this class, tests really should accompany. having alot of logic in observers is really tricky, as we do here
|
||||
observe JamRuby::RecordedBackingTrack
|
||||
|
||||
def before_validation(recorded_backing_tracks)
|
||||
|
||||
# if we see that a part was just uploaded entirely, validate that we can find the part that was just uploaded
|
||||
if recorded_backing_tracks.is_part_uploading_was && !recorded_backing_tracks.is_part_uploading
|
||||
begin
|
||||
aws_part = recorded_backing_tracks.s3_manager.multiple_upload_find_part(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id, recorded_backing_tracks.next_part_to_upload - 1)
|
||||
# calling size on a part that does not exist will throw an exception... that's what we want
|
||||
aws_part.size
|
||||
rescue SocketError => e
|
||||
raise # this should cause a 500 error, which is what we want. The client will retry later on 500.
|
||||
rescue Exception => e
|
||||
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||
rescue RuntimeError => e
|
||||
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||
rescue
|
||||
recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# if we detect that this just became fully uploaded -- if so, tell s3 to put the parts together
|
||||
if recorded_backing_tracks.marking_complete && !recorded_backing_tracks.fully_uploaded_was && recorded_backing_tracks.fully_uploaded
|
||||
|
||||
multipart_success = false
|
||||
begin
|
||||
recorded_backing_tracks.s3_manager.multipart_upload_complete(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id)
|
||||
multipart_success = true
|
||||
rescue SocketError => e
|
||||
raise # this should cause a 500 error, which is what we want. The client will retry later.
|
||||
rescue Exception => e
|
||||
#recorded_track.reload
|
||||
recorded_backing_tracks.reset_upload
|
||||
recorded_backing_tracks.errors.add(:upload_id, ValidationMessages::BAD_UPLOAD)
|
||||
end
|
||||
|
||||
# unlike RecordedTracks, only the person who uploaded can download it, so no need to notify
|
||||
|
||||
# tell all users that a download is available, except for the user who just uploaded
|
||||
# recorded_backing_tracks.recording.users.each do |user|
|
||||
#Notification.send_download_available(recorded_backing_tracks.user_id) unless user == recorded_backing_tracks.user
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def after_commit(recorded_backing_track)
|
||||
|
||||
end
|
||||
|
||||
# here we tick upload failure counts, or revert the state of the model, as needed
|
||||
def after_rollback(recorded_backing_track)
|
||||
# if fully uploaded, don't increment failures
|
||||
if recorded_backing_track.fully_uploaded
|
||||
return
|
||||
end
|
||||
|
||||
# increment part failures if there is a part currently being uploaded
|
||||
if recorded_backing_track.is_part_uploading_was
|
||||
#recorded_track.reload # we don't want anything else that the user set to get applied
|
||||
recorded_backing_track.increment_part_failures(recorded_backing_track.part_failures_was)
|
||||
if recorded_backing_track.part_failures >= APP_CONFIG.max_track_part_upload_failures
|
||||
# save upload id before we abort this bad boy
|
||||
upload_id = recorded_backing_track.upload_id
|
||||
begin
|
||||
recorded_backing_track.s3_manager.multipart_upload_abort(recorded_backing_track[:url], upload_id)
|
||||
rescue => e
|
||||
puts e.inspect
|
||||
end
|
||||
recorded_backing_track.reset_upload
|
||||
if recorded_backing_track.upload_failures >= APP_CONFIG.max_track_upload_failures
|
||||
# do anything?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def before_save(recorded_backing_track)
|
||||
# if we are on the 1st part, then we need to make sure we can save the upload_id
|
||||
if recorded_backing_track.next_part_to_upload == 1
|
||||
recorded_backing_track.upload_id = recorded_backing_track.s3_manager.multipart_upload_start(recorded_backing_track[:url])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,6 +11,7 @@ module JamRuby
|
|||
has_many :quick_mixes, :class_name => "JamRuby::QuickMix", :foreign_key => :recording_id, :dependent => :destroy
|
||||
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
|
||||
has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy
|
||||
has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy
|
||||
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
|
||||
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
|
||||
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
|
||||
|
|
@ -179,6 +180,14 @@ module JamRuby
|
|||
recorded_tracks.where(:user_id => user.id)
|
||||
end
|
||||
|
||||
def recorded_backing_tracks_for_user(user)
|
||||
unless self.users.exists?(user)
|
||||
raise PermissionError, "user was not in this session"
|
||||
end
|
||||
recorded_backing_tracks.where(:user_id => user.id)
|
||||
end
|
||||
|
||||
|
||||
def has_access?(user)
|
||||
users.exists?(user)
|
||||
end
|
||||
|
|
@ -209,6 +218,10 @@ module JamRuby
|
|||
connection.video_sources.each do |video|
|
||||
recording.recorded_videos << RecordedVideo.create_from_video_source(video, recording)
|
||||
end
|
||||
|
||||
connection.backing_tracks.each do |backing_track|
|
||||
recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -321,8 +334,7 @@ module JamRuby
|
|||
}
|
||||
)
|
||||
end
|
||||
|
||||
latest_recorded_track = downloads[-1][:next] if downloads.length > 0
|
||||
latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : 0
|
||||
|
||||
Mix.joins(:recording).joins(:recording => :claimed_recordings)
|
||||
.order('mixes.id')
|
||||
|
|
@ -345,16 +357,31 @@ module JamRuby
|
|||
}
|
||||
)
|
||||
end
|
||||
latest_mix = (downloads.length > 0) ? downloads[-1][:next] : 0
|
||||
|
||||
latest_mix = downloads[-1][:next] if downloads.length > 0
|
||||
|
||||
if !latest_mix.nil? && !latest_recorded_track.nil?
|
||||
next_date = [latest_mix, latest_recorded_track].max
|
||||
elsif latest_mix.nil?
|
||||
next_date = latest_recorded_track
|
||||
else
|
||||
next_date = latest_mix
|
||||
RecordedBackingTrack.joins(:recording).joins(:recording => :claimed_recordings)
|
||||
.order('recorded_backing_tracks.id')
|
||||
.where('recorded_backing_tracks.fully_uploaded = TRUE')
|
||||
.where('recorded_backing_tracks.id > ?', since)
|
||||
.where('recorded_backing_tracks.user_id = ?', user.id) # only the person who opened the backing track can have it back
|
||||
.where('all_discarded = false')
|
||||
.where('deleted = false')
|
||||
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_backing_track|
|
||||
downloads.push(
|
||||
{
|
||||
:type => "recorded_backing_track",
|
||||
:id => recorded_backing_track.client_track_id,
|
||||
:recording_id => recorded_backing_track.recording_id,
|
||||
:length => recorded_backing_track.length,
|
||||
:md5 => recorded_backing_track.md5,
|
||||
:url => recorded_backing_track[:url],
|
||||
:next => recorded_backing_track.id
|
||||
}
|
||||
)
|
||||
end
|
||||
latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : 0
|
||||
|
||||
next_date = [latest_mix, latest_recorded_track, latest_recorded_backing_track].max
|
||||
|
||||
if next_date.nil?
|
||||
next_date = since # echo back to the client the same value they passed in, if there are no results
|
||||
|
|
@ -417,6 +444,20 @@ module JamRuby
|
|||
Arel::Nodes::As.new('stream_mix', Arel.sql('item_type'))
|
||||
]).reorder("")
|
||||
|
||||
# Select fields for quick mix. Note that it must include
|
||||
# the same number of fields as the track or video in order for
|
||||
# the union to work:
|
||||
backing_track_arel = RecordedBackingTrack.select([
|
||||
:id,
|
||||
:recording_id,
|
||||
:user_id,
|
||||
:url,
|
||||
:fully_uploaded,
|
||||
:upload_failures,
|
||||
:client_track_id,
|
||||
Arel::Nodes::As.new('backing_track', Arel.sql('item_type'))
|
||||
]).reorder("")
|
||||
|
||||
# Glue them together:
|
||||
union = track_arel.union(vid_arel)
|
||||
|
||||
|
|
@ -439,7 +480,25 @@ module JamRuby
|
|||
])
|
||||
|
||||
# And repeat:
|
||||
union_all = arel.union(quick_mix_arel)
|
||||
union_quick = arel.union(quick_mix_arel)
|
||||
utable_quick = Arel::Nodes::TableAlias.new(union_quick, :recorded_items_quick)
|
||||
arel = arel.from(utable_quick)
|
||||
|
||||
arel = arel.except(:select)
|
||||
arel = arel.select([
|
||||
"recorded_items_quick.id",
|
||||
:recording_id,
|
||||
:user_id,
|
||||
:url,
|
||||
:fully_uploaded,
|
||||
:upload_failures,
|
||||
:client_track_id,
|
||||
:item_type
|
||||
])
|
||||
|
||||
|
||||
# And repeat for backing track:
|
||||
union_all = arel.union(backing_track_arel)
|
||||
utable_all = Arel::Nodes::TableAlias.new(union_all, :recorded_items_all)
|
||||
arel = arel.from(utable_all)
|
||||
|
||||
|
|
@ -455,7 +514,6 @@ module JamRuby
|
|||
:item_type
|
||||
])
|
||||
|
||||
|
||||
# Further joining and criteria for the unioned object:
|
||||
arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \
|
||||
.where('recorded_items_all.user_id' => user.id) \
|
||||
|
|
@ -492,6 +550,13 @@ module JamRuby
|
|||
:recording_id => recorded_item.recording_id,
|
||||
:next => recorded_item.id
|
||||
})
|
||||
elsif recorded_item.item_type == 'backing_track'
|
||||
uploads << ({
|
||||
:type => "recorded_backing_track",
|
||||
:recording_id => recorded_item.recording_id,
|
||||
:client_track_id => recorded_item.client_track_id,
|
||||
:next => recorded_item.id
|
||||
})
|
||||
else
|
||||
|
||||
end
|
||||
|
|
@ -513,6 +578,11 @@ module JamRuby
|
|||
recorded_tracks.each do |recorded_track|
|
||||
return false unless recorded_track.fully_uploaded
|
||||
end
|
||||
|
||||
recorded_backing_tracks.each do |recorded_backing_track|
|
||||
return false unless recorded_backing_track.fully_uploaded
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -55,11 +55,64 @@ module JamRuby
|
|||
return query
|
||||
end
|
||||
|
||||
def self.diff_track(track_class, existing_tracks, new_tracks, &blk)
|
||||
result = []
|
||||
if new_tracks.length == 0
|
||||
existing_tracks.delete_all
|
||||
else
|
||||
|
||||
# we will prune from this as we find matching tracks
|
||||
to_delete = Set.new(existing_tracks)
|
||||
to_add = Array.new(new_tracks)
|
||||
|
||||
existing_tracks.each do |existing_track|
|
||||
new_tracks.each do |new_track|
|
||||
|
||||
if new_track[:id] == existing_track.id || new_track[:client_track_id] == existing_track.client_track_id
|
||||
to_delete.delete(existing_track)
|
||||
to_add.delete(new_track)
|
||||
|
||||
blk.call(existing_track, new_track)
|
||||
|
||||
result.push(existing_track)
|
||||
|
||||
if existing_track.save
|
||||
next
|
||||
else
|
||||
result = existing_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
to_add.each do |new_track|
|
||||
existing_track = track_class.new
|
||||
|
||||
blk.call(existing_track, new_track)
|
||||
|
||||
if existing_track.save
|
||||
result.push(existing_track)
|
||||
else
|
||||
result = existing_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
to_delete.each do |delete_me|
|
||||
delete_me.delete
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# this is a bit different from a normal track synchronization in that the client just sends up all tracks,
|
||||
# ... some may already exist
|
||||
def self.sync(clientId, tracks)
|
||||
result = []
|
||||
def self.sync(clientId, tracks, backing_tracks = [])
|
||||
result = {}
|
||||
|
||||
backing_tracks = [] unless backing_tracks
|
||||
|
||||
Track.transaction do
|
||||
connection = Connection.find_by_client_id!(clientId)
|
||||
|
|
@ -68,67 +121,28 @@ module JamRuby
|
|||
msh = MusicSessionUserHistory.find_by_client_id!(clientId)
|
||||
instruments = []
|
||||
|
||||
if tracks.length == 0
|
||||
connection.tracks.delete_all
|
||||
else
|
||||
connection_tracks = connection.tracks
|
||||
tracks.each do |track|
|
||||
instruments << track[:instrument_id]
|
||||
end
|
||||
|
||||
# we will prune from this as we find matching tracks
|
||||
to_delete = Set.new(connection_tracks)
|
||||
to_add = Array.new(tracks)
|
||||
result[:tracks] = diff_track(Track, connection.tracks, tracks) do |track_record, track_info|
|
||||
track_record.connection = connection
|
||||
track_record.client_track_id = track_info[:client_track_id]
|
||||
track_record.client_resource_id = track_info[:client_resource_id]
|
||||
track_record.instrument_id = track_info[:instrument_id]
|
||||
track_record.sound = track_info[:sound]
|
||||
end
|
||||
|
||||
tracks.each do |track|
|
||||
instruments << track[:instrument_id]
|
||||
end
|
||||
result[:backing_tracks] = diff_track(BackingTrack, connection.backing_tracks, backing_tracks) do |track_record, track_info|
|
||||
track_record.connection = connection
|
||||
track_record.client_track_id = track_info[:client_track_id]
|
||||
track_record.client_resource_id = track_info[:client_resource_id]
|
||||
track_record.filename = track_info[:filename]
|
||||
end
|
||||
|
||||
connection_tracks.each do |connection_track|
|
||||
tracks.each do |track|
|
||||
|
||||
if track[:id] == connection_track.id || track[:client_track_id] == connection_track.client_track_id
|
||||
to_delete.delete(connection_track)
|
||||
to_add.delete(track)
|
||||
# don't update connection_id or client_id; it's unknown what would happen if these changed mid-session
|
||||
connection_track.instrument_id = track[:instrument_id]
|
||||
connection_track.sound = track[:sound]
|
||||
connection_track.client_track_id = track[:client_track_id]
|
||||
connection_track.client_resource_id = track[:client_resource_id]
|
||||
|
||||
result.push(connection_track)
|
||||
|
||||
if connection_track.save
|
||||
next
|
||||
else
|
||||
result = connection_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
msh.instruments = instruments.join("|")
|
||||
if !msh.save
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
to_add.each do |track|
|
||||
connection_track = Track.new
|
||||
connection_track.connection = connection
|
||||
connection_track.instrument_id = track[:instrument_id]
|
||||
connection_track.sound = track[:sound]
|
||||
connection_track.client_track_id = track[:client_track_id]
|
||||
connection_track.client_resource_id = track[:client_resource_id]
|
||||
if connection_track.save
|
||||
result.push(connection_track)
|
||||
else
|
||||
result = connection_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
to_delete.each do |delete_me|
|
||||
delete_me.delete
|
||||
end
|
||||
msh.instruments = instruments.join("|")
|
||||
if !msh.save
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ module JamRuby
|
|||
# saved tracks
|
||||
has_many :recorded_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedTrack", :inverse_of => :user
|
||||
has_many :recorded_videos, :foreign_key => "user_id", :class_name => "JamRuby::RecordedVideo", :inverse_of => :user
|
||||
has_many :recorded_backing_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedBackingTrack", :inverse_of => :user
|
||||
has_many :quick_mixes, :foreign_key => "user_id", :class_name => "JamRuby::QuickMix", :inverse_of => :user
|
||||
|
||||
# invited users
|
||||
|
|
@ -349,7 +350,7 @@ module JamRuby
|
|||
|
||||
def age
|
||||
now = Time.now.utc.to_date
|
||||
self.birth_date.nil? ? "unspecified" : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0)
|
||||
self.birth_date.nil? ? "" : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0)
|
||||
end
|
||||
|
||||
def session_count
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ module JamRuby
|
|||
belongs_to :recorded_track
|
||||
belongs_to :mix
|
||||
belongs_to :quick_mix
|
||||
belongs_to :recorded_backing_track
|
||||
|
||||
def self.show(id, user_id)
|
||||
self.index({user_id: user_id, id: id, limit: 1, offset: 0})[:query].first
|
||||
|
|
@ -22,7 +23,7 @@ module JamRuby
|
|||
raise 'no user id specified' if user_id.blank?
|
||||
|
||||
query = UserSync
|
||||
.includes(recorded_track: [{recording: [:owner, {claimed_recordings: [:share_token]}, {recorded_tracks: [:user]}, {comments:[:user]}, :likes, :plays, :mixes]}, user: [], instrument:[]], mix: [], quick_mix:[])
|
||||
.includes(recorded_track: [{recording: [:owner, {claimed_recordings: [:share_token]}, {recorded_tracks: [:user]}, {comments:[:user]}, :likes, :plays, :mixes]}, user: [], instrument:[]], mix: [], quick_mix:[], recorded_backing_track:[])
|
||||
.joins("LEFT OUTER JOIN claimed_recordings ON claimed_recordings.user_id = user_syncs.user_id AND claimed_recordings.recording_id = user_syncs.recording_id")
|
||||
.where(user_id: user_id)
|
||||
.where(%Q{
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ module JamRuby
|
|||
options = account_hash(current_user, billing_info)
|
||||
account = nil
|
||||
begin
|
||||
#puts "Recurly.api_key: #{Recurly.api_key}"
|
||||
account = Recurly::Account.create(options)
|
||||
raise RecurlyClientError.new(account.errors) if account.errors.any?
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
puts "Error: #{x} : #{Kernel.caller}"
|
||||
raise RecurlyClientError, x.to_s
|
||||
else
|
||||
if account
|
||||
|
|
@ -68,6 +70,46 @@ module JamRuby
|
|||
account
|
||||
end
|
||||
|
||||
def refund_user_subscription(current_user, jam_track)
|
||||
jam_track_right=JamRuby::JamTrackRight.where("user_id=? AND jam_track_id=?", current_user.id, jam_track.id).first
|
||||
if jam_track_right
|
||||
refund_subscription(jam_track_right)
|
||||
else
|
||||
raise RecurlyClientError, "The user #{current_user} does not have a subscription to #{jam_track}"
|
||||
end
|
||||
end
|
||||
|
||||
def refund_subscription(jam_track_right)
|
||||
account = get_account(jam_track_right.user)
|
||||
if (account.present?)
|
||||
terminated = false
|
||||
begin
|
||||
jam_track = jam_track_right.jam_track
|
||||
account.subscriptions.find_each do |subscription|
|
||||
puts "subscription.plan.plan_code: #{subscription.plan.plan_code} / #{jam_track.plan_code} / #{subscription.plan.plan_code == jam_track.plan_code}"
|
||||
if(subscription.plan.plan_code == jam_track.plan_code)
|
||||
subscription.terminate(:full)
|
||||
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
|
||||
terminated = true
|
||||
end
|
||||
end
|
||||
|
||||
if terminated
|
||||
jam_track_right.destroy()
|
||||
else
|
||||
raise RecurlyClientError, "Subscription '#{jam_track.plan_code}' not found for this user; could not issue refund."
|
||||
end
|
||||
|
||||
rescue Recurly::Error, NoMethodError => x
|
||||
raise RecurlyClientError, x.to_s
|
||||
end
|
||||
|
||||
else
|
||||
raise RecurlyClientError, "Could not find account to refund order."
|
||||
end
|
||||
account
|
||||
end
|
||||
|
||||
def place_order(current_user, jam_track)
|
||||
account = get_account(current_user)
|
||||
if (account.present?)
|
||||
|
|
@ -232,6 +232,11 @@ FactoryGirl.define do
|
|||
sequence(:client_resource_id) { |n| "resource_id#{n}"}
|
||||
end
|
||||
|
||||
factory :backing_track, :class => JamRuby::BackingTrack do
|
||||
sequence(:client_track_id) { |n| "client_track_id#{n}"}
|
||||
filename 'foo.mp3'
|
||||
end
|
||||
|
||||
factory :video_source, :class => JamRuby::VideoSource do
|
||||
#client_video_source_id "test_source_id"
|
||||
sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"}
|
||||
|
|
@ -250,6 +255,20 @@ FactoryGirl.define do
|
|||
association :recording, factory: :recording
|
||||
end
|
||||
|
||||
factory :recorded_backing_track, :class => JamRuby::RecordedBackingTrack do
|
||||
sequence(:client_id) { |n| "client_id-#{n}"}
|
||||
sequence(:backing_track_id) { |n| "track_id-#{n}"}
|
||||
sequence(:client_track_id) { |n| "client_track_id-#{n}"}
|
||||
sequence(:filename) { |n| "filename-{#n}"}
|
||||
sequence(:url) { |n| "/recordings/blah/#{n}"}
|
||||
md5 'abc'
|
||||
length 1
|
||||
fully_uploaded true
|
||||
association :user, factory: :user
|
||||
association :recording, factory: :recording
|
||||
end
|
||||
|
||||
|
||||
factory :recorded_video, :class => JamRuby::RecordedVideo do
|
||||
sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"}
|
||||
fully_uploaded true
|
||||
|
|
@ -700,7 +719,6 @@ FactoryGirl.define do
|
|||
factory :jam_track, :class => JamRuby::JamTrack do
|
||||
sequence(:name) { |n| "jam-track-#{n}" }
|
||||
sequence(:description) { |n| "description-#{n}" }
|
||||
bpm 100.1
|
||||
time_signature '4/4'
|
||||
status 'Production'
|
||||
recording_type 'Cover'
|
||||
|
|
@ -716,6 +734,7 @@ FactoryGirl.define do
|
|||
licensor_royalty_amount 0.999
|
||||
pro_royalty_amount 0.999
|
||||
available true
|
||||
plan_code 'jamtrack-acdc-backinblack'
|
||||
|
||||
genre JamRuby::Genre.first
|
||||
association :licensor, factory: :jam_track_licensor
|
||||
|
|
|
|||
|
|
@ -745,6 +745,29 @@ describe ActiveMusicSession do
|
|||
@music_session.errors[:claimed_recording] == [ValidationMessages::JAM_TRACK_ALREADY_OPEN]
|
||||
|
||||
end
|
||||
|
||||
it "disallow a claimed recording to be started when backing track is open" do
|
||||
# open the backing track
|
||||
@backing_track = "foo.mp3"
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_false
|
||||
|
||||
# and try to open a recording for playback
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:claimed_recording] == [ValidationMessages::BACKING_TRACK_ALREADY_OPEN]
|
||||
end
|
||||
|
||||
it "disallow a claimed recording to be started when metronome is open" do
|
||||
# open the metronome
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
|
||||
# and try to open a recording for playback
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:claimed_recording] == [ValidationMessages::METRONOME_ALREADY_OPEN]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -830,5 +853,143 @@ describe ActiveMusicSession do
|
|||
music_sessions[0].connections[0].tracks.should have(1).items
|
||||
end
|
||||
end
|
||||
|
||||
describe "open_backing_track" do
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user1)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save!
|
||||
@connection.join_the_session(@music_session, true, nil, @user1, 10)
|
||||
@backing_track = "foo/bar.mp3"
|
||||
end
|
||||
|
||||
it "allow a backing track to be associated" do
|
||||
# simple success case; just open the backing track and observe the state of the session is correct
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.backing_track_path.should == @backing_track
|
||||
@music_session.backing_track_initiator.should == @user1
|
||||
end
|
||||
|
||||
it "allow a backing track to be closed" do
|
||||
# simple success case; close an opened backing track and observe the state of the session is correct
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.close_backing_track
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.backing_track_path.should be_nil
|
||||
@music_session.backing_track_initiator.should be_nil
|
||||
end
|
||||
|
||||
it "disallow a backing track to be opened when another is already opened" do
|
||||
# if a backing track is open, don't allow another to be opened
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:backing_track] == [ValidationMessages::BACKING_TRACK_ALREADY_OPEN]
|
||||
end
|
||||
|
||||
it "disallow a backing track to be opened when recording is ongoing" do
|
||||
@recording = Recording.start(@music_session, @user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:backing_track] == [ValidationMessages::RECORDING_ALREADY_IN_PROGRESS]
|
||||
end
|
||||
|
||||
it "disallow a backing track to be opened when recording is playing back" do
|
||||
# create a recording, and open it for play back
|
||||
@recording = Recording.start(@music_session, @user1)
|
||||
@recording.errors.any?.should be_false
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@claimed_recording = @recording.claim(@user1, "name", "description", Genre.first, true)
|
||||
@claimed_recording.errors.any?.should be_false
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
|
||||
# while it's open, try to open a jam track
|
||||
@music_session.open_backing_track(@user1, @backing_track)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:backing_track] == [ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "open_metronome" do
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user1)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save!
|
||||
@connection.join_the_session(@music_session, true, nil, @user1, 10)
|
||||
end
|
||||
|
||||
it "allow a metronome to be activated" do
|
||||
# simple success case; just open the metronome and observe the state of the session is correct
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.metronome_active.should == true
|
||||
@music_session.metronome_initiator.should == @user1
|
||||
end
|
||||
|
||||
it "allow a metronome to be closed" do
|
||||
# simple success case; close an opened metronome and observe the state of the session is correct
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.close_metronome
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.metronome_active.should be_false
|
||||
@music_session.metronome_initiator.should be_nil
|
||||
end
|
||||
|
||||
it "disallow a metronome to be opened when another is already opened" do
|
||||
# if a metronome is open, don't allow another to be opened
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:metronome] == [ValidationMessages::METRONOME_ALREADY_OPEN]
|
||||
end
|
||||
|
||||
it "disallow a metronome to be opened when recording is ongoing" do
|
||||
@recording = Recording.start(@music_session, @user1)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:metronome] == [ValidationMessages::RECORDING_ALREADY_IN_PROGRESS]
|
||||
end
|
||||
|
||||
it "disallow a metronome to be opened when recording is playing back" do
|
||||
# create a recording, and open it for play back
|
||||
@recording = Recording.start(@music_session, @user1)
|
||||
@recording.errors.any?.should be_false
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@claimed_recording = @recording.claim(@user1, "name", "description", Genre.first, true)
|
||||
@claimed_recording.errors.any?.should be_false
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
|
||||
# while it's open, try to open a jam track
|
||||
@music_session.open_metronome(@user1)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:metronome] == [ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -55,25 +55,6 @@ describe JamTrack do
|
|||
end
|
||||
|
||||
describe "validations" do
|
||||
describe "bpm" do
|
||||
it "1" do
|
||||
FactoryGirl.build(:jam_track, bpm: 1).valid?.should be_true
|
||||
end
|
||||
|
||||
it "100" do
|
||||
FactoryGirl.build(:jam_track, bpm: 100).valid?.should be_true
|
||||
end
|
||||
|
||||
it "100.1" do
|
||||
FactoryGirl.build(:jam_track, bpm: 100.1).valid?.should be_true
|
||||
end
|
||||
|
||||
it "100.12" do
|
||||
jam_track = FactoryGirl.build(:jam_track, bpm: 100.12)
|
||||
jam_track.valid?.should be_false
|
||||
jam_track.errors[:bpm].should == ['is invalid']
|
||||
end
|
||||
end
|
||||
|
||||
describe "price" do
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
require 'spec_helper'
|
||||
require 'rest-client'
|
||||
|
||||
describe RecordedBackingTrack do
|
||||
|
||||
include UsesTempFiles
|
||||
|
||||
before do
|
||||
@user = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@backing_track = FactoryGirl.create(:backing_track, :connection => @connection)
|
||||
@recording = FactoryGirl.create(:recording, :music_session => @music_session, :owner => @user)
|
||||
end
|
||||
|
||||
it "should copy from a regular track properly" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
|
||||
@recorded_backing_track.user.id.should == @backing_track.connection.user.id
|
||||
@recorded_backing_track.filename.should == @backing_track.filename
|
||||
@recorded_backing_track.next_part_to_upload.should == 0
|
||||
@recorded_backing_track.fully_uploaded.should == false
|
||||
@recorded_backing_track.client_id = @connection.client_id
|
||||
@recorded_backing_track.backing_track_id = @backing_track.id
|
||||
end
|
||||
|
||||
it "should update the next part to upload properly" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.upload_part_complete(1, 1000)
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:length][0].should == "is too short (minimum is 1 characters)"
|
||||
@recorded_backing_track.errors[:md5][0].should == "can't be blank"
|
||||
end
|
||||
|
||||
it "properly finds a recorded track given its upload filename" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.save.should be_true
|
||||
RecordedBackingTrack.find_by_recording_id_and_backing_track_id(@recorded_backing_track.recording_id, @recorded_backing_track.backing_track_id).should == @recorded_backing_track
|
||||
end
|
||||
|
||||
it "gets a url for the track" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
@recorded_backing_track[:url].should == "recordings/#{@recorded_backing_track.created_at.strftime('%m-%d-%Y')}/#{@recording.id}/backing-track-#{@backing_track.client_track_id}.ogg"
|
||||
end
|
||||
|
||||
it "signs url" do
|
||||
stub_const("APP_CONFIG", app_config)
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.sign_url.should_not be_nil
|
||||
end
|
||||
|
||||
it "can not be downloaded if no claimed recording" do
|
||||
user2 = FactoryGirl.create(:user)
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.can_download?(user2).should be_false
|
||||
@recorded_backing_track.can_download?(@user).should be_false
|
||||
end
|
||||
|
||||
it "can be downloaded if there is a claimed recording" do
|
||||
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recording.claim(@user, "my recording", "my description", Genre.first, true).errors.any?.should be_false
|
||||
@recorded_backing_track.can_download?(@user).should be_true
|
||||
end
|
||||
|
||||
|
||||
describe "aws-based operations", :aws => true do
|
||||
|
||||
def put_file_to_aws(signed_data, contents)
|
||||
|
||||
begin
|
||||
RestClient.put( signed_data[:url],
|
||||
contents,
|
||||
{
|
||||
:'Content-Type' => 'audio/ogg',
|
||||
:Date => signed_data[:datetime],
|
||||
:'Content-MD5' => signed_data[:md5],
|
||||
:Authorization => signed_data[:authorization]
|
||||
})
|
||||
rescue => e
|
||||
puts e.response
|
||||
raise e
|
||||
end
|
||||
|
||||
end
|
||||
# create a test file
|
||||
upload_file='some_file.ogg'
|
||||
in_directory_with_file(upload_file)
|
||||
|
||||
upload_file_contents="ogg binary stuff in here"
|
||||
md5 = Base64.encode64(Digest::MD5.digest(upload_file_contents)).chomp
|
||||
test_config = app_config
|
||||
s3_manager = S3Manager.new(test_config.aws_bucket, test_config.aws_access_key_id, test_config.aws_secret_access_key)
|
||||
|
||||
|
||||
before do
|
||||
stub_const("APP_CONFIG", app_config)
|
||||
# this block of code will fully upload a sample file to s3
|
||||
content_for_file(upload_file_contents)
|
||||
s3_manager.delete_folder('recordings') # keep the bucket clean to save cost, and make it easier if post-mortuem debugging
|
||||
|
||||
|
||||
end
|
||||
|
||||
it "cant mark a part complete without having started it" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.upload_start(1000, "abc")
|
||||
@recorded_backing_track.upload_part_complete(1, 1000)
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:next_part_to_upload][0].should == ValidationMessages::PART_NOT_STARTED
|
||||
end
|
||||
|
||||
it "no parts" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.upload_start(1000, "abc")
|
||||
@recorded_backing_track.upload_next_part(1000, "abc")
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
@recorded_backing_track.upload_part_complete(1, 1000)
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:next_part_to_upload][0].should == ValidationMessages::PART_NOT_FOUND_IN_AWS
|
||||
end
|
||||
|
||||
it "enough part failures reset the upload" do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.upload_start(File.size(upload_file), md5)
|
||||
@recorded_backing_track.upload_next_part(File.size(upload_file), md5)
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
APP_CONFIG.max_track_part_upload_failures.times do |i|
|
||||
@recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file))
|
||||
@recorded_backing_track.errors[:next_part_to_upload] == [ValidationMessages::PART_NOT_FOUND_IN_AWS]
|
||||
part_failure_rollover = i == APP_CONFIG.max_track_part_upload_failures - 1
|
||||
expected_is_part_uploading = !part_failure_rollover
|
||||
expected_part_failures = part_failure_rollover ? 0 : i + 1
|
||||
@recorded_backing_track.reload
|
||||
@recorded_backing_track.is_part_uploading.should == expected_is_part_uploading
|
||||
@recorded_backing_track.part_failures.should == expected_part_failures
|
||||
end
|
||||
|
||||
@recorded_backing_track.reload
|
||||
@recorded_backing_track.upload_failures.should == 1
|
||||
@recorded_backing_track.file_offset.should == 0
|
||||
@recorded_backing_track.next_part_to_upload.should == 0
|
||||
@recorded_backing_track.upload_id.should be_nil
|
||||
@recorded_backing_track.md5.should be_nil
|
||||
@recorded_backing_track.length.should == 0
|
||||
end
|
||||
|
||||
it "enough upload failures fails the upload forever" do
|
||||
APP_CONFIG.stub(:max_track_upload_failures).and_return(1)
|
||||
APP_CONFIG.stub(:max_track_part_upload_failures).and_return(2)
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
APP_CONFIG.max_track_upload_failures.times do |j|
|
||||
@recorded_backing_track.upload_start(File.size(upload_file), md5)
|
||||
@recorded_backing_track.upload_next_part(File.size(upload_file), md5)
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
APP_CONFIG.max_track_part_upload_failures.times do |i|
|
||||
@recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file))
|
||||
@recorded_backing_track.errors[:next_part_to_upload] == [ValidationMessages::PART_NOT_FOUND_IN_AWS]
|
||||
part_failure_rollover = i == APP_CONFIG.max_track_part_upload_failures - 1
|
||||
expected_is_part_uploading = part_failure_rollover ? false : true
|
||||
expected_part_failures = part_failure_rollover ? 0 : i + 1
|
||||
@recorded_backing_track.reload
|
||||
@recorded_backing_track.is_part_uploading.should == expected_is_part_uploading
|
||||
@recorded_backing_track.part_failures.should == expected_part_failures
|
||||
end
|
||||
@recorded_backing_track.upload_failures.should == j + 1
|
||||
end
|
||||
|
||||
@recorded_backing_track.reload
|
||||
@recorded_backing_track.upload_failures.should == APP_CONFIG.max_track_upload_failures
|
||||
@recorded_backing_track.file_offset.should == 0
|
||||
@recorded_backing_track.next_part_to_upload.should == 0
|
||||
@recorded_backing_track.upload_id.should be_nil
|
||||
@recorded_backing_track.md5.should be_nil
|
||||
@recorded_backing_track.length.should == 0
|
||||
|
||||
# try to poke it and get the right kind of error back
|
||||
@recorded_backing_track.upload_next_part(File.size(upload_file), md5)
|
||||
@recorded_backing_track.errors[:upload_failures] = [ValidationMessages::UPLOAD_FAILURES_EXCEEDED]
|
||||
end
|
||||
|
||||
describe "correctly uploaded a file" do
|
||||
|
||||
before do
|
||||
@recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording)
|
||||
@recorded_backing_track.upload_start(File.size(upload_file), md5)
|
||||
@recorded_backing_track.upload_next_part(File.size(upload_file), md5)
|
||||
signed_data = @recorded_backing_track.upload_sign(md5)
|
||||
@response = put_file_to_aws(signed_data, upload_file_contents)
|
||||
@recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file))
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
@recorded_backing_track.upload_complete
|
||||
@recorded_backing_track.errors.any?.should be_false
|
||||
@recorded_backing_track.marking_complete = false
|
||||
end
|
||||
|
||||
it "can download an updated file" do
|
||||
@response = RestClient.get @recorded_backing_track.sign_url
|
||||
@response.body.should == upload_file_contents
|
||||
end
|
||||
|
||||
it "can't mark completely uploaded twice" do
|
||||
@recorded_backing_track.upload_complete
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:fully_uploaded][0].should == "already set"
|
||||
@recorded_backing_track.part_failures.should == 0
|
||||
end
|
||||
|
||||
it "can't ask for a next part if fully uploaded" do
|
||||
@recorded_backing_track.upload_next_part(File.size(upload_file), md5)
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:fully_uploaded][0].should == "already set"
|
||||
@recorded_backing_track.part_failures.should == 0
|
||||
end
|
||||
|
||||
it "can't ask for mark part complete if fully uploaded" do
|
||||
@recorded_backing_track.upload_part_complete(1, 1000)
|
||||
@recorded_backing_track.errors.any?.should be_true
|
||||
@recorded_backing_track.errors[:fully_uploaded][0].should == "already set"
|
||||
@recorded_backing_track.part_failures.should == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -211,6 +211,20 @@ describe Recording do
|
|||
user1_recorded_tracks[0].discard = true
|
||||
user1_recorded_tracks[0].save!
|
||||
end
|
||||
|
||||
it "should allow finding of backing tracks" do
|
||||
user2 = FactoryGirl.create(:user)
|
||||
connection2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session)
|
||||
track2 = FactoryGirl.create(:track, :connection => connection2, :instrument => @instrument)
|
||||
backing_track = FactoryGirl.create(:backing_track, :connection => connection2)
|
||||
|
||||
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.recorded_backing_tracks_for_user(@user).length.should eq(0)
|
||||
user2_recorded_tracks = @recording.recorded_backing_tracks_for_user(user2)
|
||||
user2_recorded_tracks.length.should == 1
|
||||
user2_recorded_tracks[0].should == user2.recorded_backing_tracks[0]
|
||||
end
|
||||
|
||||
it "should set up the recording properly when recording is started with 1 user in the session" do
|
||||
@music_session.is_recording?.should be_false
|
||||
|
|
@ -547,6 +561,8 @@ describe Recording do
|
|||
@genre = FactoryGirl.create(:genre)
|
||||
@recording.claim(@user, "Recording", "Recording Description", @genre, true)
|
||||
|
||||
@backing_track = FactoryGirl.create(:backing_track, :connection => @connection)
|
||||
|
||||
# We should have 2 items; a track and a video:
|
||||
uploads = Recording.list_uploads(@user)
|
||||
uploads["uploads"].should have(3).items
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ describe Track do
|
|||
let (:connection) { FactoryGirl.create(:connection, :user => user, :music_session => music_session) }
|
||||
let (:track) { FactoryGirl.create(:track, :connection => connection)}
|
||||
let (:track2) { FactoryGirl.create(:track, :connection => connection)}
|
||||
let (:backing_track) { FactoryGirl.create(:backing_track, :connection => connection)}
|
||||
let (:msuh) {FactoryGirl.create(:music_session_user_history, :history => music_session.music_session, :user => user, :client_id => connection.client_id) }
|
||||
let (:track_hash) { {:client_track_id => 'client_guid', :sound => 'stereo', :instrument_id => 'drums'} }
|
||||
let (:backing_track_hash) { {:client_track_id => 'client_guid', :filename => "blah.wav"} }
|
||||
|
||||
before(:each) do
|
||||
msuh.touch
|
||||
|
|
@ -16,7 +18,8 @@ describe Track do
|
|||
|
||||
describe "sync" do
|
||||
it "create one track" do
|
||||
tracks = Track.sync(connection.client_id, [track_hash])
|
||||
result = Track.sync(connection.client_id, [track_hash])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
track = tracks[0]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
|
|
@ -25,7 +28,8 @@ describe Track do
|
|||
end
|
||||
|
||||
it "create two tracks" do
|
||||
tracks = Track.sync(connection.client_id, [track_hash, track_hash])
|
||||
result = Track.sync(connection.client_id, [track_hash, track_hash])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 2
|
||||
track = tracks[0]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
|
|
@ -40,7 +44,8 @@ describe Track do
|
|||
it "delete only track" do
|
||||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
tracks = Track.sync(connection.client_id, [])
|
||||
result = Track.sync(connection.client_id, [])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 0
|
||||
end
|
||||
|
||||
|
|
@ -49,7 +54,8 @@ describe Track do
|
|||
track.id.should_not be_nil
|
||||
track2.id.should_not be_nil
|
||||
connection.tracks.length.should == 2
|
||||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
|
|
@ -62,7 +68,8 @@ describe Track do
|
|||
track.id.should_not be_nil
|
||||
track2.id.should_not be_nil
|
||||
connection.tracks.length.should == 2
|
||||
tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
result = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
|
|
@ -75,7 +82,8 @@ describe Track do
|
|||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
set_updated_at(track, 1.days.ago)
|
||||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
|
|
@ -87,7 +95,8 @@ describe Track do
|
|||
it "updates a single track using .client_track_id to correlate" do
|
||||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
result = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
|
|
@ -99,11 +108,69 @@ describe Track do
|
|||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
set_updated_at(track, 1.days.ago)
|
||||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}])
|
||||
result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
expect(found.id).to eq track.id
|
||||
expect(found.updated_at.to_i).to eq track.updated_at.to_i
|
||||
end
|
||||
|
||||
describe "backing tracks" do
|
||||
it "create one track and one backing track" do
|
||||
result = Track.sync(connection.client_id, [track_hash], [backing_track_hash])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
track = tracks[0]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
track.sound = track_hash[:sound]
|
||||
track.instrument.should == Instrument.find('drums')
|
||||
|
||||
backing_tracks = result[:backing_tracks]
|
||||
backing_tracks.length.should == 1
|
||||
track = backing_tracks[0]
|
||||
track.client_track_id.should == backing_track_hash[:client_track_id]
|
||||
end
|
||||
|
||||
it "delete only backing_track" do
|
||||
track.id.should_not be_nil
|
||||
backing_track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
connection.backing_tracks.length.should == 1
|
||||
result = Track.sync(connection.client_id,
|
||||
[{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}],
|
||||
[])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
expect(found.id).to eq track.id
|
||||
expect(found.updated_at.to_i).to eq track.updated_at.to_i
|
||||
|
||||
backing_tracks = result[:backing_tracks]
|
||||
backing_tracks.length.should == 0
|
||||
end
|
||||
|
||||
it "does not touch updated_at when nothing changes" do
|
||||
track.id.should_not be_nil
|
||||
backing_track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
set_updated_at(track, 1.days.ago)
|
||||
set_updated_at(backing_track, 1.days.ago)
|
||||
result = Track.sync(connection.client_id,
|
||||
[{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}],
|
||||
[{:id => backing_track.id, :client_track_id => backing_track.client_track_id, :filename => backing_track.filename, client_resource_id: backing_track.client_resource_id}])
|
||||
tracks = result[:tracks]
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
expect(found.id).to eq track.id
|
||||
expect(found.updated_at.to_i).to eq track.updated_at.to_i
|
||||
|
||||
backing_tracks = result[:backing_tracks]
|
||||
backing_tracks.length.should == 1
|
||||
found = backing_tracks[0]
|
||||
expect(found.id).to eq backing_track.id
|
||||
expect(found.updated_at.to_i).to eq backing_track.updated_at.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -676,7 +676,7 @@ describe User do
|
|||
user.age.should == 9
|
||||
|
||||
user.birth_date = nil
|
||||
user.age.should == "unspecified"
|
||||
user.age.should == ""
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,49 @@ describe UserSync do
|
|||
data[:next].should be_nil
|
||||
end
|
||||
|
||||
describe "backing_tracks" do
|
||||
|
||||
let!(:recording1) {
|
||||
recording = FactoryGirl.create(:recording, owner: user1, band: nil, duration:1)
|
||||
recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner, fully_uploaded:false)
|
||||
recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: user2, fully_uploaded:false)
|
||||
recording.recorded_backing_tracks << FactoryGirl.create(:recorded_backing_track, recording: recording, user: recording.owner, fully_uploaded:false)
|
||||
recording.save!
|
||||
recording.reload
|
||||
recording
|
||||
}
|
||||
|
||||
let(:sorted_tracks) {
|
||||
Array.new(recording1.recorded_tracks).sort! {|a, b|
|
||||
if a.created_at == b.created_at
|
||||
a.id <=> b.id
|
||||
else
|
||||
a.created_at <=> b.created_at
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
# backing tracks should only list download, or upload, for the person who opened it, for legal reasons
|
||||
it "lists backing track for opener" do
|
||||
data = UserSync.index({user_id: user1.id})
|
||||
data[:next].should be_nil
|
||||
user_syncs = data[:query]
|
||||
user_syncs.count.should eq(3)
|
||||
user_syncs[0].recorded_track.should == sorted_tracks[0]
|
||||
user_syncs[1].recorded_track.should == sorted_tracks[1]
|
||||
user_syncs[2].recorded_backing_track.should == recording1.recorded_backing_tracks[0]
|
||||
end
|
||||
|
||||
it "does not list backing track for non-opener" do
|
||||
data = UserSync.index({user_id: user2.id})
|
||||
data[:next].should be_nil
|
||||
user_syncs = data[:query]
|
||||
user_syncs.count.should eq(2)
|
||||
user_syncs[0].recorded_track.should == sorted_tracks[0]
|
||||
user_syncs[1].recorded_track.should == sorted_tracks[1]
|
||||
end
|
||||
end
|
||||
|
||||
it "one mix and quick mix" do
|
||||
mix = FactoryGirl.create(:mix)
|
||||
mix.recording.duration = 1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
require 'spec_helper'
|
||||
require "recurly_client"
|
||||
require "jam_ruby/recurly_client"
|
||||
describe RecurlyClient do
|
||||
let(:jamtrack) { FactoryGirl.create(:jam_track) }
|
||||
#let(:client) { RecurlyClient.new }
|
||||
|
|
@ -98,6 +98,24 @@ describe RecurlyClient do
|
|||
@user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id)
|
||||
end
|
||||
|
||||
it "can refund subscription" do
|
||||
@client.find_or_create_account(@user, @billing_info)
|
||||
|
||||
# Place order:
|
||||
expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
|
||||
active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'}
|
||||
@jamtrack.reload
|
||||
@jamtrack.jam_track_rights.should have(1).items
|
||||
|
||||
# Refund:
|
||||
expect{@client.refund_user_subscription(@user, @jamtrack)}.not_to raise_error()
|
||||
active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'}
|
||||
active_subs.should have(0).items
|
||||
|
||||
@jamtrack.reload
|
||||
@jamtrack.jam_track_rights.should have(0).items
|
||||
end
|
||||
|
||||
it "detects error on double order" do
|
||||
@client.find_or_create_account(@user, @billing_info)
|
||||
expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
|
||||
|
|
@ -46,6 +46,7 @@ ActiveRecord::Base.add_observer InvitedUserObserver.instance
|
|||
ActiveRecord::Base.add_observer UserObserver.instance
|
||||
ActiveRecord::Base.add_observer FeedbackObserver.instance
|
||||
ActiveRecord::Base.add_observer RecordedTrackObserver.instance
|
||||
ActiveRecord::Base.add_observer RecordedBackingTrackObserver.instance
|
||||
ActiveRecord::Base.add_observer QuickMixObserver.instance
|
||||
|
||||
#RecordedTrack.observers.disable :all # only a few tests want this observer active
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ gem 'netaddr'
|
|||
gem 'quiet_assets', :group => :development
|
||||
gem 'bugsnag'
|
||||
gem 'multi_json', '1.9.0'
|
||||
gem 'rest_client'
|
||||
gem 'rest-client'
|
||||
gem 'iso-639'
|
||||
gem 'language_list'
|
||||
gem 'rubyzip'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
TODO:
|
||||
====
|
||||
|
||||
Jasmine Javascript Unit Tests
|
||||
=============================
|
||||
|
||||
|
|
@ -11,5 +8,3 @@ $ bundle
|
|||
$ rake jasmine
|
||||
|
||||
Open browser to localhost:8888
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
|
|
@ -7,6 +7,7 @@
|
|||
var rest = context.JK.Rest();
|
||||
var showing = false;
|
||||
var perPage = 10;
|
||||
var openingRecording = false;
|
||||
|
||||
function tbody() {
|
||||
return $('#local-recordings-dialog table.local-recordings tbody');
|
||||
|
|
@ -22,6 +23,7 @@
|
|||
|
||||
|
||||
function beforeShow() {
|
||||
openingRecording = false;
|
||||
emptyList();
|
||||
resetPagination();
|
||||
showing = true;
|
||||
|
|
@ -89,6 +91,12 @@
|
|||
function registerStaticEvents() {
|
||||
$('#local-recordings-dialog table.local-recordings tbody').on('click', 'tr', function(e) {
|
||||
|
||||
if(openingRecording) {
|
||||
// prevent double-click spam
|
||||
logger.debug("localRecordingDialog: ignoring duplicate open attempt")
|
||||
return false;
|
||||
}
|
||||
|
||||
var localState = $(this).attr('data-local-state');
|
||||
|
||||
if(localState == 'MISSING') {
|
||||
|
|
@ -109,9 +117,15 @@
|
|||
{
|
||||
var claimedRecording = $(this).data('server-model');
|
||||
|
||||
openingRecording = true;
|
||||
|
||||
// tell the server we are about to start a recording
|
||||
rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id})
|
||||
.done(function(response) {
|
||||
|
||||
// update session info
|
||||
context.JK.CurrentSessionModel.updateSession(response);
|
||||
|
||||
var recordingId = $(this).attr('data-recording-id');
|
||||
var openRecordingResult = context.jamClient.OpenRecording(claimedRecording.recording);
|
||||
|
||||
|
|
@ -142,6 +156,9 @@
|
|||
app.notifyServerError(jqXHR, "Unable to Open Recording For Playback");
|
||||
|
||||
})
|
||||
.always(function() {
|
||||
openingRecording = false;
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
context.JK = context.JK || {};
|
||||
context.JK.OpenBackingTrackDialog = function(app) {
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var showing = false;
|
||||
var perPage = 10;
|
||||
var $dialog = null;
|
||||
var $tbody = null;
|
||||
var $paginatorHolder = null;
|
||||
var $templateOpenBackingTrackRow = null;
|
||||
var $downloadedTrackHelp = null;
|
||||
var $whatAreBackingTracks = null;
|
||||
var $displayAudioFileFolder = null;
|
||||
|
||||
|
||||
function emptyList() {
|
||||
$tbody.empty();
|
||||
}
|
||||
|
||||
function resetPagination() {
|
||||
$dialog.find('.paginator').remove();
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
emptyList();
|
||||
resetPagination();
|
||||
showing = true;
|
||||
getBackingTracks();
|
||||
$dialog.data('result', null);
|
||||
// .done(function(data, textStatus, jqXHR) {
|
||||
// // initialize pagination
|
||||
// var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected)
|
||||
// $paginatorHolder.append($paginator);
|
||||
// });
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
showing = false;
|
||||
}
|
||||
|
||||
|
||||
function onPageSelected(targetPage) {
|
||||
return getBackingTracks(targetPage);
|
||||
}
|
||||
|
||||
function getBackingTracks(page) {
|
||||
|
||||
var result = context.jamClient.getBackingTrackList();
|
||||
console.log("result", result)
|
||||
var backingTracks = result.backing_tracks;
|
||||
|
||||
if (!backingTracks || backingTracks.length == 0) {
|
||||
$tbody.append("<tr><td colspan='100%'>No Tracks found</td></tr>");
|
||||
} else {
|
||||
$.each(backingTracks, function(index, backingTrack) {
|
||||
var extension = backingTrack.name
|
||||
var options = {
|
||||
backingTrackState: null,
|
||||
name: backingTrack.name,
|
||||
type: getExtension(backingTrack.name),
|
||||
length: displaySize(backingTrack.size)
|
||||
}
|
||||
var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' }));
|
||||
$tr.data('server-model', backingTrack);
|
||||
$tbody.append($tr);
|
||||
});
|
||||
}//end
|
||||
}
|
||||
|
||||
// from http://stackoverflow.com/questions/190852/how-can-i-get-file-extensions-with-javascript
|
||||
function getExtension(filename) {
|
||||
return filename.substr((~-filename.lastIndexOf(".") >>> 0) + 2)
|
||||
}
|
||||
|
||||
// from seth:
|
||||
function displaySize(length) {
|
||||
var size = (length==null || typeof(length)=='undefined') ? 0 : Number(length)
|
||||
return (Math.round(size * 10 / (1024 * 1024) ) / 10).toString() + "M"
|
||||
}
|
||||
|
||||
function registerStaticEvents() {
|
||||
$tbody.on('click', 'tr', function(e) {
|
||||
var backingTrack = $(this).data('server-model');
|
||||
|
||||
// tell the server we are about to open a backing track:
|
||||
rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name})
|
||||
.done(function(response) {
|
||||
var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name, false);
|
||||
console.log("BackingTrackPlay response: %o", result);
|
||||
|
||||
// TODO: Possibly actually check the result. Investigate
|
||||
// what real client returns:
|
||||
// // if(result) {
|
||||
// let callers see which backing track was chosen
|
||||
$dialog.data('result', backingTrack);
|
||||
app.layout.closeDialog('open-backing-track-dialog');
|
||||
// }
|
||||
// else {
|
||||
// logger.error("unable to open backing track")
|
||||
// }
|
||||
context.JK.CurrentSessionModel.refreshCurrentSession(true);
|
||||
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
app.notifyServerError(jqXHR, "Unable to Open BackingTrack For Playback");
|
||||
})
|
||||
|
||||
return false;
|
||||
})
|
||||
|
||||
context.JK.helpBubble($whatAreBackingTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog})
|
||||
$whatAreBackingTracks.on('click', false) // no help yet
|
||||
|
||||
$displayAudioFileFolder.on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
context.jamClient.OpenBackingTracksDirectory();
|
||||
})
|
||||
}
|
||||
|
||||
function initialize(){
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterHide': afterHide
|
||||
};
|
||||
|
||||
app.bindDialog('open-backing-track-dialog', dialogBindings);
|
||||
|
||||
$dialog = $('#open-backing-track-dialog');
|
||||
$tbody = $dialog.find('table.open-backing-tracks tbody');
|
||||
$paginatorHolder = $dialog.find('.paginator-holder');
|
||||
$templateOpenBackingTrackRow = $('#template-backing-track-row')
|
||||
$whatAreBackingTracks = $dialog.find('.what-are-backingtracks')
|
||||
$displayAudioFileFolder = $dialog.find('.display-backingtracks-folder')
|
||||
|
||||
registerStaticEvents();
|
||||
};
|
||||
|
||||
|
||||
this.initialize = initialize;
|
||||
this.isShowing = function isShowing() { return showing; }
|
||||
}
|
||||
|
||||
return this;
|
||||
})(window,jQuery);
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
|
||||
.done(function(response) {
|
||||
context.jamClient.JamTrackStopPlay();
|
||||
var result = context.jamClient.JamTrackPlay('t');
|
||||
var result = context.jamClient.JamTrackPlay(jamTrack.id);
|
||||
|
||||
logger.debug("JamTrackPlay response: %o", result);
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
else {
|
||||
|
||||
// load recording
|
||||
var openRecordingResult = context.jamClient.OpenRecording(recording);
|
||||
var openRecordingResult = context.jamClient.PreviewRecording(recording);
|
||||
|
||||
logger.debug("OpenRecording response: %o", openRecordingResult);
|
||||
|
||||
|
|
@ -78,6 +78,23 @@
|
|||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
}
|
||||
else {
|
||||
// hunt for missing backing tracks; if so, mark them as silent
|
||||
context._.each(openRecordingResult.backing_tracks, function(backingTrack) {
|
||||
if(backingTrack.local_state == "MISSING") {
|
||||
// mark this as deleted
|
||||
logger.debug("marking recorded track as deleted")
|
||||
rest.markRecordedBackingTrackSilent({recording_id: openRecordingResult.recording_id, backing_track_id: backingTrack.client_track_id})
|
||||
.fail(function() {
|
||||
app.notify({
|
||||
"title": "Unable to Mark Backing Track",
|
||||
"text": "A backing track was never played, but we could not tell the server to remove it from the recording.",
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
playbackControls.startMonitor();
|
||||
}
|
||||
|
|
@ -88,7 +105,7 @@
|
|||
function afterHide() {
|
||||
recording = null;
|
||||
playbackControls.stopMonitor();
|
||||
context.jamClient.CloseRecording();
|
||||
context.jamClient.ClosePreviewRecording();
|
||||
}
|
||||
|
||||
function discardRecording(e) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//= require backend_alerts
|
||||
//= require stun
|
||||
//= require influxdb-latest
|
||||
//= require jam_track_utils
|
||||
|
||||
(function (context, $) {
|
||||
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
var ALERT_NAMES = context.JK.ALERT_NAMES;
|
||||
var logger = context.JK.logger;
|
||||
var stun = null;
|
||||
var rest = context.JK.Rest();
|
||||
|
||||
$(document).on('JAMKAZAM_CONSTRUCTED', function(e, data) {
|
||||
|
||||
|
|
@ -51,6 +53,8 @@
|
|||
operationalEvents(app);
|
||||
|
||||
handleGettingStarted(app);
|
||||
|
||||
initShoppingCart(app);
|
||||
});
|
||||
|
||||
function watchPreferencesEvent(app) {
|
||||
|
|
@ -207,4 +211,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
function initShoppingCart(app) {
|
||||
|
||||
var user = app.user()
|
||||
if(user) {
|
||||
user.done(function(userProfile) {
|
||||
context.JK.JamTrackUtils.checkShoppingCart();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
if($fader.data('showHelpAboutMediaMixers')) {
|
||||
if(window.JK.CurrentSessionModel) {
|
||||
if(!window.JK.CurrentSessionModel.hasShownAudioMediaMixerHelp()) {
|
||||
window.JK.prodBubble($fader, 'volume-media-mixers', {}, {positions:['top'], offsetParent: $fader.closest('.screen')})
|
||||
window.JK.CurrentSessionModel.markShownAudioMediaMixerHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draggingOrientation = $fader.attr('orientation');
|
||||
var offset = $fader.offset();
|
||||
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
|
||||
|
|
@ -137,6 +146,16 @@
|
|||
}
|
||||
|
||||
function onFaderDragStop(e, ui) {
|
||||
|
||||
if($draggingFader.data('showHelpAboutMediaMixers')) {
|
||||
if(window.JK.CurrentSessionModel) {
|
||||
if(!window.JK.CurrentSessionModel.hasShownAudioMediaMixerHelp()) {
|
||||
window.JK.prodBubble($draggingFader, 'volume-media-mixers', {}, {positions:['bottom'], offsetParent: $draggingFader.closest('.screen')})
|
||||
window.JK.CurrentSessionModel.markShownAudioMediaMixerHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var faderPct = faderValue($draggingFader, e, ui.position);
|
||||
|
||||
// protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows
|
||||
|
|
@ -179,7 +198,10 @@
|
|||
|
||||
selector.html(g._.template(templateSource, options));
|
||||
|
||||
selector.find('div[control="fader"]').data('media-controls-disabled', selector.data('media-controls-disabled')).data('media-track-opener', selector.data('media-track-opener'))
|
||||
selector.find('div[control="fader"]')
|
||||
.data('media-controls-disabled', selector.data('media-controls-disabled'))
|
||||
.data('media-track-opener', selector.data('media-track-opener'))
|
||||
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
|
||||
|
||||
selector.find('div[control="fader-handle"]').draggable({
|
||||
drag: onFaderDrag,
|
||||
|
|
@ -187,7 +209,9 @@
|
|||
stop: onFaderDragStop,
|
||||
containment: "parent",
|
||||
axis: options.faderType === 'horizontal' ? 'x' : 'y'
|
||||
}).data('media-controls-disabled', selector.data('media-controls-disabled')).data('media-track-opener', selector.data('media-track-opener'))
|
||||
}).data('media-controls-disabled', selector.data('media-controls-disabled'))
|
||||
.data('media-track-opener', selector.data('media-track-opener'))
|
||||
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
|
||||
|
||||
// Embed any custom styles, applied to the .fader below selector
|
||||
if ("style" in options) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@
|
|||
var frameSize = 2.5;
|
||||
var fakeJamClientRecordings = null;
|
||||
var p2pCallbacks = null;
|
||||
var metronomeActive=false;
|
||||
var metronomeBPM=false;
|
||||
var metronomeSound=false;
|
||||
var metronomeMeter=0;
|
||||
var backingTrackPath="";
|
||||
var backingTrackLoop=false;
|
||||
|
||||
function dbg(msg) { logger.debug('FakeJamClient: ' + msg); }
|
||||
|
||||
|
|
@ -398,21 +404,42 @@
|
|||
}
|
||||
function SessionGetControlState(mixerIds, isMasterOrPersonal) {
|
||||
dbg("SessionGetControlState");
|
||||
var groups = [0, 1, 2, 3, 7, 9];
|
||||
var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12];
|
||||
var names = [
|
||||
"FW AP Multi",
|
||||
"FW AP Multi",
|
||||
"FW AP Multi",
|
||||
"FW AP Multi",
|
||||
"",
|
||||
""
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
];
|
||||
|
||||
var media_types = [
|
||||
"Master",
|
||||
"Monitor",
|
||||
"AudioInputMusic",
|
||||
"AudioInputChat",
|
||||
"StreamOutMusic",
|
||||
"UserMusicInput",
|
||||
"PeerAudioInputMusic",
|
||||
"PeerMediaTrack",
|
||||
"JamTrack",
|
||||
"MetronomeTrack"
|
||||
]
|
||||
var clientIds = [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"3933ebec-913b-43ab-a4d3-f21dc5f8955b",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
];
|
||||
var response = [];
|
||||
|
|
@ -422,6 +449,7 @@
|
|||
group_id: groups[i],
|
||||
id: mixerIds[i] + (isMasterOrPersonal ? 'm' : 'p'),
|
||||
master: isMasterOrPersonal,
|
||||
media_type: media_types[i],
|
||||
monitor: !isMasterOrPersonal,
|
||||
mute: false,
|
||||
name: names[i],
|
||||
|
|
@ -686,6 +714,55 @@
|
|||
function GetScoreWorkTimingInterval() { return {interval: 1000, backoff:60000} }
|
||||
function SetScoreWorkTimingInterval(knobs) {return true;}
|
||||
|
||||
function SessionOpenBackingTrackFile(path, loop) {
|
||||
backingTrackPath = path
|
||||
backingTrackLoop = loop
|
||||
}
|
||||
|
||||
function SessionSetBackingTrackFileLoop(path, loop) {
|
||||
backingTrackPath = path
|
||||
backingTrackLoop = loop
|
||||
}
|
||||
|
||||
function SessionCloseBackingTrackFile(path) {
|
||||
backingTrackPath=""
|
||||
}
|
||||
|
||||
function SessionOpenMetronome(bpm, click, meter, mode){
|
||||
console.log("Setting metronome BPM: ", bpm)
|
||||
metronomeActive =true
|
||||
metronomeBPM = bpm
|
||||
metronomeSound = click
|
||||
metronomeMeter = meter
|
||||
}
|
||||
|
||||
//change setting - click. Mode 0: = mono, 1, = left ear, 2= right ear
|
||||
function SessionSetMetronome(bpm,click,meter, mode){
|
||||
SessionOpenMetronome(bpm, click, meter, mode)
|
||||
}
|
||||
|
||||
//close everywhere
|
||||
function SessionCloseMetronome(){
|
||||
metronomeActive=false
|
||||
}
|
||||
|
||||
function setMetronomeOpenCallback(callback) {
|
||||
|
||||
}
|
||||
|
||||
function getMyNetworkState() {
|
||||
return {
|
||||
ntp_stable: Math.random() > 0.5
|
||||
}
|
||||
}
|
||||
|
||||
function getPeerState(clientId) {
|
||||
return {
|
||||
ntp_stable: Math.random() > 0.5
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// stun
|
||||
function NetworkTestResult() { return {remote_udp_blocked: false} }
|
||||
|
||||
|
|
@ -717,6 +794,14 @@
|
|||
fire();
|
||||
|
||||
}
|
||||
|
||||
function getBackingTrackList() {
|
||||
return {backing_tracks: [
|
||||
{name:"This is a really long name for a song dude.mp3", size:4283},
|
||||
{name:"foo.mp3",size:325783838}
|
||||
]};
|
||||
}
|
||||
|
||||
function ClientUpdateStartUpdate(path, successCallback, failureCallback) {}
|
||||
|
||||
// -------------------------------
|
||||
|
|
@ -796,7 +881,11 @@
|
|||
function OpenRecording(claimedRecording) {
|
||||
return {success: true}
|
||||
}
|
||||
function PreviewRecording(claimedRecording) {
|
||||
return OpenRecording(claimedRecording);
|
||||
}
|
||||
function CloseRecording() {}
|
||||
function ClosePreviewRecording() {CloseRecording();}
|
||||
function OnDownloadAvailable() {}
|
||||
function SaveToClipboard(text) {}
|
||||
function IsNativeClient() { /* must always return false in all scenarios due to not ruin scoring !*/ return false; }
|
||||
|
|
@ -977,6 +1066,20 @@
|
|||
this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval;
|
||||
this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval;
|
||||
|
||||
// Backing tracks:
|
||||
this.getBackingTrackList = getBackingTrackList;
|
||||
this.SessionCloseBackingTrackFile = SessionCloseBackingTrackFile;
|
||||
this.SessionOpenBackingTrackFile = SessionOpenBackingTrackFile;
|
||||
this.SessionSetBackingTrackFileLoop = SessionSetBackingTrackFileLoop;
|
||||
|
||||
// Metronome:
|
||||
this.SessionCloseMetronome = SessionCloseMetronome;
|
||||
this.SessionOpenMetronome = SessionOpenMetronome;
|
||||
this.SessionSetMetronome = SessionSetMetronome;
|
||||
this.setMetronomeOpenCallback = setMetronomeOpenCallback;
|
||||
this.getMyNetworkState = getMyNetworkState;
|
||||
this.getPeerState = getPeerState;
|
||||
|
||||
// Client Update
|
||||
this.IsAppInWritableVolume = IsAppInWritableVolume;
|
||||
this.ClientUpdateVersion = ClientUpdateVersion;
|
||||
|
|
@ -1000,6 +1103,8 @@
|
|||
this.GetLocalRecordingState = GetLocalRecordingState;
|
||||
this.OpenRecording = OpenRecording;
|
||||
this.CloseRecording = CloseRecording;
|
||||
this.PreviewRecording = PreviewRecording;
|
||||
this.ClosePreviewRecording = ClosePreviewRecording;
|
||||
this.OnDownloadAvailable = OnDownloadAvailable;
|
||||
|
||||
// Clipboard
|
||||
|
|
|
|||
|
|
@ -1057,6 +1057,18 @@
|
|||
})
|
||||
}
|
||||
|
||||
function markRecordedBackingTrackSilent(options) {
|
||||
var recordingId = options["recording_id"];
|
||||
var trackId = options["backing_track_id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: {},
|
||||
url: "/api/recordings/" + recordingId + "/backing_tracks/" + trackId + '/silent'
|
||||
});
|
||||
}
|
||||
function getRecordedTrack(options) {
|
||||
var recordingId = options["recording_id"];
|
||||
var trackId = options["track_id"];
|
||||
|
|
@ -1069,6 +1081,18 @@
|
|||
});
|
||||
}
|
||||
|
||||
function getRecordedBackingTrack(options) {
|
||||
var recordingId = options["recording_id"];
|
||||
var trackId = options["track_id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/recordings/" + recordingId + "/backing_tracks/" + trackId
|
||||
});
|
||||
}
|
||||
|
||||
function getRecording(options) {
|
||||
var recordingId = options["id"];
|
||||
|
||||
|
|
@ -1171,6 +1195,32 @@
|
|||
})
|
||||
}
|
||||
|
||||
function openBackingTrack(options) {
|
||||
var musicSessionId = options["id"];
|
||||
delete options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/sessions/" + musicSessionId + "/backing_tracks/open",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function closeBackingTrack(options) {
|
||||
var musicSessionId = options["id"];
|
||||
delete options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/sessions/" + musicSessionId + "/backing_tracks/close",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function openJamTrack(options) {
|
||||
var musicSessionId = options["id"];
|
||||
var jamTrackId = options["jam_track_id"];
|
||||
|
|
@ -1199,6 +1249,32 @@
|
|||
})
|
||||
}
|
||||
|
||||
function openMetronome(options) {
|
||||
var musicSessionId = options["id"];
|
||||
delete options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/sessions/" + musicSessionId + "/metronome/open",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function closeMetronome(options) {
|
||||
var musicSessionId = options["id"];
|
||||
delete options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/sessions/" + musicSessionId + "/metronome/close",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function discardRecording(options) {
|
||||
var recordingId = options["id"];
|
||||
|
||||
|
|
@ -1381,6 +1457,15 @@
|
|||
});
|
||||
}
|
||||
|
||||
function getBackingTracks(options) {
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
url: '/api/backing_tracks?' + $.param(options),
|
||||
dataType: "json",
|
||||
contentType: 'application/json'
|
||||
});
|
||||
}
|
||||
|
||||
function addJamtrackToShoppingCart(options) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
|
|
@ -1515,6 +1600,7 @@
|
|||
this.cancelSession = cancelSession;
|
||||
this.updateScheduledSession = updateScheduledSession;
|
||||
this.getUserDetail = getUserDetail;
|
||||
this.getUserProfile = getUserProfile;
|
||||
this.getCities = getCities;
|
||||
this.getRegions = getRegions;
|
||||
this.getCountries = getCountries;
|
||||
|
|
@ -1577,6 +1663,7 @@
|
|||
this.stopRecording = stopRecording;
|
||||
this.getRecording = getRecording;
|
||||
this.getRecordedTrack = getRecordedTrack;
|
||||
this.getRecordedBackingTrack = getRecordedBackingTrack;
|
||||
this.getClaimedRecordings = getClaimedRecordings;
|
||||
this.getClaimedRecording = getClaimedRecording;
|
||||
this.updateClaimedRecording = updateClaimedRecording;
|
||||
|
|
@ -1585,8 +1672,13 @@
|
|||
this.claimRecording = claimRecording;
|
||||
this.startPlayClaimedRecording = startPlayClaimedRecording;
|
||||
this.stopPlayClaimedRecording = stopPlayClaimedRecording;
|
||||
this.openJamTrack = openJamTrack;
|
||||
this.openJamTrack = openJamTrack
|
||||
this.openBackingTrack = openBackingTrack
|
||||
this.closeBackingTrack = closeBackingTrack
|
||||
this.closeMetronome = closeMetronome;
|
||||
this.closeJamTrack = closeJamTrack;
|
||||
this.openMetronome = openMetronome;
|
||||
this.closeMetronome = closeMetronome;
|
||||
this.discardRecording = discardRecording;
|
||||
this.putTrackSyncChange = putTrackSyncChange;
|
||||
this.createBand = createBand;
|
||||
|
|
@ -1617,6 +1709,7 @@
|
|||
this.updateAudioLatency = updateAudioLatency;
|
||||
this.getJamtracks = getJamtracks;
|
||||
this.getPurchasedJamTracks = getPurchasedJamTracks;
|
||||
this.getBackingTracks = getBackingTracks;
|
||||
this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
|
||||
this.getShoppingCarts = getShoppingCarts;
|
||||
this.removeShoppingCart = removeShoppingCart;
|
||||
|
|
@ -1631,6 +1724,7 @@
|
|||
this.getMount = getMount;
|
||||
this.createSourceChange = createSourceChange;
|
||||
this.validateUrlSite = validateUrlSite;
|
||||
this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
|
||||
$ = jQuery
|
||||
context = window
|
||||
context.JK ||= {};
|
||||
|
||||
class JamTrackUtils
|
||||
constructor: () ->
|
||||
@logger = context.JK.logger
|
||||
@rest = new context.JK.Rest();
|
||||
|
||||
init: () =>
|
||||
|
||||
# check if the shopping cart should be shown
|
||||
checkShoppingCart: () =>
|
||||
@rest.getShoppingCarts().done(this.displayCartIcon)
|
||||
|
||||
displayCartIcon: (carts) =>
|
||||
cartLink = $("a[href='" + "/client#/shoppingCart" + "']")
|
||||
if carts.length > 0
|
||||
cartLink.removeClass("hidden")
|
||||
else
|
||||
cartLink.addClass("hidden")
|
||||
|
||||
|
||||
|
||||
|
||||
# global instance
|
||||
context.JK.JamTrackUtils = new JamTrackUtils()
|
||||
|
|
@ -11,9 +11,94 @@
|
|||
var rest = context.JK.Rest();
|
||||
var decrementedFriendCountOnce = false;
|
||||
var sentFriendRequest = false;
|
||||
var profileScreen = null;
|
||||
var textMessageDialog = null;
|
||||
var feed = null;
|
||||
var profileUtils = context.JK.ProfileUtils;
|
||||
|
||||
var NOT_SPECIFIED_TEXT = 'Not specified';
|
||||
|
||||
var $screen = $('#user-profile');
|
||||
|
||||
// musical experience
|
||||
var $instruments = $screen.find('#instruments');
|
||||
var $musicianStatus = $screen.find('#musician-status');
|
||||
var $genres = $screen.find('#genres');
|
||||
var $concertCount = $screen.find('#concert-count');
|
||||
var $studioCount = $screen.find('#studio-count');
|
||||
|
||||
// performance samples
|
||||
var $noSamples = $screen.find('no-samples');
|
||||
|
||||
// online presence
|
||||
var $noOnlinePresence = $screen.find('no-online-presence');
|
||||
|
||||
// current interests
|
||||
var $noInterests = $screen.find('#no-interests');
|
||||
var $paidGigSection = $screen.find('#paid-gigs');
|
||||
var $paidGigDetails = $screen.find('#paid-gig-details');
|
||||
|
||||
var $freeGigSection = $screen.find('#free-gigs');
|
||||
var $freeGigDetails = $screen.find('#free-gig-details');
|
||||
|
||||
var $cowritingSection = $screen.find('#cowriting');
|
||||
var $cowritingDetails = $screen.find('#cowriting-details');
|
||||
|
||||
var $traditionalBandSection = $screen.find("#traditional-band");
|
||||
var $traditionalBandDetails = $screen.find('#traditional-band-details');
|
||||
|
||||
var $virtualBandSection = $screen.find("#virtual-band");
|
||||
var $virtualBandDetails = $screen.find('#virtual-band-details');
|
||||
|
||||
// tabs
|
||||
var $aboutLink = $screen.find('#about-link');
|
||||
var $aboutContent = $screen.find('#about-content');
|
||||
|
||||
var $historyLink = $screen.find('#history-link');
|
||||
var $historyContent = $screen.find('#history-content');
|
||||
|
||||
var $bandsLink = $screen.find('#bands-link');
|
||||
var $bandsContent = $screen.find('#bands-content');
|
||||
|
||||
var $socialLink = $screen.find('#social-link');
|
||||
var $socialContent = $screen.find('#social-content');
|
||||
|
||||
var $favoritesLink = $screen.find('#favorites-link');
|
||||
var $favoritesContent = $screen.find('#favorites-content');
|
||||
|
||||
// stats
|
||||
var $friendStats = $screen.find('#friend-stats');
|
||||
var $followerStats = $screen.find('#follower-stats');
|
||||
var $sessionStats = $screen.find('#session-stats');
|
||||
var $recordingStats = $screen.find('#recording-stats');
|
||||
var $followingStats = $screen.find('#following-stats');
|
||||
var $favoriteStats = $screen.find('#favorite-stats');
|
||||
|
||||
// miscellaneous
|
||||
var $userName = $screen.find('#username');
|
||||
var $avatar = $screen.find('#avatar');
|
||||
var $typeLabel = $screen.find('#type-label');
|
||||
var $location = $screen.find('#location');
|
||||
var $age = $screen.find('#age');
|
||||
|
||||
// buttons
|
||||
var $btnEdit = $screen.find('#btn-edit');
|
||||
var $btnAddFriend = $screen.find('#btn-add-friend');
|
||||
var $btnFollowUser = $screen.find('#btn-follow-user');
|
||||
var $btnMessageUser = $screen.find('#btn-message-user');
|
||||
|
||||
// social
|
||||
var $socialLeft = $screen.find('.profile-social-left');
|
||||
var $socialFriends = $screen.find('#social-friends');
|
||||
var $socialFollowings = $screen.find('#social-followings');
|
||||
var $socialFollowers = $screen.find('#social-followers');
|
||||
|
||||
var $bioTextArea = $screen.find('.user-biography');
|
||||
var $showBio = $screen.find('.have-bio');
|
||||
var $noBio = $screen.find('.no-bio');
|
||||
var $biographyEditor = $screen.find('.update-biography');
|
||||
var $submitBiographyButton = $screen.find('#btn-update-user-biography');
|
||||
var $cancelBiographyButton = $screen.find('#btn-cancel-user-biography');
|
||||
var $biographyText = $screen.find('#biography');
|
||||
|
||||
var instrument_logo_map = context.JK.getInstrumentIconMap24();
|
||||
|
||||
|
|
@ -44,23 +129,23 @@
|
|||
}
|
||||
|
||||
function resetForm() {
|
||||
$('#profile-instruments').empty();
|
||||
$instruments.empty();
|
||||
|
||||
$('#profile-about').show();
|
||||
$('#profile-history').hide();
|
||||
$('#profile-bands').hide();
|
||||
$('#profile-social').hide();
|
||||
$('#profile-favorites').hide();
|
||||
$aboutContent.show();
|
||||
$historyContent.hide();
|
||||
$bandsContent.hide();
|
||||
$socialContent.hide();
|
||||
$favoritesContent.hide();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-about-link').addClass('active');
|
||||
$aboutLink.addClass('active');
|
||||
}
|
||||
|
||||
function initUser() {
|
||||
user = null;
|
||||
decrementedFriendCountOnce = false;
|
||||
sentFriendRequest = false;
|
||||
userDefer = rest.getUserDetail({id: userId})
|
||||
userDefer = rest.getUserProfile({id: userId})
|
||||
.done(function (response) {
|
||||
user = response;
|
||||
configureUserType();
|
||||
|
|
@ -89,43 +174,43 @@
|
|||
|
||||
function configureUserType() {
|
||||
if (isMusician()) {
|
||||
$('#profile-history-link').show();
|
||||
$('#profile-bands-link').show();
|
||||
$('#profile-instruments').show();
|
||||
$('#profile-session-stats').show();
|
||||
$('#profile-recording-stats').show();
|
||||
// $('#profile-following-stats').hide();
|
||||
// $('#profile-favorites-stats').hide();
|
||||
$('.profile-social-left').show();
|
||||
$('#profile-type-label').text('musician');
|
||||
$('#profile-location-label').text('Location');
|
||||
$historyLink.show();
|
||||
$bandsLink.show();
|
||||
$instruments.show();
|
||||
$sessionStats.show();
|
||||
$recordingStats.show();
|
||||
// $followingStats.hide();
|
||||
// $favoriteStats.hide();
|
||||
$socialLeft.show();
|
||||
$typeLabel.text('musician');
|
||||
$location.text('Location');
|
||||
}
|
||||
else {
|
||||
$('#profile-history-link').hide();
|
||||
$('#profile-bands-link').hide();
|
||||
$('#profile-instruments').hide();
|
||||
$('#profile-session-stats').hide();
|
||||
$('#profile-recording-stats').hide();
|
||||
// $('#profile-following-stats').show();
|
||||
// $('#profile-favorites-stats').show();
|
||||
$('.profile-social-left').hide();
|
||||
$('#profile-type-label').text('fan');
|
||||
$('#profile-location-label').text('Presence');
|
||||
$historyLink.hide();
|
||||
$bandsLink.hide();
|
||||
$instruments.hide();
|
||||
$sessionStats.hide();
|
||||
$recordingStats.hide();
|
||||
// $followingStats.show();
|
||||
// $favoriteStats.show();
|
||||
$socialLeft.hide();
|
||||
$typeLabel.text('fan');
|
||||
$location.text('Presence');
|
||||
}
|
||||
|
||||
if (isCurrentUser()) {
|
||||
$('#btn-profile-edit').show();
|
||||
$('#btn-add-friend').hide();
|
||||
$('#btn-follow-user').hide();
|
||||
$('#btn-message-user').hide();
|
||||
$btnEdit.show();
|
||||
$btnAddFriend.hide();
|
||||
$btnFollowUser.hide();
|
||||
$btnMessageUser.hide();
|
||||
}
|
||||
else {
|
||||
configureFriendFollowersControls();
|
||||
|
||||
$('#btn-profile-edit').hide();
|
||||
$('#btn-add-friend').show();
|
||||
$('#btn-follow-user').show();
|
||||
$('#btn-message-user').show();
|
||||
$btnEdit.hide();
|
||||
$btnAddFriend.show();
|
||||
$btnFollowUser.show();
|
||||
$btnMessageUser.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,26 +226,30 @@
|
|||
// events for main screen
|
||||
function events() {
|
||||
// wire up panel clicks -- these need to check deferred because they can't be hidden when in an invalid state
|
||||
$('#profile-about-link').click(function () {
|
||||
$aboutLink.click(function () {
|
||||
renderTabDeferred(renderAbout)
|
||||
});
|
||||
$('#profile-history-link').click(function () {
|
||||
|
||||
$historyLink.click(function () {
|
||||
renderTabDeferred(renderHistory)
|
||||
});
|
||||
$('#profile-bands-link').click(function () {
|
||||
|
||||
$bandsLink.click(function () {
|
||||
renderTabDeferred(renderBands)
|
||||
});
|
||||
$('#profile-social-link').click(function () {
|
||||
|
||||
$socialLink.click(function () {
|
||||
renderTabDeferred(renderSocial)
|
||||
});
|
||||
$('#profile-favorites-link').click(function () {
|
||||
|
||||
$favoritesLink.click(function () {
|
||||
renderTabDeferred(renderFavorites)
|
||||
});
|
||||
|
||||
// this doesn't need deferred because it's only shown when valid
|
||||
$('#btn-add-friend').click(handleFriendChange);
|
||||
$('#btn-follow-user').click(handleFollowingChange);
|
||||
$('#btn-message-user').click(handleMessageMusician);
|
||||
$btnAddFriend.click(handleFriendChange);
|
||||
$btnFollowUser.click(handleFollowingChange);
|
||||
$btnMessageUser.click(handleMessageMusician);
|
||||
}
|
||||
|
||||
function handleFriendChange(evt) {
|
||||
|
|
@ -221,10 +310,10 @@
|
|||
|
||||
function configureFriendButton() {
|
||||
if (isFriend()) {
|
||||
$('#btn-add-friend').text('DISCONNECT');
|
||||
$btnAddFriend.text('DISCONNECT');
|
||||
}
|
||||
else {
|
||||
$('#btn-add-friend').text('CONNECT');
|
||||
$btnAddFriend.text('CONNECT');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,32 +356,32 @@
|
|||
function configureFollowingButton() {
|
||||
|
||||
if (isFollowing()) {
|
||||
$('#btn-follow-user').text('UNFOLLOW');
|
||||
$btnFollowUser.text('UNFOLLOW');
|
||||
}
|
||||
else {
|
||||
$('#btn-follow-user').text('FOLLOW');
|
||||
$btnFollowUser.text('FOLLOW');
|
||||
}
|
||||
}
|
||||
|
||||
function configureEditProfileButton() {
|
||||
$('#btn-follow-user').click(addFollowing);
|
||||
$btnFollowUser.click(addFollowing);
|
||||
}
|
||||
|
||||
// refreshes the currently active tab
|
||||
function renderActive() {
|
||||
if ($('#profile-about-link').hasClass('active')) {
|
||||
if ($aboutLink.hasClass('active')) {
|
||||
renderAbout();
|
||||
}
|
||||
else if ($('#profile-history-link').hasClass('active')) {
|
||||
else if ($historyLink.hasClass('active')) {
|
||||
renderHistory();
|
||||
}
|
||||
else if ($('#profile-bands-link').hasClass('active')) {
|
||||
else if ($bandsLink.hasClass('active')) {
|
||||
renderBands();
|
||||
}
|
||||
else if ($('#profile-social-link').hasClass('active')) {
|
||||
else if ($socialLink.hasClass('active')) {
|
||||
renderSocial();
|
||||
}
|
||||
else if ($('#profile-favorites-link').hasClass('active')) {
|
||||
else if ($favoritesLink.hasClass('active')) {
|
||||
renderFavorites();
|
||||
}
|
||||
}
|
||||
|
|
@ -310,29 +399,29 @@
|
|||
|
||||
/****************** ABOUT TAB *****************/
|
||||
function renderAbout() {
|
||||
$('#profile-instruments').empty();
|
||||
$instruments.empty();
|
||||
|
||||
$('#profile-about').show();
|
||||
$('#profile-history').hide();
|
||||
$('#profile-bands').hide();
|
||||
$('#profile-social').hide();
|
||||
$('#profile-favorites').hide();
|
||||
$aboutContent.show();
|
||||
$historyContent.hide();
|
||||
$bandsContent.hide();
|
||||
$socialContent.hide();
|
||||
$favoritesContent.hide();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-about-link').addClass('active');
|
||||
$aboutLink.addClass('active');
|
||||
|
||||
bindAbout();
|
||||
}
|
||||
|
||||
function bindAbout() {
|
||||
|
||||
$('#profile-instruments').empty();
|
||||
$instruments.empty();
|
||||
|
||||
// name
|
||||
$('#profile-username').html(user.name);
|
||||
$userName.html(user.name);
|
||||
|
||||
// avatar
|
||||
$('#profile-avatar').attr('src', context.JK.resolveAvatarUrl(user.photo_url));
|
||||
$avatar.attr('src', context.JK.resolveAvatarUrl(user.photo_url));
|
||||
|
||||
// instruments
|
||||
if (user.instruments) {
|
||||
|
|
@ -351,35 +440,152 @@
|
|||
proficiency_level_css: proficiencyCssMap[proficiency]
|
||||
});
|
||||
|
||||
$('#profile-instruments').append(instrumentHtml);
|
||||
$instruments.append(instrumentHtml);
|
||||
}
|
||||
}
|
||||
$('#profile-genres').empty();
|
||||
for (var i=0; i< user.genres.length; i++) {
|
||||
$('#profile-genres').append(user.genres[i].description + '<br />');
|
||||
}
|
||||
|
||||
// status
|
||||
var status = user.skill_level;
|
||||
$musicianStatus.html(status ? profileUtils.skillLevelMap[status] + ' musician' : NOT_SPECIFIED_TEXT)
|
||||
|
||||
// genres
|
||||
$genres.empty();
|
||||
var profileGenres = profileUtils.profileGenreList(user.genres);
|
||||
$genres.append(profileGenres);
|
||||
|
||||
// concert gigs
|
||||
var concertGigCount = user.concert_count;
|
||||
$concertCount.html(concertGigCount > 0 ? 'Has played ' + profileUtils.gigMap[concertGigCount] + ' live concert gigs' : NOT_SPECIFIED_TEXT);
|
||||
|
||||
// studio gigs
|
||||
var studioGigCount = user.studio_session_count;
|
||||
$studioCount.html(studioGigCount > 0 ? 'Has played ' + profileUtils.gigMap[studioGigCount] + ' studio session gigs' : NOT_SPECIFIED_TEXT);
|
||||
|
||||
// location
|
||||
$('#profile-location').html(user.location);
|
||||
$location.html(user.location);
|
||||
|
||||
$age.html(user.age ? user.age + " years old" : "");
|
||||
|
||||
// stats
|
||||
var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend";
|
||||
$('#profile-friend-stats').html('<span class="friend-count">' + user.friend_count + '</span>' + text);
|
||||
$friendStats.html('<span class="friend-count">' + user.friend_count + '</span>' + text);
|
||||
|
||||
text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower";
|
||||
$('#profile-follower-stats').html('<span class="follower-count">' + user.follower_count + '</span>' + text);
|
||||
$followerStats.html('<span class="follower-count">' + user.follower_count + '</span>' + text);
|
||||
|
||||
// text = user.following_count > 1 || user.following_count === 0 ? " Followings" : " Following";
|
||||
// $('#profile-following-stats').html('<span class="following-count">' + user.following_count + '</span>' + text);
|
||||
|
||||
// performance samples
|
||||
var performanceSamples = user.performance_samples;
|
||||
if (!performanceSamples || performanceSamples.length === 0) {
|
||||
$noSamples.show();
|
||||
}
|
||||
else {
|
||||
$noSamples.hide();
|
||||
}
|
||||
|
||||
// online presences
|
||||
var onlinePresences = user.online_presences;
|
||||
if ((!onlinePresences || onlinePresences.length === 0) && !user.website) {
|
||||
$noOnlinePresence.show();
|
||||
}
|
||||
else {
|
||||
$noOnlinePresence.hide();
|
||||
}
|
||||
|
||||
// current interests
|
||||
var noInterests = !user.paid_sessions && !user.free_sessions && !user.cowriting && !user.virtual_band && !user.traditional_band;
|
||||
if (noInterests) {
|
||||
$noInterests.show();
|
||||
$paidGigSection.hide();
|
||||
$freeGigSection.hide();
|
||||
$cowritingSection.hide();
|
||||
$traditionalBandSection.hide();
|
||||
$virtualBandSection.hide();
|
||||
}
|
||||
|
||||
else {
|
||||
// paid sessions
|
||||
if (user.paid_sessions) {
|
||||
$paidGigSection.show();
|
||||
|
||||
$paidGigDetails.find("ul li:nth-child(1)").append(profileUtils.paidSessionGenreList(user.genres));
|
||||
|
||||
var hourlyRate = user.paid_sessions_hourly_rate;
|
||||
$paidGigDetails.find("ul li:nth-child(2)").append(hourlyRate ? hourlyRate : NOT_SPECIFIED_TEXT);
|
||||
|
||||
var dailyRate = user.paid_sessions_daily_rate;
|
||||
$paidGigDetails.find("ul li:nth-child(3)").append(dailyRate ? dailyRate : NOT_SPECIFIED_TEXT);
|
||||
}
|
||||
else {
|
||||
$paidGigSection.hide();
|
||||
}
|
||||
|
||||
// free sessions
|
||||
if (user.free_sessions) {
|
||||
$freeGigSection.show();
|
||||
$freeGigDetails.find("ul li:nth-child(1)").append(profileUtils.freeSessionGenreList(user.genres));
|
||||
}
|
||||
else {
|
||||
$freeGigSection.hide();
|
||||
}
|
||||
|
||||
// cowriting
|
||||
if (user.cowriting) {
|
||||
$cowritingSection.show();
|
||||
|
||||
$cowritingDetails.find("ul li:nth-child(1)").append(profileUtils.cowritingGenreList(user.genres));
|
||||
|
||||
var purpose = user.cowriting_purpose;
|
||||
$cowritingDetails.find("ul li:nth-child(2)").append(purpose ? profileUtils.cowritingPurposeMap[purpose] : NOT_SPECIFIED_TEXT);
|
||||
}
|
||||
else {
|
||||
$cowritingSection.hide();
|
||||
}
|
||||
|
||||
// traditional bands
|
||||
if (user.traditional_band) {
|
||||
$traditionalBandSection.show();
|
||||
|
||||
$traditionalBandDetails.find("ul li:nth-child(1)").append(profileUtils.traditionalBandGenreList(user.genres));
|
||||
|
||||
var commitment = user.traditional_band_commitment;
|
||||
$traditionalBandDetails.find("ul li:nth-child(2)").append(commitment ? profileUtils.bandCommitmentMap[commitment] : NOT_SPECIFIED_TEXT);
|
||||
|
||||
var canTour = user.traditional_band_touring;
|
||||
var canTourResponse = canTour ? "Yes" : (canTour === false ? "No" : NOT_SPECIFIED_TEXT);
|
||||
$traditionalBandDetails.find("ul li:nth-child(3)").append(canTourResponse);
|
||||
}
|
||||
else {
|
||||
$traditionalBandSection.hide();
|
||||
}
|
||||
|
||||
// virtual band
|
||||
if (user.virtual_band) {
|
||||
$virtualBandSection.show();
|
||||
|
||||
$virtualBandDetails.find("ul li:nth-child(1)").append(profileUtils.virtualBandGenreList(user.genres));
|
||||
|
||||
var commitment = user.virtual_band_commitment;
|
||||
$virtualBandDetails.find("ul li:nth-child(2)").append(commitment ? profileUtils.bandCommitmentMap[commitment] : NOT_SPECIFIED_TEXT);
|
||||
}
|
||||
else {
|
||||
$virtualBandSection.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (isMusician()) {
|
||||
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
|
||||
$('#profile-session-stats').html(user.session_count + text);
|
||||
$sessionStats.html(user.session_count + text);
|
||||
|
||||
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
|
||||
$('#profile-recording-stats').html(user.recording_count + text);
|
||||
$recordingStats.html(user.recording_count + text);
|
||||
} else {
|
||||
text = " Following";
|
||||
$('#profile-following-stats').html(user.following_count + text);
|
||||
$followingStats.html(user.following_count + text);
|
||||
text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite";
|
||||
$('#profile-favorite-stats').html(user.favorite_count + text);
|
||||
$favoriteStats.html(user.favorite_count + text);
|
||||
}
|
||||
|
||||
renderBio();
|
||||
|
|
@ -400,12 +606,6 @@
|
|||
if(user.biography) {
|
||||
|
||||
$showBio.show();
|
||||
if(isCurrentUser()) {
|
||||
$editBiographyButton.show();
|
||||
}
|
||||
else {
|
||||
$editBiographyButton.hide();
|
||||
}
|
||||
$biographyText.text(user.biography).show();
|
||||
}
|
||||
else {
|
||||
|
|
@ -415,30 +615,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
var $bioTextArea = $('.user-biography', profileScreen);
|
||||
var $showBio = $('.have-bio', profileScreen);
|
||||
var $noBio = $('.no-bio', profileScreen);
|
||||
var $biographyEditor = $('.update-biography', profileScreen);
|
||||
var $addBiographyButton = $('a.enter-bio', profileScreen);
|
||||
var $editBiographyButton = $('#profile-edit-biography', profileScreen);
|
||||
var $submitBiographyButton = $('#btn-update-user-biography', profileScreen);
|
||||
var $cancelBiographyButton = $('#btn-cancel-user-biography', profileScreen);
|
||||
var $biographyText = $('#profile-biography', profileScreen);
|
||||
|
||||
initializeBioVisibility();
|
||||
|
||||
$addBiographyButton.unbind('click').click(function() {
|
||||
$biographyEditor.val(user.biography).show();
|
||||
return false;
|
||||
});
|
||||
|
||||
$editBiographyButton.unbind('click').click(function() {
|
||||
$editBiographyButton.hide();
|
||||
$biographyText.hide();
|
||||
$bioTextArea.val(user.biography);
|
||||
$biographyEditor.show();
|
||||
return false;
|
||||
})
|
||||
// $addBiographyButton.unbind('click').click(function() {
|
||||
// $biographyEditor.val(user.biography).show();
|
||||
// return false;
|
||||
// });
|
||||
|
||||
$submitBiographyButton.unbind('click').click(function() {
|
||||
var bio = $bioTextArea.val();
|
||||
|
|
@ -477,18 +659,18 @@
|
|||
|
||||
/****************** SOCIAL TAB *****************/
|
||||
function renderSocial() {
|
||||
$('#profile-social-friends').empty();
|
||||
$('#profile-social-followings').empty();
|
||||
$('#profile-social-followers').empty();
|
||||
$socialFriends.empty();
|
||||
$socialFollowings.empty();
|
||||
$socialFollowers.empty();
|
||||
|
||||
$('#profile-about').hide();
|
||||
$('#profile-history').hide();
|
||||
$('#profile-bands').hide();
|
||||
$('#profile-social').show();
|
||||
$('#profile-favorites').hide();
|
||||
$aboutContent.hide();
|
||||
$historyContent.hide();
|
||||
$bandsContent.hide();
|
||||
$socialContent.show();
|
||||
$favoritesContent.hide();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-social-link').addClass('active');
|
||||
$socialLink.addClass('active');
|
||||
|
||||
bindSocial();
|
||||
}
|
||||
|
|
@ -511,11 +693,11 @@
|
|||
type: "Friends"
|
||||
});
|
||||
|
||||
$('#profile-social-friends').append(friendHtml);
|
||||
$socialFriends.append(friendHtml);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#profile-social-friends').html(' ');
|
||||
$socialFriends.html(' ');
|
||||
}
|
||||
context.JK.bindHoverEvents();
|
||||
})
|
||||
|
|
@ -536,11 +718,11 @@
|
|||
location: val.location
|
||||
});
|
||||
|
||||
$('#profile-social-followings').append(followingHtml);
|
||||
$socialFollowings.append(followingHtml);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#profile-social-followings').html(' ');
|
||||
$socialFollowings.html(' ');
|
||||
}
|
||||
context.JK.bindHoverEvents();
|
||||
})
|
||||
|
|
@ -559,7 +741,7 @@
|
|||
location: val.location
|
||||
});
|
||||
|
||||
$('#profile-social-followers').append(followerHtml);
|
||||
$socialFollowers.append(followerHtml);
|
||||
});
|
||||
context.JK.bindHoverEvents();
|
||||
})
|
||||
|
|
@ -568,14 +750,14 @@
|
|||
|
||||
/****************** HISTORY TAB *****************/
|
||||
function renderHistory() {
|
||||
$('#profile-about').hide();
|
||||
$('#profile-history').show();
|
||||
$('#profile-bands').hide();
|
||||
$('#profile-social').hide();
|
||||
$('#profile-favorites').hide();
|
||||
$aboutContent.hide();
|
||||
$historyContent.show();
|
||||
$bandsContent.hide();
|
||||
$socialContent.hide();
|
||||
$favoritesContent.hide();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-history-link').addClass('active');
|
||||
$historyLink.addClass('active');
|
||||
|
||||
bindHistory();
|
||||
}
|
||||
|
|
@ -586,16 +768,16 @@
|
|||
|
||||
/****************** BANDS TAB *****************/
|
||||
function renderBands() {
|
||||
$('#profile-bands').empty();
|
||||
$bandsContent.empty();
|
||||
|
||||
$('#profile-about').hide();
|
||||
$('#profile-history').hide();
|
||||
$('#profile-bands').show();
|
||||
$('#profile-social').hide();
|
||||
$('#profile-favorites').hide();
|
||||
$aboutContent.hide();
|
||||
$historyContent.hide();
|
||||
$bandsContent.show();
|
||||
$socialContent.hide();
|
||||
$favoritesContent.hide();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-bands-link').addClass('active');
|
||||
$bandsLink.addClass('active');
|
||||
|
||||
bindBands();
|
||||
}
|
||||
|
|
@ -606,7 +788,7 @@
|
|||
.done(function (response) {
|
||||
if ((!response || response.length === 0) && isCurrentUser()) {
|
||||
var noBandHtml = $('#template-no-bands').html();
|
||||
$("#profile-bands").html(noBandHtml);
|
||||
$bandsContent.html(noBandHtml);
|
||||
}
|
||||
else {
|
||||
addMoreBandsLink();
|
||||
|
|
@ -664,7 +846,7 @@
|
|||
musicians: musicianHtml
|
||||
});
|
||||
|
||||
$('#profile-bands').append(bandHtml);
|
||||
$bandsContent.append(bandHtml);
|
||||
|
||||
$('.profile-band-link-member-true').each(function(idx) {
|
||||
isBandMember ? $(this).show() : $(this).hide();
|
||||
|
|
@ -689,7 +871,7 @@
|
|||
function addMoreBandsLink() {
|
||||
if (isCurrentUser()) {
|
||||
var moreBandsHtml = $('#template-more-bands').html();
|
||||
$("#profile-bands").append(moreBandsHtml);
|
||||
$bandsContent.append(moreBandsHtml);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -716,13 +898,13 @@
|
|||
}
|
||||
|
||||
function updateFollowingCount(value) {
|
||||
var followingCount = $('#profile-follower-stats span.follower-count');
|
||||
followingCount.text(value + parseInt(followingCount.text()));
|
||||
var $followingCount = $('#follower-stats span.follower-count');
|
||||
$followingCount.text(value + parseInt($followingCount.text()));
|
||||
}
|
||||
|
||||
function updateBandFollowingCount(bandId, value) {
|
||||
var bandFollowing = $('div[band-id="' + bandId + '"].profile-bands span.follower-count');
|
||||
bandFollowing.text(value + parseInt(bandFollowing.text()));
|
||||
var $bandFollowing = $('div[band-id="' + bandId + '"].profile-bands span.follower-count');
|
||||
$bandFollowing.text(value + parseInt($bandFollowing.text()));
|
||||
}
|
||||
|
||||
function addBandFollowing(evt) {
|
||||
|
|
@ -734,7 +916,6 @@
|
|||
|
||||
rest.addFollowing(newFollowing)
|
||||
.done(function (response) {
|
||||
logger.debug("following band " + bandId);
|
||||
updateBandFollowingCount(bandId, 1); // increase counter
|
||||
configureBandFollowingButton(true, bandId);
|
||||
context.JK.GA.trackJKSocial(context.JK.GA.Categories.jkFollow, context.JK.GA.JKSocialTargets.band);
|
||||
|
|
@ -743,7 +924,7 @@
|
|||
}
|
||||
|
||||
function configureBandFollowingButton(following, bandId) {
|
||||
var $btnFollowBand = $('div[band-id=' + bandId + ']', '#profile-bands').find('#btn-follow-band-2');
|
||||
var $btnFollowBand = $('div[band-id=' + bandId + ']', '#bands-content').find('#btn-follow-band');
|
||||
$btnFollowBand.unbind("click");
|
||||
|
||||
if (following) {
|
||||
|
|
@ -762,14 +943,14 @@
|
|||
|
||||
/****************** FAVORITES TAB *****************/
|
||||
function renderFavorites() {
|
||||
$('#profile-about').hide();
|
||||
$('#profile-history').hide();
|
||||
$('#profile-bands').hide();
|
||||
$('#profile-social').hide();
|
||||
$('#profile-favorites').show();
|
||||
$aboutContent.hide();
|
||||
$historyContent.hide();
|
||||
$bandsContent.hide();
|
||||
$socialContent.hide();
|
||||
$favoritesContent.show();
|
||||
|
||||
$('.profile-nav a.active').removeClass('active');
|
||||
$('.profile-nav a#profile-favorites-link').addClass('active');
|
||||
$favoritesLink.addClass('active');
|
||||
|
||||
bindFavorites();
|
||||
}
|
||||
|
|
@ -780,16 +961,16 @@
|
|||
|
||||
function initializeFeed() {
|
||||
|
||||
var $scroller = profileScreen.find('.content-body-scroller#user-profile-feed-scroller');
|
||||
var $content = profileScreen.find('.feed-content#user-profile-feed-entry-list');
|
||||
var $noMoreFeeds = $('#user-profile-end-of-feeds-list');
|
||||
var $refresh = profileScreen.find('.btn-refresh-entries');
|
||||
var $sortFeedBy = profileScreen.find('#feed_order_by');
|
||||
var $includeDate = profileScreen.find('#feed_date');
|
||||
var $includeType = profileScreen.find('#feed_show');
|
||||
var $scroller = $screen.find('.content-body-scroller#user-profile-feed-scroller');
|
||||
var $content = $screen.find('.feed-content#user-profile-feed-entry-list');
|
||||
var $noMoreFeeds = $screen.find('#user-profile-end-of-feeds-list');
|
||||
var $refresh = $screen.find('.btn-refresh-entries');
|
||||
var $sortFeedBy = $screen.find('#feed_order_by');
|
||||
var $includeDate = $screen.find('#feed_date');
|
||||
var $includeType = $screen.find('#feed_show');
|
||||
|
||||
feed = new context.JK.Feed(app);
|
||||
feed.initialize(profileScreen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType, {time_range: 'all'});
|
||||
feed.initialize($screen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType, {time_range: 'all'});
|
||||
}
|
||||
|
||||
function initialize(textMessageDialogInstance) {
|
||||
|
|
@ -800,7 +981,6 @@
|
|||
'beforeHide' : beforeHide
|
||||
};
|
||||
app.bindScreen('profile', screenBindings);
|
||||
profileScreen = $('#user-profile');
|
||||
events();
|
||||
initializeFeed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* Common utility functions.
|
||||
*/
|
||||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
var profileUtils = {};
|
||||
context.JK.ProfileUtils = profileUtils;
|
||||
|
||||
// genre types
|
||||
var PROFILE_GENRE_TYPE = 'profile';
|
||||
var VIRTUAL_BAND_GENRE_TYPE = 'virtual_band';
|
||||
var TRADITIONAL_BAND_GENRE_TYPE = 'traditional_band';
|
||||
var PAID_SESSION_GENRE_TYPE = 'paid_session';
|
||||
var FREE_SESSION_GENRE_TYPE = 'free_session';
|
||||
var COWRITING_GENRE_TYPE = 'cowriting';
|
||||
|
||||
// performance samples
|
||||
var JAMKAZAM = 'jamkazam';
|
||||
var SOUNDCLOUD = 'soundcloud';
|
||||
var YOUTUBE = 'youtube';
|
||||
|
||||
var USER_TYPE = 'JamRuby::User';
|
||||
|
||||
profileUtils.skillLevelMap = {
|
||||
"1": "Amateur",
|
||||
"2": "Professional"
|
||||
};
|
||||
|
||||
profileUtils.gigMap = {
|
||||
"0": "zero",
|
||||
"1": "under 10",
|
||||
"2": "10 to 50",
|
||||
"3": "50 to 100",
|
||||
"4": "over 100"
|
||||
};
|
||||
|
||||
profileUtils.cowritingPurposeMap = {
|
||||
"1": "just for fun",
|
||||
"2": "sell music"
|
||||
};
|
||||
|
||||
profileUtils.bandCommitmentMap = {
|
||||
"1": "infrequent",
|
||||
"2": "once a week",
|
||||
"3": "2-3 times a week",
|
||||
"4": "4+ times a week"
|
||||
}
|
||||
|
||||
function buildGenreList(genres) {
|
||||
var list = '';
|
||||
|
||||
for (var i=0; i < genres.length; i++) {
|
||||
list = list.concat(genres[i].genre_id);
|
||||
if (i !== genres.length - 1) {
|
||||
list = list.concat(', ');
|
||||
}
|
||||
}
|
||||
|
||||
return list.length > 0 ? list : 'None specified';
|
||||
}
|
||||
|
||||
// profile genres
|
||||
profileUtils.profileGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === PROFILE_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.profileGenreList = function(genres) {
|
||||
var matches = profileUtils.profileGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
// virtual band genres
|
||||
profileUtils.virtualBandGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === VIRTUAL_BAND_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.virtualBandGenreList = function(genres) {
|
||||
var matches = profileUtils.virtualBandGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
// traditional band genres
|
||||
profileUtils.traditionalBandGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === TRADITIONAL_BAND_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.traditionalBandGenreList = function(genres) {
|
||||
var matches = profileUtils.traditionalBandGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
// paid session genres
|
||||
profileUtils.paidSessionGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === PAID_SESSION_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.paidSessionGenreList = function(genres) {
|
||||
var matches = profileUtils.paidSessionGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
// free session genres
|
||||
profileUtils.freeSessionGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === FREE_SESSION_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.freeSessionGenreList = function(genres) {
|
||||
var matches = profileUtils.freeSessionGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
// cowriting genres
|
||||
profileUtils.cowritingGenres = function(genres) {
|
||||
var matches = $.grep(genres, function(g) {
|
||||
return g.player_type === USER_TYPE && g.genre_type === COWRITING_GENRE_TYPE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.cowritingGenreList = function(genres) {
|
||||
var matches = profileUtils.cowritingGenres(genres);
|
||||
return buildGenreList(matches);
|
||||
}
|
||||
|
||||
profileUtils.jamkazamSamples = function(samples) {
|
||||
var matches = $.grep(samples, function(s) {
|
||||
return s.service_type === JAMKAZAM;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.soundCloudSamples = function(samples) {
|
||||
var matches = $.grep(samples, function(s) {
|
||||
return s.service_type === SOUNDCLOUD;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
profileUtils.youTubeSamples = function(samples) {
|
||||
var matches = $.grep(samples, function(s) {
|
||||
return s.service_type === YOUTUBE;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
})(window, jQuery);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -34,6 +34,9 @@
|
|||
var sessionPageEnterTimeout = null;
|
||||
var startTime = null;
|
||||
var joinDeferred = null;
|
||||
var previousBackingTracks = [];
|
||||
var openBackingTrack = null;
|
||||
var shownAudioMediaMixerHelp = false;
|
||||
|
||||
var mixerMode = MIX_MODES.PERSONAL;
|
||||
|
||||
|
|
@ -67,7 +70,7 @@
|
|||
function isPlayingRecording() {
|
||||
// this is the server's state; there is no guarantee that the local tracks
|
||||
// requested from the backend will have corresponding track information
|
||||
return currentSession && currentSession.claimed_recording;
|
||||
return !!(currentSession && currentSession.claimed_recording);
|
||||
}
|
||||
|
||||
function recordedTracks() {
|
||||
|
|
@ -79,6 +82,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
function recordedBackingTracks() {
|
||||
if(currentSession && currentSession.claimed_recording) {
|
||||
return currentSession.claimed_recording.recording.recorded_backing_tracks
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function backingTracks() {
|
||||
var backingTracks = []
|
||||
// this may be wrong if we loosen the idea that only one person can have a backing track open.
|
||||
// but for now, the 1st person we find with a backing track open is all there is to find...
|
||||
context._.each(participants(), function(participant) {
|
||||
if(participant.backing_tracks.length > 0) {
|
||||
backingTracks = participant.backing_tracks;
|
||||
return false; // break
|
||||
}
|
||||
})
|
||||
return backingTracks;
|
||||
}
|
||||
|
||||
function jamTracks() {
|
||||
if(currentSession && currentSession.jam_track) {
|
||||
return currentSession.jam_track.tracks
|
||||
|
|
@ -88,6 +113,27 @@
|
|||
}
|
||||
}
|
||||
|
||||
function backingTrack() {
|
||||
if(currentSession) {
|
||||
// TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668
|
||||
return {
|
||||
path: currentSession.backing_track_path
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function metronomeActive() {
|
||||
if(currentSession) {
|
||||
return currentSession.metronome_active
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function creatorId() {
|
||||
if(!currentSession) {
|
||||
throw "creator is not known"
|
||||
|
|
@ -303,6 +349,9 @@
|
|||
}
|
||||
currentSessionId = null;
|
||||
currentParticipants = {}
|
||||
previousBackingTracks = []
|
||||
openBackingTrack = null
|
||||
shownAudioMediaMixerHelp = false
|
||||
}
|
||||
|
||||
// you should only update currentSession with this function
|
||||
|
|
@ -321,6 +370,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
function updateSession(response) {
|
||||
updateSessionInfo(response, null, true);
|
||||
}
|
||||
|
||||
function updateSessionInfo(response, callback, force) {
|
||||
if(force === true || currentTrackChanges < response.track_changes_counter) {
|
||||
logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter)
|
||||
currentTrackChanges = response.track_changes_counter;
|
||||
sendClientParticipantChanges(currentSession, response);
|
||||
updateCurrentSession(response);
|
||||
if(callback != null) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the session data from the REST server, calling
|
||||
* the provided callback when complete.
|
||||
|
|
@ -344,18 +412,7 @@
|
|||
type: "GET",
|
||||
url: url,
|
||||
success: function(response) {
|
||||
if(force === true || currentTrackChanges < response.track_changes_counter) {
|
||||
logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter)
|
||||
currentTrackChanges = response.track_changes_counter;
|
||||
sendClientParticipantChanges(currentSession, response);
|
||||
updateCurrentSession(response);
|
||||
if(callback != null) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter);
|
||||
}
|
||||
updateSessionInfo(response, callback, force);
|
||||
},
|
||||
error: function(jqXHR) {
|
||||
if(jqXHR.status != 404) {
|
||||
|
|
@ -532,9 +589,49 @@
|
|||
return mixerMode == MIX_MODES.PERSONAL;
|
||||
}
|
||||
|
||||
function getMixMode() {
|
||||
return mixerMode;
|
||||
}
|
||||
function getMixMode() {
|
||||
return mixerMode;
|
||||
}
|
||||
|
||||
function syncTracks(backingTracks) {
|
||||
// double check that we are in session, since a bunch could have happened since then
|
||||
if(!inSession()) {
|
||||
logger.debug("dropping queued up sync tracks because no longer in session");
|
||||
return null;
|
||||
}
|
||||
|
||||
// this is a local change to our tracks. we need to tell the server about our updated track information
|
||||
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
|
||||
|
||||
// backingTracks can be passed in as an optimization, so that we don't hit the backend excessively
|
||||
if(backingTracks === undefined ) {
|
||||
backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient);
|
||||
}
|
||||
|
||||
// create a trackSync request based on backend data
|
||||
var syncTrackRequest = {};
|
||||
syncTrackRequest.client_id = app.clientId;
|
||||
syncTrackRequest.tracks = inputTracks;
|
||||
syncTrackRequest.backing_tracks = backingTracks;
|
||||
syncTrackRequest.id = id();
|
||||
|
||||
return rest.putTrackSyncChange(syncTrackRequest)
|
||||
.done(function() {
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status != 404) {
|
||||
app.notify({
|
||||
"title": "Can't Sync Local Tracks",
|
||||
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.debug("Unable to sync local tracks because session is gone.")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function onWebsocketDisconnected(in_error) {
|
||||
// kill the streaming of the session immediately
|
||||
|
|
@ -678,45 +775,25 @@
|
|||
// wait until we are fully in session before trying to sync tracks to server
|
||||
if(joinDeferred) {
|
||||
joinDeferred.done(function() {
|
||||
|
||||
// double check that we are in session, since a bunch could have happened since then
|
||||
if(!inSession()) {
|
||||
logger.debug("dropping queued up sync tracks because no longer in session");
|
||||
return;
|
||||
}
|
||||
|
||||
// this is a local change to our tracks. we need to tell the server about our updated track information
|
||||
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
|
||||
|
||||
// create a trackSync request based on backend data
|
||||
var syncTrackRequest = {};
|
||||
syncTrackRequest.client_id = app.clientId;
|
||||
syncTrackRequest.tracks = inputTracks;
|
||||
syncTrackRequest.id = id();
|
||||
|
||||
rest.putTrackSyncChange(syncTrackRequest)
|
||||
.done(function() {
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status != 404) {
|
||||
app.notify({
|
||||
"title": "Can't Sync Local Tracks",
|
||||
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.debug("Unable to sync local tracks because session is gone.")
|
||||
}
|
||||
|
||||
})
|
||||
syncTracks();
|
||||
})
|
||||
}
|
||||
|
||||
}, 100);
|
||||
}
|
||||
else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) {
|
||||
refreshCurrentSession(true);
|
||||
|
||||
var backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient);
|
||||
|
||||
// the way we know if backing tracks changes, or recordings are opened, is via this event.
|
||||
// but we want to report to the user when backing tracks change; so we need to detect change on our own
|
||||
if(previousBackingTracks != backingTracks) {
|
||||
logger.debug("backing tracks changed")
|
||||
syncTracks(backingTracks);
|
||||
}
|
||||
else {
|
||||
refreshCurrentSession(true);
|
||||
}
|
||||
}
|
||||
else if(inSession() && (text == 'Global Peer Input Mixer Mode')) {
|
||||
setMixerMode(MIX_MODES.MASTER);
|
||||
|
|
@ -729,6 +806,10 @@
|
|||
// Public interface
|
||||
this.id = id;
|
||||
this.start = start;
|
||||
this.backingTrack = backingTrack;
|
||||
this.backingTracks = backingTracks;
|
||||
this.recordedBackingTracks = recordedBackingTracks;
|
||||
this.metronomeActive = metronomeActive;
|
||||
this.setUserTracks = setUserTracks;
|
||||
this.recordedTracks = recordedTracks;
|
||||
this.jamTracks = jamTracks;
|
||||
|
|
@ -736,6 +817,7 @@
|
|||
this.joinSession = joinSession;
|
||||
this.leaveCurrentSession = leaveCurrentSession;
|
||||
this.refreshCurrentSession = refreshCurrentSession;
|
||||
this.updateSession = updateSession;
|
||||
this.subscribe = subscribe;
|
||||
this.participantForClientId = participantForClientId;
|
||||
this.isPlayingRecording = isPlayingRecording;
|
||||
|
|
@ -767,6 +849,19 @@
|
|||
this.getParticipant = function(clientId) {
|
||||
return participantsEverSeen[clientId]
|
||||
};
|
||||
this.setBackingTrack = function(backingTrack) {
|
||||
openBackingTrack = backingTrack;
|
||||
};
|
||||
this.getBackingTrack = function() {
|
||||
return openBackingTrack;
|
||||
};
|
||||
this.hasShownAudioMediaMixerHelp = function() {
|
||||
return shownAudioMediaMixerHelp;
|
||||
}
|
||||
this.markShownAudioMediaMixerHelp = function() {
|
||||
shownAudioMediaMixerHelp = true;
|
||||
}
|
||||
|
||||
|
||||
// call to report if the current user was able to establish audio with the specified clientID
|
||||
this.setAudioEstablished = function(clientId, audioEstablished) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
context.JK.ShoppingCartScreen = function(app) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var jamTrackUtils = context.JK.JamTrackUtils;
|
||||
|
||||
var $screen = null;
|
||||
var $content = null;
|
||||
|
|
@ -16,6 +17,10 @@
|
|||
function afterShow(data) {
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
jamTrackUtils.checkShoppingCart();
|
||||
}
|
||||
|
||||
function events() {
|
||||
$screen.find("a.remove-cart").on('click', removeCart);
|
||||
$screen.find("a.proceed-checkout").on('click', proceedCheckout);
|
||||
|
|
@ -94,7 +99,8 @@
|
|||
function initialize() {
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow
|
||||
'afterShow': afterShow,
|
||||
'afterHide' : afterHide
|
||||
};
|
||||
app.bindScreen('shoppingCart', screenBindings);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,74 +11,157 @@ context.JK.SiteValidator = class SiteValidator
|
|||
@input_div = $(".site_validator#"+site_type+"_validator")
|
||||
@data_input = @input_div.find('input')
|
||||
@logger = context.JK.logger
|
||||
@site_status = null
|
||||
@spinner = @input_div.find('span.spinner-small')
|
||||
@checkmark = @input_div.find('.validate-checkmark')
|
||||
this.show_format_status()
|
||||
this.setSiteStatus(null)
|
||||
this.showFormatStatus()
|
||||
@is_rec_src = false
|
||||
@deferred_status_check = null
|
||||
@is_validating = false
|
||||
|
||||
init: () =>
|
||||
this.renderErrors({})
|
||||
@spinner.hide()
|
||||
validator = this
|
||||
@data_input.on 'blur', ->
|
||||
validator.did_blur()
|
||||
validator.didBlur()
|
||||
@data_input.on 'focus', ->
|
||||
validator.show_format_status()
|
||||
validator.showFormatStatus()
|
||||
@data_input.on 'change', ->
|
||||
@site_status = null
|
||||
|
||||
data_to_validate: () =>
|
||||
dataToValidate: () =>
|
||||
url = @data_input.val()
|
||||
if 0 < url.length
|
||||
url.substring(0,2000)
|
||||
else
|
||||
null
|
||||
|
||||
show_format_status: () =>
|
||||
data = this.data_to_validate()
|
||||
showFormatStatus: () =>
|
||||
data = this.dataToValidate()
|
||||
yn = true
|
||||
if data && 'url' == @site_type
|
||||
if data && ('url' == @site_type || @is_rec_src)
|
||||
regexp = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
|
||||
yn = regexp.test(this.data_to_validate())
|
||||
if yn
|
||||
@checkmark.show()
|
||||
else
|
||||
yn = regexp.test(this.dataToValidate())
|
||||
unless yn
|
||||
@checkmark.hide()
|
||||
yn
|
||||
|
||||
did_blur: () =>
|
||||
if this.show_format_status()
|
||||
this.validate_site()
|
||||
didBlur: () =>
|
||||
if this.showFormatStatus()
|
||||
this.validateSite()
|
||||
|
||||
validate_site: () =>
|
||||
@site_status = null
|
||||
validateSite: () =>
|
||||
unless data = this.dataToValidate()
|
||||
return null
|
||||
this.setSiteStatus(null)
|
||||
@spinner.show()
|
||||
@checkmark.hide()
|
||||
@rest.validateUrlSite(this.data_to_validate(), @site_type)
|
||||
.done(this.processSiteCheck)
|
||||
@rest.validateUrlSite(data, @site_type)
|
||||
.done(this.processSiteCheckSucceed)
|
||||
.fail(this.processSiteCheckFail)
|
||||
|
||||
processSiteCheck: (response) =>
|
||||
processSiteCheckSucceed: (response) =>
|
||||
@spinner.hide()
|
||||
|
||||
if 'Valid Site' == response.message
|
||||
@site_status = 'valid'
|
||||
this.setSiteStatus(true)
|
||||
this.renderErrors({})
|
||||
if @deferred_status_check
|
||||
@deferred_status_check.resolve()
|
||||
else
|
||||
@site_status = 'invalid'
|
||||
this.setSiteStatus(false)
|
||||
this.renderErrors(response)
|
||||
if @deferred_status_check
|
||||
@deferred_status_check.reject()
|
||||
@deferred_status_check = null
|
||||
@logger.debug("site_status = "+@site_status)
|
||||
|
||||
processSiteCheckFail: (response) =>
|
||||
@spinner.hide()
|
||||
@checkmark.hide()
|
||||
@logger.error("site check error")
|
||||
@site_status = 'invalid'
|
||||
this.setSiteStatus(false)
|
||||
if @deferred_status_check
|
||||
@deferred_status_check.reject()
|
||||
@deferred_status_check = null
|
||||
|
||||
setSiteStatus: (status) =>
|
||||
@site_status = status
|
||||
@spinner.hide()
|
||||
if true == status
|
||||
@checkmark.show()
|
||||
else
|
||||
@checkmark.hide()
|
||||
|
||||
siteIsValid: () =>
|
||||
this.setSiteStatus(true)
|
||||
|
||||
siteIsInvalid: () =>
|
||||
this.setSiteStatus(false)
|
||||
|
||||
renderErrors: (errors) =>
|
||||
errdiv = @input_div.find('.error')
|
||||
if errmsg = context.JK.format_errors("site", errors)
|
||||
@checkmark.hide()
|
||||
errdiv.show()
|
||||
errdiv.html(errmsg)
|
||||
else
|
||||
@checkmark.show()
|
||||
errdiv.hide()
|
||||
errdiv.html('')
|
||||
|
||||
state: () =>
|
||||
dfr = $.Deferred()
|
||||
if null == @site_status
|
||||
@deferred_status_check = dfr
|
||||
this.validateSite()
|
||||
else
|
||||
if true == @site_status
|
||||
dfr.resolve()
|
||||
else
|
||||
dfr.reject()
|
||||
return dfr.promise()
|
||||
|
||||
|
||||
context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator
|
||||
constructor: (site_type) ->
|
||||
super(site_type)
|
||||
@recording_sources = []
|
||||
@is_rec_src = true
|
||||
@add_btn = @input_div.find('a.add-recording-source')
|
||||
|
||||
init: (sources) =>
|
||||
super()
|
||||
if sources
|
||||
@recording_sources = sources
|
||||
@add_btn.on 'click', =>
|
||||
this.attemptAdd()
|
||||
|
||||
processSiteCheckSucceed: (response) =>
|
||||
super(response)
|
||||
@add_btn.removeClass('disabled')
|
||||
@recording_sources.push({ url: response.data, recording_id: response.recording_id })
|
||||
|
||||
processSiteCheckFail: (response) =>
|
||||
super(response)
|
||||
@add_btn.removeClass('disabled')
|
||||
|
||||
didBlur: () =>
|
||||
# do nothing, validate on add only
|
||||
|
||||
validateSite: () =>
|
||||
@add_btn.addClass('disabled')
|
||||
super()
|
||||
|
||||
attemptAdd: () =>
|
||||
if data = this.dataToValidate()
|
||||
unless this.containsRecordingUrl(data)
|
||||
this.validateSite()
|
||||
|
||||
removeRecordingId: (recording_id) =>
|
||||
start_len = @recording_sources.length
|
||||
@recording_sources = $.grep @recording_sources, (src_data) ->
|
||||
src_data['recording_id'] != recording_id
|
||||
start_len != @recording_sources.length
|
||||
|
||||
containsRecordingUrl: (url) =>
|
||||
vals = $.grep @recording_sources, (src_data) ->
|
||||
src_data['url'] == url
|
||||
0 < vals.length
|
||||
|
||||
|
|
|
|||
|
|
@ -27,16 +27,19 @@ context.JK.SyncViewer = class SyncViewer
|
|||
@list = @root.find('.list')
|
||||
@logList = @root.find('.log-list')
|
||||
@templateRecordedTrack = $('#template-sync-viewer-recorded-track')
|
||||
@templateRecordedBackingTrack = $('#template-sync-viewer-recorded-backing-track')
|
||||
@templateStreamMix = $('#template-sync-viewer-stream-mix')
|
||||
@templateMix = $('#template-sync-viewer-mix')
|
||||
@templateNoSyncs = $('#template-sync-viewer-no-syncs')
|
||||
@templateRecordingWrapperDetails = $('#template-sync-viewer-recording-wrapper-details')
|
||||
@templateHoverRecordedTrack = $('#template-sync-viewer-hover-recorded-track')
|
||||
@templateHoverRecordedBackingTrack = $('#template-sync-viewer-hover-recorded-backing-track')
|
||||
@templateHoverMix = $('#template-sync-viewer-hover-mix')
|
||||
@templateDownloadReset = $('#template-sync-viewer-download-progress-reset')
|
||||
@templateUploadReset = $('#template-sync-viewer-upload-progress-reset')
|
||||
@templateGenericCommand = $('#template-sync-viewer-generic-command')
|
||||
@templateRecordedTrackCommand = $('#template-sync-viewer-recorded-track-command')
|
||||
@templateRecordedBackingTrackCommand = $('#template-sync-viewer-recorded-backing-track-command')
|
||||
@templateLogItem = $('#template-sync-viewer-log-item')
|
||||
@tabSelectors = @root.find('.dialog-tabs .tab')
|
||||
@tabs = @root.find('.tab-content')
|
||||
|
|
@ -50,7 +53,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
them_upload_soon: 'them-upload-soon'
|
||||
missing: 'missing',
|
||||
me_uploaded: 'me-uploaded',
|
||||
them_uploaded: 'them-uploaded'
|
||||
them_uploaded: 'them-uploaded',
|
||||
not_mine: 'not-mine'
|
||||
}
|
||||
@clientStates = {
|
||||
unknown: 'unknown',
|
||||
|
|
@ -58,7 +62,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
hq: 'hq',
|
||||
sq: 'sq',
|
||||
missing: 'missing',
|
||||
discarded: 'discarded'
|
||||
discarded: 'discarded',
|
||||
not_mine: 'not-mine'
|
||||
}
|
||||
|
||||
throw "no sync-viewer" if not @root.exists()
|
||||
|
|
@ -329,12 +334,138 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$clientRetry.hide()
|
||||
$uploadRetry.hide()
|
||||
|
||||
updateBackingTrackState: ($track) =>
|
||||
clientInfo = $track.data('client-info')
|
||||
serverInfo = $track.data('server-info')
|
||||
myTrack = serverInfo.user.id == context.JK.currentUserId
|
||||
|
||||
# determine client state
|
||||
clientStateMsg = 'UNKNOWN'
|
||||
clientStateClass = 'unknown'
|
||||
clientState = @clientStates.unknown
|
||||
|
||||
if serverInfo.mine
|
||||
if serverInfo.download.should_download
|
||||
if serverInfo.download.too_many_downloads
|
||||
clientStateMsg = 'EXCESS DOWNLOADS'
|
||||
clientStateClass = 'error'
|
||||
clientState = @clientStates.too_many_uploads
|
||||
else
|
||||
if clientInfo?
|
||||
if clientInfo.local_state == 'HQ'
|
||||
clientStateMsg = 'HIGHEST QUALITY'
|
||||
clientStateClass = 'hq'
|
||||
clientState = @clientStates.hq
|
||||
else if clientInfo.local_state == 'MISSING'
|
||||
clientStateMsg = 'MISSING'
|
||||
clientStateClass = 'missing'
|
||||
clientState = @clientStates.missing
|
||||
else
|
||||
clientStateMsg = 'MISSING'
|
||||
clientStateClass = 'missing'
|
||||
clientState = @clientStates.missing
|
||||
else
|
||||
clientStateMsg = 'DISCARDED'
|
||||
clientStateClass = 'discarded'
|
||||
clientState = @clientStates.discarded
|
||||
else
|
||||
clientStateMsg = 'NOT MINE'
|
||||
clientStateClass = 'not_mine'
|
||||
clientState = @clientStates.not_mine
|
||||
|
||||
# determine upload state
|
||||
uploadStateMsg = 'UNKNOWN'
|
||||
uploadStateClass = 'unknown'
|
||||
uploadState = @uploadStates.unknown
|
||||
|
||||
if serverInfo.mine
|
||||
if !serverInfo.fully_uploaded
|
||||
if serverInfo.upload.too_many_upload_failures
|
||||
uploadStateMsg = 'UPLOAD FAILURE'
|
||||
uploadStateClass = 'error'
|
||||
uploadState = @uploadStates.too_many_upload_failures
|
||||
else
|
||||
if myTrack
|
||||
if clientInfo?
|
||||
if clientInfo.local_state == 'HQ'
|
||||
uploadStateMsg = 'PENDING UPLOAD'
|
||||
uploadStateClass = 'upload-soon'
|
||||
uploadState = @uploadStates.me_upload_soon
|
||||
else
|
||||
uploadStateMsg = 'MISSING'
|
||||
uploadStateClass = 'missing'
|
||||
uploadState = @uploadStates.missing
|
||||
else
|
||||
uploadStateMsg = 'MISSING'
|
||||
uploadStateClass = 'missing'
|
||||
uploadState = @uploadStates.missing
|
||||
else
|
||||
uploadStateMsg = 'PENDING UPLOAD'
|
||||
uploadStateClass = 'upload-soon'
|
||||
uploadState = @uploadStates.them_upload_soon
|
||||
else
|
||||
uploadStateMsg = 'UPLOADED'
|
||||
uploadStateClass = 'uploaded'
|
||||
if myTrack
|
||||
uploadState = @uploadStates.me_uploaded
|
||||
else
|
||||
uploadState = @uploadStates.them_uploaded
|
||||
else
|
||||
uploadStateMsg = 'NOT MINE'
|
||||
uploadStateClass = 'not_mine'
|
||||
uploadState = @uploadStates.not_mine
|
||||
|
||||
|
||||
$clientState = $track.find('.client-state')
|
||||
$clientStateMsg = $clientState.find('.msg')
|
||||
$clientStateProgress = $clientState.find('.progress')
|
||||
$uploadState = $track.find('.upload-state')
|
||||
$uploadStateMsg = $uploadState.find('.msg')
|
||||
$uploadStateProgress = $uploadState.find('.progress')
|
||||
|
||||
$clientState.removeClass('discarded missing hq unknown error not-mine').addClass(clientStateClass).attr('data-state', clientState).data('custom-class', clientStateClass)
|
||||
$clientStateMsg.text(clientStateMsg)
|
||||
$clientStateProgress.css('width', '0')
|
||||
$uploadState.removeClass('upload-soon error unknown missing uploaded not-mine').addClass(uploadStateClass).attr('data-state', uploadState).data('custom-class', uploadStateClass)
|
||||
$uploadStateMsg.text(uploadStateMsg)
|
||||
$uploadStateProgress.css('width', '0')
|
||||
|
||||
# this allows us to make styling decisions based on the combination of both client and upload state.
|
||||
$track.addClass("clientState-#{clientStateClass}").addClass("uploadState-#{uploadStateClass}")
|
||||
|
||||
$clientRetry = $clientState.find('.retry')
|
||||
$uploadRetry = $uploadState.find('.retry')
|
||||
|
||||
if gon.isNativeClient
|
||||
# handle client state
|
||||
|
||||
# only show RETRY button if you have a SQ or if it's missing, and it's been uploaded already
|
||||
if (clientState == @clientStates.missing) and (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded)
|
||||
$clientRetry.show()
|
||||
else
|
||||
$clientRetry.hide()
|
||||
|
||||
# only show RETRY button if you have the HQ track, it's your track, and the server doesn't yet have it
|
||||
if myTrack and @clientStates.hq and (uploadState == @uploadStates.error or uploadState == @uploadStates.me_upload_soon)
|
||||
$uploadRetry.show()
|
||||
else
|
||||
$uploadRetry.hide()
|
||||
else
|
||||
$clientRetry.hide()
|
||||
$uploadRetry.hide()
|
||||
|
||||
|
||||
associateClientInfo: (recording) =>
|
||||
for clientInfo in recording.local_tracks
|
||||
$track = @list.find(".recorded-track[data-recording-id='#{recording.recording_id}'][data-client-track-id='#{clientInfo.client_track_id}']")
|
||||
$track.data('client-info', clientInfo)
|
||||
$track.data('total-size', recording.size)
|
||||
|
||||
for clientInfo in recording.backing_tracks
|
||||
$track = @list.find(".recorded-backing-track[data-recording-id='#{recording.recording_id}'][data-client-track-id='#{clientInfo.client_track_id}']")
|
||||
$track.data('client-info', clientInfo)
|
||||
$track.data('total-size', recording.size)
|
||||
|
||||
$track = @list.find(".mix[data-recording-id='#{recording.recording_id}']")
|
||||
$track.data('client-info', recording.mix)
|
||||
$track.data('total-size', recording.size)
|
||||
|
|
@ -457,11 +588,77 @@ context.JK.SyncViewer = class SyncViewer
|
|||
uploadStateClass: uploadStateClass}
|
||||
{variable: 'data'})
|
||||
|
||||
onHoverOfStateIndicator: () ->
|
||||
displayBackingTrackHover: ($recordedTrack) =>
|
||||
$clientState = $recordedTrack.find('.client-state')
|
||||
$clientStateMsg = $clientState.find('.msg')
|
||||
clientStateClass = $clientState.data('custom-class')
|
||||
clientState = $clientState.attr('data-state')
|
||||
clientInfo = $recordedTrack.data('client-info')
|
||||
|
||||
$uploadState = $recordedTrack.find('.upload-state')
|
||||
$uploadStateMsg = $uploadState.find('.msg')
|
||||
uploadStateClass = $uploadState.data('custom-class')
|
||||
uploadState = $uploadState.attr('data-state')
|
||||
serverInfo = $recordedTrack.data('server-info')
|
||||
|
||||
# decide on special case strings first
|
||||
|
||||
summary = ''
|
||||
if clientState == @clientStates.not_mine && @uploadStates.them_uploaded
|
||||
# this is not our backing track
|
||||
summary = "#{serverInfo.user.name} opened this backing track. Due to legal concerns, we can not distribute it to you."
|
||||
else if clientState == @clientStates.not_mine && @uploadStates.them_upload_soon
|
||||
# this is not our backing track
|
||||
summary = "#{serverInfo.user.name} has not yet uploaded their backing track."
|
||||
else if clientState == @clientStates.missing && uploadState == @uploadStates.me_uploaded
|
||||
# we have no version of the track at all, and the other user has uploaded the HQ version... it's coming soon!
|
||||
summary = "You have previously uploaded the high-quality version of this track. JamKazam will soon restore it and then this backing track will no longer be missing."
|
||||
else if clientState == @clientStates.discarded && (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded)
|
||||
# we decided not to keep the recording... so it's important to clarify why they are seeing it at all
|
||||
summary = "When this recording was made, you elected to not keep it. JamKazam already uploaded your high-quality backing track for the recording, because at least one other person decided to keep the recording and needs your backing track to make a high-quality mix."
|
||||
else if clientState == @clientStates.discarded
|
||||
# we decided not to keep the recording... so it's important to clarify why they are seeing it at all
|
||||
summary = "When this recording was made, you elected to not keep it. JamKazam will still try to upload your high-quality backing track for the recording, because at least one other person decided to keep the recording and needs your backing track to make a high-quality mix."
|
||||
else if clientState == @clientStates.hq and ( uploadState == @uploadStates.me_uploaded )
|
||||
summary = "Both you and the JamKazam server have the high-quality version of this track. Once all the other tracks for this recording are also synchronized, then the final mix can be made."
|
||||
|
||||
clientStateDefinition = switch clientState
|
||||
when @clientStates.too_many_downloads then "This backing track has been downloaded an unusually large number of times. No more downloads are allowed."
|
||||
when @clientStates.hq then "HIGHEST QUALITY means you have the original version of this backing track."
|
||||
when @clientStates.missing then "MISSING means you do not have this backing track anymore."
|
||||
when @clientStates.discarded then "DISCARDED means you chose to not keep this recording when the recording was over."
|
||||
when @clientStates.not_mine then "NOT MINE means someone else opened and played this backing track."
|
||||
else 'There is no help for this state'
|
||||
|
||||
uploadStateDefinition = switch uploadState
|
||||
when @uploadStates.too_many_upload_failures then "Failed attempts at uploading this backing track has happened an unusually large times. No more uploads will be attempted."
|
||||
when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this backing track soon."
|
||||
when @uploadStates.them_up_soon then "PENDING UPLOAD means #{serverInfo.user.name} will upload this backing track soon."
|
||||
when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this backing track."
|
||||
when @uploadStates.them_uploaded then "UPLOADED means #{serverInfo.user.name} has already uploaded this backing track."
|
||||
when @uploadStates.missing then "MISSING means your JamKazam application does not have this backing track, and the server does not either."
|
||||
when @uploadStates.not_mine then "NOT MINE means someone else opened and played this backing track."
|
||||
|
||||
context._.template(@templateHoverRecordedBackingTrack.html(),
|
||||
{summary: summary,
|
||||
clientStateDefinition: clientStateDefinition,
|
||||
uploadStateDefinition: uploadStateDefinition,
|
||||
clientStateMsg: $clientStateMsg.text(),
|
||||
uploadStateMsg: $uploadStateMsg.text(),
|
||||
clientStateClass: clientStateClass,
|
||||
uploadStateClass: uploadStateClass}
|
||||
{variable: 'data'})
|
||||
|
||||
onTrackHoverOfStateIndicator: () ->
|
||||
$recordedTrack = $(this).closest('.recorded-track.sync')
|
||||
self = $recordedTrack.data('sync-viewer')
|
||||
self.displayTrackHover($recordedTrack)
|
||||
|
||||
onBackingTrackHoverOfStateIndicator: () ->
|
||||
$recordedTrack = $(this).closest('.recorded-backing-track.sync')
|
||||
self = $recordedTrack.data('sync-viewer')
|
||||
self.displayBackingTrackHover($recordedTrack)
|
||||
|
||||
onStreamMixHover: () ->
|
||||
$streamMix = $(this).closest('.stream-mix.sync')
|
||||
self = $streamMix.data('sync-viewer')
|
||||
|
|
@ -512,6 +709,39 @@ context.JK.SyncViewer = class SyncViewer
|
|||
|
||||
return false
|
||||
|
||||
retryDownloadRecordedBackingTrack: (e) =>
|
||||
$retry = $(e.target)
|
||||
$track = $retry.closest('.recorded-backing-track')
|
||||
serverInfo = $track.data('server-info')
|
||||
|
||||
console.log("track serverInfo", $track, serverInfo)
|
||||
this.sendCommand($retry, {
|
||||
type: 'recorded_backing_track',
|
||||
action: 'download'
|
||||
queue: 'download',
|
||||
recording_id: serverInfo.recording_id
|
||||
track_id: serverInfo.client_track_id
|
||||
})
|
||||
|
||||
return false
|
||||
|
||||
retryUploadRecordedBackingTrack: (e) =>
|
||||
$retry = $(e.target)
|
||||
$track = $retry.closest('.recorded-backing-track')
|
||||
serverInfo = $track.data('server-info')
|
||||
|
||||
console.log("track serverInfo", $track, serverInfo)
|
||||
|
||||
this.sendCommand($retry, {
|
||||
type: 'recorded_backing_track',
|
||||
action: 'upload'
|
||||
queue: 'upload',
|
||||
recording_id: serverInfo.recording_id
|
||||
track_id: serverInfo.client_track_id
|
||||
})
|
||||
|
||||
return false
|
||||
|
||||
createMix: (userSync) =>
|
||||
recordingInfo = null
|
||||
if userSync == 'fake'
|
||||
|
|
@ -548,8 +778,26 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$uploadStateRetry.click(this.retryUploadRecordedTrack)
|
||||
context.JK.bindHoverEvents($track)
|
||||
context.JK.bindInstrumentHover($track, {positions:['top'], shrinkToFit: true});
|
||||
context.JK.hoverBubble($clientState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']})
|
||||
context.JK.hoverBubble($uploadState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']})
|
||||
context.JK.hoverBubble($clientState, this.onTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']})
|
||||
context.JK.hoverBubble($uploadState, this.onTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']})
|
||||
$clientState.addClass('is-native-client') if gon.isNativeClient
|
||||
$uploadState.addClass('is-native-client') if gon.isNativeClient
|
||||
$track
|
||||
|
||||
createBackingTrack: (userSync) =>
|
||||
$track = $(context._.template(@templateRecordedBackingTrack.html(), userSync, {variable: 'data'}))
|
||||
$track.data('server-info', userSync)
|
||||
$track.data('sync-viewer', this)
|
||||
$clientState = $track.find('.client-state')
|
||||
$uploadState = $track.find('.upload-state')
|
||||
$clientStateRetry = $clientState.find('.retry')
|
||||
$clientStateRetry.click(this.retryDownloadRecordedBackingTrack)
|
||||
$uploadStateRetry = $uploadState.find('.retry')
|
||||
$uploadStateRetry.click(this.retryUploadRecordedBackingTrack)
|
||||
context.JK.bindHoverEvents($track)
|
||||
context.JK.bindInstrumentHover($track, {positions:['top'], shrinkToFit: true});
|
||||
context.JK.hoverBubble($clientState, this.onBackingTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']})
|
||||
context.JK.hoverBubble($uploadState, this.onBackingTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']})
|
||||
$clientState.addClass('is-native-client') if gon.isNativeClient
|
||||
$uploadState.addClass('is-native-client') if gon.isNativeClient
|
||||
$track
|
||||
|
|
@ -687,6 +935,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
for userSync in response.entries
|
||||
if userSync.type == 'recorded_track'
|
||||
@list.append(this.createTrack(userSync))
|
||||
if userSync.type == 'recorded_backing_track'
|
||||
@list.append(this.createBackingTrack(userSync))
|
||||
else if userSync.type == 'mix'
|
||||
@list.append(this.createMix(userSync))
|
||||
else if userSync.type == 'stream_mix'
|
||||
|
|
@ -707,6 +957,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
|
||||
for track in @list.find('.recorded-track.sync')
|
||||
this.updateTrackState($(track))
|
||||
for track in @list.find('.recorded-backing-track.sync')
|
||||
this.updateBackingTrackState($(track))
|
||||
for streamMix in @list.find('.stream-mix.sync')
|
||||
this.updateStreamMixState($(streamMix))
|
||||
|
||||
|
|
@ -726,6 +978,18 @@ context.JK.SyncViewer = class SyncViewer
|
|||
deferred.resolve(matchingTrack.data('server-info'))
|
||||
return deferred
|
||||
|
||||
resolveBackingTrack: (commandMetadata) =>
|
||||
recordingId = commandMetadata['recording_id']
|
||||
clientTrackId = commandMetadata['track_id']
|
||||
|
||||
matchingTrack = @list.find(".recorded-backing-track[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
|
||||
if matchingTrack.length == 0
|
||||
return @rest.getRecordedBackingTrack({recording_id: recordingId, track_id: clientTrackId})
|
||||
else
|
||||
deferred = $.Deferred();
|
||||
deferred.resolve(matchingTrack.data('server-info'))
|
||||
return deferred
|
||||
|
||||
renderFullUploadRecordedTrack: (serverInfo) =>
|
||||
$track = $(context._.template(@templateRecordedTrackCommand.html(), $.extend(serverInfo, {action:'UPLOADING'}), {variable: 'data'}))
|
||||
$busy = @uploadProgress.find('.busy')
|
||||
|
|
@ -738,6 +1002,18 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$busy.empty().append($track)
|
||||
@downloadProgress.find('.progress').css('width', '0%')
|
||||
|
||||
renderFullUploadRecordedBackingTrack: (serverInfo) =>
|
||||
$track = $(context._.template(@templateRecordedBackingTrackCommand.html(), $.extend(serverInfo, {action:'UPLOADING'}), {variable: 'data'}))
|
||||
$busy = @uploadProgress.find('.busy')
|
||||
$busy.empty().append($track)
|
||||
@uploadProgress.find('.progress').css('width', '0%')
|
||||
|
||||
renderFullDownloadRecordedBackingTrack: (serverInfo) =>
|
||||
$track = $(context._.template(@templateRecordedBackingTrackCommand.html(), $.extend(serverInfo, {action:'DOWNLOADING'}), {variable: 'data'}))
|
||||
$busy = @downloadProgress.find('.busy')
|
||||
$busy.empty().append($track)
|
||||
@downloadProgress.find('.progress').css('width', '0%')
|
||||
|
||||
# this will either show a generic placeholder, or immediately show the whole track
|
||||
renderDownloadRecordedTrack: (commandId, commandMetadata) =>
|
||||
# try to find the info in the list; if we can't find it, then resolve it
|
||||
|
|
@ -756,6 +1032,23 @@ context.JK.SyncViewer = class SyncViewer
|
|||
|
||||
deferred.done(this.renderFullUploadRecordedTrack).fail(()=> @logger.error("unable to fetch recorded_track info") )
|
||||
|
||||
# this will either show a generic placeholder, or immediately show the whole track
|
||||
renderDownloadRecordedBackingTrack: (commandId, commandMetadata) =>
|
||||
# try to find the info in the list; if we can't find it, then resolve it
|
||||
deferred = this.resolveBackingTrack(commandMetadata)
|
||||
if deferred.state() == 'pending'
|
||||
this.renderGeneric(commandId, 'download', commandMetadata)
|
||||
|
||||
deferred.done(this.renderFullDownloadRecordedBackingTrack).fail(()=> @logger.error("unable to fetch recorded_backing_track info") )
|
||||
|
||||
renderUploadRecordedBackingTrack: (commandId, commandMetadata) =>
|
||||
# try to find the info in the list; if we can't find it, then resolve it
|
||||
deferred = this.resolveBackingTrack(commandMetadata)
|
||||
if deferred.state() == 'pending'
|
||||
this.renderGeneric(commandId, 'upload', commandMetadata)
|
||||
|
||||
deferred.done(this.renderFullUploadRecordedBackingTrack).fail(()=> @logger.error("unable to fetch recorded_backing_track info") )
|
||||
|
||||
renderGeneric: (commandId, category, commandMetadata) =>
|
||||
commandMetadata.displayType = this.displayName(commandMetadata)
|
||||
|
||||
|
|
@ -794,6 +1087,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
@downloadProgress.addClass('busy')
|
||||
if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'download'
|
||||
this.renderDownloadRecordedTrack(commandId, commandMetadata)
|
||||
else if commandMetadata.type == 'recorded_backing_track' and commandMetadata.action == 'download'
|
||||
this.renderDownloadRecordedBackingTrack(commandId, commandMetadata)
|
||||
else
|
||||
this.renderGeneric(commandId, 'download', commandMetadata)
|
||||
else if commandMetadata.queue == 'upload'
|
||||
|
|
@ -803,6 +1098,8 @@ context.JK.SyncViewer = class SyncViewer
|
|||
@uploadProgress.addClass('busy')
|
||||
if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'upload'
|
||||
this.renderUploadRecordedTrack(commandId, commandMetadata)
|
||||
else if commandMetadata.type == 'recorded_backing_track' and commandMetadata.action == 'upload'
|
||||
this.renderUploadRecordedBackingTrack(commandId, commandMetadata)
|
||||
else
|
||||
this.renderGeneric(commandId, 'upload', commandMetadata)
|
||||
else if commandMetadata.queue == 'cleanup'
|
||||
|
|
@ -820,6 +1117,12 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$track.data('server-info', userSync)
|
||||
this.associateClientInfo(clientRecordings.recordings[0])
|
||||
this.updateTrackState($track)
|
||||
else if userSync.type == 'recorded_backing_track'
|
||||
$track = @list.find(".sync[data-id='#{userSync.id}']")
|
||||
continue if $track.length == 0
|
||||
$track.data('server-info', userSync)
|
||||
this.associateClientInfo(clientRecordings.recordings[0])
|
||||
this.updateBackingTrackState($track)
|
||||
else if userSync.type == 'mix'
|
||||
# check if there is a virtual mix 1st; if so, update it
|
||||
$mix = @list.find(".mix.virtual[data-recording-id='#{userSync.recording.id}']")
|
||||
|
|
@ -839,20 +1142,6 @@ context.JK.SyncViewer = class SyncViewer
|
|||
updateSingleRecording: (recording_id) =>
|
||||
@rest.getUserSyncs({recording_id: recording_id}).done(this.renderSingleRecording)
|
||||
|
||||
updateSingleRecordedTrack: ($track) =>
|
||||
serverInfo = $track.data('server-info')
|
||||
@rest.getUserSync({user_sync_id: serverInfo.id})
|
||||
.done((userSync) =>
|
||||
# associate new server-info with this track
|
||||
$track.data('server-info', userSync)
|
||||
# associate new client-info with this track
|
||||
clientRecordings = context.jamClient.GetLocalRecordingState(recordings: [userSync.recording])
|
||||
this.associateClientInfo(clientRecordings.recordings[0])
|
||||
|
||||
this.updateTrackState($track)
|
||||
)
|
||||
.fail(@app.ajaxError)
|
||||
|
||||
updateProgressOnSync: ($track, queue, percentage) =>
|
||||
state = if queue == 'upload' then '.upload-state' else '.client-state'
|
||||
$progress = $track.find("#{state} .progress")
|
||||
|
|
@ -892,10 +1181,10 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$progress = @downloadProgress.find('.progress')
|
||||
$progress.css('width', percentage + '%')
|
||||
|
||||
if @downloadMetadata.type == 'recorded_track'
|
||||
if @downloadMetadata.type == 'recorded_track' or @downloadMetadata.type == 'recorded_backing_track'
|
||||
clientTrackId = @downloadMetadata['track_id']
|
||||
recordingId = @downloadMetadata['recording_id']
|
||||
$matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
|
||||
$matchingTrack = @list.find(".track-item.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
|
||||
if $matchingTrack.length > 0
|
||||
this.updateProgressOnSync($matchingTrack, 'download', percentage)
|
||||
|
||||
|
|
@ -903,10 +1192,10 @@ context.JK.SyncViewer = class SyncViewer
|
|||
$progress = @uploadProgress.find('.progress')
|
||||
$progress.css('width', percentage + '%')
|
||||
|
||||
if @uploadMetadata.type == 'recorded_track' and @uploadMetadata.action == 'upload'
|
||||
if (@uploadMetadata.type == 'recorded_track' or @uploadMetadata.type == 'recorded_backing_track') and @uploadMetadata.action == 'upload'
|
||||
clientTrackId = @uploadMetadata['track_id']
|
||||
recordingId = @uploadMetadata['recording_id']
|
||||
$matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
|
||||
$matchingTrack = @list.find(".track-item.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']")
|
||||
if $matchingTrack.length > 0
|
||||
this.updateProgressOnSync($matchingTrack, 'upload', percentage)
|
||||
else if @uploadMetadata.type == 'stream_mix' and @uploadMetadata.action == 'upload'
|
||||
|
|
@ -977,15 +1266,15 @@ context.JK.SyncViewer = class SyncViewer
|
|||
this.logResult(data.commandMetadata, false, data.commandReason, true)
|
||||
|
||||
displayName: (metadata) =>
|
||||
if metadata.type == 'recorded_track' && metadata.action == 'download'
|
||||
if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'download'
|
||||
return 'DOWNLOADING TRACK'
|
||||
else if metadata.type == 'recorded_track' && metadata.action == 'upload'
|
||||
else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'upload'
|
||||
return 'UPLOADING TRACK'
|
||||
else if metadata.type == 'mix' && metadata.action == 'download'
|
||||
return 'DOWNLOADING MIX'
|
||||
else if metadata.type == 'recorded_track' && metadata.action == 'convert'
|
||||
else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'convert'
|
||||
return 'COMPRESSING TRACK'
|
||||
else if metadata.type == 'recorded_track' && metadata.action == 'delete'
|
||||
else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'delete'
|
||||
return 'CLEANUP TRACK'
|
||||
else if metadata.type == 'stream_mix' && metadata.action == 'upload'
|
||||
return 'UPLOADING STREAM MIX'
|
||||
|
|
|
|||
|
|
@ -30,6 +30,28 @@
|
|||
return tracks;
|
||||
},
|
||||
|
||||
getBackingTracks: function(jamClient) {
|
||||
var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4);
|
||||
|
||||
console.log("mediaTracks", mediaTracks)
|
||||
|
||||
var backingTracks = []
|
||||
context._.each(mediaTracks, function(mediaTrack) {
|
||||
// the check for 'not managed' means this is not a track opened by a recording, basically
|
||||
// we do not try and sync these sorts of backing tracks to the server, because they
|
||||
// are already encompassed by
|
||||
if(mediaTrack.media_type == "BackingTrack" && !mediaTrack.managed) {
|
||||
var track = {};
|
||||
track.client_track_id = mediaTrack.persisted_track_id;
|
||||
track.client_resource_id = mediaTrack.rid;
|
||||
track.filename = mediaTrack.filename;
|
||||
backingTracks.push(track);
|
||||
}
|
||||
})
|
||||
|
||||
return backingTracks;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function resolves which tracks to configure for a user
|
||||
* when creating or joining a session. By default, tracks are pulled
|
||||
|
|
|
|||
|
|
@ -987,6 +987,15 @@
|
|||
return hasFlash;
|
||||
}
|
||||
|
||||
context.JK.getNameOfFile = function(filename) {
|
||||
|
||||
var index = filename.lastIndexOf('/');
|
||||
if(index == -1) {
|
||||
index = filename.lastIndexOf('\\');
|
||||
}
|
||||
return index == -1 ? filename : filename.substring(index + 1, filename.length)
|
||||
}
|
||||
|
||||
context.JK.hasOneConfiguredDevice = function () {
|
||||
var result = context.jamClient.FTUEGetGoodConfigurationList();
|
||||
logger.debug("hasOneConfiguredDevice: ", result);
|
||||
|
|
|
|||
|
|
@ -208,6 +208,10 @@ $fair: #cc9900;
|
|||
background-color: $error;
|
||||
}
|
||||
|
||||
&.not_mine {
|
||||
background-color: $good;
|
||||
}
|
||||
|
||||
&.discarded {
|
||||
background-color: $unknown;
|
||||
}
|
||||
|
|
@ -252,6 +256,10 @@ $fair: #cc9900;
|
|||
background-color: $good;
|
||||
}
|
||||
|
||||
&.not_mine {
|
||||
background-color: $good;
|
||||
}
|
||||
|
||||
.retry {
|
||||
display:none;
|
||||
position:absolute;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ body.jam, body.web, .dialog{
|
|||
}
|
||||
}
|
||||
|
||||
.help-hover-recorded-tracks, .help-hover-stream-mix {
|
||||
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {
|
||||
|
||||
font-size:12px;
|
||||
padding:5px;
|
||||
|
|
|
|||
|
|
@ -19,22 +19,26 @@
|
|||
}
|
||||
.profile-header {
|
||||
padding:10px 20px;
|
||||
// height:120px;
|
||||
}
|
||||
|
||||
.profile-header h2 {
|
||||
font-weight:200;
|
||||
font-size: 28px;
|
||||
float:left;
|
||||
margin: 0px 150px 0px 0px;
|
||||
margin: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.profile-status {
|
||||
.profile-about-right .section-header {
|
||||
font-weight:600;
|
||||
font-size:18px;
|
||||
float:left;
|
||||
margin: 0px 0px 10px 0px;
|
||||
}
|
||||
|
||||
.profile-details {
|
||||
font-size:12px;
|
||||
float:left;
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
line-height:30px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.profile-photo {
|
||||
|
|
@ -159,7 +163,7 @@
|
|||
font-weight:600;
|
||||
}
|
||||
|
||||
#profile-bands .when-empty {
|
||||
#bands-content .when-empty {
|
||||
margin: 0px;
|
||||
padding:0px;
|
||||
display:block;
|
||||
|
|
@ -170,7 +174,7 @@
|
|||
line-height: 150%;
|
||||
}
|
||||
|
||||
#profile-bands .when-empty a {
|
||||
#bands-content .when-empty a {
|
||||
text-decoration: underline;
|
||||
color: inherit;
|
||||
}
|
||||
|
|
@ -330,7 +334,7 @@
|
|||
display:none;
|
||||
}
|
||||
|
||||
#profile-history {
|
||||
#history-content {
|
||||
padding:0 10px 0 20px;
|
||||
width:100%;
|
||||
position:relative;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
.track {
|
||||
width:70px;
|
||||
height:290px;
|
||||
height:300px;
|
||||
display:inline-block;
|
||||
margin-right:8px;
|
||||
position:relative;
|
||||
|
|
@ -50,6 +50,9 @@
|
|||
vertical-align:top;
|
||||
}
|
||||
|
||||
.session-recordedtracks-container {
|
||||
//display: block;
|
||||
}
|
||||
|
||||
.recording-controls {
|
||||
display:none;
|
||||
|
|
@ -74,17 +77,23 @@
|
|||
left:5px;
|
||||
}
|
||||
|
||||
.open-media-file-header {
|
||||
.open-media-file-header, .use-metronome-header {
|
||||
font-size:16px;
|
||||
line-height:100%;
|
||||
margin:0;
|
||||
float:left;
|
||||
|
||||
img {
|
||||
position:relative;
|
||||
top:3px;
|
||||
}
|
||||
}
|
||||
.open-media-file-header {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.use-metronome-header {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.open-media-file-options {
|
||||
font-size:16px;
|
||||
|
|
@ -110,8 +119,21 @@
|
|||
|
||||
.session-recording-name-wrapper{
|
||||
position:relative;
|
||||
white-space:nowrap;
|
||||
display:none;
|
||||
white-space:normal;
|
||||
display:none;
|
||||
|
||||
.session-recording-name {
|
||||
position:relative;
|
||||
margin-top:9px;
|
||||
margin-bottom:8px;
|
||||
font-size:16px;
|
||||
height: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
display: inline-block;
|
||||
width:60%;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
|
||||
.session-add {
|
||||
margin-top:9px;
|
||||
|
|
@ -126,13 +148,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.session-recording-name {
|
||||
width:60%;
|
||||
overflow:hidden;
|
||||
margin-top:9px;
|
||||
margin-bottom:8px;
|
||||
font-size:16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -201,6 +216,9 @@ table.vu td {
|
|||
position: absolute;
|
||||
text-align:center;
|
||||
width: 55px;
|
||||
height: 15px;
|
||||
min-height: 11px;
|
||||
max-height: 33px;
|
||||
max-width: 55px;
|
||||
white-space:normal;
|
||||
top: 3px;
|
||||
|
|
@ -208,6 +226,7 @@ table.vu td {
|
|||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
|
||||
.track-close {
|
||||
|
|
@ -319,8 +338,6 @@ table.vu td {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.session-add {
|
||||
margin-top:9px;
|
||||
margin-bottom:8px;
|
||||
|
|
@ -345,7 +362,7 @@ table.vu td {
|
|||
overflow-x:auto;
|
||||
overflow-y:hidden;
|
||||
width:100%;
|
||||
height:340px;
|
||||
height:370px;
|
||||
float:left;
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
|
@ -482,12 +499,9 @@ table.vu td {
|
|||
.track-gain {
|
||||
position:absolute;
|
||||
width:28px;
|
||||
height:83px;
|
||||
height:63px;
|
||||
top:138px;
|
||||
left:23px;
|
||||
background-image:url('/assets/content/bkg_gain_slider.png');
|
||||
background-repeat:repeat-y;
|
||||
background-position:bottom;
|
||||
}
|
||||
|
||||
.track-gain-wrapper {
|
||||
|
|
@ -514,6 +528,45 @@ table.vu td {
|
|||
height: 18px;
|
||||
background-image:url('/assets/content/icon_mute.png');
|
||||
background-repeat:no-repeat;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.track-icon-loop {
|
||||
cursor: pointer;
|
||||
position:absolute;
|
||||
top:250px;
|
||||
left:11px;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
font-weight: bold;
|
||||
|
||||
.icheckbox_minimal {
|
||||
top:5px;
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
|
||||
.metronome-selects {
|
||||
position: absolute;
|
||||
width: 52px;
|
||||
top:252px;
|
||||
left: 10px;
|
||||
|
||||
height: 18px;
|
||||
text-align: center;
|
||||
//display: block;
|
||||
//padding: 4px;
|
||||
|
||||
select.metronome-select {
|
||||
position: relative;
|
||||
padding: 4px 0px 4px 0px;
|
||||
margin: 0;
|
||||
width: 100% !important;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.track-icon-mute.muted {
|
||||
|
|
@ -524,12 +577,12 @@ table.vu td {
|
|||
}
|
||||
|
||||
.session-livetracks .track-icon-mute, .session-recordings .track-icon-mute {
|
||||
top:245px;
|
||||
top:225px;
|
||||
}
|
||||
|
||||
.track-icon-settings {
|
||||
position:absolute;
|
||||
top:255px;
|
||||
top:235px;
|
||||
left:28px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@import "client/common";
|
||||
|
||||
|
||||
table.findsession-table, table.local-recordings, table.open-jam-tracks, #account-session-detail {
|
||||
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks, #account-session-detail {
|
||||
|
||||
.latency-unacceptable {
|
||||
width: 50px;
|
||||
|
|
@ -64,7 +64,7 @@ table.findsession-table, table.local-recordings, table.open-jam-tracks, #account
|
|||
text-align:center;
|
||||
}
|
||||
}
|
||||
table.findsession-table, table.local-recordings, table.open-jam-tracks {
|
||||
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks {
|
||||
width:98%;
|
||||
height:10%;
|
||||
font-size:11px;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
@import "client/common";
|
||||
|
||||
.site_validator {
|
||||
|
||||
.validator-input {
|
||||
float: left;
|
||||
}
|
||||
.validator-add-rec {
|
||||
float: left;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
padding: 5px 5px 5px 30px;
|
||||
float: left;
|
||||
}
|
||||
.validate-checkmark {
|
||||
|
|
@ -15,10 +23,8 @@
|
|||
background-size: 50% 50%;
|
||||
display:inline-block;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
margin-top: 0px;
|
||||
margin-left: 520px;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
margin-top: -40px;
|
||||
left: 0px;
|
||||
}
|
||||
.error {
|
||||
|
|
@ -26,8 +32,7 @@
|
|||
span.spinner-small {
|
||||
display:inline-block;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
margin-top: 0px;
|
||||
margin-left: 520px;
|
||||
position: relative;
|
||||
margin-top: -40px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
@import "client/common";
|
||||
|
||||
#open-backing-track-dialog {
|
||||
table.open-backing-tracks {
|
||||
tbody {
|
||||
tr:hover {
|
||||
background-color: #777;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
tr[data-local-state=MISSING], tr[data-local-state=PARTIALLY_MISSING] {
|
||||
background-color:#777;
|
||||
color:#aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.help-links {
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
margin: 0 auto;
|
||||
width: 70%;
|
||||
//left: 15%;
|
||||
font-size: 12px;
|
||||
padding-top:5px;
|
||||
|
||||
a {
|
||||
margin:0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.paginator-holder {
|
||||
padding-top:3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
class ApiBackingTracksController < ApiController
|
||||
|
||||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user
|
||||
|
||||
before_filter :lookup_recorded_backing_track, :only => [ :backing_track_silent ]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
tracks = [
|
||||
{:name=>'foo',:path=>"foobar.mp3", :length=>4283},
|
||||
{:name=>'bar',:path=>"foo.mp3",:length=>3257}
|
||||
]
|
||||
@backing_tracks, @next = tracks, nil
|
||||
render "api_backing_tracks/index", :layout => nil
|
||||
end
|
||||
|
||||
def backing_track_silent
|
||||
@recorded_backing_track.mark_silent
|
||||
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lookup_recorded_backing_track
|
||||
@recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
end # class ApiBackingTracksController
|
||||
|
|
@ -4,7 +4,7 @@ class ApiMusicSessionsController < ApiController
|
|||
|
||||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user, :except => [ :add_like, :show, :show_history, :add_session_info_comment ]
|
||||
before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close]
|
||||
before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close, :backing_track_open, :backing_track_close, :metronome_open, :metronome_close]
|
||||
skip_before_filter :api_signed_in_user, only: [:perf_upload]
|
||||
|
||||
respond_to :json
|
||||
|
|
@ -357,7 +357,7 @@ class ApiMusicSessionsController < ApiController
|
|||
end
|
||||
|
||||
def track_sync
|
||||
@tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks])
|
||||
@tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks], params[:backing_tracks])
|
||||
|
||||
unless @tracks.kind_of? Array
|
||||
# we have to do this because api_session_detail_url will fail with a bad @tracks
|
||||
|
|
@ -597,8 +597,44 @@ class ApiMusicSessionsController < ApiController
|
|||
respond_with_model(@music_session)
|
||||
end
|
||||
|
||||
def backing_track_open
|
||||
unless @music_session.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
private
|
||||
@backing_track_path = params[:backing_track_path]
|
||||
@music_session.open_backing_track(current_user, @backing_track_path)
|
||||
respond_with_model(@music_session)
|
||||
end
|
||||
|
||||
def backing_track_close
|
||||
unless @music_session.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
@music_session.close_backing_track()
|
||||
respond_with_model(@music_session)
|
||||
end
|
||||
|
||||
def metronome_open
|
||||
unless @music_session.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
@music_session.open_metronome(current_user)
|
||||
respond_with_model(@music_session)
|
||||
end
|
||||
|
||||
def metronome_close
|
||||
unless @music_session.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
@music_session.close_metronome()
|
||||
respond_with_model(@music_session)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lookup_session
|
||||
@music_session = ActiveMusicSession.find(params[:id])
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ class ApiRecordingsController < ApiController
|
|||
|
||||
before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ]
|
||||
before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
|
||||
before_filter :lookup_recorded_backing_track, :only => [ :backing_track_download, :backing_track_upload_next_part, :backing_track_upload_sign, :backing_track_upload_part_complete, :backing_track_upload_complete ]
|
||||
before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ]
|
||||
before_filter :lookup_stream_mix, :only => [ :upload_next_part_stream_mix, :upload_sign_stream_mix, :upload_part_complete_stream_mix, :upload_complete_stream_mix ]
|
||||
|
||||
|
|
@ -43,7 +44,11 @@ class ApiRecordingsController < ApiController
|
|||
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id])
|
||||
end
|
||||
|
||||
def download
|
||||
def show_recorded_backing_track
|
||||
@recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id])
|
||||
end
|
||||
|
||||
def download # track
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.can_download?(current_user)
|
||||
|
||||
@recorded_track.current_user = current_user
|
||||
|
|
@ -58,6 +63,21 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def backing_track_download
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.can_download?(current_user)
|
||||
|
||||
@recorded_backing_track.current_user = current_user
|
||||
@recorded_backing_track.update_download_count
|
||||
|
||||
@recorded_backing_track.valid?
|
||||
if !@recorded_backing_track.errors.any?
|
||||
@recorded_backing_track.save!
|
||||
redirect_to @recorded_backing_track.sign_url
|
||||
else
|
||||
render :json => { :message => "download limit surpassed" }, :status => 404
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
music_session = ActiveMusicSession.find(params[:music_session_id])
|
||||
|
||||
|
|
@ -227,6 +247,61 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def backing_track_upload_next_part
|
||||
length = params[:length]
|
||||
md5 = params[:md5]
|
||||
|
||||
@recorded_backing_track.upload_next_part(length, md5)
|
||||
|
||||
if @recorded_backing_track.errors.any?
|
||||
|
||||
response.status = :unprocessable_entity
|
||||
# this is not typical, but please don't change this line unless you are sure it won't break anything
|
||||
# this is needed because after_rollback in the RecordedTrackObserver touches the model and something about it's
|
||||
# state doesn't cause errors to shoot out like normal.
|
||||
render :json => { :errors => @recorded_backing_track.errors }, :status => 422
|
||||
else
|
||||
result = {
|
||||
:part => @recorded_backing_track.next_part_to_upload,
|
||||
:offset => @recorded_backing_track.file_offset.to_s
|
||||
}
|
||||
|
||||
render :json => result, :status => 200
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def backing_track_upload_sign
|
||||
render :json => @recorded_backing_track.upload_sign(params[:md5]), :status => 200
|
||||
end
|
||||
|
||||
def backing_track_upload_part_complete
|
||||
part = params[:part]
|
||||
offset = params[:offset]
|
||||
|
||||
@recorded_backing_track.upload_part_complete(part, offset)
|
||||
|
||||
if @recorded_backing_track.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @recorded_backing_track
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
def backing_track_upload_complete
|
||||
@recorded_backing_track.upload_complete
|
||||
@recorded_backing_track.recording.upload_complete
|
||||
|
||||
if @recorded_backing_track.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @recorded_backing_track
|
||||
return
|
||||
else
|
||||
render :json => {}, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
# POST /api/recordings/:id/videos/:video_id/upload_sign
|
||||
def video_upload_sign
|
||||
length = params[:length]
|
||||
|
|
@ -314,6 +389,11 @@ class ApiRecordingsController < ApiController
|
|||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
def lookup_recorded_backing_track
|
||||
@recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.recording.has_access?(current_user)
|
||||
end
|
||||
|
||||
def lookup_stream_mix
|
||||
@quick_mix = QuickMix.find_by_recording_id_and_user_id!(params[:id], current_user.id)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @quick_mix.recording.has_access?(current_user)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require 'recurly_client'
|
||||
require 'jam_ruby/recurly_client'
|
||||
class ApiRecurlyController < ApiController
|
||||
before_filter :api_signed_in_user
|
||||
before_filter :create_client
|
||||
|
|
|
|||
|
|
@ -759,14 +759,23 @@ class ApiUsersController < ApiController
|
|||
site = params[:sitetype]
|
||||
if site.blank? || 'url'==site
|
||||
url = data
|
||||
elsif Utils.recording_source?(site)
|
||||
rec_id = Utils.extract_recording_id(site, data)
|
||||
if rec_id
|
||||
render json: { message: 'Valid Site', recording_id: rec_id, data: data }, status: 200
|
||||
return
|
||||
else
|
||||
render json: { message: 'Invalid Site', data: data, errors: { site: ["Could not detect recording identifier"] } }, status: 200
|
||||
return
|
||||
end
|
||||
else
|
||||
url = Utils.username_url(data, site)
|
||||
end
|
||||
unless url.blank?
|
||||
if errmsg = Utils.site_validator(url, site)
|
||||
render json: { message: 'Invalid Site', errors: { site: [errmsg] } }, status: 200
|
||||
render json: { message: 'Invalid Site', data: data, errors: { site: [errmsg] } }, status: 200
|
||||
else
|
||||
render json: { message: 'Valid Site' }, status: 200
|
||||
render json: { message: 'Valid Site', data: data }, status: 200
|
||||
end
|
||||
else
|
||||
render json: { message: "unknown validation for data '#{params[:data]}', site '#{params[:site]}'" }, status: :unprocessable_entity
|
||||
|
|
|
|||
|
|
@ -42,4 +42,8 @@ class SpikesController < ApplicationController
|
|||
render :layout => 'web'
|
||||
end
|
||||
|
||||
def recording_source
|
||||
render :layout => 'web'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,4 +91,10 @@ module SessionsHelper
|
|||
current_user.musician? ? 'Musician' : 'Fan'
|
||||
end
|
||||
end
|
||||
|
||||
def metronome_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
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
node :next do |page|
|
||||
@next
|
||||
end
|
||||
|
||||
node :backing_tracks do |page|
|
||||
@backing_tracks
|
||||
end
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
# I don't think I need to include URLs since that's handled by syncing. This is just to make the metadata
|
||||
# depictable.
|
||||
|
||||
# THIS IS USED DIRECTLY BY THE CLIENT. DO NOT CHANGE FORMAT UNLESS YOU VERIFY CLIENT FIRST. IN PARTICULAR RecordingFileStorage#getLocalRecordingState
|
||||
|
||||
object @claimed_recording
|
||||
|
||||
attributes :id, :name, :description, :is_public, :genre_id, :discarded
|
||||
|
|
@ -36,6 +38,18 @@ child(:recording => :recording) {
|
|||
}
|
||||
}
|
||||
|
||||
child(:recorded_backing_tracks => :recorded_backing_tracks) {
|
||||
attributes :id, :fully_uploaded, :client_track_id, :client_id, :filename
|
||||
|
||||
child(:user => :user) {
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url
|
||||
}
|
||||
|
||||
node :mine do |recorded_backing_track|
|
||||
recorded_backing_track.user == current_user
|
||||
end
|
||||
}
|
||||
|
||||
child(:comments => :comments) {
|
||||
attributes :comment, :created_at
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ if !current_user
|
|||
}
|
||||
else
|
||||
|
||||
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score
|
||||
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active
|
||||
|
||||
node :can_join do |session|
|
||||
session.can_join?(current_user, true)
|
||||
|
|
@ -54,6 +54,10 @@ else
|
|||
child(:tracks => :tracks) {
|
||||
attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :client_resource_id, :updated_at
|
||||
}
|
||||
|
||||
child(:backing_tracks => :backing_tracks) {
|
||||
attributes :id, :connection_id, :filename, :client_track_id, :client_resource_id, :updated_at
|
||||
}
|
||||
}
|
||||
|
||||
child({:invitations => :invitations}) {
|
||||
|
|
@ -114,6 +118,14 @@ else
|
|||
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url
|
||||
}
|
||||
}
|
||||
|
||||
child(:recorded_backing_tracks => :recorded_backing_tracks) {
|
||||
attributes :id, :fully_uploaded, :client_track_id, :client_id, :filename
|
||||
|
||||
child(:user => :user) {
|
||||
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ child(:recorded_tracks => :recorded_tracks) {
|
|||
end
|
||||
}
|
||||
|
||||
child(:recorded_backing_tracks => :recorded_backing_tracks) {
|
||||
node do |recorded_backing_track|
|
||||
partial("api_recordings/show_recorded_backing_track", :object => recorded_backing_track)
|
||||
end
|
||||
}
|
||||
|
||||
child(:comments => :comments) {
|
||||
attributes :comment, :created_at
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
object @recorded_backing_track
|
||||
|
||||
attributes :id, :fully_uploaded, :client_track_id, :client_id, :recording_id, :filename
|
||||
|
||||
node :mine do |recorded_backing_track|
|
||||
recorded_backing_track.user == current_user
|
||||
end
|
||||
|
||||
child(:user => :user) {
|
||||
attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ glue :recorded_track do
|
|||
partial("api_recordings/show", :object => recorded_track.recording)
|
||||
end
|
||||
|
||||
|
||||
node :upload do |recorded_track|
|
||||
{
|
||||
should_upload: true,
|
||||
|
|
@ -35,6 +34,45 @@ glue :recorded_track do
|
|||
|
||||
end
|
||||
|
||||
|
||||
glue :recorded_backing_track do
|
||||
|
||||
@object.current_user = current_user
|
||||
|
||||
node :type do |i|
|
||||
'recorded_backing_track'
|
||||
end
|
||||
|
||||
attributes :id, :recording_id, :client_id, :track_id, :client_track_id, :md5, :length, :download_count, :fully_uploaded, :upload_failures, :part_failures, :created_at, :filename
|
||||
|
||||
node :user do |recorded_backing_track|
|
||||
partial("api_users/show_minimal", :object => recorded_backing_track.user)
|
||||
end
|
||||
|
||||
node :recording do |recorded_backing_track|
|
||||
partial("api_recordings/show", :object => recorded_backing_track.recording)
|
||||
end
|
||||
|
||||
node :mine do |recorded_backing_track|
|
||||
recorded_backing_track.user == current_user
|
||||
end
|
||||
|
||||
node :upload do |recorded_backing_track|
|
||||
{
|
||||
should_upload: true,
|
||||
too_many_upload_failures: recorded_backing_track.too_many_upload_failures?
|
||||
}
|
||||
end
|
||||
|
||||
node :download do |recorded_backing_track|
|
||||
{
|
||||
should_download: recorded_backing_track.can_download?(current_user),
|
||||
too_many_downloads: recorded_backing_track.too_many_downloads?
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
glue :mix do
|
||||
|
||||
@object.current_user = current_user
|
||||
|
|
|
|||
|
|
@ -17,4 +17,21 @@ end
|
|||
|
||||
child :genre_players => :genres do
|
||||
attributes :genre_id, :player_type, :genre_type
|
||||
end
|
||||
end
|
||||
|
||||
child :band_musicians => :bands do
|
||||
attributes :id, :name, :admin, :photo_url, :logo_url
|
||||
|
||||
child :genres => :genres do
|
||||
attributes :id, :description
|
||||
#partial('api_genres/index', :object => @user.bands.genres)
|
||||
end
|
||||
end
|
||||
|
||||
child :musician_instruments => :instruments do
|
||||
attributes :description, :proficiency_level, :priority, :instrument_id
|
||||
end
|
||||
|
||||
# child :genres do
|
||||
# attributes :description, :id
|
||||
# end
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<form id="account-edit-profile-form">
|
||||
|
||||
<h2>profile:</h2>
|
||||
<h2>edit profile: basics</h2>
|
||||
|
||||
<div class="location w30 left">
|
||||
<a href="#" class="avatar_large"><img src="{photoUrl}" id="profile-avatar" /></a>
|
||||
|
|
|
|||
|
|
@ -199,6 +199,10 @@ script type="text/template" id="template-help-media-controls-disabled"
|
|||
| Only the person who opened the recording can control the volume levels.
|
||||
| {% } %}
|
||||
|
||||
|
||||
script type="text/template" id="template-help-volume-media-mixers"
|
||||
| Audio files only expose both master and personal mix controls, so any change here will also affect everyone in the session.
|
||||
|
||||
script type="text/template" id="template-help-downloaded-jamtrack"
|
||||
.downloaded-jamtrack
|
||||
p When a JamTrack is first purchased, a user-specific version of it is created on the server. Once it's ready, it's then downloaded to the client.
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
.jamtrack-price
|
||||
{{"$ " + data.jamtrack.price}}
|
||||
= "{% if (data.jamtrack.added_cart) { %}"
|
||||
%a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Added to Cart
|
||||
%a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Purchased
|
||||
= "{% } else { %}"
|
||||
%a.jamtrack-add-cart.button-orange{href: "#", "data-jamtrack-id" => "{{data.jamtrack.id}}"} Add to Cart
|
||||
= "{% }; %}"
|
||||
|
|
|
|||
|
|
@ -1,159 +1,271 @@
|
|||
<!-- Profile -->
|
||||
<div layout="screen" layout-id="profile" layout-arg="id" class="screen secondary" id="user-profile">
|
||||
<div class="content-head">
|
||||
<div class="content-icon">
|
||||
<%= image_tag "content/icon_profile.png", :size => "19x19" %>
|
||||
<div class="content-head">
|
||||
<div class="content-icon">
|
||||
<%= image_tag "content/icon_profile.png", :size => "19x19" %>
|
||||
</div>
|
||||
|
||||
<h1><span id="type-label">musician</span> profile</h1>
|
||||
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<form id="profile-form" class="inner-content">
|
||||
<div class="profile-header profile-head">
|
||||
|
||||
<div class="left">
|
||||
<h2 id="username"></h2>
|
||||
<%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-edit", :class => "button-orange") %>
|
||||
</div>
|
||||
|
||||
<h1><span id="profile-type-label">musician</span> profile</h1>
|
||||
<!-- action buttons -->
|
||||
<div class="right">
|
||||
<a id="btn-add-friend" class="button-orange">ADD FRIEND</a>
|
||||
<a id="btn-follow-user" class="button-orange">FOLLOW</a>
|
||||
<a id="btn-message-user" class="button-orange">MESSAGE</a>
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
<div class="content-body">
|
||||
<form id="profile-form" class="inner-content">
|
||||
<div class="profile-header profile-head">
|
||||
<!-- avatar -->
|
||||
<div class="profile-photo">
|
||||
<div class="avatar-profile">
|
||||
<img id="avatar" width="200" height="200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- profile name -->
|
||||
<h2 id="profile-username"></h2>
|
||||
<!-- profile navigation -->
|
||||
<div class="profile-nav">
|
||||
<a id="about-link" class="active">about</a>
|
||||
<a id="history-link">history</a>
|
||||
<a id="bands-link">bands</a>
|
||||
<a id="social-link">social</a>
|
||||
<a id="favorites-link" class="last">favorites</a>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
|
||||
<!-- profile status -->
|
||||
<div class="profile-status">
|
||||
</div>
|
||||
<div class="profile-body">
|
||||
<div id="about-content" class="profile-body-content">
|
||||
<!-- stats & location -->
|
||||
<div class="profile-wrapper">
|
||||
<div class="profile-about-left">
|
||||
<h3>Location:</h3><br />
|
||||
<span id="location"></span><br />
|
||||
<span id="age"></span><br /><br />
|
||||
<h3>Stats:</h3><br />
|
||||
<span id="friend-stats"></span><br />
|
||||
<span id="follower-stats"></span><br />
|
||||
<span id="session-stats"></span><br />
|
||||
<span id="recording-stats"></span><br />
|
||||
<span id="following-stats"></span><br />
|
||||
<span id="favorite-stats"></span><br />
|
||||
</div>
|
||||
<div class="profile-about-right">
|
||||
|
||||
<div class="section-header">Bio</div>
|
||||
<br clear="all" />
|
||||
|
||||
<div class="no-bio">
|
||||
Not specified.
|
||||
</div>
|
||||
|
||||
<div><a href="/client#/account/profile" class="enter-bio">Edit Bio</a></div>
|
||||
<br clear="all" />
|
||||
|
||||
<!-- action buttons -->
|
||||
<div class="right">
|
||||
<a id="btn-add-friend" class="button-orange">ADD FRIEND</a>
|
||||
<a id="btn-follow-user" class="button-orange">FOLLOW</a>
|
||||
<a id="btn-message-user" class="button-orange">MESSAGE</a>
|
||||
<%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-profile-edit", :class => "button-orange") %>
|
||||
</div>
|
||||
<br clear="all" /><br />
|
||||
|
||||
<!-- avatar -->
|
||||
<div class="profile-photo">
|
||||
<div class="avatar-profile">
|
||||
<img id="profile-avatar" width="200" height="200" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- profile navigation -->
|
||||
<div class="profile-nav">
|
||||
<a id="profile-about-link" class="active">about</a>
|
||||
<a id="profile-history-link">history</a>
|
||||
<a id="profile-bands-link">bands</a>
|
||||
<a id="profile-social-link">social</a>
|
||||
<a id="profile-favorites-link" class="last">favorites</a>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
<div class="have-bio">
|
||||
<p id="biography"></p>
|
||||
</div>
|
||||
<!-- <div class="update-biography">
|
||||
<div class="field">
|
||||
<textarea name="biography" class="user-biography"></textarea>
|
||||
</div>
|
||||
<br clear="left" /><br />
|
||||
<div class="right">
|
||||
<a id="btn-update-user-biography" layout-action="close" class="button-orange">OK</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a id="btn-cancel-user-biography" layout-action="close" class="button-grey">CANCEL</a>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</div> -->
|
||||
<br clear="all" />
|
||||
|
||||
<div class="profile-body">
|
||||
<div id="profile-about" class="profile-body-content">
|
||||
<!-- stats & location -->
|
||||
<div class="profile-wrapper">
|
||||
<div class="profile-about-left">
|
||||
<h3>Location:</h3><br />
|
||||
<span id="profile-location"></span><br /><br /><br />
|
||||
<h3>Stats:</h3><br />
|
||||
<span id="profile-friend-stats"></span><br />
|
||||
<span id="profile-follower-stats"></span><br />
|
||||
<span id="profile-following-stats"></span><br />
|
||||
<span id="profile-favorite-stats"></span><br />
|
||||
<span id="profile-session-stats"></span><br />
|
||||
<span id="profile-recording-stats"></span><br />
|
||||
</div>
|
||||
<div class="profile-about-right">
|
||||
<div class="no-bio">
|
||||
<span>You have no bio to describe yourself as a musician. <a href="#" class="enter-bio">Enter one now!</a></span>
|
||||
</div>
|
||||
<div class="have-bio">
|
||||
<p id="profile-biography"></p><a id="profile-edit-biography" class="button-orange right" href="#">EDIT BIO</a>
|
||||
</div>
|
||||
<div class="update-biography">
|
||||
<div class="field">
|
||||
<textarea name="biography" class="user-biography"></textarea>
|
||||
</div>
|
||||
<br clear="left" /><br />
|
||||
<div class="right">
|
||||
<a id="btn-update-user-biography" layout-action="close" class="button-orange">OK</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a id="btn-cancel-user-biography" layout-action="close" class="button-grey">CANCEL</a>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</div>
|
||||
<br />
|
||||
<div id="profile-instruments"></div>
|
||||
<br />
|
||||
<div id="profile-genres"></div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-history" class="profile-wrapper">
|
||||
<%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %>
|
||||
<%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %>
|
||||
<div class="filter-body">
|
||||
<div class="content-body-scroller" id="user-profile-feed-scroller">
|
||||
<div class="profile-wrapper">
|
||||
<div class="feed-content" id="user-profile-feed-entry-list"></div>
|
||||
<div id="user-profile-end-of-feeds-list" class="end-of-list">No more feed entries</div>
|
||||
<div id="user-profile-loading-feeds" class="infinite-scroll-loader" style="padding:5px">Loading ...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-header">Musical Experience</div>
|
||||
<br clear="all" />
|
||||
|
||||
<div id="instruments"></div>
|
||||
<br clear="all" />
|
||||
<br clear="all" />
|
||||
|
||||
<div>
|
||||
<div class="left profile-details">Status:</div>
|
||||
<div id="musician-status"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="left profile-details">Genres:</div>
|
||||
<div id="genres"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="left profile-details">Concert Gigs:</div>
|
||||
<div id="concert-count"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="left profile-details">Studio Gigs:</div>
|
||||
<div id="studio-count"></div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
<br clear="all" />
|
||||
|
||||
<div class="section-header">Performance Samples</div>
|
||||
<br clear="all" />
|
||||
<div id="no-samples" class="left">None specified</div>
|
||||
|
||||
<br clear="all" />
|
||||
<br clear="all" />
|
||||
|
||||
<div class="section-header">Online Presence</div>
|
||||
<br clear="all" />
|
||||
<div id="no-online-presence" class="left">None specified</div>
|
||||
|
||||
<br clear="all" />
|
||||
<br clear="all" />
|
||||
|
||||
<div class="section-header">Current Interests</div>
|
||||
<br clear="all" />
|
||||
<div id="no-interests" class="left">None specified</div>
|
||||
|
||||
<div id="paid-gigs">
|
||||
<div class="left profile-details">I'm interested in playing paid gigs</div>
|
||||
<br clear="all" />
|
||||
<div id="paid-gig-details">
|
||||
<ul>
|
||||
<li>Genre(s): </li>
|
||||
<li>Hourly rate = </li>
|
||||
<li>Day rate = </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
|
||||
<div id="free-gigs">
|
||||
<div class="left profile-details">I'm interested in playing free gigs</div>
|
||||
<br clear="all" />
|
||||
<div id="free-gig-details">
|
||||
<ul>
|
||||
<li>Genre(s): </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
|
||||
<div id="cowriting">
|
||||
<div class="left profile-details">Concert Gigs:</div>
|
||||
<br clear="all" />
|
||||
<div id="cowriting-details">
|
||||
<ul>
|
||||
<li>Genre(s): </li>
|
||||
<li>Purpose: </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
|
||||
<div id="traditional-band">
|
||||
<div class="left">I'm interested in forming traditional band(s)</div>
|
||||
<br clear="all" />
|
||||
<div id="traditional-band-details">
|
||||
<ul>
|
||||
<li>Genre(s): </li>
|
||||
<li>Commitment: </li>
|
||||
<li>Touring: </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
|
||||
<div id="virtual-band">
|
||||
<div class="left profile-details">I'm interested in forming virtual band(s)</div>
|
||||
<br clear="all" />
|
||||
<div id="virtual-band-details">
|
||||
<ul>
|
||||
<li>Genre(s): </li>
|
||||
<li>Commitment: </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br clear="all" />
|
||||
<br clear="all" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="history-content" class="profile-wrapper">
|
||||
<%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %>
|
||||
<%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %>
|
||||
<div class="filter-body">
|
||||
<div class="content-body-scroller" id="user-profile-feed-scroller">
|
||||
<div class="profile-wrapper">
|
||||
<div class="feed-content" id="user-profile-feed-entry-list"></div>
|
||||
<div id="user-profile-end-of-feeds-list" class="end-of-list">No more feed entries</div>
|
||||
<div id="user-profile-loading-feeds" class="infinite-scroll-loader" style="padding:5px">Loading ...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="bands-content" class="profile-wrapper profile-body-content">
|
||||
<br clear="all" />
|
||||
</div>
|
||||
<div id="social-content" class="profile-body-content outer">
|
||||
<div class="profile-social-head">
|
||||
<div class="profile-social-left">
|
||||
<h2>Friends</h2>
|
||||
</div>
|
||||
<div class="profile-social-mid">
|
||||
<h2>Following</h2>
|
||||
</div>
|
||||
<div class="profile-social-right">
|
||||
<h2>Followers</h2>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
<div class="profile-social-body">
|
||||
<div class="profile-social-body-wrapper">
|
||||
<div class="content-body-scroller">
|
||||
<!-- @FIXME: seems like too many divs -->
|
||||
<div class="profile-social-content">
|
||||
<div class="profile-social-left">
|
||||
<div id="social-friends">
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="profile-bands" class="profile-wrapper profile-body-content">
|
||||
<br clear="all" />
|
||||
</div>
|
||||
<div id="profile-social" class="profile-body-content outer">
|
||||
<div class="profile-social-head">
|
||||
<div class="profile-social-left">
|
||||
<h2>Friends</h2>
|
||||
</div>
|
||||
<div class="profile-social-mid">
|
||||
<h2>Following</h2>
|
||||
</div>
|
||||
<div class="profile-social-right">
|
||||
<h2>Followers</h2>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
<div class="profile-social-body">
|
||||
<div class="profile-social-body-wrapper">
|
||||
<div class="content-body-scroller">
|
||||
<!-- @FIXME: seems like too many divs -->
|
||||
<div class="profile-social-content">
|
||||
<div class="profile-social-left">
|
||||
<div id="profile-social-friends">
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-social-mid">
|
||||
<div id="profile-social-followings">
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-social-right">
|
||||
<div id="profile-social-followers">
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
<!-- @FIXME: end -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-favorites" class="profile-body-content">
|
||||
<div class="profile-wrapper">
|
||||
<div class="content-body-scroller">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<div class="profile-social-mid">
|
||||
<div id="social-followings">
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-social-right">
|
||||
<div id="social-followers">
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="favorites-content" class="profile-body-content">
|
||||
<div class="profile-wrapper">
|
||||
<div class="content-body-scroller">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-no-bands">
|
||||
|
|
@ -208,7 +320,7 @@
|
|||
</div>
|
||||
<div class="result-list-button-wrapper">
|
||||
<a class="button-orange smallbutton" href="{profile_url}">PROFILE</a>
|
||||
<span class="profile-band-link-member-false"><a id="btn-follow-band-2" class="button-orange smallbutton">FOLLOW</a></span>
|
||||
<span class="profile-band-link-member-false"><a id="btn-follow-band" class="button-orange smallbutton">FOLLOW</a></span>
|
||||
<span class="profile-band-link-member-true"><a href="{band_edit_url}" class="button-orange smallbutton">EDIT BAND</a></span>
|
||||
<span class="profile-band-link-member-true"><a href="{band_member_url}" class="button-orange smallbutton">INVITE</a></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
<!-- Actual Session Screen -->
|
||||
<div layout="screen" layout-id="session" layout-arg="id" class="screen secondary" id="session-screen">
|
||||
<div class="content-head">
|
||||
<div class="content-icon">
|
||||
<%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %>
|
||||
</div>
|
||||
<h1>session</h1>
|
||||
</div>
|
||||
|
||||
<div class="content-body">
|
||||
<!-- session controls -->
|
||||
<div id="session-controls">
|
||||
<a class="button-grey resync left" id="session-resync">
|
||||
<%= image_tag "content/icon_resync.png", {:align => "texttop", :height => 14, :width => 12} %>
|
||||
RESYNC
|
||||
</a>
|
||||
<a class="button-grey left" layout-link="session-settings" id="session-settings-button">
|
||||
<%= image_tag "content/icon_settings_sm.png", {:align => "texttop", :height => 12, :width => 12} %>
|
||||
SETTINGS
|
||||
</a>
|
||||
<a layout-link="share-dialog" class="button-grey left">
|
||||
<%= image_tag "content/icon_share.png", {:align => "texttop", :height => 12, :width => 12} %>
|
||||
SHARE
|
||||
</a>
|
||||
|
||||
<!-- Volume Slider -->
|
||||
<div class="block">
|
||||
<div class="label">VOLUME:</div>
|
||||
<div id="volume" class="fader lohi" mixer-id=""></div>
|
||||
</div>
|
||||
|
||||
<!-- Mix: Me versus Others -->
|
||||
|
||||
<div class="block monitor-mode-holder">
|
||||
<div class="label">MIX:</div>
|
||||
<select class="monitor-mode easydropdown">
|
||||
<option value="personal" class="label">Personal</option>
|
||||
<option value="master">Master</option>
|
||||
</select>
|
||||
</div>
|
||||
<!--
|
||||
<div class="block">
|
||||
<div class="label">MONITOR:</div>
|
||||
<div class="label"><small>others</small></div>
|
||||
<div id="l2m" class="fader flat" mixer-id="__L2M__"></div>
|
||||
<div class="label"><small>me</small></div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- Leave Button -->
|
||||
<a class="button-grey right leave" href="/client#/home" id="session-leave">X LEAVE</a>
|
||||
</div>
|
||||
<!-- end session controls -->
|
||||
|
||||
<!-- content scrolling area -->
|
||||
<div id="tracks">
|
||||
<div class="content-scroller">
|
||||
|
||||
<!-- content wrapper -->
|
||||
<div class="content-wrapper">
|
||||
|
||||
<!-- my tracks -->
|
||||
<div class="session-mytracks">
|
||||
<h2>my tracks</h2>
|
||||
<div id="track-settings" class="session-add" style="display:block;" layout-link="configure-tracks">
|
||||
<%= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18} %>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
|
||||
<div class="session-tracks-scroller">
|
||||
<div id="session-mytracks-container"></div>
|
||||
<div id="voice-chat" class="voicechat" style="display:none;" mixer-id="">
|
||||
<div class="voicechat-label">CHAT</div>
|
||||
<div class="voicechat-gain"></div>
|
||||
<div class="voicechat-mute enabled" control="mute" mixer-id=""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- live tracks -->
|
||||
<div class="session-livetracks">
|
||||
<h2>live tracks</h2>
|
||||
<div class="session-add" layout-link="select-invites">
|
||||
<a href="#" id="session-invite-musicians">
|
||||
<%= image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"} %> Invite Musicians
|
||||
</a>
|
||||
</div>
|
||||
<div class="session-tracks-scroller">
|
||||
<div id="session-livetracks-container">
|
||||
<div class="when-empty livetracks">
|
||||
No other musicians <br/>
|
||||
are in your session
|
||||
</div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
<div class="recording" id="recording-start-stop">
|
||||
<a>
|
||||
<%= image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} %> <span id="recording-status">Make a Recording</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- recordings -->
|
||||
<div class="session-recordings">
|
||||
<h2>other audio</h2>
|
||||
<div class="session-recording-name-wrapper">
|
||||
<div class="session-recording-name left">(No recording loaded)</div>
|
||||
<div class="session-add right">
|
||||
<a id='close-playback-recording' href="#"><%= image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} %> Close</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="session-tracks-scroller">
|
||||
<div id="session-recordedtracks-container">
|
||||
<div class="when-empty recordings">
|
||||
<span class="open-media-file-header"><%= image_tag "content/icon_folder.png", {width:22, height:20} %> Open:</span>
|
||||
<ul class="open-media-file-options">
|
||||
<li><a href="#" id="open-a-recording">Recording</a></li>
|
||||
<% if Rails.application.config.jam_tracks_available %>
|
||||
<li><a href="#" id="open-a-jamtrack">JamTrack</a></li>
|
||||
<% end %>
|
||||
<!--<li>Audio File</li>-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
|
||||
<%= render "play_controls" %>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- recording name and close button -->
|
||||
<!--
|
||||
<div class="session-recording-name-wrapper">
|
||||
<div class="session-recording-name left">(No recording loaded)</div>
|
||||
<div class="session-add right">
|
||||
<a>
|
||||
<%= image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} %> Close
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- dialogs needed for Session screen (ORDER MATTERS) -->
|
||||
<%= render "configureTrack" %>
|
||||
<%= render "addTrack" %>
|
||||
<%= render "addNewGear" %>
|
||||
<%= render "error" %>
|
||||
<%= render "sessionSettings" %>
|
||||
|
||||
<!-- Track Template -->
|
||||
<script type="text/template" id="template-session-track">
|
||||
<div track-id="{trackId}" class="session-track track" client-id="{clientId}">
|
||||
<div class="track-vu-left" mixer-id="{vuMixerId}_vul"></div>
|
||||
<div class="track-vu-right" mixer-id="{vuMixerId}_vur"></div>
|
||||
<div class="track-label">{name}</div>
|
||||
<div id="div-track-close" track-id="{trackId}" class="track-close op30">
|
||||
<%= image_tag "content/icon_closetrack.png", {:width => 12, :height => 12} %>
|
||||
</div>
|
||||
<div class="{avatarClass}">
|
||||
<img src="{avatar}"/>
|
||||
</div>
|
||||
<div class="track-instrument {preMasteredClass}">
|
||||
<img src="{instrumentIcon}" width="45" height="45"/>
|
||||
</div>
|
||||
<div class="track-gain" mixer-id="{mixerId}"></div>
|
||||
<!--
|
||||
<div class="track-gain-wrapper"
|
||||
control="fader" orientation="vertical">
|
||||
<div class="track-gain-slider" style="bottom:{gainPercent}%;" control="fader-handle">
|
||||
<%= image_tag "content/slider_gain_vertical.png", {:width => 28, :height => 11} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div class="track-icon-mute {muteClass}" control="mute" mixer-id="{muteMixerId}">
|
||||
</div>
|
||||
<!-- TODO - connection class from curly param -->
|
||||
<div mixer-id="{mixerId}_connection" class="track-connection grey">CONNECTION</div>
|
||||
<div class="disabled-track-overlay"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-option">
|
||||
<option value="{value}" title="{label}" {selected}>{label}</option>
|
||||
</script>
|
||||
|
||||
<!-- Genre option template -->
|
||||
<script type="text/template" id="template-genre-option">
|
||||
<option value="{value}">{label}</option>
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
#session-screen.screen.secondary[layout="screen" layout-id="session" layout-arg="id"]
|
||||
.content-head
|
||||
.content-icon
|
||||
= image_tag "shared/icon_session.png", {:height => 19, :width => 19}
|
||||
h1
|
||||
| session
|
||||
.content-body
|
||||
#session-controls
|
||||
a#session-resync.button-grey.resync.left
|
||||
= image_tag "content/icon_resync.png", {:align => "texttop", :height => 14, :width => 12}
|
||||
| RESYNC
|
||||
a#session-settings-button.button-grey.left[layout-link="session-settings"]
|
||||
= image_tag "content/icon_settings_sm.png", {:align => "texttop", :height => 12, :width => 12}
|
||||
| SETTINGS
|
||||
a.button-grey.left[layout-link="share-dialog"]
|
||||
= image_tag "content/icon_share.png", {:align => "texttop", :height => 12, :width => 12}
|
||||
| SHARE
|
||||
.block
|
||||
.label
|
||||
| VOLUME:
|
||||
#volume.fader.lohi[mixer-id=""]
|
||||
.block.monitor-mode-holder
|
||||
.label
|
||||
| MIX:
|
||||
select.monitor-mode.easydropdown
|
||||
option.label[value="personal"]
|
||||
| Personal
|
||||
option[value="master"]
|
||||
| Master
|
||||
a#session-leave.button-grey.right.leave[href="/client#/home"]
|
||||
| X LEAVE
|
||||
#tracks
|
||||
.content-scroller
|
||||
.content-wrapper
|
||||
.session-mytracks
|
||||
h2
|
||||
| my tracks
|
||||
#track-settings.session-add[style="display:block;" layout-link="configure-tracks"]
|
||||
= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18}
|
||||
span
|
||||
| Settings
|
||||
.session-tracks-scroller
|
||||
#session-mytracks-container
|
||||
#voice-chat.voicechat[style="display:none;" mixer-id=""]
|
||||
.voicechat-label
|
||||
| CHAT
|
||||
.voicechat-gain
|
||||
.voicechat-mute.enabled[control="mute" mixer-id=""]
|
||||
.session-livetracks
|
||||
h2
|
||||
| live tracks
|
||||
.session-add[layout-link="select-invites"]
|
||||
a#session-invite-musicians[href="#"]
|
||||
= image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"}
|
||||
| Invite Musicians
|
||||
.session-tracks-scroller
|
||||
#session-livetracks-container
|
||||
.when-empty.livetracks
|
||||
| No other musicians
|
||||
br
|
||||
| are in your session
|
||||
br[clear="all"]
|
||||
#recording-start-stop.recording
|
||||
a
|
||||
= image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"}
|
||||
|
|
||||
span#recording-status
|
||||
| Make a Recording
|
||||
.session-recordings
|
||||
h2
|
||||
| other audio
|
||||
.session-recording-name-wrapper
|
||||
.session-recording-name.left
|
||||
| (No recording loaded)
|
||||
.session-add.right
|
||||
a#close-playback-recording[href="#"]
|
||||
= image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"}
|
||||
| Close
|
||||
.session-tracks-scroller
|
||||
#session-recordedtracks-container
|
||||
.when-empty.recordings
|
||||
span.open-media-file-header
|
||||
= image_tag "content/icon_folder.png", {width:22, height:20}
|
||||
| Open:
|
||||
ul.open-media-file-options
|
||||
li
|
||||
a#open-a-recording[href="#"]
|
||||
| Recording
|
||||
- if Rails.application.config.jam_tracks_available
|
||||
li
|
||||
a#open-a-jamtrack[href="#"]
|
||||
| JamTrack
|
||||
- if Rails.application.config.backing_tracks_available
|
||||
li
|
||||
a#open-a-backingtrack[href="#"]
|
||||
| Audio File
|
||||
.when-empty.use-metronome-header
|
||||
- if Rails.application.config.metronome_available
|
||||
= image_tag "content/icon_metronome.png", {width:22, height:20}
|
||||
a#open-a-metronome[href="#"]
|
||||
| Use Metronome
|
||||
br[clear="all"]
|
||||
= render "play_controls"
|
||||
= render "configureTrack"
|
||||
= render "addTrack"
|
||||
= render "addNewGear"
|
||||
= render "error"
|
||||
= render "sessionSettings"
|
||||
script#template-session-track[type="text/template"]
|
||||
.session-track.track client-id="{clientId}" track-id="{trackId}"
|
||||
.track-vu-left.mixer-id="{vuMixerId}_vul"
|
||||
.track-vu-right.mixer-id="{vuMixerId}_vur"
|
||||
.track-label[title="{name}"]
|
||||
span.name-text="{name}"
|
||||
#div-track-close.track-close.op30 track-id="{trackId}"
|
||||
=image_tag("content/icon_closetrack.png", {width: 12, height: 12})
|
||||
div class="{avatarClass}"
|
||||
img src="{avatar}"
|
||||
.track-instrument class="{preMasteredClass}"
|
||||
img height="45" src="{instrumentIcon}" width="45"
|
||||
.track-gain mixer-id="{mixerId}"
|
||||
.track-icon-mute class="{muteClass}" control="mute" mixer-id="{muteMixerId}"
|
||||
.track-icon-loop.hidden control="loop"
|
||||
input#loop-button type="checkbox" value="loop" Loop
|
||||
.track-connection.grey mixer-id="{mixerId}_connection"
|
||||
CONNECTION
|
||||
.disabled-track-overlay
|
||||
.metronome-selects.hidden
|
||||
select.metronome-select.metro-sound title="Metronome Sound"
|
||||
option.label value="Beep" Bleep
|
||||
option.label value="Click" Click
|
||||
option.label value="Snare" Drum
|
||||
br
|
||||
select.metronome-select.metro-tempo title="Metronome Tempo"
|
||||
- metronome_tempos.each do |t|
|
||||
option.label value=t
|
||||
=t
|
||||
|
||||
script#template-option type="text/template"
|
||||
option value="{value}" title="{label}" selected="{selected}"
|
||||
="{label}"
|
||||
|
||||
script#template-genre-option type="text/template"
|
||||
option value="{value}"
|
||||
="{label}"
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
div class="site_validator" id="#{site_type}_validator"
|
||||
span class="validate-checkmark"
|
||||
span class="spinner-small upload-spinner"
|
||||
input type='text' id="validate_input_#{site_type}" maxlength="2000"
|
||||
br
|
||||
div class="error"
|
||||
div class="validator-input"
|
||||
input type='text' id="validate_input_#{site_type}" maxlength="2000"
|
||||
span class="validate-checkmark"
|
||||
span class="spinner-small upload-spinner"
|
||||
div class="error"
|
||||
- if site_type =~ /^#{Utils::RECORDING_SRC_PREFIX}/
|
||||
div class="validator-add-rec"
|
||||
a id="add_btn_#{site_type}" class="button-grey add-recording-source right" ADD
|
||||
br clear="all"
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,23 @@ script type="text/template" id='template-sync-viewer-recorded-track'
|
|||
a.retry href='#'
|
||||
= image_tag('content/icon_resync.png', width:12, height: 14)
|
||||
|
||||
script type="text/template" id='template-sync-viewer-recorded-backing-track'
|
||||
.recorded-backing-track.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}" data-client-id="{{data.client_id}}" data-client-track-id="{{data.client_track_id}}" data-track-id="{{data.backing_track_id}}" data-fully-uploaded="{{data.fully_uploaded}}"
|
||||
.type
|
||||
span.text BACKING
|
||||
a.avatar-tiny href="#" user-id="{{data.user.id}}" hoveraction="musician"
|
||||
img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}"
|
||||
.client-state.bar
|
||||
.progress
|
||||
span.msg
|
||||
a.retry href='#'
|
||||
= image_tag('content/icon_resync.png', width:12, height: 14)
|
||||
.upload-state.bar
|
||||
.progress
|
||||
span.msg
|
||||
a.retry href='#'
|
||||
= image_tag('content/icon_resync.png', width:12, height: 14)
|
||||
|
||||
|
||||
script type="text/template" id='template-sync-viewer-stream-mix'
|
||||
.stream-mix.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}"
|
||||
|
|
@ -128,6 +145,33 @@ script type="text/template" id="template-sync-viewer-hover-recorded-track"
|
|||
| {{data.summary}}
|
||||
| {% } %}
|
||||
|
||||
script type="text/template" id="template-sync-viewer-hover-recorded-backing-track"
|
||||
.help-hover-recorded-backing-tracks
|
||||
|
||||
.client-box
|
||||
.client-state-info
|
||||
span.special-text is the file on your system?
|
||||
.client-state class="{{data.clientStateClass}}"
|
||||
span.msg
|
||||
| {{data.clientStateMsg}}
|
||||
.client-state-definition.sync-definition
|
||||
| {{data.clientStateDefinition}}
|
||||
.upload-box
|
||||
.upload-state-info
|
||||
span.special-text is it uploaded?
|
||||
.upload-state class="{{data.uploadStateClass}}"
|
||||
span.msg
|
||||
| {{data.uploadStateMsg}}
|
||||
.upload-state-definition.sync-definition
|
||||
| {{data.uploadStateDefinition}}
|
||||
br clear="both"
|
||||
|
||||
| {% if(data.summary) { %}
|
||||
.summary
|
||||
.title what's next?
|
||||
| {{data.summary}}
|
||||
| {% } %}
|
||||
|
||||
script type="text/template" id="template-sync-viewer-hover-stream-mix"
|
||||
.help-hover-stream-mix
|
||||
|
||||
|
|
@ -189,7 +233,7 @@ script type="text/template" id="template-sync-viewer-generic-command"
|
|||
| {{data.displayType}}
|
||||
|
||||
script type="text/template" id="template-sync-viewer-recorded-track-command"
|
||||
.recorded-track.sync
|
||||
.recorded-track.track-item.sync
|
||||
.type
|
||||
.progress
|
||||
span.text
|
||||
|
|
@ -198,6 +242,15 @@ script type="text/template" id="template-sync-viewer-recorded-track-command"
|
|||
img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}"
|
||||
img.instrument-icon data-instrument-id="{{data.instrument_id}}" hoveraction="instrument" src="{{JK.getInstrumentIconMap24()[data.instrument_id].asset}}"
|
||||
|
||||
script type="text/template" id="template-sync-viewer-recorded-backing-track-command"
|
||||
.recorded-backing-track.track-item.sync
|
||||
.type
|
||||
.progress
|
||||
span.text
|
||||
| {{data.action}} BACKING TRACK
|
||||
a.avatar-tiny href="#" user-id="{{data.user.id}}" hoveraction="musician"
|
||||
img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}"
|
||||
|
||||
script type="text/template" id="template-sync-viewer-log-item"
|
||||
.log class="success-{{data.success}}"
|
||||
.command
|
||||
|
|
|
|||
|
|
@ -155,6 +155,9 @@
|
|||
var openJamTrackDialog = new JK.OpenJamTrackDialog(JK.app);
|
||||
openJamTrackDialog.initialize();
|
||||
|
||||
var openBackingTrackDialog = new JK.OpenBackingTrackDialog(JK.app);
|
||||
openBackingTrackDialog.initialize();
|
||||
|
||||
var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app);
|
||||
configureTracksDialog.initialize();
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ script type='text/template' id='template-mixer-mode-change'
|
|||
li
|
||||
span.definition Personal Mix
|
||||
div The personal mix controls the audio mix that you individually hear while playing in the session, and you can customize this mix to hear more or less of the music stream from each other musician playing in the session. This does not affect the master mix used for recordings or broadcasts. With personal mix selected, when you adjust the faders on the session screen up or down, it changes the personal mix only for you locally.
|
||||
li
|
||||
span.definition Note on Audio Files
|
||||
div The volume control on any audio file is always both the master and personal volume control, regardless of the current mode.
|
||||
br
|
||||
div
|
||||
| For more detailed information on this topic, read our knowledge base article on
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@
|
|||
= render 'dialogs/allSyncsDialog'
|
||||
= render 'dialogs/adjustGearSpeedDialog'
|
||||
= render 'dialogs/openJamTrackDialog'
|
||||
= render 'dialogs/openBackingTrackDialog'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
.dialog.openBackingTrackDialog-overlay.ftue-overlay.tall#open-backing-track-dialog layout="dialog" layout-id="open-backing-track-dialog"
|
||||
|
||||
.content-head
|
||||
= image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' }
|
||||
h1
|
||||
| open an audio file
|
||||
|
||||
.dialog-inner
|
||||
|
||||
.recording-wrapper
|
||||
table.open-backing-tracks cellspacing="0" cellpadding="0" border="0"
|
||||
thead
|
||||
tr
|
||||
th align="left"
|
||||
| NAME
|
||||
th align="left"
|
||||
| SIZE
|
||||
th align="left"
|
||||
| TYPE
|
||||
tbody
|
||||
br
|
||||
|
||||
/ .left.paginator-holder
|
||||
|
||||
.help-links
|
||||
a.display-backingtracks-folder href='#'
|
||||
| Display audio file folder
|
||||
a.what-are-backingtracks href='#'
|
||||
| What are Backing Tracks?
|
||||
.right
|
||||
a href="#" class="button-grey" layout-action="close"
|
||||
| CANCEL
|
||||
br clear="all"
|
||||
|
||||
script#template-backing-track-row type="text/template"
|
||||
tr data-recording-id="{{data.backingTrackId}}" data-local-state="{{data.backingTrackState}}"
|
||||
td
|
||||
| {{data.name}}
|
||||
td
|
||||
| {{data.length}}
|
||||
td
|
||||
| {{data.type}}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
= javascript_include_tag "site_validator"
|
||||
div style="width:50%"
|
||||
= render "clients/site_validator", site_type: params[:site_type]
|
||||
= stylesheet_link_tag "client/site_validator"
|
||||
<br />
|
||||
= select_tag "site_type", options_for_select(Utils::RECORDING_SOURCES, params[:site_type])
|
||||
|
||||
javascript:
|
||||
var initialized = false;
|
||||
$(document).on('JAMKAZAM_READY', function(e, data) {
|
||||
setTimeout(function() {
|
||||
window.site_validator = new JK.SiteValidator('#{params[:site_type] || 'rec_youtube'}');
|
||||
site_validator.init();
|
||||
$('#validate_input_'+'#{params[:site_type] || 'url'}').val('jonathankolyer');
|
||||
}, 1)
|
||||
});
|
||||
$('#site_type').change(function(){
|
||||
location.href = 'recording_source?site_type='+$(this).val();
|
||||
});
|
||||
|
|
@ -39,7 +39,7 @@ if defined?(Bundler)
|
|||
|
||||
# Activate observers that should always be running.
|
||||
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
||||
config.active_record.observers = "JamRuby::InvitedUserObserver", "JamRuby::UserObserver", "JamRuby::FeedbackObserver", "JamRuby::RecordedTrackObserver", "JamRuby::QuickMixObserver"
|
||||
config.active_record.observers = "JamRuby::InvitedUserObserver", "JamRuby::UserObserver", "JamRuby::FeedbackObserver", "JamRuby::RecordedTrackObserver", "JamRuby::QuickMixObserver", "JamRuby::RecordedBackingTrackObserver"
|
||||
|
||||
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
||||
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
||||
|
|
@ -312,5 +312,7 @@ if defined?(Bundler)
|
|||
|
||||
config.show_jamblaster_notice = true
|
||||
config.show_jamblaster_kickstarter_link = true
|
||||
config.metronome_available = true
|
||||
config.backing_tracks_available = true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ SampleApp::Application.routes.draw do
|
|||
match '/websocket', to: 'spikes#websocket'
|
||||
match '/test_subscription', to: 'spikes#subscription'
|
||||
match '/site_validate', to: 'spikes#site_validate'
|
||||
match '/username_validate', to: 'spikes#username_validate'
|
||||
match '/recording_source', to: 'spikes#recording_source'
|
||||
|
||||
# junk pages
|
||||
match '/help', to: 'static_pages#help'
|
||||
|
|
@ -184,6 +184,10 @@ SampleApp::Application.routes.draw do
|
|||
match '/sessions/:id/details/comments' => 'api_music_sessions#add_session_info_comment', :via => :post
|
||||
match '/sessions/:id/jam_tracks/:jam_track_id/open' => 'api_music_sessions#jam_track_open', :via => :post
|
||||
match '/sessions/:id/jam_tracks/close' => 'api_music_sessions#jam_track_close', :via => :post
|
||||
match '/sessions/:id/backing_tracks/open' => 'api_music_sessions#backing_track_open', :via => :post
|
||||
match '/sessions/:id/backing_tracks/close' => 'api_music_sessions#backing_track_close', :via => :post
|
||||
match '/sessions/:id/metronome/open' => 'api_music_sessions#metronome_open', :via => :post
|
||||
match '/sessions/:id/metronome/close' => 'api_music_sessions#metronome_close', :via => :post
|
||||
|
||||
# music session tracks
|
||||
match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post
|
||||
|
|
@ -197,6 +201,9 @@ SampleApp::Application.routes.draw do
|
|||
match '/music_notations' => 'api_music_notations#create', :via => :post
|
||||
match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation
|
||||
|
||||
# Backing track_show
|
||||
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
|
||||
|
||||
# Jamtracks
|
||||
match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
|
||||
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
|
||||
|
|
@ -457,13 +464,22 @@ SampleApp::Application.routes.draw do
|
|||
match '/recordings/:id/tracks/:track_id/upload_sign' => 'api_recordings#upload_sign', :via => :get
|
||||
match '/recordings/:id/tracks/:track_id/upload_part_complete' => 'api_recordings#upload_part_complete', :via => :post
|
||||
match '/recordings/:id/tracks/:track_id/upload_complete' => 'api_recordings#upload_complete', :via => :post
|
||||
match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get
|
||||
|
||||
# Recordings - stream_mix
|
||||
match '/recordings/:id/stream_mix/upload_sign' => 'api_recordings#upload_sign_stream_mix', :via => :get
|
||||
match '/recordings/:id/stream_mix/upload_part_complete' => 'api_recordings#upload_part_complete_stream_mix', :via => :post
|
||||
match '/recordings/:id/stream_mix/upload_complete' => 'api_recordings#upload_complete_stream_mix', :via => :post
|
||||
|
||||
match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get
|
||||
|
||||
# Recordings - backing tracks
|
||||
match '/recordings/:id/backing_tracks/:track_id' => 'api_recordings#show_recorded_backing_track', :via => :get, :as => 'api_recordings_show_recorded_backing_track'
|
||||
match '/recordings/:id/backing_tracks/:track_id/download' => 'api_recordings#backing_track_download', :via => :get, :as => 'api_recordings_download'
|
||||
match '/recordings/:id/backing_tracks/:track_id/upload_next_part' => 'api_recordings#backing_track_upload_next_part', :via => :get
|
||||
match '/recordings/:id/backing_tracks/:track_id/upload_sign' => 'api_recordings#backing_track_upload_sign', :via => :get
|
||||
match '/recordings/:id/backing_tracks/:track_id/upload_part_complete' => 'api_recordings#backing_track_upload_part_complete', :via => :post
|
||||
match '/recordings/:id/backing_tracks/:track_id/upload_complete' => 'api_recordings#backing_track_upload_complete', :via => :post
|
||||
match '/recordings/:id/backing_tracks/:track_id/silent' => 'api_backing_tracks#backing_track_silent', :via => :post
|
||||
|
||||
# Recordings - recorded_videos
|
||||
match '/recordings/:id/tracks/:video_id/upload_sign' => 'api_recordings#video_upload_sign', :via => :get
|
||||
match '/recordings/:id/videos/:video_id/upload_start' => 'api_recordings#video_upload_start', :via => :post
|
||||
|
|
|
|||
|
|
@ -140,10 +140,10 @@ class MusicSessionManager < BaseManager
|
|||
Notification.send_session_depart(active_music_session, connection.client_id, user, recordingId)
|
||||
end
|
||||
|
||||
def sync_tracks(active_music_session, client_id, new_tracks)
|
||||
def sync_tracks(active_music_session, client_id, new_tracks, backing_tracks)
|
||||
tracks = nil
|
||||
active_music_session.with_lock do # VRFS-1297
|
||||
tracks = Track.sync(client_id, new_tracks)
|
||||
tracks = Track.sync(client_id, new_tracks, backing_tracks)
|
||||
active_music_session.tick_track_changes
|
||||
end
|
||||
Notification.send_tracks_changed(active_music_session)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,40 @@
|
|||
class Utils
|
||||
|
||||
RECORDING_SRC_PREFIX = 'rec_'
|
||||
RECORDING_SOURCES = ["#{RECORDING_SRC_PREFIX}youtube", "#{RECORDING_SRC_PREFIX}soundcloud"]
|
||||
USERNAME_SITES = %W{youtube facebook soundcloud bandcamp fandalism twitter reverbnation}
|
||||
SITE_TYPES = ['url'].concat(USERNAME_SITES)
|
||||
SITE_TYPES = ['url'].concat(USERNAME_SITES).concat(RECORDING_SOURCES)
|
||||
|
||||
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36"
|
||||
|
||||
|
||||
def self.recording_source?(site)
|
||||
RECORDING_SOURCES.include?(site)
|
||||
end
|
||||
|
||||
def self.extract_recording_id(site, recording_url)
|
||||
recording_url.strip!
|
||||
case site
|
||||
when 'rec_youtube'
|
||||
# regex derived from: https://gist.github.com/afeld/1254889
|
||||
if recording_url =~ /(youtu.be\/|youtube.com\/(watch\?(.*&)?v=|(embed|v)\/))([^\?&\"\'>]+)/
|
||||
return $5
|
||||
end
|
||||
when 'rec_soundcloud'
|
||||
if recording_url =~ /^https?:\/\/.*soundcloud.com\/.+/
|
||||
tmpfile = Tempfile.new(site)
|
||||
tmpfile.close
|
||||
curl_args = "-A '#{USER_AGENT}' -L --output #{tmpfile.path} --fail --show-error "
|
||||
`curl #{curl_args} '#{recording_url}' 2>&1`
|
||||
result = File.read(tmpfile.path)
|
||||
File.delete(tmpfile.path)
|
||||
if result =~ /"soundcloud:\/\/sounds:(\d+)"/
|
||||
return $1
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def self.username_url(username, site)
|
||||
case site
|
||||
|
|
@ -27,8 +60,7 @@ class Utils
|
|||
end
|
||||
|
||||
def self.site_validator(url, site=nil)
|
||||
uagent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36"
|
||||
curl_args = "-A '#{uagent}' --silent --head --fail --show-error "
|
||||
curl_args = "-A '#{USER_AGENT}' --silent --head --fail --show-error "
|
||||
case site
|
||||
when 'bandcamp'
|
||||
cmd = "curl #{curl_args} '#{url}' 2>&1"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue