* VRFS-3519 - client and server jamtrack mixdown support
This commit is contained in:
parent
30965c6351
commit
4a647b8bd1
|
|
@ -302,4 +302,5 @@ jam_track_onboarding_enhancements.sql
|
|||
jam_track_name_drop_unique.sql
|
||||
jam_track_searchability.sql
|
||||
harry_fox_agency.sql
|
||||
jam_track_slug.sql
|
||||
jam_track_slug.sql
|
||||
mixdown.sql
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
CREATE TABLE jam_track_mixdowns (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
jam_track_id VARCHAR(64) NOT NULL REFERENCES jam_tracks(id) ON DELETE CASCADE,
|
||||
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
settings JSON NOT NULL,
|
||||
name VARCHAR(1000) NOT NULL,
|
||||
description VARCHAR(1000),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE jam_track_mixdown_packages (
|
||||
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
jam_track_mixdown_id VARCHAR(64) NOT NULL REFERENCES jam_track_mixdowns(id) ON DELETE CASCADE,
|
||||
file_type VARCHAR NOT NULL ,
|
||||
sample_rate INTEGER NOT NULL,
|
||||
url VARCHAR(2048),
|
||||
md5 VARCHAR,
|
||||
length INTEGER,
|
||||
downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
last_step_at TIMESTAMP,
|
||||
last_signed_at TIMESTAMP,
|
||||
download_count INTEGER NOT NULL DEFAULT 0,
|
||||
signed_at TIMESTAMP,
|
||||
downloaded_at TIMESTAMP,
|
||||
signing_queued_at TIMESTAMP,
|
||||
error_count INTEGER NOT NULL DEFAULT 0,
|
||||
error_reason VARCHAR,
|
||||
error_detail VARCHAR,
|
||||
should_retry BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
packaging_steps INTEGER,
|
||||
current_packaging_step INTEGER,
|
||||
private_key VARCHAR,
|
||||
signed BOOLEAN,
|
||||
signing_started_at TIMESTAMP,
|
||||
first_downloaded TIMESTAMP,
|
||||
signing BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
encrypt_type VARCHAR,
|
||||
first_downloaded_at TIMESTAMP,
|
||||
last_downloaded_at TIMESTAMP,
|
||||
version VARCHAR NOT NULL DEFAULT '1',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE jam_track_rights ADD COLUMN last_mixdown_id VARCHAR(64) REFERENCES jam_track_mixdowns(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE notifications ADD COLUMN jam_track_mixdown_package_id VARCHAR(64) REFERENCES jam_track_mixdown_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE jam_track_mixdown_packages ADD COLUMN last_errored_at TIMESTAMP;
|
||||
ALTER TABLE jam_track_mixdown_packages ADD COLUMN queued BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE jam_track_mixdown_packages ADD COLUMN speed_pitched BOOLEAN DEFAULT FALSE;
|
||||
ALTER TABLE jam_track_rights ADD COLUMN queued BOOLEAN DEFAULT FALSE;
|
||||
|
||||
CREATE INDEX jam_track_rights_queued ON jam_track_rights(queued);
|
||||
CREATE INDEX jam_track_rights_signing_queued ON jam_track_rights(signing_queued_at);
|
||||
CREATE INDEX jam_track_rights_updated ON jam_track_rights(updated_at);
|
||||
|
||||
CREATE INDEX jam_track_mixdown_packages_queued ON jam_track_mixdown_packages(queued);
|
||||
CREATE INDEX jam_track_mixdown_packages_signing_queued ON jam_track_mixdown_packages(signing_queued_at);
|
||||
CREATE INDEX jam_track_mixdown_packages_updated ON jam_track_mixdown_packages(updated_at);
|
||||
|
|
@ -82,6 +82,10 @@ message ClientMessage {
|
|||
JAM_TRACK_SIGN_COMPLETE = 260;
|
||||
JAM_TRACK_SIGN_FAILED = 261;
|
||||
|
||||
// jamtracks mixdown notifications
|
||||
MIXDOWN_SIGN_COMPLETE = 270;
|
||||
MIXDOWN_SIGN_FAILED = 271;
|
||||
|
||||
TEST_SESSION_MESSAGE = 295;
|
||||
|
||||
PING_REQUEST = 300;
|
||||
|
|
@ -188,6 +192,10 @@ message ClientMessage {
|
|||
optional JamTrackSignComplete jam_track_sign_complete = 260;
|
||||
optional JamTrackSignFailed jam_track_sign_failed = 261;
|
||||
|
||||
// jamtrack mixdown notification
|
||||
optional MixdownSignComplete mixdown_sign_complete = 270;
|
||||
optional MixdownSignFailed mixdown_sign_failed = 271;
|
||||
|
||||
|
||||
// Client-Session messages (to/from)
|
||||
optional TestSessionMessage test_session_message = 295;
|
||||
|
|
@ -612,6 +620,15 @@ message JamTrackSignFailed {
|
|||
required int32 jam_track_right_id = 1; // jam track right id
|
||||
}
|
||||
|
||||
message MixdownSignComplete {
|
||||
required string mixdown_package_id = 1; // jam track mixdown package id
|
||||
}
|
||||
|
||||
message MixdownSignFailed {
|
||||
required string mixdown_package_id = 1; // jam track mixdown package id
|
||||
}
|
||||
|
||||
|
||||
message SubscriptionMessage {
|
||||
optional string type = 1; // the type of the subscription
|
||||
optional string id = 2; // data about what to subscribe to, specifically
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ require "jam_ruby/resque/scheduled/jam_tracks_cleaner"
|
|||
require "jam_ruby/resque/scheduled/stats_maker"
|
||||
require "jam_ruby/resque/scheduled/tally_affiliates"
|
||||
require "jam_ruby/resque/jam_tracks_builder"
|
||||
require "jam_ruby/resque/jam_track_mixdown_packager"
|
||||
require "jam_ruby/resque/google_analytics_event"
|
||||
require "jam_ruby/resque/batch_email_job"
|
||||
require "jam_ruby/resque/long_running"
|
||||
|
|
@ -209,6 +210,8 @@ require "jam_ruby/models/jam_track_track"
|
|||
require "jam_ruby/models/jam_track_right"
|
||||
require "jam_ruby/models/jam_track_tap_in"
|
||||
require "jam_ruby/models/jam_track_file"
|
||||
require "jam_ruby/models/jam_track_mixdown"
|
||||
require "jam_ruby/models/jam_track_mixdown_package"
|
||||
require "jam_ruby/models/genre_jam_track"
|
||||
require "jam_ruby/app/mailers/async_mailer"
|
||||
require "jam_ruby/app/mailers/batch_mailer"
|
||||
|
|
|
|||
|
|
@ -51,4 +51,7 @@ module NotificationTypes
|
|||
JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE"
|
||||
JAM_TRACK_SIGN_FAILED = "JAM_TRACK_SIGN_FAILED"
|
||||
|
||||
MIXDOWN_SIGN_COMPLETE = "MIXDOWN_SIGN_COMPLETE"
|
||||
MIXDOWN_SIGN_FAILED = "MIXDOWN_SIGN_FAILED"
|
||||
|
||||
end
|
||||
|
|
@ -444,7 +444,10 @@ module JamRuby
|
|||
jam_track.alternative_license_status = false
|
||||
jam_track.hfa_license_desired = true
|
||||
jam_track.server_fixation_date = Time.now
|
||||
jam_track.slug = metadata['slug'] || jam_track.generate_slug
|
||||
jam_track.slug = metadata['slug']
|
||||
unless jam_track.slug
|
||||
jam_track.generate_slug
|
||||
end
|
||||
|
||||
if is_tency_storage?
|
||||
jam_track.vendor_id = metadata[:id]
|
||||
|
|
@ -1758,7 +1761,7 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def synchronize_all(options)
|
||||
def synchronize_all(options)
|
||||
importers = []
|
||||
|
||||
count = 0
|
||||
|
|
|
|||
|
|
@ -14,15 +14,29 @@ module JamRuby
|
|||
end
|
||||
|
||||
def self.mount_source_up_requested(mount)
|
||||
Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_UP_REQUEST}.to_json )
|
||||
Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_UP_REQUEST}.to_json)
|
||||
end
|
||||
|
||||
def self.mount_source_down_requested(mount)
|
||||
Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_DOWN_REQUEST}.to_json )
|
||||
Notification.send_subscription_message('mount', mount.id, {change_type: IcecastSourceChange::CHANGE_TYPE_MOUNT_DOWN_REQUEST}.to_json)
|
||||
end
|
||||
|
||||
def self.jam_track_signing_job_change(jam_track_right)
|
||||
Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state, current_packaging_step: jam_track_right.current_packaging_step, packaging_steps: jam_track_right.packaging_steps}.to_json )
|
||||
Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s,
|
||||
{signing_state: jam_track_right.signing_state,
|
||||
current_packaging_step: jam_track_right.current_packaging_step,
|
||||
packaging_steps: jam_track_right.packaging_steps}.to_json)
|
||||
end
|
||||
|
||||
def self.mixdown_signing_job_change(jam_track_mixdown_package)
|
||||
Notification.send_subscription_message('mixdown', jam_track_mixdown_package.id.to_s,
|
||||
{signing_state: jam_track_mixdown_package.signing_state,
|
||||
current_packaging_step: jam_track_mixdown_package.current_packaging_step,
|
||||
packaging_steps: jam_track_mixdown_package.packaging_steps}.to_json)
|
||||
end
|
||||
|
||||
def self.test
|
||||
Notification.send_subscription_message('some_key', '1', {field1: 'field1', field2: 'field2'}.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -736,6 +736,30 @@ module JamRuby
|
|||
)
|
||||
end
|
||||
|
||||
def mixdown_sign_complete(receiver_id, mixdown_package_id)
|
||||
signed = Jampb::MixdownSignComplete.new(
|
||||
:mixdown_package_id => mixdown_package_id
|
||||
)
|
||||
|
||||
Jampb::ClientMessage.new(
|
||||
:type => ClientMessage::Type::MIXDOWN_SIGN_COMPLETE,
|
||||
:route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET,
|
||||
:mixdown_sign_complete => signed
|
||||
)
|
||||
end
|
||||
|
||||
def mixdown_sign_failed(receiver_id, mixdown_package_id)
|
||||
signed = Jampb::MixdownSignFailed.new(
|
||||
:mixdown_package_id => mixdown_package_id
|
||||
)
|
||||
|
||||
Jampb::ClientMessage.new(
|
||||
:type => ClientMessage::Type::MIXDOWN_SIGN_FAILED,
|
||||
:route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET,
|
||||
:mixdown_sign_failed=> signed
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def recording_master_mix_complete(receiver_id, recording_id, claimed_recording_id, band_id, msg, notification_id, created_at)
|
||||
recording_master_mix_complete = Jampb::RecordingMasterMixComplete.new(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module JamRuby
|
|||
before_validation(:on => :create) do
|
||||
self.created_at ||= Time.now
|
||||
self.id = SecureRandom.uuid
|
||||
self.uri = "dump/#{self.id}-#{self.created_at.to_i}"
|
||||
self.uri = "dump/#{created_at.strftime('%Y-%m-%d')}/#{self.id}"
|
||||
end
|
||||
|
||||
def user_email
|
||||
|
|
|
|||
|
|
@ -438,6 +438,10 @@ module JamRuby
|
|||
jam_track_rights.where("user_id=?", user).first
|
||||
end
|
||||
|
||||
def mixdowns_for_user(user)
|
||||
JamTrackMixdown.where(user_id: user.id).where(jam_track_id: self.id)
|
||||
end
|
||||
|
||||
def short_plan_code
|
||||
prefix = 'jamtrack-'
|
||||
plan_code[prefix.length..-1]
|
||||
|
|
@ -450,7 +454,6 @@ module JamRuby
|
|||
|
||||
def generate_slug
|
||||
self.slug = sluggarize(original_artist) + '-' + sluggarize(name)
|
||||
puts "Self.slug #{self.slug}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
module JamRuby
|
||||
|
||||
# describes what users have rights to which tracks
|
||||
class JamTrackMixdown < ActiveRecord::Base
|
||||
|
||||
@@log = Logging.logger[JamTrackMixdown]
|
||||
|
||||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
has_many :jam_track_mixdown_packages, class_name: "JamRuby::JamTrackMixdownPackage", order: 'created_at DESC'
|
||||
has_one :jam_track_right, class_name: 'JamRuby::JamTrackRight', foreign_key: 'last_mixdown_id', inverse_of: :last_mixdown
|
||||
|
||||
validates :name, presence: true, length: {maximum: 100}
|
||||
validates :description, length: {maximum: 1000}
|
||||
validates :user, presence: true
|
||||
validates :jam_track, presence: true
|
||||
validates :settings, presence: true
|
||||
|
||||
validates_uniqueness_of :name, scope: :user_id
|
||||
|
||||
validate :verify_settings
|
||||
validate :verify_max_mixdowns
|
||||
|
||||
def self.index(params, user)
|
||||
jam_track_id = params[:id]
|
||||
|
||||
limit = 20
|
||||
|
||||
query = JamTrackMixdown.where('jam_track_id = ?', jam_track_id).where('user_id = ?', user.id).order('created_at').paginate(page: 1, per_page: limit)
|
||||
|
||||
count = query.total_entries
|
||||
|
||||
if count == 0
|
||||
[query, nil, count]
|
||||
elsif query.length < limit
|
||||
[query, nil, count]
|
||||
else
|
||||
[query, start + limit, count]
|
||||
end
|
||||
end
|
||||
|
||||
def verify_max_mixdowns
|
||||
if self.jam_track && self.user && self.jam_track.mixdowns_for_user(self.user).length >= 5
|
||||
errors.add(:jam_track, 'allowed 5 mixes')
|
||||
end
|
||||
end
|
||||
|
||||
def verify_settings
|
||||
|
||||
# the user has to specify at least at least one tweak to volume, speed, pitch, pan. otherwise there is nothing to do
|
||||
|
||||
parsed = JSON.parse(self.settings)
|
||||
specified_track_count = parsed["tracks"] ? parsed["tracks"].length : 0
|
||||
|
||||
tweaked = false
|
||||
all_quiet = jam_track.stem_tracks.length == 0 ? false : jam_track.stem_tracks.length == specified_track_count # we already say 'all_quiet is false' if the user did not specify as many tracks as there are on the JamTrack, because omission implies 'include this track'
|
||||
|
||||
|
||||
if parsed["speed"]
|
||||
tweaked = true
|
||||
end
|
||||
if parsed["pitch"]
|
||||
tweaked = true
|
||||
end
|
||||
|
||||
|
||||
if parsed["tracks"]
|
||||
parsed["tracks"].each do |track|
|
||||
if track["mute"]
|
||||
tweaked = true
|
||||
end
|
||||
if track["vol"] && track["vol"] != 0
|
||||
tweaked = true
|
||||
end
|
||||
if track["pan"] && track["pan"] != 0
|
||||
tweaked = true
|
||||
end
|
||||
|
||||
# there is at least one track with volume specified.
|
||||
if !track["mute"] && track["vol"] != 0
|
||||
all_quiet = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if all_quiet
|
||||
errors.add(:settings, 'are all muted')
|
||||
end
|
||||
if !tweaked
|
||||
errors.add(:settings, 'have nothing specified')
|
||||
end
|
||||
|
||||
if parsed["speed"] && !parsed["speed"].is_a?(Integer)
|
||||
errors.add(:settings, 'has non-integer speed')
|
||||
end
|
||||
|
||||
if parsed["pitch"] && !parsed["pitch"].is_a?(Integer)
|
||||
errors.add(:settings, 'has non-integer pitch')
|
||||
end
|
||||
end
|
||||
|
||||
def self.create(name, description, user, jam_track, settings)
|
||||
mixdown = JamTrackMixdown.new
|
||||
mixdown.name = name
|
||||
mixdown.description = description
|
||||
mixdown.user = user
|
||||
mixdown.jam_track = jam_track
|
||||
mixdown.settings = settings.to_json # RAILS 4 CAN REMOVE .to_json
|
||||
mixdown.save
|
||||
mixdown
|
||||
end
|
||||
|
||||
def will_pitch_shift?
|
||||
self.settings["pitch"] != 0 || self.settings["speed"] != 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
module JamRuby
|
||||
|
||||
# describes what users have rights to which tracks
|
||||
class JamTrackMixdownPackage < ActiveRecord::Base
|
||||
include JamRuby::S3ManagerMixin
|
||||
|
||||
@@log = Logging.logger[JamTrackMixdownPackage]
|
||||
|
||||
# these are used as extensions for the files stored in s3
|
||||
FILE_TYPE_OGG = 'ogg'
|
||||
FILE_TYPE_AAC = 'aac'
|
||||
FILE_TYPES = [FILE_TYPE_OGG, FILE_TYPE_AAC]
|
||||
|
||||
SAMPLE_RATE_44 = 44
|
||||
SAMPLE_RATE_48 = 48
|
||||
SAMPLE_RATES = [SAMPLE_RATE_44, SAMPLE_RATE_48]
|
||||
|
||||
ENCRYPT_TYPE_JKZ = 'jkz'
|
||||
ENCRYPT_TYPES = [ENCRYPT_TYPE_JKZ, nil]
|
||||
|
||||
default_scope { order('created_at desc') }
|
||||
|
||||
belongs_to :jam_track_mixdown, class_name: "JamRuby::JamTrackMixdown", dependent: :destroy
|
||||
|
||||
validates :jam_track_mixdown, presence: true
|
||||
|
||||
validates :file_type, inclusion: {in: FILE_TYPES}
|
||||
validates :sample_rate, inclusion: {in: SAMPLE_RATES}
|
||||
validates :encrypt_type, inclusion: {in: ENCRYPT_TYPES}
|
||||
validates_uniqueness_of :file_type, scope: [:sample_rate, :encrypt_type, :jam_track_mixdown_id]
|
||||
validates :signing, inclusion: {in: [true, false]}
|
||||
validates :signed, inclusion: {in: [true, false]}
|
||||
|
||||
validate :verify_download_count
|
||||
before_destroy :delete_s3_files
|
||||
after_save :after_save
|
||||
|
||||
MAX_JAM_TRACK_DOWNLOADS = 1000
|
||||
|
||||
def self.estimated_queue_time
|
||||
jam_track_signing_count = JamTrackRight.where(queued: true).count
|
||||
mixdowns = JamTrackMixdownPackage.unscoped.select('count(CASE WHEN queued THEN 1 ELSE NULL END) as queue_count, count(CASE WHEN speed_pitched THEN 1 ELSE NULL END) as speed_pitch_count').where(queued: true).first
|
||||
total_mixdowns = mixdowns['queue_count'].to_i
|
||||
slow_mixdowns = mixdowns['speed_pitch_count'].to_i
|
||||
fast_mixdowns = total_mixdowns - slow_mixdowns
|
||||
|
||||
guess = APP_CONFIG.estimated_jam_track_time * jam_track_signing_count + APP_CONFIG.estimated_fast_mixdown_time * fast_mixdowns + APP_CONFIG.estimated_slow_mixdown_time * slow_mixdowns
|
||||
|
||||
Stats.write('web.jam_track.queue_time', {value: guess / 60.0, jam_tracks: jam_track_signing_count, slow_mixdowns: slow_mixdowns, fast_mixdowns: fast_mixdowns})
|
||||
guess
|
||||
end
|
||||
|
||||
def after_save
|
||||
# try to catch major transitions:
|
||||
|
||||
# if just queue time changes, start time changes, or signed time changes, send out a notice
|
||||
if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at || current_packaging_step != current_packaging_step_was || packaging_steps != packaging_steps_was
|
||||
SubscriptionMessage.mixdown_signing_job_change(self)
|
||||
end
|
||||
end
|
||||
|
||||
def self.create(mixdown, file_type, sample_rate, encrypt_type)
|
||||
|
||||
package = JamTrackMixdownPackage.new
|
||||
package.speed_pitched = mixdown.will_pitch_shift?
|
||||
package.jam_track_mixdown = mixdown
|
||||
package.file_type = file_type
|
||||
package.sample_rate = sample_rate
|
||||
package.signed = false
|
||||
package.signing = false
|
||||
package.encrypt_type = encrypt_type
|
||||
package.save
|
||||
package
|
||||
end
|
||||
|
||||
def verify_download_count
|
||||
if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin
|
||||
errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}")
|
||||
end
|
||||
end
|
||||
|
||||
def is_pitch_speed_shifted?
|
||||
mix_settings = JSON.parse(self.settings)
|
||||
mix_settings["speed"] || mix_settings["pitch"]
|
||||
end
|
||||
|
||||
def finish_errored(error_reason, error_detail)
|
||||
self.last_errored_at = Time.now
|
||||
self.last_signed_at = Time.now
|
||||
self.error_count = self.error_count + 1
|
||||
self.error_reason = error_reason
|
||||
self.error_detail = error_detail
|
||||
self.should_retry = self.error_count < 5
|
||||
self.signing = false
|
||||
self.signing_queued_at = nil # if left set, throws off signing_state on subsequent signing attempts
|
||||
|
||||
if save
|
||||
Notification.send_mixdown_sign_failed(self)
|
||||
else
|
||||
raise "Error sending notification #{self.errors}"
|
||||
end
|
||||
end
|
||||
|
||||
def finish_sign(url, private_key, length, md5)
|
||||
self.url = url
|
||||
self.private_key = private_key
|
||||
self.signing_queued_at = nil # if left set, throws off signing_state on subsequent signing attempts
|
||||
self.downloaded_since_sign = false
|
||||
self.last_signed_at = Time.now
|
||||
self.length = length
|
||||
self.md5 = md5
|
||||
self.signed = true
|
||||
self.signing = false
|
||||
self.error_count = 0
|
||||
self.error_reason = nil
|
||||
self.error_detail = nil
|
||||
self.should_retry = false
|
||||
save!
|
||||
end
|
||||
|
||||
def store_dir
|
||||
"jam_track_mixdowns/#{created_at.strftime('%m-%d-%Y')}/#{self.jam_track_mixdown.user_id}"
|
||||
end
|
||||
|
||||
def filename
|
||||
if encrypt_type
|
||||
"#{id}.#{encrypt_type}"
|
||||
else
|
||||
"#{id}.#{file_type}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# creates a short-lived URL that has access to the object.
|
||||
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
|
||||
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
|
||||
# but the url is short lived enough so that it wouldn't be easily shared
|
||||
def sign_url(expiration_time = 120)
|
||||
s3_manager.sign_url(self['url'], {:expires => expiration_time, :secure => true})
|
||||
end
|
||||
|
||||
|
||||
def enqueue
|
||||
begin
|
||||
self.signing_queued_at = Time.now
|
||||
self.signing_started_at = nil
|
||||
self.last_signed_at = nil
|
||||
self.queued = true
|
||||
self.save
|
||||
|
||||
queue_time = JamTrackMixdownPackage.estimated_queue_time
|
||||
|
||||
# is_pitch_speed_shifted?
|
||||
Resque.enqueue(JamTrackMixdownPackager, self.id)
|
||||
return queue_time
|
||||
rescue Exception => e
|
||||
puts "e: #{e}"
|
||||
# implies redis is down. we don't update started_at by bailing out here
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off
|
||||
def enqueue_if_needed
|
||||
state = signing_state
|
||||
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
|
||||
false
|
||||
else
|
||||
return enqueue
|
||||
end
|
||||
end
|
||||
|
||||
def ready?
|
||||
self.signed && self.url.present?
|
||||
end
|
||||
|
||||
# returns easy to digest state field
|
||||
# SIGNED - the package is ready to be downloaded
|
||||
# ERROR - the package was built unsuccessfully
|
||||
# SIGNING_TIMEOUT - the package was kicked off to be signed, but it seems to have hung
|
||||
# SIGNING - the package is currently signing
|
||||
# QUEUED_TIMEOUT - the package signing job (JamTrackBuilder) was queued, but never executed
|
||||
# QUEUED - the package is queued to sign
|
||||
# QUIET - the jam_track_right exists, but no job has been kicked off; a job needs to be enqueued
|
||||
def signing_state
|
||||
state = nil
|
||||
|
||||
if signed
|
||||
state = 'SIGNED'
|
||||
elsif signing_started_at && signing
|
||||
# the maximum amount of time the packaging job can take is 10 seconds * num steps. For a 10 track song, this will be 110 seconds. It's a bit long.
|
||||
if Time.now - signing_started_at > APP_CONFIG.signing_job_signing_max_time
|
||||
state = 'SIGNING_TIMEOUT'
|
||||
elsif Time.now - last_step_at > APP_CONFIG.mixdown_step_max_time
|
||||
state = 'SIGNING_TIMEOUT'
|
||||
else
|
||||
state = 'SIGNING'
|
||||
end
|
||||
elsif signing_queued_at
|
||||
if Time.now - signing_queued_at > APP_CONFIG.mixdown_job_queue_max_time
|
||||
state = 'QUEUED_TIMEOUT'
|
||||
else
|
||||
state = 'QUEUED'
|
||||
end
|
||||
elsif error_count > 0
|
||||
state = 'ERROR'
|
||||
else
|
||||
if Time.now - created_at > 60 # it should not take more than a minute to get QUIET out
|
||||
state = 'QUIET_TIMEOUT'
|
||||
else
|
||||
state = 'QUIET' # needs to be poked to go build
|
||||
end
|
||||
|
||||
end
|
||||
state
|
||||
end
|
||||
|
||||
def signed?
|
||||
signed
|
||||
end
|
||||
|
||||
def update_download_count(count=1)
|
||||
self.download_count = self.download_count + count
|
||||
self.last_downloaded_at = Time.now
|
||||
|
||||
if self.signed
|
||||
self.downloaded_since_sign = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.stats
|
||||
stats = {}
|
||||
|
||||
result = JamTrackMixdownPackage.unscoped.select('count(id) as total, count(CASE WHEN signing THEN 1 ELSE NULL END) as signing_count')
|
||||
|
||||
stats['count'] = result[0]['total'].to_i
|
||||
stats['signing_count'] = result[0]['signing_count'].to_i
|
||||
stats
|
||||
end
|
||||
|
||||
|
||||
def delete_s3_files
|
||||
s3_manager.delete(self.url) if self.url && s3_manager.exists?(self.url)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ module JamRuby
|
|||
attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44
|
||||
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
|
||||
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
|
||||
belongs_to :last_mixdown, class_name: 'JamRuby::JamTrackMixdown', foreign_key: 'last_mixdown_id', inverse_of: :jam_track_right
|
||||
|
||||
validates :user, presence: true
|
||||
validates :jam_track, presence: true
|
||||
|
|
@ -25,9 +26,16 @@ module JamRuby
|
|||
mount_uploader :url_48, JamTrackRightUploader
|
||||
mount_uploader :url_44, JamTrackRightUploader
|
||||
before_destroy :delete_s3_files
|
||||
before_create :create_private_keys
|
||||
|
||||
MAX_JAM_TRACK_DOWNLOADS = 1000
|
||||
|
||||
def create_private_keys
|
||||
rsa_key = OpenSSL::PKey::RSA.new(1024)
|
||||
key = rsa_key.to_pem()
|
||||
self.private_key_44 = key
|
||||
self.private_key_48 = key
|
||||
end
|
||||
def after_save
|
||||
# try to catch major transitions:
|
||||
|
||||
|
|
@ -58,6 +66,7 @@ module JamRuby
|
|||
|
||||
def finish_errored(error_reason, error_detail, sample_rate)
|
||||
self.last_signed_at = Time.now
|
||||
self.queued = false
|
||||
self.error_count = self.error_count + 1
|
||||
self.error_reason = error_reason
|
||||
self.error_detail = error_detail
|
||||
|
|
@ -77,6 +86,7 @@ module JamRuby
|
|||
|
||||
def finish_sign(length, md5, bitrate)
|
||||
self.last_signed_at = Time.now
|
||||
self.queued = false
|
||||
if bitrate==48
|
||||
self.length_48 = length
|
||||
self.md5_48 = md5
|
||||
|
|
@ -112,7 +122,7 @@ module JamRuby
|
|||
|
||||
def enqueue(sample_rate=48)
|
||||
begin
|
||||
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at_44 => nil, :signing_started_at_48 => nil, :last_signed_at => nil)
|
||||
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at_44 => nil, :signing_started_at_48 => nil, :last_signed_at => nil, :queued => true)
|
||||
Resque.enqueue(JamTracksBuilder, self.id, sample_rate)
|
||||
true
|
||||
rescue Exception => e
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module JamRuby
|
|||
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => "music_session_id"
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id"
|
||||
belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight", :foreign_key => "jam_track_right_id"
|
||||
belongs_to :jam_track_mixdown_package, :class_name => "JamRuby::JamTrackMixdownPackage", :foreign_key => "jam_track_mixdown_package_id"
|
||||
|
||||
validates :target_user, :presence => true
|
||||
validates :message, length: {minimum: 1, maximum: 400}, no_profanity: true, if: :text_message?
|
||||
|
|
@ -1255,7 +1256,7 @@ module JamRuby
|
|||
def send_jam_track_sign_complete(jam_track_right)
|
||||
|
||||
notification = Notification.new
|
||||
notification.jam_track_right_id = jam_track_right.id
|
||||
notification.jam_track_mixdown_package = jam_track_right.id
|
||||
notification.description = NotificationTypes::JAM_TRACK_SIGN_COMPLETE
|
||||
notification.target_user_id = jam_track_right.user_id
|
||||
notification.save!
|
||||
|
|
@ -1265,6 +1266,30 @@ module JamRuby
|
|||
#@@mq_router.publish_to_all_clients(msg)
|
||||
end
|
||||
|
||||
def send_mixdown_sign_failed(jam_track_mixdown_package)
|
||||
|
||||
notification = Notification.new
|
||||
notification.jam_track_mixdown_package_id = jam_track_mixdown_package.id
|
||||
notification.description = NotificationTypes::MIXDOWN_SIGN_FAILED
|
||||
notification.target_user_id = jam_track_mixdown_package.jam_track_mixdown.user_id
|
||||
notification.save!
|
||||
|
||||
msg = @@message_factory.mixdown_sign_failed(jam_track_mixdown_package.jam_track_mixdown.user_id, jam_track_mixdown_package.id)
|
||||
@@mq_router.publish_to_user(jam_track_mixdown_package.jam_track_mixdown.user_id, msg)
|
||||
end
|
||||
|
||||
def send_mixdown_sign_complete(jam_track_mixdown_package)
|
||||
|
||||
notification = Notification.new
|
||||
notification.jam_track_mixdown_package_id = jam_track_mixdown_package.id
|
||||
notification.description = NotificationTypes::MIXDOWN_SIGN_COMPLETE
|
||||
notification.target_user_id = jam_track_mixdown_package.jam_track_mixdown.user_id
|
||||
notification.save!
|
||||
|
||||
msg = @@message_factory.mixdown_sign_complete(jam_track_mixdown_package.jam_track_mixdown.user_id, jam_track_mixdown_package.id)
|
||||
@@mq_router.publish_to_user(jam_track_mixdown_package.jam_track_mixdown.user_id, msg)
|
||||
end
|
||||
|
||||
def send_client_update(product, version, uri, size)
|
||||
msg = @@message_factory.client_update( product, version, uri, size)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,461 @@
|
|||
require 'json'
|
||||
require 'resque'
|
||||
require 'resque-retry'
|
||||
require 'net/http'
|
||||
require 'digest/md5'
|
||||
|
||||
module JamRuby
|
||||
class JamTrackMixdownPackager
|
||||
extend JamRuby::ResqueStats
|
||||
|
||||
include JamRuby::S3ManagerMixin
|
||||
|
||||
|
||||
MAX_PAN = 90
|
||||
MIN_PAN = -90
|
||||
|
||||
attr_accessor :mixdown_package_id, :settings, :mixdown_package, :mixdown, :step
|
||||
@queue = :jam_track_mixdown_packager
|
||||
|
||||
def log
|
||||
@log || Logging.logger[JamTrackMixdownPackager]
|
||||
end
|
||||
|
||||
def self.perform(mixdown_package_id, bitrate=48)
|
||||
jam_track_builder = JamTrackMixdownPackager.new()
|
||||
jam_track_builder.mixdown_package_id = mixdown_package_id
|
||||
jam_track_builder.run
|
||||
end
|
||||
|
||||
def compute_steps
|
||||
@step = 0
|
||||
number_downloads = @track_settings.length
|
||||
number_volume_adjustments = (@track_settings.select { |track| should_alter_volume? track }).length
|
||||
|
||||
pitch_shift_steps = @mixdown.will_pitch_shift? ? 1 : 0
|
||||
mix_steps = 1
|
||||
package_steps = 1
|
||||
|
||||
number_downloads + number_volume_adjustments + pitch_shift_steps + mix_steps + package_steps
|
||||
end
|
||||
|
||||
def run
|
||||
begin
|
||||
log.info("Mixdown job starting. mixdown_packager_id #{mixdown_package_id}")
|
||||
begin
|
||||
@mixdown_package = JamTrackMixdownPackage.find(mixdown_package_id)
|
||||
|
||||
|
||||
# bailout check
|
||||
if @mixdown_package.signed?
|
||||
log.debug("package is already signed. bailing")
|
||||
return
|
||||
end
|
||||
|
||||
@mixdown = @mixdown_package.jam_track_mixdown
|
||||
@settings = JSON.parse(@mixdown.settings)
|
||||
|
||||
track_settings
|
||||
|
||||
# compute the step count
|
||||
total_steps = compute_steps
|
||||
|
||||
# track that it's started ( and avoid db validations )
|
||||
signing_started_at = Time.now
|
||||
last_step_at = Time.now
|
||||
#JamTrackMixdownPackage.where(:id => @mixdown_package.id).update_all(:signing_started_at => signing_started_at, :should_retry => false, packaging_steps: total_steps, current_packaging_step: 0, last_step_at: last_step_at, :signing => true)
|
||||
|
||||
# because we are skipping 'after_save', we have to keep the model current for the notification. A bit ugly...
|
||||
|
||||
@mixdown_package.current_packaging_step = 0
|
||||
@mixdown_package.packaging_steps = total_steps
|
||||
@mixdown_package.signing_started_at = signing_started_at
|
||||
@mixdown_package.signing = true
|
||||
@mixdown_package.should_retry = false
|
||||
@mixdown_package.last_step_at = last_step_at
|
||||
@mixdown_package.queued = false
|
||||
@mixdown_package.save
|
||||
|
||||
SubscriptionMessage.mixdown_signing_job_change(@mixdown_package)
|
||||
|
||||
package
|
||||
|
||||
log.info "Signed mixdown package to #{@mixdown_package[:url]}"
|
||||
|
||||
rescue Exception => e
|
||||
# record the error in the database
|
||||
post_error(e)
|
||||
|
||||
#SubscriptionMessage.mixdown_signing_job_change(@mixdown_package)
|
||||
# and let the job fail, alerting ops too
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def should_alter_volume? track
|
||||
|
||||
# short cut is possible if vol = 1.0 and pan = 0
|
||||
vol = track[:vol]
|
||||
pan = track[:pan]
|
||||
|
||||
vol != 1.0 || pan != 0
|
||||
end
|
||||
|
||||
# creates a list of tracks to actually mix
|
||||
def track_settings
|
||||
altered_tracks = @settings["tracks"] || []
|
||||
|
||||
@track_settings = []
|
||||
|
||||
#void slider2Pan(int i, float *f);
|
||||
|
||||
stems = @mixdown.jam_track.stem_tracks
|
||||
@track_count = stems.length
|
||||
|
||||
stems.each do |stem|
|
||||
|
||||
vol = 1.0
|
||||
pan = 0
|
||||
match = false
|
||||
skipped = false
|
||||
# is this stem in the altered_tracks list?
|
||||
altered_tracks.each do |alteration|
|
||||
|
||||
if alteration["id"] == stem.id
|
||||
if alteration["mute"] || alteration["vol"] == 0
|
||||
log.debug("leaving out track because muted or 0 volume #{alteration.inspect}")
|
||||
skipped = true
|
||||
next
|
||||
else
|
||||
vol = alteration["vol"] || vol
|
||||
pan = alteration["pan"] || pan
|
||||
end
|
||||
@track_settings << {stem: stem, vol: vol, pan: pan}
|
||||
match = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# if we didn't deliberately skip this one, and if there was no 'match' (meaning user did not specify), then we leave this in unchanged
|
||||
if !skipped && !match
|
||||
@track_settings << {stem:stem, vol:vol, pan:pan}
|
||||
end
|
||||
end
|
||||
|
||||
@track_settings
|
||||
end
|
||||
|
||||
def slider_to_pan(pan)
|
||||
# transpose MIN_PAN to MAX_PAN to
|
||||
# 0-1.0 range
|
||||
#assumes abs(MIN_PAN) == abs(MAX_PAN)
|
||||
# k = f(i) = (i)/(2*MAX_PAN) + 0.5
|
||||
# so f(MIN_PAN) = -0.5 + 0.5 = 0
|
||||
|
||||
k = ((pan * (1.0))/ (2.0 * MAX_PAN )) + 0.5
|
||||
l, r = 0
|
||||
|
||||
if k == 0
|
||||
l = 0.0
|
||||
r = 1.0
|
||||
else
|
||||
l = Math.sqrt(k)
|
||||
r = Math.sqrt(1-k)
|
||||
end
|
||||
|
||||
[l, r]
|
||||
end
|
||||
|
||||
def package
|
||||
|
||||
puts @settings.inspect
|
||||
puts @track_count
|
||||
puts @track_settings
|
||||
puts @track_settings.count
|
||||
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
||||
# download all files
|
||||
@track_settings.each do |track|
|
||||
jam_track_track = track[:stem]
|
||||
|
||||
file = File.join(tmp_dir, jam_track_track.id + '.ogg')
|
||||
|
||||
bump_step(@mixdown_package)
|
||||
|
||||
# download each track needed
|
||||
s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file)
|
||||
|
||||
|
||||
track[:file] = file
|
||||
end
|
||||
|
||||
audio_process tmp_dir
|
||||
end
|
||||
end
|
||||
|
||||
def audio_process(tmp_dir)
|
||||
# use sox remix to apply mute, volume, pan settings
|
||||
|
||||
|
||||
# step 1: apply pan and volume per track. mute and vol of 0 has already been handled, by virtue of those tracks not being present in @track_settings
|
||||
# step 2: mix all tracks into single track, dividing by constant number of jam tracks, which is same as done by client backend
|
||||
# step 3: apply pitch and speed (if applicable)
|
||||
# step 4: encrypt with jkz (if applicable)
|
||||
|
||||
apply_vol_and_pan tmp_dir
|
||||
|
||||
mix tmp_dir
|
||||
|
||||
pitch_speed tmp_dir
|
||||
|
||||
final_packaging tmp_dir
|
||||
end
|
||||
|
||||
# output is :volumed_file in each track in @track_settings
|
||||
def apply_vol_and_pan(tmp_dir)
|
||||
@track_settings.each do |track|
|
||||
|
||||
jam_track_track = track[:stem]
|
||||
file = track[:file]
|
||||
|
||||
unless should_alter_volume? track
|
||||
track[:volumed_file] = file
|
||||
else
|
||||
pan_l, pan_r = slider_to_pan(track[:pan])
|
||||
|
||||
vol = track[:vol]
|
||||
|
||||
# short
|
||||
channel_l = pan_l * vol
|
||||
channel_r = pan_r * vol
|
||||
|
||||
bump_step(@mixdown_package)
|
||||
|
||||
# sox claps.wav claps-remixed.wav remix 1v1.0 2v1.0
|
||||
|
||||
volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg')
|
||||
|
||||
cmd("sox \"#{file}\" \"#{volumed_file}\" remix 1v#{channel_r} 2v#{channel_l}", 'vol_pan')
|
||||
|
||||
track[:volumed_file] = volumed_file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# output is @mix_file
|
||||
def mix(tmp_dir)
|
||||
|
||||
bump_step(@mixdown_package)
|
||||
|
||||
@mix_file = File.join(tmp_dir, "mix.ogg")
|
||||
|
||||
# if there is only one track to mix, we need to skip mixing (sox will barf if you try to mix one file), but still divide by number of tracks
|
||||
if @track_settings.count == 1
|
||||
mix_divide = 1.0/@track_count
|
||||
cmd = "sox -v #{mix_divide} \"#{@track_settings[0][:volumed_file]}\" \"#{@mix_file}\""
|
||||
cmd(cmd, 'volume_adjust')
|
||||
else
|
||||
# sox -m will divide by number of inputs by default. But we purposefully leave out tracks that are mute/no volume (to save downloading/processing time in this job)
|
||||
# so we need to tell sox to divide by how many tracks there are as a constant, because this is how the client works today
|
||||
#sox -m -v 1/n file1 -v 1/n file2 out
|
||||
cmd = "sox -m"
|
||||
mix_divide = 1.0/@track_count
|
||||
@track_settings.each do |track|
|
||||
volumed_file = track[:volumed_file]
|
||||
cmd << " -v #{mix_divide} \"#{volumed_file}\""
|
||||
end
|
||||
|
||||
|
||||
cmd << " \"#{@mix_file}\""
|
||||
cmd(cmd, 'mix_adjust')
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
# output is @speed_mix_file
|
||||
def pitch_speed tmp_dir
|
||||
|
||||
# # usage
|
||||
# This app will take an ogg, wav, or mp3 file (for the uploads) as its input and output an ogg file.
|
||||
# Usage:
|
||||
# sbsms path-to-input.ogg path-to-output.ogg TimeStrech PitchShift
|
||||
|
||||
# input is @mix_file, created by mix()
|
||||
# output is @speed_mix_file
|
||||
|
||||
pitch = @settings['pitch'] || 0
|
||||
speed = @settings['speed'] || 0
|
||||
|
||||
# if pitch and speed are 0, we do nothing here
|
||||
if pitch == 0 && speed == 0
|
||||
@speed_mix_file = @mix_file
|
||||
else
|
||||
bump_step(@mixdown_package)
|
||||
|
||||
@speed_mix_file = File.join(tmp_dir, "speed_mix_file.ogg")
|
||||
|
||||
# usage: sbsms infile<.wav|.aif|.mp3|.ogg> outfile<.ogg> rate[0.01:100] halfsteps[-48:48] outSampleRateInHz
|
||||
|
||||
sample_rate = 48000
|
||||
if @mixdown_package.sample_rate != 48
|
||||
sample_rate = 44100
|
||||
end
|
||||
|
||||
# rate comes in as a percent (like 5, -5 for 5%, -5%). We need to change that to 1.05/
|
||||
sbsms_speed = speed/100.0
|
||||
sbsms_speed = 1.0 + sbsms_speed
|
||||
|
||||
sbsms_pitch = pitch
|
||||
cmd( "sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift')
|
||||
end
|
||||
end
|
||||
|
||||
def final_packaging tmp_dir
|
||||
|
||||
bump_step(@mixdown_package)
|
||||
|
||||
url = nil
|
||||
private_key = nil
|
||||
md5 = nil
|
||||
length = 0
|
||||
output = nil
|
||||
|
||||
if @mixdown_package.encrypt_type
|
||||
output, private_key = encrypt_jkz tmp_dir
|
||||
else
|
||||
# create output file to correct output format
|
||||
output = convert tmp_dir
|
||||
end
|
||||
|
||||
# upload output to S3
|
||||
s3_url = "#{@mixdown_package.store_dir}/#{@mixdown_package.filename}"
|
||||
s3_manager.upload(s3_url, output)
|
||||
|
||||
length = File.size(output)
|
||||
computed_md5 = Digest::MD5.new
|
||||
File.open(output, 'rb').each {|line| computed_md5.update(line)}
|
||||
md5 = computed_md5.to_s
|
||||
|
||||
@mixdown_package.finish_sign(s3_url, private_key, length, md5.to_s)
|
||||
end
|
||||
|
||||
# returns output destination, converting if necessary
|
||||
def convert(tmp_dir)
|
||||
# if the file already ends with the desired file type, call it a win
|
||||
if @speed_mix_file.end_with?(@mixdown_package.file_type)
|
||||
@speed_mix_file
|
||||
else
|
||||
# otherwise we need to convert from lastly created file to correct
|
||||
output = File.join(tmp_dir, "output.#{@mixdown_package.file_type}")
|
||||
|
||||
raise 'unknown file_type' if @mixdown_package.file_type != JamTrackMixdownPackage::FILE_TYPE_AAC
|
||||
|
||||
cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
def encrypt_jkz(tmp_dir)
|
||||
py_root = APP_CONFIG.jamtracks_dir
|
||||
step = 0
|
||||
|
||||
private_key = nil
|
||||
# we need to make the id of the custom mix be the name of the file (ID.ogg)
|
||||
custom_mix_name = File.join(tmp_dir, "#{@mixdown.id}.ogg")
|
||||
FileUtils.mv(@speed_mix_file, custom_mix_name)
|
||||
jam_file_opts = ""
|
||||
jam_file_opts << " -i #{Shellwords.escape("#{custom_mix_name}+mixdown")}"
|
||||
|
||||
sku = @mixdown_package.id
|
||||
title = @mixdown.name
|
||||
output = File.join(tmp_dir, "#{title.parameterize}.jkz")
|
||||
py_file = File.join(py_root, "jkcreate.py")
|
||||
version = @mixdown_package.version
|
||||
|
||||
right = @mixdown.jam_track.right_for_user(@mixdown.user)
|
||||
|
||||
if @mixdown_package.sample_rate == 48
|
||||
private_key = right.private_key_48
|
||||
else
|
||||
private_key = right.private_key_44
|
||||
end
|
||||
|
||||
unless private_key
|
||||
@error_reason = 'no_private_key'
|
||||
@error_detail = 'user needs to generate JamTrack for given sample rate'
|
||||
raise @error_reason
|
||||
end
|
||||
|
||||
private_key_file = File.join(tmp_dir, 'skey.pem')
|
||||
File.open(private_key_file, 'w') {|f| f.write(private_key) }
|
||||
|
||||
log.debug("PRIVATE KEY")
|
||||
log.debug(private_key)
|
||||
log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output})"
|
||||
|
||||
cli = "python #{py_file} -D -k #{sku} -p #{Shellwords.escape(tmp_dir)}/pkey.pem -s #{Shellwords.escape(tmp_dir)}/skey.pem #{jam_file_opts} -o #{Shellwords.escape(output)} -t #{Shellwords.escape(title)} -V #{Shellwords.escape(version)}"
|
||||
|
||||
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
||||
pid = wait_thr.pid
|
||||
exit_status = wait_thr.value
|
||||
err = stderr.read(1000)
|
||||
out = stdout.read(1000)
|
||||
#puts "stdout: #{out}, stderr: #{err}"
|
||||
raise ArgumentError, "Error calling python script: #{err}" if err.present?
|
||||
raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file"))
|
||||
|
||||
private_key = File.read(private_key_file)
|
||||
end
|
||||
return output, private_key
|
||||
end
|
||||
|
||||
def cmd(cmd, type)
|
||||
|
||||
log.debug("executing #{cmd}")
|
||||
|
||||
output = `#{cmd}`
|
||||
|
||||
result_code = $?.to_i
|
||||
|
||||
if result_code == 0
|
||||
output
|
||||
else
|
||||
@error_reason = type + "_fail"
|
||||
@error_detail = "#{cmd}, #{output}"
|
||||
raise "command `#{cmd}` failed."
|
||||
end
|
||||
end
|
||||
|
||||
# increment the step, which causes a notification to be sent to the client so it can keep the UI fresh as the packaging step goes on
|
||||
def bump_step(mixdown_package)
|
||||
step = @step
|
||||
last_step_at = Time.now
|
||||
mixdown_package.current_packaging_step = step
|
||||
mixdown_package.last_step_at = last_step_at
|
||||
JamTrackMixdownPackage.where(:id => mixdown_package.id).update_all(last_step_at: last_step_at, current_packaging_step: step)
|
||||
SubscriptionMessage.mixdown_signing_job_change(mixdown_package)
|
||||
|
||||
@step = step + 1
|
||||
end
|
||||
|
||||
# set @error_reason before you raise an exception, and it will be sent back as the error reason
|
||||
# otherwise, the error_reason will be unhandled-job-exception
|
||||
def post_error(e)
|
||||
begin
|
||||
# if error_reason is null, assume this is an unhandled error
|
||||
unless @error_reason
|
||||
@error_reason = "unhandled-job-exception"
|
||||
@error_detail = e.to_s
|
||||
end
|
||||
@mixdown_package.finish_errored(@error_reason, @error_detail)
|
||||
|
||||
rescue Exception => e
|
||||
log.error "unable to post back to the database the error #{e}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -42,7 +42,7 @@ module JamRuby
|
|||
signing_started_model_symbol = bitrate == 48 ? :signing_started_at_48 : :signing_started_at_44
|
||||
signing_state_symbol = bitrate == 48 ? :signing_48 : :signing_44
|
||||
last_step_at = Time.now
|
||||
JamTrackRight.where(:id => @jam_track_right.id).update_all(signing_started_model_symbol => signing_started_at, :should_retry => false, packaging_steps: total_steps, current_packaging_step: 0, last_step_at: last_step_at, signing_state_symbol => true)
|
||||
JamTrackRight.where(:id => @jam_track_right.id).update_all(signing_started_model_symbol => signing_started_at, :should_retry => false, packaging_steps: total_steps, current_packaging_step: 0, last_step_at: last_step_at, signing_state_symbol => true, queued: false)
|
||||
# because we are skipping 'after_save', we have to keep the model current for the notification. A bit ugly...
|
||||
@jam_track_right.current_packaging_step = 0
|
||||
@jam_track_right.packaging_steps = total_steps
|
||||
|
|
@ -50,6 +50,7 @@ module JamRuby
|
|||
@jam_track_right[signing_state_symbol] = true
|
||||
@jam_track_right.should_retry = false
|
||||
@jam_track_right.last_step_at = Time.now
|
||||
@jam_track_right.queued = false
|
||||
SubscriptionMessage.jam_track_signing_job_change(@jam_track_right)
|
||||
JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right, self.bitrate)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ module JamRuby
|
|||
|
||||
def perform
|
||||
# this needs more testing
|
||||
|
||||
# let's make sure jobs don't stay falsely queued for too long. 1 hour seems more than enough
|
||||
JamTrackRight.where("queued = true AND (NOW() - signing_queued_at > '1 hour'::INTERVAL OR NOW() - updated_at > '1 hour'::INTERVAL").update_all(queued:false)
|
||||
JamTrackRightMixdown.unscoped.where("queued = true AND (NOW() - signing_queued_at > '1 hour'::INTERVAL OR NOW() - updated_at > '1 hour'::INTERVAL").update_all(queued:false)
|
||||
|
||||
return
|
||||
#JamTrackRight.ready_to_clean.each do |jam_track_right|
|
||||
# log.debug("deleting files for jam_track_right #{jam_track_right.id}")
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ module JamRuby
|
|||
Stats.write('users', User.stats)
|
||||
Stats.write('sessions', ActiveMusicSession.stats)
|
||||
Stats.write('jam_track_rights', JamTrackRight.stats)
|
||||
Stats.write('jam_track_mixdown_packages', JamTrackMixdownPackage.stats)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,23 @@ FactoryGirl.define do
|
|||
sequence(:phone) { |n| "phone-#{n}" }
|
||||
end
|
||||
|
||||
factory :jam_track_mixdown, :class => JamRuby::JamTrackMixdown do
|
||||
association :user, factory: :user
|
||||
association :jam_track, factory: :jam_track
|
||||
sequence(:name) { |n| "mixdown-#{n}"}
|
||||
settings '{"speed":5}'
|
||||
end
|
||||
|
||||
factory :jam_track_mixdown_package, :class => JamRuby::JamTrackMixdownPackage do
|
||||
file_type JamRuby::JamTrackMixdownPackage::FILE_TYPE_OGG
|
||||
sample_rate 48
|
||||
signing false
|
||||
signed false
|
||||
|
||||
association :jam_track_mixdown, factory: :jam_track_mixdown
|
||||
end
|
||||
|
||||
|
||||
factory :jam_track, :class => JamRuby::JamTrack do
|
||||
sequence(:name) { |n| "jam-track-#{n}" }
|
||||
sequence(:description) { |n| "description-#{n}" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe JamTrackMixdownPackage do
|
||||
include UsesTempFiles
|
||||
|
||||
it "can be created (factory girl)" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package)
|
||||
end
|
||||
|
||||
it "can be created" do
|
||||
mixdown= FactoryGirl.create(:jam_track_mixdown)
|
||||
|
||||
package = JamTrackMixdownPackage.create(mixdown, JamTrackMixdownPackage::FILE_TYPE_OGG, 48, 'jkz')
|
||||
|
||||
package.errors.any?.should == false
|
||||
end
|
||||
|
||||
|
||||
describe "signing_state" do
|
||||
it "quiet" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package)
|
||||
package.signing_state.should eq('QUIET')
|
||||
end
|
||||
|
||||
it "signed" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, signed: true, signing_started_at: Time.now)
|
||||
package.signing_state.should eq('SIGNED')
|
||||
end
|
||||
|
||||
it "error" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, error_count: 1)
|
||||
package.signing_state.should eq('ERROR')
|
||||
end
|
||||
|
||||
it "signing" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, signing:true, signing_started_at: Time.now, packaging_steps: 3, current_packaging_step:0, last_step_at:Time.now)
|
||||
package.signing_state.should eq('SIGNING')
|
||||
end
|
||||
|
||||
it "signing timeout" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, signing: true, signing_started_at: Time.now - (APP_CONFIG.signing_job_signing_max_time + 1), packaging_steps: 3, current_packaging_step:0, last_step_at:Time.now)
|
||||
package.signing_state.should eq('SIGNING_TIMEOUT')
|
||||
end
|
||||
|
||||
it "queued" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, signing_queued_at: Time.now)
|
||||
package.signing_state.should eq('QUEUED')
|
||||
end
|
||||
|
||||
it "signing timeout" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, signing_queued_at: Time.now - (APP_CONFIG.mixdown_job_queue_max_time + 1))
|
||||
package.signing_state.should eq('QUEUED_TIMEOUT')
|
||||
end
|
||||
end
|
||||
|
||||
describe "stats" do
|
||||
|
||||
it "empty" do
|
||||
JamTrackMixdownPackage.stats['count'].should eq(0)
|
||||
end
|
||||
|
||||
it "signing" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package)
|
||||
JamTrackMixdownPackage.stats.should eq('count' => 1,
|
||||
'signing_count' => 0)
|
||||
|
||||
package.signing = true
|
||||
package.save!
|
||||
|
||||
JamTrackMixdownPackage.stats.should eq('count' => 1,
|
||||
'signing_count' => 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "estimated_queue_time" do
|
||||
it "succeeds with no data" do
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(0)
|
||||
end
|
||||
|
||||
it "mixdown packages of different sorts" do
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, speed_pitched: true)
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(0)
|
||||
|
||||
package.queued = true
|
||||
package.save!
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(APP_CONFIG.estimated_slow_mixdown_time * 1)
|
||||
|
||||
package.speed_pitched = false
|
||||
package.save!
|
||||
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(APP_CONFIG.estimated_fast_mixdown_time * 1)
|
||||
|
||||
right = FactoryGirl.create(:jam_track_right)
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(APP_CONFIG.estimated_fast_mixdown_time * 1)
|
||||
|
||||
right.queued = true
|
||||
right.save!
|
||||
JamTrackMixdownPackage.estimated_queue_time.should eq(APP_CONFIG.estimated_fast_mixdown_time * 1 + APP_CONFIG.estimated_jam_track_time * 1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe JamTrackMixdown do
|
||||
|
||||
let(:user) {FactoryGirl.create(:user)}
|
||||
let(:jam_track) {FactoryGirl.create(:jam_track)}
|
||||
let(:settings) { {speed:5} }
|
||||
|
||||
it "can be created (factory girl)" do
|
||||
mixdown = FactoryGirl.create(:jam_track_mixdown)
|
||||
|
||||
mixdown = JamTrackMixdown.find(mixdown.id)
|
||||
mixdown.settings.should eq('{"speed":5}')
|
||||
end
|
||||
|
||||
it "can be created" do
|
||||
mixdown = JamTrackMixdown.create('abc', 'description', user, jam_track, settings)
|
||||
mixdown.errors.any?.should == false
|
||||
end
|
||||
|
||||
it "index" do
|
||||
query, start, count = JamTrackMixdown.index({id: jam_track}, user)
|
||||
|
||||
query.length.should eq(0)
|
||||
start.should be_nil
|
||||
count.should eq(0)
|
||||
|
||||
mixdown = FactoryGirl.create(:jam_track_mixdown, user: user, jam_track: jam_track)
|
||||
|
||||
query, start, count = JamTrackMixdown.index({id: jam_track}, user)
|
||||
query[0].should eq(mixdown)
|
||||
start.should be_nil
|
||||
count.should eq(1)
|
||||
end
|
||||
|
||||
describe "settings" do
|
||||
it "validates empty settings" do
|
||||
invalid = FactoryGirl.build(:jam_track_mixdown, settings: {}.to_json)
|
||||
invalid.save
|
||||
invalid.errors.any?.should be_true
|
||||
invalid.errors["settings"].should eq(["have nothing specified"])
|
||||
end
|
||||
|
||||
it "validates speed numeric" do
|
||||
invalid = FactoryGirl.build(:jam_track_mixdown, settings: {"speed": "5"}.to_json)
|
||||
invalid.save
|
||||
invalid.errors.any?.should be_true
|
||||
invalid.errors["settings"].should eq(["has non-integer speed"])
|
||||
end
|
||||
|
||||
it "validates pitch numeric" do
|
||||
invalid = FactoryGirl.build(:jam_track_mixdown, settings: {"pitch": "5"}.to_json)
|
||||
invalid.save
|
||||
invalid.errors.any?.should be_true
|
||||
invalid.errors["settings"].should eq(["has non-integer pitch"])
|
||||
end
|
||||
|
||||
it "validates speed not-float" do
|
||||
invalid = FactoryGirl.build(:jam_track_mixdown, settings: {"speed": 5.5}.to_json)
|
||||
invalid.save
|
||||
invalid.errors.any?.should be_true
|
||||
invalid.errors["settings"].should eq(["has non-integer speed"])
|
||||
end
|
||||
|
||||
it "validates pitch not-float" do
|
||||
invalid = FactoryGirl.build(:jam_track_mixdown, settings: {"pitch": 10.5}.to_json)
|
||||
invalid.save
|
||||
invalid.errors.any?.should be_true
|
||||
invalid.errors["settings"].should eq(["has non-integer pitch"])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -29,6 +29,15 @@ describe JamTrackRight do
|
|||
end
|
||||
end
|
||||
|
||||
describe "private keys automatically created" do
|
||||
it "created automatically" do
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right)
|
||||
jam_track_right.private_key_44.should_not be_nil
|
||||
jam_track_right.private_key_48.should_not be_nil
|
||||
jam_track_right.private_key_44.should eq(jam_track_right.private_key_48)
|
||||
end
|
||||
end
|
||||
|
||||
describe "JKZ" do
|
||||
before(:all) do
|
||||
original_storage = JamTrackTrackUploader.storage = :fog
|
||||
|
|
@ -109,12 +118,14 @@ describe JamTrackRight do
|
|||
end
|
||||
|
||||
it "valid track with rights to it by querying user" do
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right, private_key_44: 'keyabc')
|
||||
jam_track_right = FactoryGirl.create(:jam_track_right)
|
||||
keys = JamTrackRight.list_keys(jam_track_right.user, [jam_track_right.jam_track.id])
|
||||
keys.should have(1).items
|
||||
keys[0].id.should == jam_track_right.jam_track.id
|
||||
keys[0]['private_key_44'].should eq('keyabc')
|
||||
keys[0]['private_key_48'].should be_nil
|
||||
keys[0]['private_key_44'].should_not be_nil
|
||||
keys[0]['private_key_48'].should_not be_nil
|
||||
keys[0]['private_key_44'].should eq(jam_track_right.private_key_44)
|
||||
keys[0]['private_key_48'].should eq(jam_track_right.private_key_48)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ def app_config
|
|||
end
|
||||
|
||||
def signing_job_queue_max_time
|
||||
20 # 20 seconds
|
||||
600 # 20 seconds
|
||||
end
|
||||
|
||||
def one_free_jamtrack_per_user
|
||||
|
|
@ -210,6 +210,34 @@ def app_config
|
|||
"AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
||||
end
|
||||
|
||||
def estimated_jam_track_time
|
||||
40
|
||||
end
|
||||
|
||||
def estimated_fast_mixdown_time
|
||||
30
|
||||
end
|
||||
|
||||
def estimated_slow_mixdown_time
|
||||
80
|
||||
end
|
||||
|
||||
def num_packaging_nodes
|
||||
2
|
||||
end
|
||||
|
||||
def signing_job_signing_max_time
|
||||
300
|
||||
end
|
||||
|
||||
def mixdown_job_queue_max_time
|
||||
600
|
||||
end
|
||||
|
||||
def mixdown_step_max_time
|
||||
300
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def audiomixer_workspace_path
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ gem 'bower-rails', "~> 0.9.2"
|
|||
gem 'react-rails', '~> 1.0'
|
||||
#gem "browserify-rails", "~> 0.7"
|
||||
|
||||
gem 'react-rails-img'
|
||||
|
||||
source 'https://rails-assets.org' do
|
||||
gem 'rails-assets-reflux'
|
||||
gem 'rails-assets-classnames'
|
||||
|
|
|
|||
|
|
@ -103,9 +103,8 @@
|
|||
sampleRate = context.jamClient.GetSampleRate()
|
||||
sampleRateForFilename = sampleRate == 48 ? '48' : '44';
|
||||
doSearch();
|
||||
|
||||
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
showing = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
|
|||
showDownloading: () =>
|
||||
@logger.debug("showing #{@state.name}")
|
||||
# while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually
|
||||
context.jamClient.JamTrackDownload(@jamTrack.id, context.JK.currentUserId,
|
||||
context.jamClient.JamTrackDownload(@jamTrack.id, null, context.JK.currentUserId,
|
||||
this.makeDownloadProgressCallback(),
|
||||
this.makeDownloadSuccessCallback(),
|
||||
this.makeDownloadFailureCallback())
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@
|
|||
return 30;
|
||||
}
|
||||
|
||||
function GetSampleRate() {
|
||||
return 48;
|
||||
}
|
||||
function FTUESetVideoShareEnable(){
|
||||
|
||||
}
|
||||
|
|
@ -502,6 +505,9 @@
|
|||
return 0;
|
||||
}
|
||||
|
||||
function GetJamTrackSettings() {
|
||||
return {tracks:[]}
|
||||
}
|
||||
function SessionGetJamTracksPlayDurationMs() {
|
||||
return 60000;
|
||||
}
|
||||
|
|
@ -1211,6 +1217,7 @@
|
|||
this.TrackGetChatUsesMusic = TrackGetChatUsesMusic;
|
||||
this.TrackSetChatUsesMusic = TrackSetChatUsesMusic;
|
||||
|
||||
this.GetJamTrackSettings = GetJamTrackSettings;
|
||||
this.JamTrackStopPlay = JamTrackStopPlay;
|
||||
this.JamTrackPlay = JamTrackPlay;
|
||||
this.JamTrackIsPlayable = JamTrackIsPlayable;
|
||||
|
|
@ -1275,6 +1282,7 @@
|
|||
this.FTUESetSendFrameRates = FTUESetSendFrameRates;
|
||||
this.GetCurrentVideoResolution = GetCurrentVideoResolution;
|
||||
this.GetCurrentVideoFrameRate = GetCurrentVideoFrameRate;
|
||||
this.GetSampleRate = GetSampleRate;
|
||||
this.FTUESetVideoShareEnable = FTUESetVideoShareEnable;
|
||||
this.FTUEGetVideoShareEnable = FTUEGetVideoShareEnable;
|
||||
this.isSessVideoShared = isSessVideoShared;
|
||||
|
|
|
|||
|
|
@ -1322,6 +1322,85 @@
|
|||
})
|
||||
}
|
||||
|
||||
function markMixdownActive(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/jamtracks/" + id + "/mixdowns/active",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function createMixdown(options) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdowns/",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function editMixdown(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdowns/" + id ,
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteMixdown(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdowns/" + id
|
||||
})
|
||||
}
|
||||
|
||||
function getMixdown(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdowns/" + id
|
||||
})
|
||||
}
|
||||
|
||||
function getMixdownPackage(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdown_packages/" + id
|
||||
})
|
||||
}
|
||||
|
||||
function enqueueMixdown(options) {
|
||||
var id = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/mixdowns/" + id + '/enqueue' ,
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function openJamTrack(options) {
|
||||
var musicSessionId = options["id"];
|
||||
var jamTrackId = options["jam_track_id"];
|
||||
|
|
@ -1951,6 +2030,13 @@
|
|||
this.claimRecording = claimRecording;
|
||||
this.startPlayClaimedRecording = startPlayClaimedRecording;
|
||||
this.stopPlayClaimedRecording = stopPlayClaimedRecording;
|
||||
this.markMixdownActive = markMixdownActive;
|
||||
this.createMixdown = createMixdown;
|
||||
this.editMixdown = editMixdown;
|
||||
this.deleteMixdown = deleteMixdown;
|
||||
this.enqueueMixdown = enqueueMixdown;
|
||||
this.getMixdown = getMixdown;
|
||||
this.getMixdownPackage = getMixdownPackage;
|
||||
this.openJamTrack = openJamTrack
|
||||
this.openBackingTrack = openBackingTrack
|
||||
this.closeBackingTrack = closeBackingTrack
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//= require react-input-autosize
|
||||
//= require react-select
|
||||
// //= require react_rails_img
|
||||
//= require_directory ./react-components/helpers
|
||||
//= require_directory ./react-components/actions
|
||||
//= require ./react-components/stores/AppStore
|
||||
|
|
|
|||
|
|
@ -38,9 +38,11 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
@state.controls.onPlayStopEvent()
|
||||
else if changes.playbackState == 'play_pause'
|
||||
@state.controls.onPlayPauseEvent();
|
||||
else if changes.positionUpdateChanged
|
||||
if changes.positionUpdateChanged
|
||||
if @state.controls?
|
||||
@state.controls.executeMonitor(changes.positionMs, changes.durationMs, changes.isPlaying)
|
||||
if changes.currentTimeChanged
|
||||
@setState({time: changes.time})
|
||||
|
||||
onInputsChanged: (sessionMixers) ->
|
||||
|
||||
|
|
@ -69,8 +71,8 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
|
||||
monitorControls: (controls, mediaSummary) ->
|
||||
|
||||
if mediaSummary.mediaOpen
|
||||
if mediaSummary.jamTrackOpen
|
||||
if mediaSummary.mediaOpen || mediaSummary.jamTrack?
|
||||
if mediaSummary.jamTrack?
|
||||
controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK)
|
||||
else if mediaSummary.backingTrackOpen
|
||||
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)
|
||||
|
|
@ -163,7 +165,7 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="recording-time start-time">0:00</div>
|
||||
<div className="recording-time start-time">{this.state.time}</div>
|
||||
<div className="recording-playback">
|
||||
<div className="recording-slider"><img src="/assets/content/slider_playcontrols.png" height="16" width="5" /></div>
|
||||
</div>
|
||||
|
|
@ -179,7 +181,7 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
|
||||
|
||||
getInitialState: () ->
|
||||
{controls: null, mediaSummary: {}, initializedMetronomeControls: false}
|
||||
{controls: null, mediaSummary: {}, initializedMetronomeControls: false, time: '0:00'}
|
||||
|
||||
tryPrepareMetronome: (metro) ->
|
||||
if @state.mediaSummary.metronomeOpen && !@state.initializedMetronomeControls
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
context = window
|
||||
logger = context.JK.logger
|
||||
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
||||
|
||||
mixins = []
|
||||
|
||||
|
|
@ -16,30 +17,72 @@ if window.opener?
|
|||
|
||||
if accessOpener
|
||||
SessionActions = window.opener.SessionActions
|
||||
MediaPlaybackStore = window.opener.MediaPlaybackStore
|
||||
MixerActions = window.opener.MixerActions
|
||||
JamTrackActions = window.opener.JamTrackActions
|
||||
JamTrackMixdownActions = window.opener.JamTrackMixdownActions
|
||||
#JamTrackMixdownStore = window.opener.JamTrackMixdownStore
|
||||
JamTrackMixdown = window.opener.JamTrackMixdown
|
||||
JamTrackStore = window.opener.JamTrackStore
|
||||
MixerStore = window.opener.MixerStore
|
||||
SessionStore = window.opener.SessionStore
|
||||
|
||||
mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
||||
mixins.push(Reflux.listenTo(MixerStore, 'onMixersChanged'))
|
||||
mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackChanged'))
|
||||
|
||||
@PopupMediaControls = React.createClass({
|
||||
|
||||
mixins: mixins
|
||||
|
||||
onMixersChanged: (sessionMixers) ->
|
||||
|
||||
session = sessionMixers.session
|
||||
mixers = sessionMixers.mixers
|
||||
|
||||
# the backend delete/adds the metronome rapidly when the user hits play. this is custom code to deal with that
|
||||
|
||||
state =
|
||||
isRecording: session.isRecording
|
||||
mediaSummary: mixers.mediaSummary
|
||||
backingTracks: mixers.backingTracks
|
||||
jamTracks: mixers.jamTracks
|
||||
recordedTracks: mixers.recordedTracks
|
||||
metronome: mixers.metronome
|
||||
recordingName: mixers.recordingName()
|
||||
jamTrackName: mixers.jamTrackName()
|
||||
|
||||
@setState(media: state, downloadingJamTrack: session.downloadingJamTrack)
|
||||
|
||||
onMediaStateChanged: (changes) ->
|
||||
if changes.currentTimeChanged && @root?
|
||||
@setState({time: changes.time})
|
||||
|
||||
onJamTrackMixdownChanged: (changes) ->
|
||||
@setState({mixdown: changes})
|
||||
|
||||
onJamTrackChanged: (changes) ->
|
||||
logger.debug("PopupMediaControls: jamtrack changed", changes)
|
||||
@setState({jamTrackState: changes})
|
||||
|
||||
showMetronome: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
SessionActions.showNativeMetronomeGui()
|
||||
|
||||
getInitialState: () ->
|
||||
{time: '0:00'}
|
||||
{
|
||||
media: @props.media,
|
||||
mixdown: @props.mixdown,
|
||||
jamTrackState: @props.jamTrackState,
|
||||
creatingMixdown: false,
|
||||
createMixdownErrors: null,
|
||||
editingMixdownId: null,
|
||||
downloadingJamTrack: @props.downloadingJamTrack
|
||||
}
|
||||
|
||||
close: () ->
|
||||
window.close()
|
||||
|
||||
|
||||
render: () ->
|
||||
|
||||
closeLinkText = null
|
||||
|
|
@ -47,21 +90,245 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
extraControls = null
|
||||
|
||||
# give the users options to close it
|
||||
if @props.mediaSummary.recordingOpen
|
||||
if @state.media.mediaSummary.recordingOpen
|
||||
mediaType = "Recording"
|
||||
mediaName = @props.recordedTracks[0].recordingName
|
||||
mediaName = @state.media.recordedTracks[0].recordingName
|
||||
closeLinkText = 'close recording'
|
||||
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
|
||||
else if @props.mediaSummary.jamTrackOpen
|
||||
header = `<h3>{mediaType}: {mediaName}</h3>`
|
||||
else if @state.jamTrackState.jamTrack?
|
||||
jamTrack = @state.jamTrackState.jamTrack
|
||||
mediaType = "JamTrack"
|
||||
mediaName = @props.jamTracks[0].name
|
||||
closeLinkText = 'close JamTrack'
|
||||
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
|
||||
else if @props.mediaSummary.backingTrackOpen
|
||||
mediaName = jamTrack.name
|
||||
closeLinkText = 'CLOSE JAMTRACK'
|
||||
|
||||
|
||||
selectedMixdown = jamTrack.activeMixdown
|
||||
|
||||
|
||||
if selectedMixdown?
|
||||
jamTrackTypeHeader = 'Custom Mix'
|
||||
|
||||
disabled = true
|
||||
if selectedMixdown.client_state?
|
||||
switch selectedMixdown.client_state
|
||||
when 'cant_open'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'keying_timeout'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'download_fail'
|
||||
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
|
||||
when 'keying'
|
||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
when 'downloading'
|
||||
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
when 'ready'
|
||||
customMixName = `<h5>{selectedMixdown.name}</h5>`
|
||||
disabled = false
|
||||
else
|
||||
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif" /></h5>`
|
||||
|
||||
else
|
||||
if SessionStore.downloadingJamTrack
|
||||
downloader = `<img src="/assets/shared/spinner.gif" />`
|
||||
|
||||
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
|
||||
|
||||
header = `
|
||||
<div className="header">
|
||||
<h3>{mediaType}: {mediaName}</h3>
|
||||
<h4>{jamTrackTypeHeader}</h4>
|
||||
{customMixName}
|
||||
</div>`
|
||||
|
||||
myMixes = null
|
||||
if @state.showMyMixes
|
||||
myMixdowns = []
|
||||
|
||||
boundPlayClick = this.jamTrackPlay.bind(this, jamTrack);
|
||||
|
||||
active = jamTrack.last_mixdown_id == null
|
||||
|
||||
myMixdowns.push `
|
||||
<div key="full-track" className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
|
||||
<div className="mixdown-name">
|
||||
Full JamTrack
|
||||
</div>
|
||||
<div className="mixdown-actions">
|
||||
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
for mixdown in jamTrack.mixdowns
|
||||
boundPlayClick = this.mixdownPlay.bind(this, mixdown);
|
||||
boundEditClick = this.mixdownEdit.bind(this, mixdown);
|
||||
boundSaveClick = this.mixdownSave.bind(this, mixdown);
|
||||
boundDeleteClick = this.mixdownDelete.bind(this, mixdown);
|
||||
boundErrorClick = this.mixdownError.bind(this, mixdown);
|
||||
boundEditKeydown = this.onEditKeydown.bind(this, mixdown);
|
||||
|
||||
mixdown_package = mixdown.myPackage
|
||||
|
||||
active = mixdown.id == jamTrack.last_mixdown_id
|
||||
|
||||
editing = mixdown.id == @state.editingMixdownId
|
||||
|
||||
# if there is a package, check it's state; otherwise let the user enqueue it
|
||||
if mixdown_package
|
||||
switch mixdown_package.signing_state
|
||||
when 'QUIET_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'QUIET'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'QUEUED'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'QUEUED_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'SIGNING'
|
||||
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
|
||||
when 'SIGNING_TIMEOUT'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
when 'SIGNED'
|
||||
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>`
|
||||
when 'ERROR'
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
else
|
||||
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
|
||||
|
||||
if editing
|
||||
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name} onKeyDown={boundEditKeydown} />`
|
||||
editIcon = `<img src="/assets/content/icon-save@2X.png" className="mixdown-edit" onClick={boundSaveClick}/>`
|
||||
else
|
||||
mixdownName = mixdown.name
|
||||
editIcon = `<img src="/assets/content/icon-edit@2X.png" className="mixdown-edit" onClick={boundEditClick}/>`
|
||||
|
||||
myMixdowns.push `
|
||||
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
|
||||
<div className="mixdown-name">
|
||||
{mixdownName}
|
||||
</div>
|
||||
<div className="mixdown-actions">
|
||||
{action}
|
||||
|
||||
{editIcon}
|
||||
|
||||
<img src ="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick} />
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
myMixes = `<div key="my-mixes" className="my-mixes">{myMixdowns}</div>`
|
||||
|
||||
mixControls = null
|
||||
if @state.showCustomMixes
|
||||
|
||||
nameClassData = {field: true}
|
||||
if @state.createMixdownErrors?
|
||||
|
||||
errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
|
||||
|
||||
createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown})
|
||||
mixControls = `
|
||||
<div key="create-mix" className="create-mix">
|
||||
<p>Use the JamTrack controls on the session screen to set levels, mute/unmute, or pan any of the parts of the JamTrack as you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button. Please note that changing the tempo or pitch of the JamTrack may take a long time, and won't be ready right away.</p>
|
||||
<div className="field">
|
||||
<label>Change Tempo:</label>
|
||||
<select name="mix-speed">
|
||||
<option value="">No change</option>
|
||||
<option value="separator-1">------------</option>
|
||||
<option value="-5">Slower by 5%</option>
|
||||
<option value="-10">Slower by 10%</option>
|
||||
<option value="-15">Slower by 15%</option>
|
||||
<option value="-20">Slower by 20%</option>
|
||||
<option value="-25">Slower by 25%</option>
|
||||
<option value="-30">Slower by 30%</option>
|
||||
<option value="-35">Slower by 35%</option>
|
||||
<option value="-40">Slower by 40%</option>
|
||||
<option value="-45">Slower by 45%</option>
|
||||
<option value="-50">Slower by 50%</option>
|
||||
<option value="-60">Slower by 60%</option>
|
||||
<option value="-70">Slower by 70%</option>
|
||||
<option value="-80">Slower by 80%</option>
|
||||
<option value="separator-2">------------</option>
|
||||
<option value="5">Faster by 5%</option>
|
||||
<option value="10">Faster by 10%</option>
|
||||
<option value="15">Faster by 15%</option>
|
||||
<option value="20">Faster by 20%</option>
|
||||
<option value="30">Faster by 30%</option>
|
||||
<option value="40">Faster by 40%</option>
|
||||
<option value="50">Faster by 50%</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Change Pitch:</label>
|
||||
<select name="mix-pitch">
|
||||
<option value="">No change</option>
|
||||
<option value="separator-1">---------------</option>
|
||||
<option value="-1">Down 1 Semitone</option>
|
||||
<option value="-2">Down 2 Semitones</option>
|
||||
<option value="-3">Down 3 Semitones</option>
|
||||
<option value="-4">Down 4 Semitones</option>
|
||||
<option value="-5">Down 5 Semitones</option>
|
||||
<option value="-6">Down 6 Semitones</option>
|
||||
<option value="-7">Down 7 Semitones</option>
|
||||
<option value="-8">Down 8 Semitones</option>
|
||||
<option value="-9">Down 9 Semitones</option>
|
||||
<option value="-10">Down 10 Semitones</option>
|
||||
<option value="-11">Down 11 Semitones</option>
|
||||
<option value="-12">Down 12 Semitones</option>
|
||||
<option value="separator-2">---------------</option>
|
||||
<option value="1">Up 1 Semitone</option>
|
||||
<option value="2">Up 2 Semitones</option>
|
||||
<option value="3">Up 3 Semitones</option>
|
||||
<option value="4">Up 4 Semitones</option>
|
||||
<option value="5">Up 5 Semitones</option>
|
||||
<option value="6">Up 6 Semitones</option>
|
||||
<option value="7">Up 7 Semitones</option>
|
||||
<option value="8">Up 8 Semitones</option>
|
||||
<option value="9">Up 9 Semitones</option>
|
||||
<option value="10">Up 10 Semitones</option>
|
||||
<option value="11">Up 11 Semitones</option>
|
||||
<option value="12">Up 12 Semitones</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className={classNames(nameClassData)}>
|
||||
<label>Mix Name:</label>
|
||||
<input type="text" name="mix-name"/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<a className={createMixClasses} onClick={this.createMix}>CREATE MIX</a>
|
||||
{errorHtml}
|
||||
</div>
|
||||
<div className="clearall"/>
|
||||
|
||||
</div>`
|
||||
|
||||
if @state.showMyMixes
|
||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>hide my mixes <div className="details-arrow arrow-up" /></a>`
|
||||
else
|
||||
showMyMixesText = `<a onClick={this.toggleMyMixes}>show my mixes <div className="details-arrow arrow-down" /></a>`
|
||||
|
||||
if @state.showCustomMixes
|
||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>hide mix controls <div className="details-arrow arrow-up" /></a>`
|
||||
else
|
||||
showMixControlsText = `<a onClick={this.toggleCustomMixes}>show mix controls <div className="details-arrow arrow-down" /></a>`
|
||||
|
||||
|
||||
extraControls = `
|
||||
<div className="extra-controls">
|
||||
<h4>My Mixes {showMyMixesText}</h4>
|
||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
||||
{myMixes}
|
||||
</ReactCSSTransitionGroup>
|
||||
<h4 className="custom-mix-header">Create Custom Mix {showMixControlsText}</h4>
|
||||
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
|
||||
{mixControls}
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>`
|
||||
|
||||
else if @state.media.mediaSummary.backingTrackOpen
|
||||
mediaType = "Audio File"
|
||||
mediaName = context.JK.getNameOfFile(@props.backingTracks[0].shortFilename)
|
||||
closeLinkText = 'close audio file'
|
||||
header = `<h3>{mediaType}: {mediaName} ({this.state.time})</h3>`
|
||||
mediaName = context.JK.getNameOfFile(@state.media.backingTracks[0].shortFilename)
|
||||
closeLinkText = 'CLOSE AUDIO FILE'
|
||||
header = `<h3>{mediaType}: {mediaName}</h3>`
|
||||
extraControls =
|
||||
`<div>
|
||||
<div className="field">
|
||||
|
|
@ -69,9 +336,9 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
</div>
|
||||
<br className="clearall"/>
|
||||
</div>`
|
||||
else if @props.mediaSummary.metronomeOpen
|
||||
else if @state.media.mediaSummary.metronomeOpen
|
||||
mediaType = "Metronome"
|
||||
closeLinkText = 'close metronome'
|
||||
closeLinkText = 'CLOSE METRONOME'
|
||||
header = `<h3>Metronome</h3>`
|
||||
extraControls =
|
||||
`<div>
|
||||
|
|
@ -84,12 +351,161 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
{header}
|
||||
<MediaControls />
|
||||
{extraControls}
|
||||
<a className="close-link" onClick={this.close}>{closeLinkText}</a>
|
||||
<a className="close-link button-orange" onClick={this.close}>{closeLinkText}</a>
|
||||
</div>`
|
||||
|
||||
windowUnloaded: () ->
|
||||
SessionActions.closeMedia(false) unless window.DontAutoCloseMedia
|
||||
|
||||
toggleMyMixes: (e) ->
|
||||
e.preventDefault()
|
||||
@setState({showMyMixes: !@state.showMyMixes})
|
||||
|
||||
toggleCustomMixes: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
@setState({showCustomMixes: !@state.showCustomMixes})
|
||||
|
||||
mixdownPlay: (mixdown, e) ->
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if @disableLoading
|
||||
alert('Certain actions are disabled while a track is being loaded.')
|
||||
return
|
||||
|
||||
# make this package the active one
|
||||
JamTrackMixdownActions.openMixdown(mixdown)
|
||||
|
||||
jamTrackPlay: (jamtrack, e) ->
|
||||
e.preventDefault()
|
||||
# user wants to select the full track
|
||||
|
||||
if @disableLoading
|
||||
alert('Certain actions are disabled while a track is being loaded.')
|
||||
return
|
||||
|
||||
JamTrackActions.activateNoMixdown(jamtrack)
|
||||
|
||||
onEditKeydown: (mixdown, e) ->
|
||||
logger.debug("on edit keydown", e)
|
||||
if e.keyCode == 13 # enter
|
||||
@mixdownSave(mixdown, e)
|
||||
else if e.keyCode == 27 # esc
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
mixdownEdit: (mixdown) ->
|
||||
@setState({editingMixdownId: mixdown.id})
|
||||
|
||||
mixdownSave: (mixdown, e) ->
|
||||
e.preventDefault()
|
||||
$input = $(this.getDOMNode()).find('input.edit-name')
|
||||
newValue = $input.val()
|
||||
logger.debug("editing mixdown name to be: " + newValue)
|
||||
JamTrackMixdownActions.editMixdown({id: mixdown.id, name: newValue})
|
||||
@setState({editingMixdownId: null})
|
||||
|
||||
mixdownDelete: (mixdown) ->
|
||||
if @state.editingMixdownId?
|
||||
@setState({editingMixdownId:null})
|
||||
return
|
||||
|
||||
if confirm("Delete this custom mix?")
|
||||
JamTrackMixdownActions.deleteMixdown(mixdown)
|
||||
|
||||
|
||||
mixdownError: (mixdown) ->
|
||||
|
||||
myPackage = mixdown.myPackage
|
||||
|
||||
if myPackage?
|
||||
switch myPackage.signing_state
|
||||
when 'QUIET_TIMEOUT'
|
||||
action = 'Custom mix never got created. Retry?'
|
||||
when 'QUEUED_TIMEOUT'
|
||||
action = 'Custom mix was never built. Retry?'
|
||||
when 'SIGNING_TIMEOUT'
|
||||
action = 'Custom mix took took long to build. Retry?'
|
||||
when 'ERROR'
|
||||
action = 'Custom mix failed to build. Retry?'
|
||||
else
|
||||
action = 'Custom mix never got created. Retry?'
|
||||
|
||||
return unless action?
|
||||
|
||||
if confirm(action)
|
||||
JamTrackMixdownActions.enqueueMixdown(mixdown, @enqueueDone)
|
||||
|
||||
enqueueDone: (enqueued) ->
|
||||
@promptEstimate(enqueued)
|
||||
|
||||
promptEstimate: (enqueued) ->
|
||||
time = enqueued.queue_time
|
||||
|
||||
if time == 0
|
||||
alert("It will take approximately 1 minute to create your custom mix.")
|
||||
else
|
||||
guess = Math.ceil(time / 60.0)
|
||||
if guess == 1
|
||||
msg = '1 minute'
|
||||
else
|
||||
msg = "#{guess} minutes"
|
||||
alert("Your custom mix will take approximately #{msg} to be created.")
|
||||
|
||||
|
||||
createMix: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
return if @state.creatingMix
|
||||
|
||||
$root = $(@getDOMNode())
|
||||
|
||||
name = $root.find('input[name="mix-name"]').val()
|
||||
speed = $root.find('select[name="mix-speed"]').val()
|
||||
pitch = $root.find('select[name="mix-pitch"]').val()
|
||||
|
||||
if @state.jamTrackState.jamTrack?.activeMixdown?
|
||||
@setState({createMixdownErrors: {errors: {'Full JamTrack': ['must be selected']}}})
|
||||
return
|
||||
|
||||
if name == null || name == ''
|
||||
@setState({createMixdownErrors: {errors: {'Mix Name': ["can't be blank"]}}})
|
||||
return
|
||||
|
||||
# sanitize junk out of speed/pitch
|
||||
if speed == '' || speed.indexOf('separator') > -1
|
||||
speed = undefined
|
||||
else
|
||||
speed = parseInt(speed)
|
||||
if pitch == '' || pitch.indexOf('separator') > -1
|
||||
pitch = undefined
|
||||
else
|
||||
pitch = parseInt(pitch)
|
||||
|
||||
mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch}}
|
||||
|
||||
JamTrackMixdownActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)
|
||||
|
||||
@setState({creatingMixdown: true, createMixdownErrors: null})
|
||||
|
||||
createMixdownDone: (created) ->
|
||||
logger.debug("created (within PopupMediaControls)", created)
|
||||
# automatically close the create custom mix area
|
||||
@setState({creatingMixdown: false, showCustomMixes: false, showMyMixes: true})
|
||||
|
||||
@promptEstimate(created)
|
||||
|
||||
createMixdownFail: (jqXHR) ->
|
||||
logger.debug("create mixdown fail (within PopupMediaControls)", jqXHR.status)
|
||||
@setState({creatingMixdown: false})
|
||||
if jqXHR.status == 422
|
||||
response = JSON.parse(jqXHR.responseText)
|
||||
logger.warn("failed to create mixdown", response, jqXHR.responseText)
|
||||
|
||||
@setState({createMixdownErrors: response})
|
||||
|
||||
|
||||
componentDidMount: () ->
|
||||
|
||||
$(window).unload(@windowUnloaded)
|
||||
|
|
@ -100,13 +516,12 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
context.JK.checkbox($loop)
|
||||
|
||||
$loop.on('ifChecked', () =>
|
||||
logger.debug("@props", @props)
|
||||
# it doesn't matter if you do personal or master, because backend just syncs both
|
||||
MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, true)
|
||||
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, true)
|
||||
)
|
||||
$loop.on('ifUnchecked', () =>
|
||||
# it doesn't matter if you do personal or master, because backend just syncs both
|
||||
MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, false)
|
||||
MixerActions.loopChanged(@state.media.backingTracks[0].mixers.personal.mixer, false)
|
||||
)
|
||||
|
||||
@resizeWindow()
|
||||
|
|
@ -116,6 +531,7 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
|
||||
componentDidUpdate: () ->
|
||||
@resizeWindow()
|
||||
setTimeout(@resizeWindow, 1000)
|
||||
|
||||
resizeWindow: () =>
|
||||
$container = $('#minimal-container')
|
||||
|
|
@ -134,4 +550,25 @@ mixins.push(Reflux.listenTo(MediaPlaybackStore, 'onMediaStateChanged'))
|
|||
#offset += 25
|
||||
|
||||
window.resizeTo(width, height + offset)
|
||||
|
||||
componentWillUpdate: (nextProps, nextState) ->
|
||||
|
||||
@disableLoading = false
|
||||
|
||||
return unless nextState?
|
||||
|
||||
selectedMixdown = nextState?.jamTrackState?.jamTrack?.activeMixdown
|
||||
|
||||
mixdownDownloading = false
|
||||
if selectedMixdown?
|
||||
switch selectedMixdown.client_state
|
||||
when 'keying'
|
||||
mixdownDownloading = true
|
||||
when 'downloading'
|
||||
mixdownDownloading = true
|
||||
|
||||
|
||||
@disableLoading = SessionStore.downloadingJamTrack || mixdownDownloading
|
||||
|
||||
|
||||
})
|
||||
|
|
@ -14,10 +14,19 @@ ChannelGroupIds = context.JK.ChannelGroupIds
|
|||
Reflux.listenTo(@AppStore,"onAppInit"),
|
||||
Reflux.listenTo(@JamTrackStore, "onJamTrackStateChanged")]
|
||||
|
||||
onJamTrackStateChanged: (jamTrack) ->
|
||||
if jamTrack?
|
||||
@loadJamTrack(jamTrack)
|
||||
else
|
||||
onJamTrackStateChanged: (jamTrackState) ->
|
||||
if jamTrackState.fullTrackActivated || jamTrackState.opened && jamTrackState.jamTrack.activeMixdown == null
|
||||
@loadJamTrack(jamTrackState.jamTrack)
|
||||
else if jamTrackState.closed
|
||||
logger.debug("SessionMediaTracks: jamtrack has been closed")
|
||||
|
||||
if @state.downloadJamTrack?
|
||||
logger.debug("closing DownloadJamTrack widget")
|
||||
@state.downloadJamTrack.root.remove()
|
||||
@state.downloadJamTrack.destroy()
|
||||
SessionActions.downloadingJamTrack(false)
|
||||
@setState({downloadJamTrack: null})
|
||||
|
||||
SessionActions.closeMedia(true)
|
||||
|
||||
#inputsChangedProcessed: (state) ->
|
||||
|
|
@ -264,8 +273,8 @@ ChannelGroupIds = context.JK.ChannelGroupIds
|
|||
# All the JamTracks
|
||||
mediaTracks.push(`<SessionJamTrackCategory key="JamTrackCategory" jamTrackName={this.state.jamTrackName} mixers={this.state.mediaCategoryMixer} mode={MIX_MODES.PERSONAL} />`)
|
||||
|
||||
|
||||
if @state.metronome?
|
||||
# show metronome only if it's a full jamtrack
|
||||
if @state.metronome? && @state.jamTrackMixdown.id == null
|
||||
@state.metronome.mode = MIX_MODES.PERSONAL
|
||||
mediaTracks.push(`<SessionMetronome key="JamTrackMetronome" {...this.state.metronome} location="jam-track" />`)
|
||||
|
||||
|
|
@ -334,11 +343,11 @@ ChannelGroupIds = context.JK.ChannelGroupIds
|
|||
@handlePopup()
|
||||
|
||||
handlePopup: () ->
|
||||
if @state.mediaSummary.mediaOpen
|
||||
if @state.mediaSummary.userNeedsMediaControls
|
||||
unless @childWindow?
|
||||
logger.debug("opening media control window")
|
||||
@childWindow = window.open("/popups/media-controls", 'Media Controls', 'scrollbars=yes,toolbar=no,status=no,height=155,width=350')
|
||||
@childWindow.PopupProps = @state
|
||||
@childWindow.PopupProps = {media: @state, jamTrackState: context.JamTrackStore.getState(), downloadingJamTrack: context.SessionStore.downloadingJamTrack }
|
||||
else
|
||||
if @childWindow?
|
||||
@childWindow.DontAutoCloseMedia = true
|
||||
|
|
|
|||
|
|
@ -113,9 +113,10 @@ ptrCount = 0
|
|||
context.JK.checkbox($checkbox)
|
||||
$checkbox.on('ifChanged', this.handleMuteCheckbox);
|
||||
|
||||
|
||||
# using iCheck causes a 'ifChanged' event, so we need to swallow this up
|
||||
@iCheckMaint = true
|
||||
if muteMixer.mute
|
||||
if muteMixer?.mute
|
||||
$checkbox.iCheck('check').attr('checked', true)
|
||||
else
|
||||
$checkbox.iCheck('uncheck').attr('checked', false)
|
||||
|
|
@ -139,7 +140,7 @@ ptrCount = 0
|
|||
|
||||
# using iCheck causes a 'ifChanged' event, so we need to swallow this up
|
||||
@iCheckMaint = true
|
||||
if muteMixer.mute
|
||||
if muteMixer?.mute
|
||||
$checkbox.iCheck('check').attr('checked', true)
|
||||
else
|
||||
$checkbox.iCheck('uncheck').attr('checked', false)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ context = window
|
|||
@JamTrackActions = Reflux.createActions({
|
||||
open: {}
|
||||
close: {}
|
||||
activateNoMixdown: {}
|
||||
requestSearch: {}
|
||||
requestFilter: {}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
context = window
|
||||
|
||||
@JamTrackMixdownActions = Reflux.createActions({
|
||||
createMixdown: {}
|
||||
editMixdown: {}
|
||||
refreshMixdown: {}
|
||||
deleteMixdown: {}
|
||||
openMixdown: {}
|
||||
closeMixdown: {}
|
||||
enqueueMixdown: {}
|
||||
downloadMixdown: {}
|
||||
})
|
||||
|
||||
|
|
@ -19,4 +19,5 @@ context = window
|
|||
broadcastFailure: {}
|
||||
broadcastSuccess: {}
|
||||
broadcastStopped: {}
|
||||
mixdownActive: {}
|
||||
})
|
||||
|
|
@ -193,6 +193,11 @@ MIX_MODES = context.JK.MIX_MODES;
|
|||
|
||||
@mediaSummary.mediaOpen = mediaOpenSummary
|
||||
|
||||
# the user needs media controls if any media is open, or, if the user has indicated they want to open a JamTrack
|
||||
@mediaSummary.userNeedsMediaControls = @mediaSummary.mediaOpen || window.JamTrackStore.jamTrack?
|
||||
|
||||
# this defines what the user wants to be open, not what actually is open in the backend and/or session
|
||||
@mediaSummary.jamTrack = window.JamTrackStore.jamTrack
|
||||
|
||||
# figure out if we opened any media
|
||||
isOpener = false
|
||||
|
|
@ -294,6 +299,7 @@ MIX_MODES = context.JK.MIX_MODES;
|
|||
jamTrackMixers = @jamTrackMixers.slice();
|
||||
jamTracks = []
|
||||
jamTrackName = null;
|
||||
jamTrackMixdown = {id: null}
|
||||
|
||||
if @session.isPlayingRecording()
|
||||
# only return managed mixers for recorded backing tracks
|
||||
|
|
@ -303,6 +309,7 @@ MIX_MODES = context.JK.MIX_MODES;
|
|||
# only return un-managed (ad-hoc) mixers for normal backing tracks
|
||||
jamTracks = @session.jamTracks()
|
||||
jamTrackName = @session.jamTrackName()
|
||||
jamTrackMixdown = @session.jamTrackMixdown()
|
||||
|
||||
# pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer)
|
||||
# if it's a locally opened track (JamTrackGroup), then we can say this person is the opener
|
||||
|
|
@ -310,55 +317,90 @@ MIX_MODES = context.JK.MIX_MODES;
|
|||
|
||||
if jamTracks
|
||||
noCorrespondingTracks = false
|
||||
for jamTrack in jamTracks
|
||||
mixer = null
|
||||
preMasteredClass = ""
|
||||
# find the track or tracks that correspond to the mixer
|
||||
correspondingTracks = []
|
||||
|
||||
for matchMixer in @jamTrackMixers
|
||||
if matchMixer.id == jamTrack.id
|
||||
correspondingTracks.push(jamTrack)
|
||||
mixer = matchMixer
|
||||
|
||||
if correspondingTracks.length == 0
|
||||
# Are we opening a mixdown, or a full track?
|
||||
if jamTrackMixdown.id?
|
||||
logger.debug("MixerHelper: mixdown is active. id: #{jamTrackMixdown.id}")
|
||||
if jamTrackMixers.length == 0
|
||||
noCorrespondingTracks = true
|
||||
logger.error("could not correlate jam tracks", jamTrackMixers, jamTracks)
|
||||
logger.error("could not correlate mixdown tracks", jamTrackMixers, jamTrackMixdown)
|
||||
@app.notify({
|
||||
title: "Unable to Open JamTrack",
|
||||
title: "Unable to Open Custom Mix",
|
||||
text: "Could not correlate server and client tracks",
|
||||
icon_url: "/assets/content/icon_alert_big.png"})
|
||||
return _jamTracks
|
||||
|
||||
#jamTracks = $.grep(jamTracks, (value) =>
|
||||
# $.inArray(value, correspondingTracks) < 0
|
||||
#)
|
||||
|
||||
# prune found mixers
|
||||
jamTrackMixers.splice(mixer);
|
||||
|
||||
oneOfTheTracks = correspondingTracks[0];
|
||||
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument.id);
|
||||
|
||||
part = oneOfTheTracks.part
|
||||
|
||||
instrumentName = oneOfTheTracks.instrument.description
|
||||
|
||||
if part?
|
||||
trackName = "#{instrumentName}: #{part}"
|
||||
else if jamTrackMixers.length > 1
|
||||
logger.warn("ignoring wrong amount of mixers for JamTrack in mixdown mode")
|
||||
return _jamTracks
|
||||
else
|
||||
trackName = instrumentName
|
||||
|
||||
data =
|
||||
name: jamTrackName
|
||||
trackName: trackName
|
||||
part: part
|
||||
isOpener: isOpener
|
||||
instrumentIcon: instrumentIcon
|
||||
track: oneOfTheTracks
|
||||
mixers: @mediaMixers(mixer, isOpener)
|
||||
instrumentIcon = context.JK.getInstrumentIcon24('other')
|
||||
part = null
|
||||
instrumentName = 'Custom Mix'
|
||||
trackName = 'Custom Mix'
|
||||
|
||||
_jamTracks.push(data)
|
||||
data =
|
||||
name: jamTrackName
|
||||
trackName: trackName
|
||||
part: part
|
||||
isOpener: isOpener
|
||||
instrumentIcon: instrumentIcon
|
||||
track: jamTrackMixdown
|
||||
mixers: @mediaMixers(jamTrackMixers[0], isOpener)
|
||||
|
||||
_jamTracks.push(data)
|
||||
else
|
||||
logger.debug("MixerHelper: full jamtrack is active")
|
||||
|
||||
for jamTrack in jamTracks
|
||||
mixer = null
|
||||
preMasteredClass = ""
|
||||
# find the track or tracks that correspond to the mixer
|
||||
correspondingTracks = []
|
||||
|
||||
for matchMixer in @jamTrackMixers
|
||||
if matchMixer.id == jamTrack.id
|
||||
correspondingTracks.push(jamTrack)
|
||||
mixer = matchMixer
|
||||
|
||||
if correspondingTracks.length == 0
|
||||
noCorrespondingTracks = true
|
||||
logger.error("could not correlate jam tracks", jamTrackMixers, jamTracks)
|
||||
@app.notify({
|
||||
title: "Unable to Open JamTrack",
|
||||
text: "Could not correlate server and client tracks",
|
||||
icon_url: "/assets/content/icon_alert_big.png"})
|
||||
return _jamTracks
|
||||
|
||||
#jamTracks = $.grep(jamTracks, (value) =>
|
||||
# $.inArray(value, correspondingTracks) < 0
|
||||
#)
|
||||
|
||||
# prune found mixers
|
||||
jamTrackMixers.splice(mixer);
|
||||
|
||||
oneOfTheTracks = correspondingTracks[0];
|
||||
instrumentIcon = context.JK.getInstrumentIcon24(oneOfTheTracks.instrument.id);
|
||||
|
||||
part = oneOfTheTracks.part
|
||||
|
||||
instrumentName = oneOfTheTracks.instrument.description
|
||||
|
||||
if part?
|
||||
trackName = "#{instrumentName}: #{part}"
|
||||
else
|
||||
trackName = instrumentName
|
||||
|
||||
data =
|
||||
name: jamTrackName
|
||||
trackName: trackName
|
||||
part: part
|
||||
isOpener: isOpener
|
||||
instrumentIcon: instrumentIcon
|
||||
track: oneOfTheTracks
|
||||
mixers: @mediaMixers(mixer, isOpener)
|
||||
|
||||
_jamTracks.push(data)
|
||||
|
||||
_jamTracks
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ context = window
|
|||
else
|
||||
null
|
||||
|
||||
jamTrackMixdown: () ->
|
||||
{ id: @session?.jam_track?.mixdown.id }
|
||||
|
||||
jamTrackName: () ->
|
||||
@session?.jam_track?.name
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ logger = context.JK.logger
|
|||
mediaCategoryMixer: mediaCategoryMixer
|
||||
recordingName: mixers.recordingName()
|
||||
jamTrackName: mixers.jamTrackName()
|
||||
jamTrackMixdown: session.jamTrackMixdown()
|
||||
|
||||
@inputsChangedProcessed(state) if @inputsChangedProcessed?
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
$ = jQuery
|
||||
context = window
|
||||
logger = context.JK.logger
|
||||
rest = context.JK.Rest()
|
||||
EVENTS = context.JK.EVENTS
|
||||
|
||||
|
||||
JamTrackActions = @JamTrackActions
|
||||
|
||||
@JamTrackMixdownStore = Reflux.createStore(
|
||||
{
|
||||
# listenables: JamTrackMixdownActions
|
||||
|
||||
# the jamtrack that contains the mixdowns in question
|
||||
jamTrack: null
|
||||
|
||||
# what mixdowns are being built right now
|
||||
building: []
|
||||
|
||||
# a currently open (loaded) mixdown
|
||||
current: null
|
||||
|
||||
init: () ->
|
||||
this.listenTo(context.AppStore, this.onAppInit);
|
||||
this.listenTo(context.JamTrackStore, this.onJamTrackChanged);
|
||||
|
||||
@changed()
|
||||
|
||||
onAppInit: (@app) ->
|
||||
|
||||
getState: () ->
|
||||
@state
|
||||
|
||||
changed: () ->
|
||||
@state = {jamTrack: @jamTrack, building:@building, current: @current}
|
||||
this.trigger(@state)
|
||||
|
||||
onJamTrackChanged: (@jamTrack) ->
|
||||
# TODO: close out building? current?
|
||||
|
||||
onCreateMixdown: (mixdown, package_settings, done, fail) ->
|
||||
logger.debug("creating mixdown", mixdown, package_settings)
|
||||
rest.createMixdown(mixdown)
|
||||
.done((created) =>
|
||||
|
||||
logger.debug("created mixdown", created)
|
||||
|
||||
package_settings.id = created.id
|
||||
|
||||
# we have to determine sample rate here, in the store, because child windows don't have access to jamClient
|
||||
sampleRate = context.jamClient.GetSampleRate()
|
||||
sampleRate = if sampleRate == 48 then 48 else 44
|
||||
package_settings.sample_rate = sampleRate
|
||||
|
||||
rest.enqueueMixdown(package_settings)
|
||||
.done((enqueued) =>
|
||||
logger.debug("enqueued mixdown package", package_settings)
|
||||
done(enqueued)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Package Mixdown', text: 'You can push the RETRY button.'})
|
||||
fail(jqxhr)
|
||||
)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
fail(jqxhr)
|
||||
)
|
||||
|
||||
onEditMixdown: (mixdown) ->
|
||||
logger.debug("editing mixdown", mixdown)
|
||||
|
||||
onDeleteMixdown: (mixdown) ->
|
||||
logger.debug("deleting mixdown", mixdown)
|
||||
|
||||
onOpenMixdown: (mixdown) ->
|
||||
logger.debug("opening mixdown", mixdown)
|
||||
|
||||
onCloseMixdown: (mixdown) ->
|
||||
logger.debug("closing mixdown", mixdown)
|
||||
|
||||
onEnqueueMixdown: (mixdown) ->
|
||||
logger.debug("enqueuing mixdown", mixdown)
|
||||
|
||||
onDownloadMixdown: (mixdown) ->
|
||||
logger.debug("download mixdown", mixdown)
|
||||
|
||||
onRefreshMixdown: (mixdown) ->
|
||||
logger.debug("refresh mixdown", mixdown)
|
||||
}
|
||||
)
|
||||
|
|
@ -9,10 +9,13 @@ JamTrackActions = @JamTrackActions
|
|||
|
||||
@JamTrackStore = Reflux.createStore(
|
||||
{
|
||||
listenables: JamTrackActions
|
||||
listenables: [JamTrackActions, JamTrackMixdownActions]
|
||||
jamTrack: null
|
||||
previous: null
|
||||
requestedSearch: null
|
||||
requestedFilter: null
|
||||
subscriptions: {}
|
||||
enqueuedMixdowns: {}
|
||||
|
||||
init: ->
|
||||
# Register with the app store to get @app
|
||||
|
|
@ -21,17 +24,245 @@ JamTrackActions = @JamTrackActions
|
|||
onAppInit: (app) ->
|
||||
@app = app
|
||||
|
||||
getState: () ->
|
||||
@state
|
||||
|
||||
pickMyPackage: () ->
|
||||
|
||||
return unless @jamTrack?
|
||||
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
|
||||
myPackage = null
|
||||
for mixdown_package in mixdown.packages
|
||||
if mixdown_package.file_type == 'ogg' && mixdown_package.encrypt_type == 'jkz' && mixdown_package.sample_rate == @sampleRate
|
||||
myPackage = mixdown_package
|
||||
break
|
||||
|
||||
mixdown.myPackage = myPackage
|
||||
|
||||
subscriptionKey: (mixdown_package) ->
|
||||
"mixdown-#{mixdown_package.id}"
|
||||
|
||||
subscribe: (mixdown_package) ->
|
||||
key = @subscriptionKey(mixdown_package)
|
||||
|
||||
if !@watchedMixdowns[key]?
|
||||
# we need to register
|
||||
context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onMixdownSubscriptionEvent)
|
||||
@watchedMixdowns[key] = {type:'mixdown', id: mixdown_package.id}
|
||||
|
||||
unsubscribe: (mixdown_package) ->
|
||||
key = @subscriptionKey(mixdown_package)
|
||||
if @watchedMixdowns[key]?
|
||||
context.JK.SubscriptionUtils.unsubscribe('mixdown', mixdown_package.id)
|
||||
delete @watchedMixdowns[key]
|
||||
|
||||
manageWatchedMixdowns: () ->
|
||||
|
||||
if @jamTrack?
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
if mixdown.myPackage
|
||||
if mixdown.myPackage.signing_state == 'SIGNED'
|
||||
@unsubscribe(mixdown.myPackage)
|
||||
else
|
||||
@subscribe(mixdown.myPackage)
|
||||
|
||||
else
|
||||
for key, subscription of @watchedMixdowns
|
||||
logger.debug("unsubscribing bulk", key, subscription)
|
||||
context.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id)
|
||||
|
||||
# we cleared them all out; clear out storage
|
||||
@watchedMixdowns = {}
|
||||
|
||||
onMixdownSubscriptionEvent: (e, data) ->
|
||||
logger.debug("JamTrackStore: subscription notification received: type:" + data.type, data)
|
||||
|
||||
return unless @jamTrack?
|
||||
|
||||
mixdown_package_id = data.id
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
for mixdown_package in mixdown.packages
|
||||
if mixdown_package.id == mixdown_package_id
|
||||
mixdown_package.signing_state = data.body.signing_state
|
||||
mixdown_package.packaging_steps = data.body.packaging_steps
|
||||
mixdown_package.current_packaging_step = data.body.current_packaging_step
|
||||
logger.debug("updated package with subscription notification event")
|
||||
|
||||
if mixdown_package.signing_state == 'SIGNING_TIMEOUT' || mixdown_package.signing_state == 'QUEUED_TIMEOUT' || mixdown_package.signing_state == 'QUIET_TIMEOUT' || mixdown_package.signing_state == 'ERROR'
|
||||
@reportError(mixdown)
|
||||
|
||||
@changed()
|
||||
break
|
||||
|
||||
# this drives the state engine required to get a Mixdown from 'available on the server' to
|
||||
manageMixdownSynchronization: () ->
|
||||
|
||||
@jamTrack.activeMixdown = null if @jamTrack
|
||||
|
||||
# let's see if we have a mixdown active?
|
||||
|
||||
if !@jamTrack?.last_mixdown_id?
|
||||
logger.debug("JamTrackStore: no mixdown active")
|
||||
@clearMixdownTimers()
|
||||
return
|
||||
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
if mixdown.id == @jamTrack.last_mixdown_id
|
||||
@jamTrack.activeMixdown = mixdown
|
||||
logger.debug("JamTrackStore: mixdown active:", mixdown)
|
||||
break
|
||||
|
||||
if @jamTrack.activeMixdown?
|
||||
|
||||
# if we don't have this on the server yet, don't engage the rest of this logic...
|
||||
return if @jamTrack.activeMixdown?.myPackage?.signing_state != 'SIGNED'
|
||||
|
||||
fqId = "#{@jamTrack.id}_#{@jamTrack.activeMixdown.id}-#{@sampleRate}"
|
||||
@trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId)
|
||||
|
||||
logger.debug("JamTrackStore: JamTrackGetTrackDetail(#{fqId}).key_state: " + @trackDetail.key_state, @trackDetail)
|
||||
|
||||
# first check if the version is not the same; if so, invalidate.
|
||||
|
||||
if @trackDetail.version? && @jamTrack.activeMixdown.myPackage?
|
||||
if @jamTrack.activeMixdown.myPackage.version != @trackDetail.version
|
||||
logger.info("JamTrackStore: JamTrack Mixdown on disk is different version (stored: #{@trackDetail.version}, server: #{@jamTrack.activeMixdown.myPackage.version}. Invalidating")
|
||||
context.jamClient.InvalidateJamTrack(fqId)
|
||||
@trackDetail = context.jamClient.JamTrackGetTrackDetail (fqId)
|
||||
|
||||
if @trackDetail.version?
|
||||
logger.error("after invalidating package, the version is still wrong!")
|
||||
throw "after invalidating package, the version is still wrong!"
|
||||
|
||||
if @jamTrack.activeMixdown.client_state == 'cant_open'
|
||||
logger.debug(" skipping state check because of earlier 'cant_open'. user should hit retry. ")
|
||||
return
|
||||
|
||||
if @jamTrack.activeMixdown.client_state == 'download_fail'
|
||||
logger.debug("skipping state check because of earlier 'download_fail'. user should hit retry. ")
|
||||
return
|
||||
|
||||
if @jamTrack.activeMixdown.client_state == 'downloading'
|
||||
logger.debug("skipping state check because we are downloading")
|
||||
|
||||
switch @trackDetail.key_state
|
||||
when 'pending'
|
||||
@attemptKeying()
|
||||
when 'not authorized'
|
||||
# TODO: if not authorized, do we need to re-initiate a keying attempt?
|
||||
@attemptKeying()
|
||||
when 'ready'
|
||||
if @jamTrack.activeMixdown.client_state != 'ready'
|
||||
|
||||
@clearMixdownTimers()
|
||||
@jamTrack.activeMixdown.client_state = 'ready'
|
||||
|
||||
# now load it:
|
||||
# JamTrackPlay means 'load'
|
||||
logger.debug("JamTrackStore: loading mixdown")
|
||||
context.jamClient.JamTrackStopPlay();
|
||||
result = context.jamClient.JamTrackPlay(fqId);
|
||||
if !result
|
||||
@jamTrack.activeMixdown.client_state = 'cant_open'
|
||||
@reportError(@jamTrack.activeMixdown)
|
||||
@app.notify(
|
||||
{
|
||||
title: "Mixdown Can Not Open",
|
||||
text: "Unable to open your JamTrack Mixdown. Please contact support@jamkazam.com"
|
||||
}
|
||||
, null, true)
|
||||
|
||||
when 'unknown'
|
||||
if @jamTrack.activeMixdown.client_state != 'downloading'
|
||||
@jamTrack.activeMixdown.client_state = 'downloading'
|
||||
logger.debug("JamTrackStore: initiating download of mixdown")
|
||||
context.jamClient.JamTrackDownload(@jamTrack.id, @jamTrack.activeMixdown.id, context.JK.currentUserId,
|
||||
this.makeDownloadProgressCallback(),
|
||||
this.makeDownloadSuccessCallback(),
|
||||
this.makeDownloadFailureCallback())
|
||||
else
|
||||
logger.debug("JamTrackStore: already downloading")
|
||||
|
||||
attemptKeying: () ->
|
||||
if @keyCheckTimeout?
|
||||
logger.debug("JamTrackStore: attemptKeying: skipping because already keying")
|
||||
return
|
||||
else if @jamTrack.activeMixdown.client_state == 'keying_timeout'
|
||||
# if we have timed out keying, we shouldn't automatically retry
|
||||
logger.debug("JamTrackStore: attempKeying: skipping because we have timed out before and user hasn't requested RETRY")
|
||||
return
|
||||
else
|
||||
@keyCheckTimeout = setTimeout(@onKeyCheckTimeout, 10000)
|
||||
@keyCheckoutInterval = setInterval(@checkOnKeying, 1000)
|
||||
@jamTrack.activeMixdown.client_state = 'keying'
|
||||
logger.debug("JamTrackStore: initiating keying requested")
|
||||
context.jamClient.JamTrackKeysRequest()
|
||||
|
||||
onKeyCheckTimeout: () ->
|
||||
@keyCheckTimeout = null
|
||||
clearInterval(@keyCheckoutInterval)
|
||||
@keyCheckoutInterval = null
|
||||
|
||||
if @jamTrack?.activeMixdown?
|
||||
@jamTrack.activeMixdown.client_state = 'keying_timeout'
|
||||
@reportError(@jamTrack.activeMixdown)
|
||||
|
||||
@changed()
|
||||
|
||||
checkOnKeying: () ->
|
||||
@manageMixdownSynchronization()
|
||||
|
||||
# if we exit keying state, we can clear our timers and poke state
|
||||
if @jamTrack.activeMixdown.client_state != 'keying'
|
||||
@clearMixdownTimers()
|
||||
@changed()
|
||||
|
||||
|
||||
# clear out any timer/watcher stuff
|
||||
clearMixdownTimers: () ->
|
||||
logger.debug("JamTrackStore: clearing mixdown timers", @keyCheckTimeout, @keyCheckoutInterval)
|
||||
clearTimeout(@keyCheckTimeout) if @keyCheckTimeout?
|
||||
clearInterval(@keyCheckoutInterval) if @keyCheckoutInterval?
|
||||
@keyCheckTimeout = null
|
||||
@keyCheckoutInterval = null
|
||||
|
||||
changed: () ->
|
||||
|
||||
@pickMyPackage()
|
||||
@manageWatchedMixdowns()
|
||||
@manageMixdownSynchronization()
|
||||
|
||||
@state = {
|
||||
jamTrack: @jamTrack,
|
||||
opened: @previous == null && @jamTrack != null,
|
||||
closed: @previous != null && @jamTrack == null,
|
||||
fullTrackActivated: @previousMixdown != null && @jamTrack?.activeMixdown == null}
|
||||
@previous = @jamTrack
|
||||
@previousMixdown = @jamTrack?.activeMixdown
|
||||
this.trigger(@state)
|
||||
|
||||
|
||||
onOpen: (jamTrack) ->
|
||||
if @jamTrack?
|
||||
@app.notify({text: 'Unable to open JamTrack because another one is already open.'})
|
||||
return
|
||||
|
||||
@enqueuedMixdowns = {}
|
||||
@jamTrack = jamTrack
|
||||
this.trigger(@jamTrack)
|
||||
|
||||
# we can cache this because you can't switch gear while in a session (and possible change sample rate!)
|
||||
sampleRate = context.jamClient.GetSampleRate()
|
||||
@sampleRate = if sampleRate == 48 then 48 else 44
|
||||
|
||||
@changed()
|
||||
|
||||
onClose: () ->
|
||||
@jamTrack = null
|
||||
this.trigger(@jamTrack)
|
||||
@changed()
|
||||
|
||||
onRequestSearch:(searchType, searchData) ->
|
||||
@requestedSearch = {searchType: searchType, searchData: searchData}
|
||||
|
|
@ -53,5 +284,261 @@ JamTrackActions = @JamTrackActions
|
|||
@requestedFilter = null
|
||||
requested
|
||||
|
||||
onCreateMixdown: (mixdown, done, fail) ->
|
||||
|
||||
volumeSettings = context.jamClient.GetJamTrackSettings();
|
||||
|
||||
track_settings = []
|
||||
|
||||
for track in volumeSettings.tracks
|
||||
track_settings.push({id: track.id, pan: track.pan, vol: track.vol_l, mute: track.mute})
|
||||
|
||||
mixdown.settings.tracks = track_settings
|
||||
|
||||
logger.debug("creating mixdown", mixdown)
|
||||
|
||||
rest.createMixdown(mixdown)
|
||||
.done((created) =>
|
||||
|
||||
@addMixdown(created)
|
||||
|
||||
logger.debug("created mixdown", created)
|
||||
|
||||
@onEnqueueMixdown({id: created.id}, done, fail)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
fail(jqxhr)
|
||||
)
|
||||
|
||||
|
||||
onEditMixdown: (mixdown) ->
|
||||
logger.debug("editing mixdown", mixdown)
|
||||
|
||||
rest.editMixdown(mixdown)
|
||||
.done((updatedMixdown) =>
|
||||
logger.debug("edited mixdown")
|
||||
@updateMixdown(updatedMixdown)
|
||||
).fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Custom Mix', text: 'The server was unable to edit this mix.'})
|
||||
)
|
||||
|
||||
onDeleteMixdown: (mixdown) ->
|
||||
logger.debug("deleting mixdown", mixdown)
|
||||
|
||||
rest.deleteMixdown(mixdown)
|
||||
.done(() =>
|
||||
logger.debug("deleted mixdown")
|
||||
|
||||
@deleteMixdown(mixdown)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Deleted Custom Mix', text: 'The server was unable to delete this mix.'})
|
||||
)
|
||||
|
||||
onOpenMixdown: (mixdown) ->
|
||||
logger.debug("opening mixdown", mixdown)
|
||||
|
||||
# check if it's already available in the backend or not
|
||||
rest.markMixdownActive({id: @jamTrack.id, mixdown_id: mixdown.id})
|
||||
.done((edited) =>
|
||||
logger.debug("marked mixdown as active")
|
||||
@jamTrack = edited
|
||||
|
||||
# unload any currently loaded JamTrack
|
||||
context.jamClient.JamTrackStopPlay();
|
||||
|
||||
@changed()
|
||||
|
||||
SessionActions.mixdownActive(mixdown)
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
|
||||
)
|
||||
|
||||
onActivateNoMixdown: (jamTrack) ->
|
||||
logger.debug("activating no mixdown")
|
||||
|
||||
rest.markMixdownActive({id: @jamTrack.id, mixdown_id: null})
|
||||
.done((edited) =>
|
||||
logger.debug("marked JamTrack as active")
|
||||
|
||||
@jamTrack = edited
|
||||
@changed()
|
||||
|
||||
SessionActions.mixdownActive({id:null})
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Edit Mixdown', text: 'Unable to mark this mixdown as active.'})
|
||||
)
|
||||
|
||||
|
||||
onCloseMixdown: (mixdown) ->
|
||||
logger.debug("closing mixdown", mixdown)
|
||||
|
||||
onEnqueueMixdown: (mixdown, done, fail) ->
|
||||
logger.debug("enqueuing mixdown", mixdown)
|
||||
|
||||
package_settings = {file_type: 'ogg', encrypt_type: 'jkz', sample_rate: @sampleRate}
|
||||
package_settings.id = mixdown.id
|
||||
|
||||
rest.enqueueMixdown(package_settings)
|
||||
.done((enqueued) =>
|
||||
|
||||
@enqueuedMixdowns[mixdown.id] = {}
|
||||
|
||||
logger.debug("enqueued mixdown package", package_settings)
|
||||
@addOrUpdatePackage(enqueued)
|
||||
done(enqueued) if done
|
||||
)
|
||||
.fail((jqxhr) =>
|
||||
@app.layout.notify({title:'Unable to Create Custom Mix', text: 'Click the error icon to retry.'})
|
||||
fail(jqxhr) if fail?
|
||||
)
|
||||
|
||||
onDownloadMixdown: (mixdown) ->
|
||||
logger.debug("download mixdown", mixdown)
|
||||
|
||||
onRefreshMixdown: (mixdown) ->
|
||||
logger.debug("refresh mixdown", mixdown)
|
||||
|
||||
addMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("adding mixdown to JamTrackStore", mixdown)
|
||||
@jamTrack.mixdowns.splice(0, 0, mixdown)
|
||||
@changed()
|
||||
else
|
||||
logger.warn("no jamtrack to add mixdown to in JamTrackStore", mixdown)
|
||||
|
||||
deleteMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("deleting mixdown from JamTrackStore", mixdown)
|
||||
index = null
|
||||
for matchMixdown, i in @jamTrack.mixdowns
|
||||
if mixdown.id == matchMixdown.id
|
||||
index = i
|
||||
if index?
|
||||
@jamTrack.mixdowns.splice(index, 1)
|
||||
|
||||
if @jamTrack.activeMixdown?.id == mixdown.id
|
||||
@onActivateNoMixdown(@jamTrack)
|
||||
|
||||
@changed()
|
||||
else
|
||||
logger.warn("unable to find mixdown to delete in JamTrackStore", mixdown)
|
||||
else
|
||||
logger.warn("no jamtrack to delete mixdown for in JamTrackStore", mixdown)
|
||||
|
||||
updateMixdown: (mixdown) ->
|
||||
if @jamTrack?
|
||||
logger.debug("editing mixdown from JamTrackStore", mixdown)
|
||||
index = null
|
||||
for matchMixdown, i in @jamTrack.mixdowns
|
||||
if mixdown.id == matchMixdown.id
|
||||
index = i
|
||||
if index?
|
||||
@jamTrack.mixdowns[index] = mixdown
|
||||
|
||||
@changed()
|
||||
else
|
||||
logger.warn("unable to find mixdown to edit in JamTrackStore", mixdown)
|
||||
else
|
||||
logger.warn("no jamtrack to edit mixdown for in JamTrackStore", mixdown)
|
||||
|
||||
addOrUpdatePackage: (mixdown_package) ->
|
||||
if @jamTrack?
|
||||
added = false
|
||||
index = null
|
||||
for mixdown in @jamTrack.mixdowns
|
||||
existing = false
|
||||
if mixdown_package.jam_track_mixdown_id == mixdown.id
|
||||
for possiblePackage, i in mixdown.packages
|
||||
if possiblePackage.id == mixdown_package.id
|
||||
existing = true
|
||||
index = i
|
||||
break
|
||||
|
||||
if existing
|
||||
mixdown.packages[index] = mixdown_package
|
||||
logger.debug("replacing mixdown package in JamTrackStore", mixdown_package)
|
||||
else
|
||||
mixdown.packages.splice(0, 0, mixdown_package)
|
||||
logger.debug("adding mixdown package in JamTrackStore")
|
||||
|
||||
added = true
|
||||
@changed()
|
||||
break
|
||||
|
||||
if !added
|
||||
logger.debug("couldn't find the mixdown associated with package in JamTrackStore", mixdown_package)
|
||||
else
|
||||
logger.warn("no mixdown to add package to in JamTrackStore", mixdown_package)
|
||||
|
||||
|
||||
updateDownloadProgress: () ->
|
||||
|
||||
if @bytesReceived? and @bytesTotal?
|
||||
progress = "#{Math.round(@bytesReceived/@bytesTotal * 100)}%"
|
||||
else
|
||||
progress = '0%'
|
||||
|
||||
#@root.find('.state-downloading .progress').text(progress)
|
||||
|
||||
downloadProgressCallback: (bytesReceived, bytesTotal) ->
|
||||
logger.debug("download #{bytesReceived}/#{bytesTotal}")
|
||||
|
||||
@bytesReceived = Number(bytesReceived)
|
||||
@bytesTotal = Number(bytesTotal)
|
||||
|
||||
# the reason this timeout is set is because, without it,
|
||||
# we observe that the client will hang. So, if you remove this timeout, make sure to test with real client
|
||||
setTimeout(this.updateDownloadProgress, 100)
|
||||
|
||||
downloadSuccessCallback: (updateLocation) ->
|
||||
# is the package loadable yet?
|
||||
logger.debug("JamTrackStore: download complete - on to keying")
|
||||
@attemptKeying()
|
||||
@changed()
|
||||
|
||||
downloadFailureCallback: (errorMsg) ->
|
||||
|
||||
if @jamTrack?.activeMixdown?
|
||||
@jamTrack.activeMixdown.client_state = 'download_fail'
|
||||
@reportError(@jamTrack.activeMixdown)
|
||||
@changed()
|
||||
|
||||
# makes a function name for the backend
|
||||
makeDownloadProgressCallback: () ->
|
||||
"JamTrackStore.downloadProgressCallback"
|
||||
|
||||
# makes a function name for the backend
|
||||
makeDownloadSuccessCallback: () ->
|
||||
"JamTrackStore.downloadSuccessCallback"
|
||||
|
||||
# makes a function name for the backend
|
||||
makeDownloadFailureCallback: () ->
|
||||
"JamTrackStore.downloadFailureCallback"
|
||||
|
||||
|
||||
reportError: (mixdown) ->
|
||||
|
||||
enqueued = @enqueuedMixdowns[mixdown?.id]
|
||||
|
||||
# don't double-report
|
||||
if !enqueued? || enqueued.marked
|
||||
return
|
||||
|
||||
enqueued.marked = true
|
||||
data = {
|
||||
value: 1,
|
||||
user_id: context.JK.currentUserId,
|
||||
user_name: context.JK.currentUserName,
|
||||
result: "signing state: #{mixdown.myPackage?.signing_state}, client state: #{mixdown.client_state}",
|
||||
mixdown: mixdown.id,
|
||||
package: mixdown.myPackage?.id
|
||||
detail: mixdown.myPackage?.error_reason
|
||||
}
|
||||
rest.createAlert("Mixdown Sync failed for #{context.JK.currentUserName}", data)
|
||||
|
||||
context.stats.write('web.mixdown.error', data)
|
||||
}
|
||||
)
|
||||
|
|
@ -53,6 +53,11 @@ VideoActions = @VideoActions
|
|||
RecordingActions.initModel(@recordingModel)
|
||||
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
|
||||
|
||||
onMixdownActive: (mixdown) ->
|
||||
if @currentSession?.jam_track?
|
||||
@currentSession.jam_track.mixdown = mixdown
|
||||
@issueChange()
|
||||
|
||||
|
||||
onVideoChanged: (@videoState) ->
|
||||
|
||||
|
|
@ -241,6 +246,7 @@ VideoActions = @VideoActions
|
|||
|
||||
rest.closeJamTrack({id: @currentSessionId})
|
||||
.done(() =>
|
||||
@downloadingJamTrack = false
|
||||
@refreshCurrentSession(true)
|
||||
)
|
||||
.fail((jqXHR) =>
|
||||
|
|
|
|||
|
|
@ -921,6 +921,24 @@
|
|||
return ul;
|
||||
}
|
||||
|
||||
context.JK.reactErrors = function (errors_data, fieldMapper) {
|
||||
var errors = errors_data["errors"];
|
||||
if (errors == null) return null;
|
||||
var items = []
|
||||
|
||||
$.each(errors, function (fieldName, field_errors) {
|
||||
var displayName = fieldMapper && fieldMapper[fieldName]
|
||||
if (!displayName) {
|
||||
displayName = fieldName;
|
||||
}
|
||||
$.each(field_errors, function (index, item) {
|
||||
items.push(React.DOM.li({key: fieldName + item}, displayName + ' ' + item))
|
||||
});
|
||||
});
|
||||
|
||||
return React.DOM.ul({className: 'error-text'}, null, items)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Way to verify that a number of parallel tasks have all completed.
|
||||
|
|
|
|||
|
|
@ -36,10 +36,213 @@ body.media-controls-popup.popup {
|
|||
.close-link {
|
||||
margin-top:20px;
|
||||
font-size:11px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.display-metronome {
|
||||
font-size:12px;
|
||||
margin-top:35px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-bottom:20px;
|
||||
h3 {
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top:15px;
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
|
||||
span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
img {
|
||||
vertical-align:middle;
|
||||
margin-left:5px;
|
||||
height:16px;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
|
||||
span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
img {
|
||||
vertical-align:middle;
|
||||
margin-left:5px;
|
||||
height:16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extra-controls {
|
||||
margin-top:20px;
|
||||
h4 {
|
||||
text-align:left;
|
||||
font-size:14px;
|
||||
a {
|
||||
font-size:11px;
|
||||
position:absolute;
|
||||
right:20px;
|
||||
}
|
||||
&.custom-mix-header {
|
||||
margin-top:20px;
|
||||
}
|
||||
}
|
||||
|
||||
.my-mixes {
|
||||
margin-top:5px;
|
||||
max-height:170px;
|
||||
border-width:1px;
|
||||
border-bottom-color:#676767;
|
||||
border-top-color:#676767;
|
||||
border-left-color:#171717;
|
||||
border-right-color:#171717;
|
||||
border-style:solid;
|
||||
overflow:auto;
|
||||
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.mixdown-display {
|
||||
display:table;
|
||||
font-size:12px;
|
||||
color:$ColorTextTypical;
|
||||
width:100%;
|
||||
|
||||
border-width:1px 0;
|
||||
border-top-color:#343434;
|
||||
border-bottom-color:#282828;
|
||||
border-style:solid;
|
||||
background-color:#2c2c2c;
|
||||
@include border_box_sizing;
|
||||
border-spacing:7px;
|
||||
text-align: left;
|
||||
|
||||
&.active {
|
||||
background-color:#44423f;
|
||||
}
|
||||
}
|
||||
|
||||
.mixdown-name {
|
||||
line-height:125%;
|
||||
width:210px;
|
||||
text-align:left;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mixdown-actions {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
margin-left:10px;
|
||||
width:100px;
|
||||
white-space:nowrap;
|
||||
min-width:100px;
|
||||
}
|
||||
|
||||
.mixdown-stateful {
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
width:24px;
|
||||
height:24px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.mixdown-play {
|
||||
width:24px;
|
||||
height:24px;
|
||||
|
||||
cursor:pointer;
|
||||
}
|
||||
.mixdown-edit {
|
||||
margin-left:10px;
|
||||
width:24px;
|
||||
height:24px;
|
||||
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.mixdown-delete {
|
||||
margin-left:10px;
|
||||
|
||||
width:24px;
|
||||
height:24px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.create-mix {
|
||||
margin-top:5px;
|
||||
border-color:$ColorTextTypical;
|
||||
border-style: solid;
|
||||
border-width:1px 0;
|
||||
padding: 7px 0 20px;
|
||||
|
||||
p {
|
||||
line-height:125%;
|
||||
color:$ColorTextTypical;
|
||||
text-align:left;
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display:block;
|
||||
height:25px;
|
||||
margin-top:15px;
|
||||
}
|
||||
|
||||
ul.error-text {
|
||||
float:right;
|
||||
display:block !important;
|
||||
color: red;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
a.create-mix-btn {
|
||||
margin-top:15px;
|
||||
float:right;
|
||||
margin-right: 2px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
label {
|
||||
display:inline;
|
||||
float:left;
|
||||
}
|
||||
|
||||
select, input {
|
||||
width:170px;
|
||||
float:right;
|
||||
@include border_box_sizing;
|
||||
background-color:$ColorTextBoxBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
float:none;
|
||||
margin-left:5px;
|
||||
margin-top:0;
|
||||
margin-right:0;
|
||||
border-top: 4px solid #fc0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
display:inline-block;
|
||||
padding-top:1px;
|
||||
}
|
||||
.arrow-up {
|
||||
float:none;
|
||||
margin-right:0;
|
||||
margin-left:5px;
|
||||
margin-bottom:2px;
|
||||
border-bottom: 4px solid #fc0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
display:inline-block;
|
||||
padding-top:1px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
class ApiJamTrackMixdownsController < ApiController
|
||||
|
||||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user
|
||||
before_filter :lookup_jam_track_mixdown, :only => [:download, :enqueue, :update]
|
||||
before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :update]
|
||||
respond_to :json
|
||||
|
||||
def log
|
||||
@log || Logging.logger[ApiJamTrackMixdownsController]
|
||||
end
|
||||
|
||||
def index
|
||||
data = JamTrackMixdown.index(params, current_user)
|
||||
@jam_track_mixdowns, @next, @count = data[0], data[1], data[2]
|
||||
|
||||
render "api_jam_track_mixdowns/index", :layout => nil
|
||||
end
|
||||
|
||||
def show
|
||||
@jam_track_mixdown = JamTrackMixdown.find(params[:id])
|
||||
end
|
||||
|
||||
def delete
|
||||
@jam_track_mixdown = JamTrackMixdown.find(params[:id])
|
||||
@jam_track_mixdown.destroy
|
||||
render json: {}, status:204
|
||||
end
|
||||
|
||||
def update
|
||||
@mixdown = JamTrackMixdown.find(params[:id])
|
||||
@mixdown.name = params[:name] if params[:name]
|
||||
@mixdown.description = params[:description] if params[:description]
|
||||
@mixdown.save
|
||||
if params[:active]
|
||||
@jam_track_right.last_mixdown = @mixdown
|
||||
@jam_track_right.save
|
||||
end
|
||||
|
||||
if @mixdown.errors.any?
|
||||
respond_with_model(@mixdown)
|
||||
return
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def show_package
|
||||
@package = JamTrackMixdownPackage.find(params[:id])
|
||||
end
|
||||
|
||||
def create
|
||||
@mixdown = JamTrackMixdown.create(params[:name], params[:description], current_user, JamTrack.find(params[:jamTrackID]), params[:settings])
|
||||
|
||||
if @mixdown.errors.any?
|
||||
respond_with_model(@mixdown)
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def download
|
||||
if @jam_track_right.valid?
|
||||
|
||||
begin
|
||||
@package = JamTrackMixdownPackage.where('jam_track_mixdown_id = ?', @jam_track_mixdown.id).where(file_type: params[:file_type]).where(encrypt_type: params[:encrypt_type]).where(sample_rate: params[:sample_rate]).first
|
||||
rescue Exception => e
|
||||
log.error("failed to find mixdown package", e)
|
||||
render :json => {:message => "unable to locate mixdown package due to error; check arguments"}, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
@package = JamTrackMixdownPackage.create(@jam_track_mixdown, params[:file_type], params[:sample_rate], params[:encrypt_type]) unless @package
|
||||
|
||||
if @package.errors.any?
|
||||
respond_with_model(@package)
|
||||
return
|
||||
end
|
||||
|
||||
if @package.ready?
|
||||
@package.update_download_count
|
||||
now = Time.now
|
||||
@package.last_downloaded_at = now
|
||||
@package.first_downloaded_at = now if @package.first_downloaded_at.nil?
|
||||
@package.save!
|
||||
redirect_to @package.sign_url(120)
|
||||
else
|
||||
@package.enqueue_if_needed
|
||||
render :json => { :message => "not available, digitally signing Jam Track Mixdown offline." }, :status => 202
|
||||
end
|
||||
else
|
||||
render :json => { :message => "download limit surpassed", :errors=>@package.errors }, :status => 403
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue
|
||||
if @jam_track_right.valid?
|
||||
|
||||
begin
|
||||
@package = JamTrackMixdownPackage.where('jam_track_mixdown_id = ?', @jam_track_mixdown.id).where(file_type: params[:file_type]).where(encrypt_type: params[:encrypt_type]).where(sample_rate: params[:sample_rate]).first
|
||||
rescue Exception => e
|
||||
puts "enqueue failure #{e}"
|
||||
log.error("failed to find mixdown package #{e}")
|
||||
render :json => {:message => "unable to locate mixdown package due to error; check arguments"}, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
@package = JamTrackMixdownPackage.create(@jam_track_mixdown, params[:file_type], params[:sample_rate], params[:encrypt_type]) unless @package
|
||||
|
||||
if @package.errors.any?
|
||||
respond_with_model(@package)
|
||||
return
|
||||
end
|
||||
|
||||
enqueued = @package.enqueue_if_needed
|
||||
log.debug("jamtrack mixdown #{enqueued ? "ENQUEUED" : "NOT ENQUEUED"}: mixdown_package=#{@package.id} ")
|
||||
@queue_time = enqueued ? enqueued : 0
|
||||
return
|
||||
else
|
||||
render :json => { :message => "download limit surpassed", :errors=>@package.errors }, :status => 403
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def lookup_jam_track_right
|
||||
@jam_track_right = JamTrackRight.where("jam_track_id=? AND user_id=?", @jam_track_mixdown.jam_track.id, current_user.id).first
|
||||
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @jam_track_right
|
||||
end
|
||||
|
||||
def lookup_jam_track_mixdown
|
||||
@jam_track_mixdown = JamTrackMixdown.find(params[:id])
|
||||
end
|
||||
|
||||
end # class ApiJamTracksController
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
class ApiJamTracksController < ApiController
|
||||
|
||||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user, :except => [:index, :show, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :api_any_user, :only => [:index, :show, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right]
|
||||
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right, :mark_active]
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
@ -12,7 +12,8 @@ class ApiJamTracksController < ApiController
|
|||
end
|
||||
|
||||
def show
|
||||
@jam_track = JamTrack.find_by_plan_code!(params[:plan_code])
|
||||
@jam_track = JamTrack.find(params[:id])
|
||||
render "api_jam_tracks/show_for_client", :layout => nil
|
||||
end
|
||||
|
||||
def show_with_artist_info
|
||||
|
|
@ -26,6 +27,23 @@ class ApiJamTracksController < ApiController
|
|||
render "api_jam_tracks/index", :layout => nil
|
||||
end
|
||||
|
||||
|
||||
def mark_active
|
||||
mixdown_id = params[:mixdown_id]
|
||||
|
||||
@jam_track_right.last_mixdown_id = mixdown_id
|
||||
@jam_track_right.save
|
||||
|
||||
if @jam_track_right.errors.any?
|
||||
respond_with_model(@jam_track_right)
|
||||
return
|
||||
else
|
||||
@jam_track = @jam_track_right.jam_track
|
||||
render "api_jam_tracks/show_for_client", :layout => nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def autocomplete
|
||||
autocomplete = JamTrack.autocomplete(params, any_user)
|
||||
|
||||
|
|
@ -62,7 +80,7 @@ class ApiJamTracksController < ApiController
|
|||
play.save
|
||||
|
||||
if play.errors.any?
|
||||
render :json => { :message => "Unexpected error occurred" }, :status => 500
|
||||
render :json => { :message => "Unexpected error occurred" }, :status => 422
|
||||
else
|
||||
render :json => {}, :status => 201
|
||||
end
|
||||
|
|
@ -139,7 +157,10 @@ class ApiJamTracksController < ApiController
|
|||
end
|
||||
|
||||
def keys
|
||||
puts "Keys"
|
||||
puts "--------------------------"
|
||||
jamtrack_holder = params[:jamtracks]
|
||||
puts jamtrack_holder.inspect
|
||||
|
||||
unless jamtrack_holder.kind_of?(Hash)
|
||||
render :json => {message: 'jamtracks parameter must be an hash'}, :status => 422
|
||||
|
|
@ -155,20 +176,49 @@ class ApiJamTracksController < ApiController
|
|||
|
||||
|
||||
# jamtracks come in the form id-44 or id-48, so we need to do a little extra parsing
|
||||
# mixdowns come in the form id_mixid-44 or id_mixid-48, so we also need to handle that
|
||||
|
||||
jamtrack_ids = Set.new
|
||||
jamtracks_fq_ids = Set.new
|
||||
jamtrack_mixdowns = {}
|
||||
|
||||
jamtracks.each do |jamtrack|
|
||||
rindex = jamtrack.rindex('-')
|
||||
if rindex
|
||||
id = jamtrack[0..(rindex-1)]
|
||||
jamtrack_ids << id
|
||||
|
||||
# let's see if a mixid is in this ID
|
||||
rindex = id.rindex('_')
|
||||
|
||||
if rindex
|
||||
# ok, this is id_mixid-44 format; so we need to parse again for the ID
|
||||
just_id = jamtrack[0..(rindex-1)]
|
||||
sample_rate = jamtrack[-2..-1]
|
||||
|
||||
jamtrack_ids << just_id
|
||||
|
||||
simulated_fq_id = "#{just_id}-#{sample_rate}"
|
||||
mixdown_info = jamtrack_mixdowns[simulated_fq_id]
|
||||
|
||||
unless mixdown_info
|
||||
mixdown_info = []
|
||||
jamtrack_mixdowns[simulated_fq_id] = mixdown_info
|
||||
end
|
||||
mixdown_info << id
|
||||
|
||||
else
|
||||
jamtrack_ids << id
|
||||
end
|
||||
|
||||
|
||||
jamtracks_fq_ids << jamtrack # includes sample rate
|
||||
end
|
||||
end
|
||||
|
||||
@jam_tracks = JamTrackRight.list_keys(current_user, jamtrack_ids)
|
||||
@jamtracks_fq_ids = jamtracks_fq_ids
|
||||
@jamtrack_mixdowns = jamtrack_mixdowns
|
||||
puts "jamtrack_mixdowns #{jamtrack_mixdowns}"
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class ApiSearchController < ApiController
|
|||
|
||||
def index
|
||||
if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i
|
||||
query = parasobj.clone
|
||||
query = params.clone
|
||||
query[:remote_ip] = request.remote_ip
|
||||
if 1 == query[Search::PARAM_MUSICIAN].to_i
|
||||
@search = Search.musician_filter(query, current_user)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
require 'sanitize'
|
||||
class ApiUsersController < ApiController
|
||||
class
|
||||
ApiUsersController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data]
|
||||
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
|
||||
|
|
@ -575,14 +576,14 @@ class ApiUsersController < ApiController
|
|||
# This should largely be moved into a library somewhere in jam-ruby.
|
||||
def crash_dump
|
||||
# example of using curl to access this API:
|
||||
# curl -L -T some_file -X PUT http://localhost:3000/api/dumps?client_type=[MACOSX/Win32/JamBox]&client_version=[VERSION]&client_id=[CLIENT_ID]&session_id=[SESSION_ID]×tamp=[TIMESTAMP]
|
||||
# curl -L -T some_file -X PUT http://localhost:3000/api/dumps?client_type=[MacOSX/Win32/JamBox]&client_version=[VERSION]&client_id=[CLIENT_ID]&session_id=[SESSION_ID]×tamp=[TIMESTAMP]
|
||||
# user_id is deduced if possible from the user's cookie.
|
||||
@dump = CrashDump.new
|
||||
|
||||
@dump.client_type = params[:client_type]
|
||||
@dump.client_version = params[:client_version]
|
||||
@dump.client_id = params[:client_id]
|
||||
@dump.user_id = current_user.try(:id)
|
||||
@dump.user_id = params[:user_id]
|
||||
@dump.session_id = params[:session_id]
|
||||
@dump.timestamp = params[:timestamp]
|
||||
|
||||
|
|
@ -603,7 +604,7 @@ class ApiUsersController < ApiController
|
|||
read_url = bucket.objects[uri].url_for(:read,
|
||||
:expires => expire,
|
||||
:'response_content_type' => 'application/octet-stream').to_s
|
||||
@dump.update_attribute(:uri, read_url)
|
||||
#@dump.update_attribute(:uri, read_url)
|
||||
|
||||
write_url = bucket.objects[uri].url_for(:write,
|
||||
:expires => Rails.application.config.crash_dump_data_signed_url_timeout,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
object @mixdown
|
||||
|
||||
extends "api_jam_track_mixdowns/show"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
object @package
|
||||
|
||||
node :queue_time do
|
||||
@queue_time
|
||||
end
|
||||
|
||||
extends "api_jam_track_mixdowns/show_package"
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
node :next do |page|
|
||||
@next
|
||||
end
|
||||
|
||||
node :count do |page|
|
||||
@count
|
||||
end
|
||||
|
||||
node :mixdowns do |page|
|
||||
partial "api_jam_track_mixdowns/show", object: @jam_track_mixdowns
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
object @jam_track_mixdown
|
||||
|
||||
attributes :id, :name, :description, :jam_track_id
|
||||
|
||||
node :settings do |item|
|
||||
JSON.parse(item.settings)
|
||||
end
|
||||
|
||||
child(:jam_track_mixdown_packages => :packages) {
|
||||
node do |package|
|
||||
partial("api_jam_track_mixdowns/show_package", :object => package)
|
||||
end
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @package
|
||||
|
||||
attributes :id, :jam_track_mixdown_id, :file_type, :sample_rate, :encrypt_type, :error_count, :error_reason, :error_detail, :signing_state, :packaging_steps, :current_packaging_step, :version
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @mixdown
|
||||
|
||||
extends "api_jam_track_mixdowns/show"
|
||||
|
|
@ -17,7 +17,35 @@ node do |jam_track|
|
|||
private: jam_track['private_key_48'],
|
||||
error: jam_track['private_key_48'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' )
|
||||
}
|
||||
end
|
||||
|
||||
# now include mixdown info
|
||||
mixdowns_44 = []
|
||||
mixdown_info = @jamtrack_mixdowns[id]
|
||||
if mixdown_info
|
||||
mixdown_info.each do |mixdown_id|
|
||||
mixdowns_44 << {
|
||||
id: mixdown_id + '-44',
|
||||
private: jam_track['private_key_44'],
|
||||
error: jam_track['private_key_44'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' )
|
||||
}
|
||||
end
|
||||
end
|
||||
result['mixdowns_44'] = mixdowns_44
|
||||
|
||||
# now include mixdown info
|
||||
mixdowns_48 = []
|
||||
mixdown_info = @jamtrack_mixdowns[id + '-48']
|
||||
if mixdown_info
|
||||
mixdown_info.each do |mixdown_id|
|
||||
mixdowns_48 << {
|
||||
id: mixdown_id + '-48',
|
||||
private: jam_track['private_key_48'],
|
||||
error: jam_track['private_key_48'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' )
|
||||
}
|
||||
end
|
||||
end
|
||||
result['mixdowns_48'] = mixdowns_48
|
||||
|
||||
result
|
||||
end
|
||||
|
|
@ -18,6 +18,19 @@ child(:jam_track_tracks => :tracks) {
|
|||
attributes :id, :part, :instrument, :track_type
|
||||
}
|
||||
|
||||
node :last_mixdown_id do |jam_track|
|
||||
jam_track.right_for_user(current_user).last_mixdown_id
|
||||
end
|
||||
|
||||
node :mixdowns do |jam_track|
|
||||
items = []
|
||||
jam_track.mixdowns_for_user(current_user).each do |mixdown|
|
||||
items << partial("api_jam_track_mixdowns/show", :object => mixdown)
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
|
||||
child(:jam_track_tap_ins => :tap_ins) {
|
||||
attributes :offset_time, :bpm, :tap_in_count
|
||||
}
|
||||
|
|
@ -81,6 +81,12 @@ else
|
|||
child({:jam_track => :jam_track}, :if => lambda { |music_session| music_session.users.exists?(current_user) }) {
|
||||
attributes :id, :name, :description
|
||||
|
||||
node :mixdown do |jam_track|
|
||||
right = jam_track.right_for_user(User.find(@music_session.jam_track_initiator_id))
|
||||
|
||||
{id: right ? right.last_mixdown_id : nil}
|
||||
end
|
||||
|
||||
child(:jam_track_tracks => :tracks) {
|
||||
attributes :id, :part, :instrument, :track_type
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,4 +349,7 @@ script type="text/template" id="template-help-ftue-video-disable"
|
|||
li If you know you never want to see anyone else's video.
|
||||
li If you are experiencing technical problems with others send you video.
|
||||
|
||||
script type="text/template" id="template-help-no-change-while-loading"
|
||||
span Certain actions are disabled while a track is being loaded.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -227,6 +227,18 @@ if defined?(Bundler)
|
|||
# amount of time to allow before giving up on a single step in packaging job
|
||||
config.signing_step_max_time = 60; # 60 seconds
|
||||
|
||||
config.signing_job_signing_max_time = 300
|
||||
# amount of time before we think the queue is stuck
|
||||
config.signing_job_queue_max_time = 300
|
||||
# amount of time to allow before giving up on a single step in packaging job
|
||||
config.mixdown_step_max_time = 300
|
||||
config.mixdown_job_queue_max_time = 300
|
||||
|
||||
config.estimated_jam_track_time = 40
|
||||
config.estimated_fast_mixdown_time = 30
|
||||
config.estimated_slow_mixdown_time = 80
|
||||
config.num_packaging_nodes = 2
|
||||
|
||||
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
|
||||
config.email_generic_from = 'nobody@jamkazam.com'
|
||||
config.email_recurly_notice = 'recurly-alerts@jamkazam.com'
|
||||
|
|
@ -355,5 +367,7 @@ if defined?(Bundler)
|
|||
|
||||
config.react.variant = :production
|
||||
config.react.addons = true
|
||||
|
||||
config.time_shift_style = :sbsms # or sox
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -98,4 +98,6 @@ SampleApp::Application.configure do
|
|||
config.guard_against_fraud = true
|
||||
|
||||
config.react.variant = :development
|
||||
|
||||
config.time_shift_style = :sox # or sbsms
|
||||
end
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ SampleApp::Application.routes.draw do
|
|||
match '/jamtracks/autocomplete' => 'api_jam_tracks#autocomplete', :via => :get, :as => 'api_jam_tracks_autocomplete'
|
||||
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
|
||||
match '/jamtracks/artists' => 'api_jam_tracks#artist_index', :via => :get, :as => 'api_jam_tracks_list_artists'
|
||||
match '/jamtracks/:plan_code' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'
|
||||
match '/jamtracks/:id' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'
|
||||
match '/jamtracks/band/:plan_code' => 'api_jam_tracks#show_with_artist_info', :via => :get, :as => 'api_jam_tracks_show_with_artist_info'
|
||||
match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
|
||||
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
|
||||
|
|
@ -249,6 +249,18 @@ SampleApp::Application.routes.draw do
|
|||
match '/jamtracks/rights/:id' => 'api_jam_tracks#show_jam_track_right', :via => :get, :as => 'api_jam_tracks_show_right'
|
||||
match '/jamtracks/keys' => 'api_jam_tracks#keys', :via => :post, :as => 'api_jam_tracks_keys'
|
||||
|
||||
# mixdowns
|
||||
match '/jamtracks/:id/mixdowns/active' => 'api_jam_tracks#mark_active', :via => :POST
|
||||
match '/jamtracks/:id/mixdowns' => 'api_jam_track_mixdowns#index', :via => :get
|
||||
match '/mixdowns/:id/download' => 'api_jam_track_mixdowns#download', :via => :get
|
||||
match '/mixdowns/:id/enqueue' => 'api_jam_track_mixdowns#enqueue', :via => :post
|
||||
match '/mixdowns/:id' => 'api_jam_track_mixdowns#show', :via => :get
|
||||
match '/mixdowns/:id' => 'api_jam_track_mixdowns#update', :via => :post
|
||||
match '/mixdowns' => 'api_jam_track_mixdowns#create', :via => :post
|
||||
match '/mixdowns/:id' => 'api_jam_track_mixdowns#delete', :via => :delete
|
||||
match '/mixdown_packages/:id' => 'api_jam_track_mixdowns#show_package', :via => :get
|
||||
|
||||
|
||||
# Shopping carts
|
||||
match '/shopping_carts/add_jamtrack' => 'api_shopping_carts#add_jamtrack', :via => :post
|
||||
match '/shopping_carts' => 'api_shopping_carts#index', :via => :get
|
||||
|
|
@ -565,7 +577,7 @@ SampleApp::Application.routes.draw do
|
|||
match '/artifacts/clients' => 'artifacts#client_downloads'
|
||||
|
||||
# crash logs
|
||||
match '/dumps' => 'api_users#crash_dump', :via => :put
|
||||
match '/crashes' => 'api_users#crash_dump', :via => :put
|
||||
|
||||
# feedback from corporate site api
|
||||
match '/feedback' => 'api_corporate#feedback', :via => :post
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ IcecastSourceCheck:
|
|||
description: "Finds icecast mounts that need their 'sourced' state to change, but haven't in some time"
|
||||
|
||||
JamTracksCleaner:
|
||||
cron: "0 5 * * *"
|
||||
class: "JamRuby::UnusedMusicNotationCleaner"
|
||||
description: "Remove unused music notations"
|
||||
cron: "0,30 * * * *"
|
||||
class: "JamRuby::JamTracksCleaner"
|
||||
description: "Clean up JamTrack related stuff; every 30 minutes"
|
||||
|
||||
CleanupFacebookSignup:
|
||||
cron: "30 2 * * *"
|
||||
|
|
|
|||
|
|
@ -158,4 +158,29 @@ namespace :jam_tracks do
|
|||
mapper = TencyStemMapping.new
|
||||
mapper.correlate
|
||||
end
|
||||
|
||||
task generate_private_key: :environment do |task, arg|
|
||||
JamTrackRight.all.each do |right|
|
||||
if right.private_key_44.nil? || right.private_key_48.nil?
|
||||
|
||||
if right.private_key_44.nil? && right.private_key_48
|
||||
right.private_key_44 = right.private_key_48
|
||||
puts "COPY 48 > 44"
|
||||
elsif right.private_key_48.nil? && right.private_key_44
|
||||
right.private_key_48 = right.private_key_44
|
||||
puts "COPY 44 > 48"
|
||||
elsif right.private_key_48.nil? && right.private_key_44.nil?
|
||||
rsa_key = OpenSSL::PKey::RSA.new(1024)
|
||||
key = rsa_key.to_pem()
|
||||
right.private_key_44 = key
|
||||
right.private_key_48 = key
|
||||
puts "GEN 44 + 48"
|
||||
end
|
||||
|
||||
right.save
|
||||
else
|
||||
puts "OK 44 + 48"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiJamTrackMixdownsController, type: :controller do
|
||||
render_views
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:jam_track) { FactoryGirl.create(:jam_track) }
|
||||
let(:mixdown) { FactoryGirl.create(:jam_track_mixdown, user: user, jam_track: jam_track) }
|
||||
let(:jam_track_right) { FactoryGirl.create(:jam_track_right, jam_track: jam_track, user:user)}
|
||||
let(:package) {FactoryGirl.create(:jam_track_mixdown_package, jam_track_mixdown: mixdown)}
|
||||
|
||||
before(:each) do
|
||||
controller.current_user = user
|
||||
JamTrackMixdown.destroy_all
|
||||
end
|
||||
|
||||
describe "index" do
|
||||
|
||||
it "one result" do
|
||||
|
||||
# make a mixdown with no packages
|
||||
get :index, {id: mixdown.jam_track.id}
|
||||
response.status.should eq(200)
|
||||
json = JSON.parse(response.body)
|
||||
json["next"].should be_nil
|
||||
json["count"].should eq(1)
|
||||
json["mixdowns"][0]["settings"].should eq({"speed" => 5})
|
||||
|
||||
# and then add a package
|
||||
package = FactoryGirl.create(:jam_track_mixdown_package, jam_track_mixdown: mixdown)
|
||||
|
||||
get :index, {id: mixdown.jam_track.id}
|
||||
response.status.should eq(200)
|
||||
json = JSON.parse(response.body)
|
||||
json["next"].should be_nil
|
||||
json["count"].should eq(1)
|
||||
json["mixdowns"][0]["packages"][0]["signing_state"].should eq('QUIET')
|
||||
end
|
||||
end
|
||||
|
||||
describe "create" do
|
||||
|
||||
it "success" do
|
||||
post :create, {:format => 'json', jamTrackID: jam_track.id, name: 'some name', description: 'some description', settings: {speed:5}}
|
||||
|
||||
response.status.should eq(200)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
json["name"].should eq('some name')
|
||||
json["jam_track_id"].should eq(jam_track.id)
|
||||
json["description"].should eq('some description')
|
||||
json["settings"].should eq({"speed" => 5})
|
||||
json["packages"].should eq([])
|
||||
end
|
||||
|
||||
it "validates name" do
|
||||
post :create, {:format => 'json', jamTrackID: jam_track.id, description: 'some description', settings: {speed:5}}
|
||||
|
||||
response.status.should eq(422)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
json["errors"]["name"].should eq(["can't be blank"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "enqueue" do
|
||||
it "success" do
|
||||
|
||||
jam_track_right.touch
|
||||
post :enqueue, {:format => 'json', id: mixdown.id, file_type: JamTrackMixdownPackage::FILE_TYPE_AAC, encrypt_type: nil, sample_rate: 48}
|
||||
|
||||
response.status.should eq(200)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
puts json
|
||||
json["id"].should_not be_nil
|
||||
|
||||
package = JamTrackMixdownPackage.find(json["id"])
|
||||
package.file_type.should eq(JamTrackMixdownPackage::FILE_TYPE_AAC)
|
||||
package.encrypt_type.should eq(nil)
|
||||
package.sample_rate.should eq(48)
|
||||
end
|
||||
|
||||
it "validates file_type" do
|
||||
jam_track_right.touch
|
||||
post :enqueue, {:format => 'json', id: mixdown.id, file_type: 'wrong', encrypt_type: nil, sample_rate: 48}
|
||||
|
||||
response.status.should eq(422)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
json["errors"]["file_type"].should eq(["is not included in the list"])
|
||||
end
|
||||
|
||||
it "finds existing package to enqueue" do
|
||||
jam_track_right.touch
|
||||
package.touch
|
||||
JamTrackMixdownPackage.count.should eq(1)
|
||||
|
||||
package.jam_track_mixdown.should eq(mixdown)
|
||||
post :enqueue, {:format => 'json', id: mixdown.id, file_type: package.file_type, encrypt_type: package.encrypt_type, sample_rate: package.sample_rate}
|
||||
|
||||
response.status.should eq(200)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
puts json
|
||||
json["id"].should eq(package.id)
|
||||
JamTrackMixdownPackage.count.should eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "download" do
|
||||
|
||||
it "enqueues if not available" do
|
||||
|
||||
jam_track_right.touch
|
||||
package.touch
|
||||
|
||||
post :download, {:format => 'json', id: mixdown.id, file_type: package.file_type, encrypt_type: package.encrypt_type, sample_rate: package.sample_rate}
|
||||
|
||||
response.status.should eq(202)
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
json["message"].should eq("not available, digitally signing Jam Track Mixdown offline.")
|
||||
|
||||
package.reload
|
||||
package.signing_state.should eq('QUEUED')
|
||||
end
|
||||
|
||||
it "success" do
|
||||
|
||||
jam_track_right.touch
|
||||
package.touch
|
||||
package.enqueue_if_needed
|
||||
package.signed = true
|
||||
package.url = 'some/bogus/place'
|
||||
package.save!
|
||||
|
||||
post :download, {:format => 'json', id: mixdown.id, file_type: package.file_type, encrypt_type: package.encrypt_type, sample_rate: package.sample_rate}
|
||||
|
||||
response.status.should eq(302)
|
||||
|
||||
response['Location'].should include('/some/bogus/place')
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ describe ApiJamTracksController do
|
|||
|
||||
it "handle api call 500" do
|
||||
post :played, { id: 999, user: @user }
|
||||
expect(response.status).to eq(500)
|
||||
expect(response.status).to eq(422)
|
||||
json = JSON.parse(response.body)
|
||||
expect(/Unexpected error occurred/).to match(json['message'])
|
||||
end
|
||||
|
|
@ -155,8 +155,8 @@ describe ApiJamTracksController do
|
|||
get :download, :id=>@jam_track.id, sample_rate: 48, all_fp: 'all', running_fp: 'running'
|
||||
response.status.should == 202
|
||||
right.download_count.should eq(0)
|
||||
right.private_key_44.should be_nil
|
||||
right.private_key_48.should be_nil
|
||||
right.private_key_44.should_not be_nil
|
||||
right.private_key_48.should_not be_nil
|
||||
|
||||
qname = "#{ResqueSpec.queue_name(JamRuby::JamTracksBuilder)}"
|
||||
#puts "ResqueSpec.peek(qname)#{ResqueSpec.peek(qname)}"
|
||||
|
|
@ -167,7 +167,7 @@ describe ApiJamTracksController do
|
|||
|
||||
JamTracksBuilder.should_not have_queued(right.id,nil).in(:jam_tracks_builder)
|
||||
right.reload
|
||||
right.private_key_44.should be_nil
|
||||
right.private_key_44.should_not be_nil
|
||||
right.private_key_48.should_not be_nil
|
||||
right.download_count.should eq(0)
|
||||
|
||||
|
|
@ -186,8 +186,8 @@ describe ApiJamTracksController do
|
|||
get :download, :id=>@jam_track.id, :sample_rate=>44, all_fp: 'all', running_fp: 'running'
|
||||
response.status.should == 202
|
||||
right.download_count.should eq(0)
|
||||
right.private_key_44.should be_nil
|
||||
right.private_key_48.should be_nil
|
||||
right.private_key_44.should_not be_nil
|
||||
right.private_key_48.should_not be_nil
|
||||
|
||||
qname = "#{ResqueSpec.queue_name(JamRuby::JamTracksBuilder)}"
|
||||
#puts "ResqueSpec.peek(qname)#{ResqueSpec.peek(qname)}"
|
||||
|
|
@ -199,7 +199,7 @@ describe ApiJamTracksController do
|
|||
JamTracksBuilder.should_not have_queued(right.id, 44).in(:jam_tracks_builder)
|
||||
right.reload
|
||||
right.private_key_44.should_not be_nil
|
||||
right.private_key_48.should be_nil
|
||||
right.private_key_48.should_not be_nil
|
||||
right.download_count.should eq(0)
|
||||
|
||||
get :download, :id=>@jam_track.id, :sample_rate=>44, all_fp: 'all', running_fp: 'running'
|
||||
|
|
@ -239,11 +239,11 @@ describe ApiJamTracksController do
|
|||
json = JSON.parse(response.body)
|
||||
json.length.should == 1
|
||||
json[0]['44'].should_not be_nil
|
||||
json[0]['44']['private'].should be_nil
|
||||
json[0]['44']['error'].should == 'no_key'
|
||||
json[0]['44']['private'].should_not be_nil
|
||||
json[0]['44']['error'].should be_nil
|
||||
json[0]['48'].should_not be_nil
|
||||
json[0]['48']['private'].should be_nil
|
||||
json[0]['48']['error'].should == 'no_key'
|
||||
json[0]['48']['private'].should_not be_nil
|
||||
json[0]['48']['error'].should be_nil
|
||||
end
|
||||
|
||||
it "track with key" do
|
||||
|
|
@ -254,11 +254,11 @@ describe ApiJamTracksController do
|
|||
json.length.should == 1
|
||||
json[0]['id'].should == @jam_track.id.to_s
|
||||
json[0]['44'].should_not be_nil
|
||||
json[0]['44']['private'].should eq('abc')
|
||||
json[0]['44']['private'].should eq(right.private_key_44)
|
||||
json[0]['44']['error'].should be_nil
|
||||
json[0]['48'].should_not be_nil
|
||||
json[0]['48']['private'].should be_nil
|
||||
json[0]['48']['error'].should == 'no_key'
|
||||
json[0]['48']['private'].should eq(right.private_key_48)
|
||||
json[0]['48']['error'].should be_nil
|
||||
end
|
||||
|
||||
it "non-owning user asking for a real track" do
|
||||
|
|
|
|||
|
|
@ -711,6 +711,23 @@ FactoryGirl.define do
|
|||
sequence(:phone) { |n| "phone-#{n}" }
|
||||
end
|
||||
|
||||
|
||||
factory :jam_track_mixdown, :class => JamRuby::JamTrackMixdown do
|
||||
association :user, factory: :user
|
||||
association :jam_track, factory: :jam_track
|
||||
sequence(:name) { |n| "mixdown-#{n}"}
|
||||
settings '{"speed":5}'
|
||||
end
|
||||
|
||||
factory :jam_track_mixdown_package, :class => JamRuby::JamTrackMixdownPackage do
|
||||
file_type JamRuby::JamTrackMixdownPackage::FILE_TYPE_OGG
|
||||
sample_rate 48
|
||||
signing false
|
||||
signed false
|
||||
|
||||
association :jam_track_mixdown, factory: :jam_track_mixdown
|
||||
end
|
||||
|
||||
factory :jam_track, :class => JamRuby::JamTrack do
|
||||
sequence(:name) { |n| "jam-track-#{n}" }
|
||||
sequence(:description) { |n| "description-#{n}" }
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur
|
|||
end
|
||||
end
|
||||
find('.browse-band a')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack/search")
|
||||
find('.browse-all a')['href'].should eq("/client#/jamtrack/search")
|
||||
find('.browse-all a')['href'].should eq("/client?search=#/jamtrack/search")
|
||||
find('a.cta-free-jamtrack')['href'].should eq("/client#/jamtrack/search")
|
||||
find('a.cta-free-jamtrack').trigger(:click)
|
||||
find('h1', text: 'check out')
|
||||
|
|
|
|||
|
|
@ -262,10 +262,13 @@ describe "Active Music Session API ", :type => :api do
|
|||
login(user2)
|
||||
get location_header + ".json", "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
participant = JSON.parse(last_response.body)
|
||||
music_session = JSON.parse(last_response.body)
|
||||
|
||||
# and the creator should be in the session
|
||||
|
||||
# and the second person should be in the session
|
||||
# and should have tracks
|
||||
music_session["participants"].length.should == 2
|
||||
participant = music_session["participants"][1]
|
||||
participant["tracks"].length.should == 1
|
||||
participant["tracks"][0]["instrument_id"].should == 'bass guitar'
|
||||
participant["tracks"][0]["sound"].should == 'mono'
|
||||
|
|
@ -451,18 +454,18 @@ describe "Active Music Session API ", :type => :api do
|
|||
# users are friends, but no invitation... so we shouldn't be able to join as user 2
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{session["music_session_id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(422)
|
||||
join_response = JSON.parse(last_response.body)
|
||||
join_response["errors"]["musician_access"].should == [ValidationMessages::INVITE_REQUIRED]
|
||||
|
||||
# but let's make sure if we then invite, that we can then join'
|
||||
login(user)
|
||||
post '/api/invitations.json', { :music_session => session["music_session_id"], :receiver => user2.id }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post '/api/invitations.json', { :music_session => music_session["id"], :receiver => user2.id }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{session["music_session_id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should eql(201)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -106,6 +106,22 @@ def web_config
|
|||
def google_public_server_key
|
||||
"AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
||||
end
|
||||
|
||||
def estimated_jam_track_time
|
||||
40
|
||||
end
|
||||
|
||||
def estimated_fast_mixdown_time
|
||||
30
|
||||
end
|
||||
|
||||
def estimated_slow_mixdown_time
|
||||
80
|
||||
end
|
||||
|
||||
def num_packaging_nodes
|
||||
2
|
||||
end
|
||||
end
|
||||
klass.new
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue